Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 0 additions & 10 deletions OneSignalSDK/coverage/jacoco.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,6 @@ subprojects {
testCoverageEnabled = true
}
}

testOptions {
// Robolectric loads classes through its own classloader, which leaves them without
// a code source location. JaCoCo skips those by default, so Robolectric tests
// would contribute no coverage at all.
unitTests.all { test ->
test.jacoco.includeNoLocationClasses = true
test.jacoco.excludes = ['jdk.internal.*']
}
}
}

def coverageExcludes = [
Expand Down
2 changes: 0 additions & 2 deletions OneSignalSDK/onesignal/notifications/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,6 @@ dependencies {

// NOTE: firebase-messaging:24.0.0 requires customer's project to use
// compileSdkVersion 34 or higher.
// `require` is intentionally non-strict: this module compiles against the preferred 24.0.0,
// while an app can select a newer version through Gradle conflict resolution.
api('com.google.firebase:firebase-messaging') {
version {
require '[23.0.8, 24.0.99]'
Expand Down
10 changes: 0 additions & 10 deletions OneSignalSDK/onesignal/notifications/consumer-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,6 @@
-dontwarn com.google.firebase.**
-dontwarn com.google.android.gms.**

# PushRegistratorFCM looks up FirebaseMessaging.register() by name, because this module compiles
# against firebase-messaging 24.x where the method does not exist yet. Nothing references it
# symbolically, it carries no @Keep, and firebase-messaging ships no consumer rules, so R8 full mode
# (AGP 8+) renames it along with the rest of the class, causing:
# java.lang.NoSuchMethodException: com.google.firebase.messaging.FirebaseMessaging.register []
# keepclassmembers rather than keep, so Huawei apps that exclude firebase-messaging are unaffected.
-keepclassmembers class com.google.firebase.messaging.FirebaseMessaging {
public *** register();
}

# ADM handlers are instantiated by name from the app manifest AND their on* lifecycle callbacks
# (onMessage/onRegistered/onRegistrationError/onUnregistered) are invoked by the ADM framework, not
# the SDK, so keep both constructors and those methods. (Amazon-device-only path, untestable in CI.)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
package com.onesignal.notifications.internal.registration.impl

import android.util.Base64
import com.google.android.gms.tasks.Task
import com.google.android.gms.tasks.Tasks
import com.google.firebase.FirebaseApp
import com.google.firebase.FirebaseOptions
import com.google.firebase.installations.FirebaseInstallations
import com.google.firebase.messaging.FirebaseMessaging
import com.onesignal.common.AndroidUtils
import com.onesignal.core.internal.application.IApplicationService
import com.onesignal.core.internal.config.ConfigModelStore
import com.onesignal.core.internal.device.IDeviceService
import java.lang.reflect.InvocationTargetException
import java.util.concurrent.ExecutionException

internal class PushRegistratorFCM(
Expand All @@ -23,8 +19,6 @@ internal class PushRegistratorFCM(
companion object {
private const val FCM_APP_NAME = "ONESIGNAL_SDK_FCM_APP_NAME"

private const val INSTALLATION_ID_ENABLED_METADATA = "firebase_messaging_installation_id_enabled"

// project_info.project_id
private const val FCM_DEFAULT_PROJECT_ID = "onesignal-shared-public"

Expand Down Expand Up @@ -55,45 +49,22 @@ internal class PushRegistratorFCM(
@Throws(ExecutionException::class, InterruptedException::class)
override suspend fun getToken(senderId: String): String {
initFirebaseApp(senderId)
return getTokenWithClassFirebaseMessaging(senderId)
return getTokenWithClassFirebaseMessaging()
}

@Throws(ExecutionException::class, InterruptedException::class)
private fun getTokenWithClassFirebaseMessaging(senderId: String): String {
private fun getTokenWithClassFirebaseMessaging(): String {
// We use firebaseApp.get(FirebaseMessaging.class) instead of FirebaseMessaging.getInstance()
// as the latter uses the default Firebase app. We need to use a custom Firebase app as
// the senderId is provided at runtime.
val fcmInstance = firebaseApp!!.get(FirebaseMessaging::class.java)
return FCMTokenProvider.getToken(
senderId,
::installationIdEnabled,
{ fcmInstance.token },
::defaultAppRegistration,
)
}

// Manifest merging means the flag can arrive from a dependency instead of the app's own
// manifest, so report what the app actually resolved to. Read as a raw value because a
// string "true" reads as false when asked for a boolean.
private fun installationIdEnabled(): String {
val metaData = AndroidUtils.getManifestMetaBundle(_applicationService.appContext)
return metaData?.get(INSTALLATION_ID_ENABLED_METADATA)?.toString() ?: "not set"
}

// Installation ID registration is rejected unless the sender id, app id, and api key all belong
// to the same Firebase project. Our own FirebaseApp pairs the app's sender id with OneSignal's
// shared project credentials, so only the host app's default FirebaseApp can be used for it.
private fun defaultAppRegistration(): FCMTokenProvider.InstallationIdRegistration? {
val defaultApp =
FirebaseApp
.getApps(_applicationService.appContext)
.firstOrNull { it.name == FirebaseApp.DEFAULT_APP_NAME } ?: return null

return FCMTokenProvider.InstallationIdRegistration(
senderId = defaultApp.options.gcmSenderId,
register = { FCMTokenProvider.invokeRegister(defaultApp.get(FirebaseMessaging::class.java)) },
installationId = { FirebaseInstallations.getInstance(defaultApp).id },
)
// FirebaseMessaging.getToken API was introduced in firebase-messaging:21.0.0
val tokenTask = fcmInstance.token
try {
return Tasks.await(tokenTask)
} catch (e: ExecutionException) {
throw tokenTask.exception ?: e
}
}

private fun initFirebaseApp(senderId: String) {
Expand All @@ -109,105 +80,3 @@ internal class PushRegistratorFCM(
firebaseApp = FirebaseApp.initializeApp(_applicationService.appContext, firebaseOptions, FCM_APP_NAME)
}
}

internal object FCMTokenProvider {
/**
* The Firebase Installation ID registration that replaces the legacy token API, along with the
* sender id of the Firebase project it would register against.
*/
class InstallationIdRegistration(
val senderId: String?,
val register: () -> Task<*>,
val installationId: () -> Task<String>,
)

/**
* Retrieves an FCM token for [senderId], falling back to Firebase Installation ID registration
* when the host app has opted into it. Opting in disables the legacy token API for the whole
* app, not just the FirebaseApp that opted in.
*/
fun getToken(
senderId: String,
installationIdEnabled: () -> String,
legacyToken: () -> Task<String>,
installationIdRegistration: () -> InstallationIdRegistration?,
): String {
return try {
await(legacyToken())
} catch (e: IllegalStateException) {
if (!isLegacyTokenApiDisabled(e)) throw e

registerInstallationId(senderId, installationIdEnabled(), installationIdRegistration())
}
}

private fun registerInstallationId(
senderId: String,
installationIdEnabled: String,
registration: InstallationIdRegistration?,
): String {
val optedIn = "firebase_messaging_installation_id_enabled=$installationIdEnabled"

if (registration == null) {
throw IllegalStateException(
"Firebase Installation ID registration is enabled ($optedIn) but this app has no " +
"default FirebaseApp to register with. Add your Firebase configuration " +
"(google-services.json), or set firebase_messaging_installation_id_enabled to " +
"false in your manifest to keep using the legacy FCM token API.",
)
}

if (registration.senderId != senderId) {
throw IllegalStateException(
"Firebase Installation ID registration is enabled ($optedIn) but the default " +
"FirebaseApp uses sender id ${registration.senderId}, while OneSignal is " +
"configured with sender id $senderId. Point both at the same Firebase project, " +
"or set firebase_messaging_installation_id_enabled to false in your manifest " +
"to keep using the legacy FCM token API.",
)
}

await(registration.register())
return await(registration.installationId())
}

/**
* Calls register() reflectively. FirebaseMessaging.register was added in firebase-messaging
* 25.1.0. This module compiles against the preferred 24.0.0, but the non-strict Gradle
* constraint lets apps select newer versions through conflict resolution.
*/
fun invokeRegister(target: Any): Task<*> {
val register =
try {
target.javaClass.getMethod("register")
} catch (e: NoSuchMethodException) {
throw IllegalStateException(
"Firebase Installation ID registration is enabled but " +
"FirebaseMessaging.register() was not found. It requires firebase-messaging " +
"25.1.0 or newer, and has to survive minification, so check that OneSignal's " +
"consumer ProGuard rules are applied.",
e,
)
}

// invoke() wraps anything register() throws synchronously, which would hide the cause.
return try {
register.invoke(target) as Task<*>
} catch (e: InvocationTargetException) {
throw e.targetException ?: e
}
}

private fun isLegacyTokenApiDisabled(exception: IllegalStateException): Boolean {
val message = exception.message ?: return false
return message.contains("API disabled") && message.contains("register()")
}

private fun <T> await(task: Task<T>): T {
try {
return Tasks.await(task)
} catch (e: ExecutionException) {
throw task.exception ?: e
}
}
}

This file was deleted.

Loading
Loading