From a3736786cd1dce267e2dd9c00448f9b47f0d2849 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 25 Jun 2026 09:48:48 +0200 Subject: [PATCH] Update dependency com.google.firebase:firebase-bom to v34.15.0 (#7089) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update dependency com.google.firebase:firebase-bom to v34.15.0 * Replace deprecated FCM `token` usages with `installationId` Renamed a few components, changed the behaviour of `FirebaseTokenRotator` so it doesn't save the new token, `FirebaseMessagingService` will do it in its `onRegistered` method * Enable firebase messaging installation_id * Make sure we delete the FID when unregistering the app from Firebase * Add `runFirebaseTask` util to convert `Task` into a `Result` returned from a suspending function Also, rename the use cases and make them use the `invoke` pattern --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Jorge Martín Co-authored-by: Benoit Marty --- gradle/libs.versions.toml | 2 +- .../firebase/src/main/AndroidManifest.xml | 4 ++ ...kt => FirebaseNewInstallationIdHandler.kt} | 18 +++---- .../firebase/FirebasePushProvider.kt | 10 ++-- .../pushproviders/firebase/FirebaseStore.kt | 26 +++++----- .../firebase/FirebaseTokenDeleter.kt | 49 ------------------ .../firebase/FirebaseTokenGetter.kt | 50 ------------------- .../firebase/FirebaseTokenRotator.kt | 33 ------------ .../firebase/FirebaseTroubleshooter.kt | 9 +--- .../firebase/RegisterFirebaseSession.kt | 36 +++++++++++++ .../firebase/RotateFirebaseSession.kt | 35 +++++++++++++ .../firebase/UnregisterFirebaseSession.kt | 44 ++++++++++++++++ .../VectorFirebaseMessagingService.kt | 10 ++-- .../troubleshoot/FirebaseTokenTest.kt | 2 +- .../firebase/util/RunFirebaseTask.kt | 35 +++++++++++++ ...ltFirebaseNewInstallationIdHandlerTest.kt} | 10 ++-- ...> FakeFirebaseNewInstallationIdHandler.kt} | 8 +-- ...otator.kt => FakeRotateFirebaseSession.kt} | 6 +-- .../firebase/FirebasePushProviderTest.kt | 24 ++++----- .../firebase/InMemoryFirebaseStore.kt | 10 ++-- .../VectorFirebaseMessagingServiceTest.kt | 10 ++-- .../troubleshoot/FirebaseTokenTestTest.kt | 4 +- 22 files changed, 227 insertions(+), 208 deletions(-) rename libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/{FirebaseNewTokenHandler.kt => FirebaseNewInstallationIdHandler.kt} (81%) delete mode 100644 libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTokenDeleter.kt delete mode 100644 libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTokenGetter.kt delete mode 100644 libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTokenRotator.kt create mode 100644 libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/RegisterFirebaseSession.kt create mode 100644 libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/RotateFirebaseSession.kt create mode 100644 libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/UnregisterFirebaseSession.kt create mode 100644 libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/util/RunFirebaseTask.kt rename libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/{DefaultFirebaseNewTokenHandlerTest.kt => DefaultFirebaseNewInstallationIdHandlerTest.kt} (96%) rename libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/{FakeFirebaseNewTokenHandler.kt => FakeFirebaseNewInstallationIdHandler.kt} (71%) rename libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/{FakeFirebaseTokenRotator.kt => FakeRotateFirebaseSession.kt} (81%) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5e8cf1a268..74e1ece60b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -84,7 +84,7 @@ kotlinpoet-ksp = { module = "com.squareup:kotlinpoet-ksp", version.ref = "kotlin kover_gradle_plugin = { module = "org.jetbrains.kotlinx:kover-gradle-plugin", version.ref = "kover" } ksp_gradle_plugin = { module = "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin", version.ref = "ksp" } # https://firebase.google.com/docs/android/setup#available-libraries -google_firebase_bom = "com.google.firebase:firebase-bom:34.14.1" +google_firebase_bom = "com.google.firebase:firebase-bom:34.15.0" firebase_appdistribution_gradle = { module = "com.google.firebase:firebase-appdistribution-gradle", version.ref = "firebaseAppDistribution" } autonomousapps_dependencyanalysis_plugin = { module = "com.autonomousapps:dependency-analysis-gradle-plugin", version.ref = "dependencyAnalysis" } ksp_plugin = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" } diff --git a/libraries/pushproviders/firebase/src/main/AndroidManifest.xml b/libraries/pushproviders/firebase/src/main/AndroidManifest.xml index 24bad30d77..9a6972e412 100644 --- a/libraries/pushproviders/firebase/src/main/AndroidManifest.xml +++ b/libraries/pushproviders/firebase/src/main/AndroidManifest.xml @@ -7,6 +7,10 @@ --> + + { - val pushKey = firebaseStore.getFcmToken() ?: return Result.failure( + val pushKey = firebaseStore.getInstallationId() ?: return Result.failure( IllegalStateException( "Unable to register pusher, Firebase token is not known." ) @@ -59,7 +59,7 @@ class FirebasePushProvider( override suspend fun getCurrentDistributor(sessionId: SessionId) = firebaseDistributor override suspend fun unregister(matrixClient: MatrixClient): Result { - val pushKey = firebaseStore.getFcmToken() + val pushKey = firebaseStore.getInstallationId() return if (pushKey == null) { Timber.tag(loggerTag.value).w("Unable to unregister pusher, Firebase token is not known.") Result.success(Unit) @@ -74,7 +74,7 @@ class FirebasePushProvider( override suspend fun onSessionDeleted(sessionId: SessionId) = Unit override suspend fun getPushConfig(sessionId: SessionId): Config? { - return firebaseStore.getFcmToken()?.let { fcmToken -> + return firebaseStore.getInstallationId()?.let { fcmToken -> Config( url = firebaseGatewayProvider.getFirebaseGateway(), pushKey = fcmToken @@ -85,7 +85,7 @@ class FirebasePushProvider( override fun canRotateToken(): Boolean = true override suspend fun rotateToken(): Result { - return firebaseTokenRotator.rotate() + return rotateFirebaseSession() } companion object { diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseStore.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseStore.kt index 1f6a4709cc..748d540a54 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseStore.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseStore.kt @@ -18,28 +18,28 @@ import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.onStart /** - * This class store the Firebase token in SharedPrefs. + * This class stores the Firebase installationId in SharedPrefs. */ interface FirebaseStore { - fun getFcmToken(): String? - fun fcmTokenFlow(): Flow - fun storeFcmToken(token: String?) + fun getInstallationId(): String? + fun fcmInstallationIdFlow(): Flow + fun storeInstallationId(installationId: String?) } @ContributesBinding(AppScope::class) class SharedPreferencesFirebaseStore( private val sharedPreferences: SharedPreferences, ) : FirebaseStore { - override fun getFcmToken(): String? { - return sharedPreferences.getString(PREFS_KEY_FCM_TOKEN, null) + override fun getInstallationId(): String? { + return sharedPreferences.getString(PREFS_KEY_FCM_INSTALLATION_ID, null) } - override fun fcmTokenFlow(): Flow { - val flow = MutableStateFlow(getFcmToken()) + override fun fcmInstallationIdFlow(): Flow { + val flow = MutableStateFlow(getInstallationId()) val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, k -> - if (k == PREFS_KEY_FCM_TOKEN) { + if (k == PREFS_KEY_FCM_INSTALLATION_ID) { try { - flow.value = getFcmToken() + flow.value = getInstallationId() } catch (_: Exception) { flow.value = null } @@ -50,13 +50,13 @@ class SharedPreferencesFirebaseStore( .onCompletion { sharedPreferences.unregisterOnSharedPreferenceChangeListener(listener) } } - override fun storeFcmToken(token: String?) { + override fun storeInstallationId(installationId: String?) { sharedPreferences.edit { - putString(PREFS_KEY_FCM_TOKEN, token) + putString(PREFS_KEY_FCM_INSTALLATION_ID, installationId) } } companion object { - private const val PREFS_KEY_FCM_TOKEN = "FCM_TOKEN" + private const val PREFS_KEY_FCM_INSTALLATION_ID = "FCM_TOKEN" } } diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTokenDeleter.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTokenDeleter.kt deleted file mode 100644 index 21fa189121..0000000000 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTokenDeleter.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2025 Element Creations Ltd. - * Copyright 2023-2025 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.libraries.pushproviders.firebase - -import com.google.firebase.messaging.FirebaseMessaging -import dev.zacsweers.metro.AppScope -import dev.zacsweers.metro.ContributesBinding -import timber.log.Timber -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException -import kotlin.coroutines.suspendCoroutine - -interface FirebaseTokenDeleter { - /** - * Deletes the current Firebase token. - */ - suspend fun delete() -} - -@ContributesBinding(AppScope::class) -class DefaultFirebaseTokenDeleter( - private val isPlayServiceAvailable: IsPlayServiceAvailable, -) : FirebaseTokenDeleter { - override suspend fun delete() { - // 'app should always check the device for a compatible Google Play services APK before accessing Google Play services features' - isPlayServiceAvailable.checkAvailableOrThrow() - suspendCoroutine { continuation -> - try { - FirebaseMessaging.getInstance().deleteToken() - .addOnSuccessListener { - continuation.resume(Unit) - } - .addOnFailureListener { e -> - Timber.e(e, "## deleteFirebaseToken() : failed") - continuation.resumeWithException(e) - } - } catch (e: Throwable) { - Timber.e(e, "## deleteFirebaseToken() : failed") - continuation.resumeWithException(e) - } - } - } -} diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTokenGetter.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTokenGetter.kt deleted file mode 100644 index 80fd9726d4..0000000000 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTokenGetter.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2025 Element Creations Ltd. - * Copyright 2023-2025 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.libraries.pushproviders.firebase - -import com.google.firebase.messaging.FirebaseMessaging -import dev.zacsweers.metro.AppScope -import dev.zacsweers.metro.ContributesBinding -import timber.log.Timber -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException -import kotlin.coroutines.suspendCoroutine - -interface FirebaseTokenGetter { - /** - * Read the current Firebase token from FirebaseMessaging. - * If the token does not exist, it will be generated. - */ - suspend fun get(): String -} - -@ContributesBinding(AppScope::class) -class DefaultFirebaseTokenGetter( - private val isPlayServiceAvailable: IsPlayServiceAvailable, -) : FirebaseTokenGetter { - override suspend fun get(): String { - // 'app should always check the device for a compatible Google Play services APK before accessing Google Play services features' - isPlayServiceAvailable.checkAvailableOrThrow() - return suspendCoroutine { continuation -> - try { - FirebaseMessaging.getInstance().token - .addOnSuccessListener { token -> - continuation.resume(token) - } - .addOnFailureListener { e -> - Timber.e(e, "## retrievedFirebaseToken() : failed") - continuation.resumeWithException(e) - } - } catch (e: Throwable) { - Timber.e(e, "## retrievedFirebaseToken() : failed") - continuation.resumeWithException(e) - } - } - } -} diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTokenRotator.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTokenRotator.kt deleted file mode 100644 index 78b33cbb08..0000000000 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTokenRotator.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2025 Element Creations Ltd. - * Copyright 2024, 2025 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.libraries.pushproviders.firebase - -import dev.zacsweers.metro.AppScope -import dev.zacsweers.metro.ContributesBinding -import io.element.android.libraries.core.extensions.runCatchingExceptions - -interface FirebaseTokenRotator { - suspend fun rotate(): Result -} - -/** - * This class delete the Firebase token and generate a new one. - */ -@ContributesBinding(AppScope::class) -class DefaultFirebaseTokenRotator( - private val firebaseTokenDeleter: FirebaseTokenDeleter, - private val firebaseTokenGetter: FirebaseTokenGetter, -) : FirebaseTokenRotator { - override suspend fun rotate(): Result { - return runCatchingExceptions { - firebaseTokenDeleter.delete() - firebaseTokenGetter.get() - } - } -} diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTroubleshooter.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTroubleshooter.kt index 78fce0338d..0e58133a60 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTroubleshooter.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTroubleshooter.kt @@ -10,7 +10,6 @@ package io.element.android.libraries.pushproviders.firebase import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import io.element.android.libraries.core.extensions.runCatchingExceptions interface FirebaseTroubleshooter { suspend fun troubleshoot(): Result @@ -21,13 +20,9 @@ interface FirebaseTroubleshooter { */ @ContributesBinding(AppScope::class) class DefaultFirebaseTroubleshooter( - private val newTokenHandler: FirebaseNewTokenHandler, - private val firebaseTokenGetter: FirebaseTokenGetter, + private val rotateFirebaseSession: RotateFirebaseSession, ) : FirebaseTroubleshooter { override suspend fun troubleshoot(): Result { - return runCatchingExceptions { - val token = firebaseTokenGetter.get() - newTokenHandler.handle(token) - } + return rotateFirebaseSession() } } diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/RegisterFirebaseSession.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/RegisterFirebaseSession.kt new file mode 100644 index 0000000000..42604bd013 --- /dev/null +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/RegisterFirebaseSession.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.pushproviders.firebase + +import com.google.firebase.messaging.FirebaseMessaging +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesBinding +import io.element.android.libraries.pushproviders.firebase.util.runFirebaseTask +import timber.log.Timber + +fun interface RegisterFirebaseSession { + /** + * Register the device to Firebase Messaging. + */ + suspend operator fun invoke(): Result +} + +@ContributesBinding(AppScope::class) +class DefaultRegisterFirebaseSession( + private val isPlayServiceAvailable: IsPlayServiceAvailable, +) : RegisterFirebaseSession { + override suspend operator fun invoke(): Result { + // 'app should always check the device for a compatible Google Play services APK before accessing Google Play services features' + isPlayServiceAvailable.checkAvailableOrThrow() + return runFirebaseTask { FirebaseMessaging.getInstance().register() } + .onFailure { Timber.e(it, "## registerFirebaseMessaging() : failed") } + // Change return type from Void! to Unit + .map {} + } +} diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/RotateFirebaseSession.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/RotateFirebaseSession.kt new file mode 100644 index 0000000000..a6e51dafa2 --- /dev/null +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/RotateFirebaseSession.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.pushproviders.firebase + +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesBinding +import io.element.android.libraries.core.extensions.runCatchingExceptions + +fun interface RotateFirebaseSession { + suspend operator fun invoke(): Result +} + +/** + * This class deletes the Firebase installation id and generates a new one. + */ +@ContributesBinding(AppScope::class) +class DefaultRotateFirebaseSession( + private val registerFirebaseSession: RegisterFirebaseSession, + private val unregisterFirebaseSession: UnregisterFirebaseSession, +) : RotateFirebaseSession { + override suspend operator fun invoke(): Result { + return runCatchingExceptions { + // Stop the current session, which will also delete the existing installation id from Firebase + unregisterFirebaseSession().getOrThrow() + // Register again to get a new installation id + registerFirebaseSession().getOrThrow() + } + } +} diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/UnregisterFirebaseSession.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/UnregisterFirebaseSession.kt new file mode 100644 index 0000000000..815f2d734c --- /dev/null +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/UnregisterFirebaseSession.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.pushproviders.firebase + +import com.google.firebase.installations.FirebaseInstallations +import com.google.firebase.messaging.FirebaseMessaging +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesBinding +import io.element.android.libraries.pushproviders.firebase.util.runFirebaseTask +import timber.log.Timber + +fun interface UnregisterFirebaseSession { + /** + * Deletes the current Firebase token. + */ + suspend operator fun invoke(): Result +} + +@ContributesBinding(AppScope::class) +class DefaultUnregisterFirebaseSession( + private val isPlayServiceAvailable: IsPlayServiceAvailable, +) : UnregisterFirebaseSession { + override suspend operator fun invoke(): Result { + // 'app should always check the device for a compatible Google Play services APK before accessing Google Play services features' + isPlayServiceAvailable.checkAvailableOrThrow() + return runFirebaseTask { + // Unregister the device from Firebase Messaging + FirebaseMessaging.getInstance().unregister() + // Also delete the existing installation id from Firebase + .continueWithTask { FirebaseInstallations.getInstance().delete() } + } + .onFailure { + Timber.e(it, "## unregisterFirebaseMessaging() : failed") + } + // Change return type from Void! to Unit + .map {} + } +} diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/VectorFirebaseMessagingService.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/VectorFirebaseMessagingService.kt index 975a3c75ca..cd6ad3c8bc 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/VectorFirebaseMessagingService.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/VectorFirebaseMessagingService.kt @@ -24,7 +24,7 @@ import timber.log.Timber private val loggerTag = LoggerTag("VectorFirebaseMessagingService", LoggerTag.PushLoggerTag) class VectorFirebaseMessagingService : FirebaseMessagingService() { - @Inject lateinit var firebaseNewTokenHandler: FirebaseNewTokenHandler + @Inject lateinit var firebaseNewInstallationIdHandler: FirebaseNewInstallationIdHandler @Inject lateinit var pushParser: FirebasePushParser @Inject lateinit var pushHandler: PushHandler @Inject lateinit var fetchPushForegroundServiceManager: FetchPushForegroundServiceManager @@ -36,10 +36,12 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { bindings().inject(this) } - override fun onNewToken(token: String) { - Timber.tag(loggerTag.value).w("New Firebase token") + override fun onRegistered(installationId: String) { + super.onRegistered(installationId) + + Timber.tag(loggerTag.value).w("New Firebase installation id") coroutineScope.launch { - firebaseNewTokenHandler.handle(token) + firebaseNewInstallationIdHandler.handle(installationId) } } diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTest.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTest.kt index 7ea7c6cee2..c3af8df542 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTest.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTest.kt @@ -49,7 +49,7 @@ class FirebaseTokenTest( override suspend fun run(coroutineScope: CoroutineScope) { currentJob?.cancel() delegate.start() - currentJob = firebaseStore.fcmTokenFlow() + currentJob = firebaseStore.fcmInstallationIdFlow() .onEach { token -> if (token != null) { delegate.updateState( diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/util/RunFirebaseTask.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/util/RunFirebaseTask.kt new file mode 100644 index 0000000000..0ddde69d1e --- /dev/null +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/util/RunFirebaseTask.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2026 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.pushproviders.firebase.util + +import com.google.android.gms.tasks.Task +import io.element.android.libraries.core.extensions.runCatchingExceptions +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException + +suspend fun runFirebaseTaskWithResult(closure: () -> Task): Result { + return runCatchingExceptions { + suspendCancellableCoroutine { continuation -> + try { + closure().addOnSuccessListener { + continuation.resume(it) + }.addOnFailureListener { e -> + continuation.resumeWithException(e) + } + } catch (e: Throwable) { + continuation.resumeWithException(e) + } + } + } +} + +// Special case for `Void!` return type, which is not a valid Kotlin type. We convert it to `Unit` instead. +suspend fun runFirebaseTask(closure: () -> Task): Result { + return runFirebaseTaskWithResult { closure().continueWith {} } +} diff --git a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/DefaultFirebaseNewTokenHandlerTest.kt b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/DefaultFirebaseNewInstallationIdHandlerTest.kt similarity index 96% rename from libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/DefaultFirebaseNewTokenHandlerTest.kt rename to libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/DefaultFirebaseNewInstallationIdHandlerTest.kt index 4db9ea667f..78d35fe2a5 100644 --- a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/DefaultFirebaseNewTokenHandlerTest.kt +++ b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/DefaultFirebaseNewInstallationIdHandlerTest.kt @@ -30,16 +30,16 @@ import io.element.android.tests.testutils.lambda.value import kotlinx.coroutines.test.runTest import org.junit.Test -class DefaultFirebaseNewTokenHandlerTest { +class DefaultFirebaseNewInstallationIdHandlerTest { @Test fun `when a new token is received it is stored in the firebase store`() = runTest { val firebaseStore = InMemoryFirebaseStore() - assertThat(firebaseStore.getFcmToken()).isNull() + assertThat(firebaseStore.getInstallationId()).isNull() val firebaseNewTokenHandler = createDefaultFirebaseNewTokenHandler( firebaseStore = firebaseStore, ) firebaseNewTokenHandler.handle("aToken") - assertThat(firebaseStore.getFcmToken()).isEqualTo("aToken") + assertThat(firebaseStore.getInstallationId()).isEqualTo("aToken") } @Test @@ -142,8 +142,8 @@ class DefaultFirebaseNewTokenHandlerTest { matrixClientProvider: MatrixClientProvider = FakeMatrixClientProvider(), firebaseStore: FirebaseStore = InMemoryFirebaseStore(), firebaseGatewayProvider: FirebaseGatewayProvider = FakeFirebaseGatewayProvider(), - ): FirebaseNewTokenHandler { - return DefaultFirebaseNewTokenHandler( + ): FirebaseNewInstallationIdHandler { + return DefaultFirebaseNewInstallationIdHandler( pusherSubscriber = pusherSubscriber, sessionStore = sessionStore, userPushStoreFactory = userPushStoreFactory, diff --git a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FakeFirebaseNewTokenHandler.kt b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FakeFirebaseNewInstallationIdHandler.kt similarity index 71% rename from libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FakeFirebaseNewTokenHandler.kt rename to libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FakeFirebaseNewInstallationIdHandler.kt index 2fa9508103..b6b310fbb8 100644 --- a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FakeFirebaseNewTokenHandler.kt +++ b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FakeFirebaseNewInstallationIdHandler.kt @@ -10,10 +10,10 @@ package io.element.android.libraries.pushproviders.firebase import io.element.android.tests.testutils.lambda.lambdaError -class FakeFirebaseNewTokenHandler( +class FakeFirebaseNewInstallationIdHandler( private val handleResult: (String) -> Unit = { lambdaError() } -) : FirebaseNewTokenHandler { - override suspend fun handle(firebaseToken: String) { - handleResult(firebaseToken) +) : FirebaseNewInstallationIdHandler { + override suspend fun handle(installationId: String) { + handleResult(installationId) } } diff --git a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FakeFirebaseTokenRotator.kt b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FakeRotateFirebaseSession.kt similarity index 81% rename from libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FakeFirebaseTokenRotator.kt rename to libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FakeRotateFirebaseSession.kt index d504aa32ae..34ae929af8 100644 --- a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FakeFirebaseTokenRotator.kt +++ b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FakeRotateFirebaseSession.kt @@ -10,10 +10,10 @@ package io.element.android.libraries.pushproviders.firebase import io.element.android.tests.testutils.lambda.lambdaError -class FakeFirebaseTokenRotator( +class FakeRotateFirebaseSession( private val rotateWithResult: () -> Result = { lambdaError() } -) : FirebaseTokenRotator { - override suspend fun rotate(): Result { +) : RotateFirebaseSession { + override suspend fun invoke(): Result { return rotateWithResult() } } diff --git a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushProviderTest.kt b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushProviderTest.kt index 312689da6e..a2737bc377 100644 --- a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushProviderTest.kt +++ b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushProviderTest.kt @@ -61,7 +61,7 @@ class FirebasePushProviderTest { val registerPusherResultLambda = lambdaRecorder> { _, _, _ -> Result.success(Unit) } val firebasePushProvider = createFirebasePushProvider( firebaseStore = InMemoryFirebaseStore( - token = "aToken" + installationId = "aToken" ), pusherSubscriber = FakePusherSubscriber( registerPusherResult = registerPusherResultLambda @@ -78,7 +78,7 @@ class FirebasePushProviderTest { fun `register ko no token`() = runTest { val firebasePushProvider = createFirebasePushProvider( firebaseStore = InMemoryFirebaseStore( - token = null + installationId = null ), pusherSubscriber = FakePusherSubscriber( registerPusherResult = { _, _, _ -> Result.success(Unit) } @@ -92,7 +92,7 @@ class FirebasePushProviderTest { fun `register ko error`() = runTest { val firebasePushProvider = createFirebasePushProvider( firebaseStore = InMemoryFirebaseStore( - token = "aToken" + installationId = "aToken" ), pusherSubscriber = FakePusherSubscriber( registerPusherResult = { _, _, _ -> Result.failure(AN_EXCEPTION) } @@ -108,7 +108,7 @@ class FirebasePushProviderTest { val unregisterPusherResultLambda = lambdaRecorder> { _, _, _ -> Result.success(Unit) } val firebasePushProvider = createFirebasePushProvider( firebaseStore = InMemoryFirebaseStore( - token = "aToken" + installationId = "aToken" ), pusherSubscriber = FakePusherSubscriber( unregisterPusherResult = unregisterPusherResultLambda @@ -125,7 +125,7 @@ class FirebasePushProviderTest { fun `unregister no token - in this case, the error is ignored`() = runTest { val firebasePushProvider = createFirebasePushProvider( firebaseStore = InMemoryFirebaseStore( - token = null + installationId = null ), ) val result = firebasePushProvider.unregister(FakeMatrixClient()) @@ -136,7 +136,7 @@ class FirebasePushProviderTest { fun `unregister ko error`() = runTest { val firebasePushProvider = createFirebasePushProvider( firebaseStore = InMemoryFirebaseStore( - token = "aToken" + installationId = "aToken" ), pusherSubscriber = FakePusherSubscriber( unregisterPusherResult = { _, _, _ -> Result.failure(AN_EXCEPTION) } @@ -150,7 +150,7 @@ class FirebasePushProviderTest { fun `getCurrentUserPushConfig no push ket`() = runTest { val firebasePushProvider = createFirebasePushProvider( firebaseStore = InMemoryFirebaseStore( - token = null + installationId = null ) ) val result = firebasePushProvider.getPushConfig(A_SESSION_ID) @@ -161,7 +161,7 @@ class FirebasePushProviderTest { fun `getCurrentUserPushConfig ok`() = runTest { val firebasePushProvider = createFirebasePushProvider( firebaseStore = InMemoryFirebaseStore( - token = "aToken" + installationId = "aToken" ), ) val result = firebasePushProvider.getPushConfig(A_SESSION_ID) @@ -169,10 +169,10 @@ class FirebasePushProviderTest { } @Test - fun `rotateToken invokes the FirebaseTokenRotator`() = runTest { + fun `rotateToken invokes the FirebaseMessagingSessionRotator`() = runTest { val lambda = lambdaRecorder> { Result.success(Unit) } val firebasePushProvider = createFirebasePushProvider( - firebaseTokenRotator = FakeFirebaseTokenRotator(lambda), + rotateFirebaseSession = FakeRotateFirebaseSession(lambda), ) firebasePushProvider.rotateToken() lambda.assertions().isCalledOnce() @@ -194,14 +194,14 @@ class FirebasePushProviderTest { firebaseStore: FirebaseStore = InMemoryFirebaseStore(), pusherSubscriber: PusherSubscriber = FakePusherSubscriber(), isPlayServiceAvailable: IsPlayServiceAvailable = FakeIsPlayServiceAvailable(false), - firebaseTokenRotator: FirebaseTokenRotator = FakeFirebaseTokenRotator(), + rotateFirebaseSession: RotateFirebaseSession = FakeRotateFirebaseSession(), firebaseGatewayProvider: FirebaseGatewayProvider = FakeFirebaseGatewayProvider() ): FirebasePushProvider { return FirebasePushProvider( firebaseStore = firebaseStore, pusherSubscriber = pusherSubscriber, isPlayServiceAvailable = isPlayServiceAvailable, - firebaseTokenRotator = firebaseTokenRotator, + rotateFirebaseSession = rotateFirebaseSession, firebaseGatewayProvider = firebaseGatewayProvider, ) } diff --git a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/InMemoryFirebaseStore.kt b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/InMemoryFirebaseStore.kt index 3e6292f445..9e55cdcb4d 100644 --- a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/InMemoryFirebaseStore.kt +++ b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/InMemoryFirebaseStore.kt @@ -12,13 +12,13 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf class InMemoryFirebaseStore( - private var token: String? = null + private var installationId: String? = null ) : FirebaseStore { - override fun getFcmToken(): String? = token + override fun getInstallationId(): String? = installationId - override fun fcmTokenFlow(): Flow = flowOf(token) + override fun fcmInstallationIdFlow(): Flow = flowOf(installationId) - override fun storeFcmToken(token: String?) { - this.token = token + override fun storeInstallationId(installationId: String?) { + this.installationId = installationId } } diff --git a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/VectorFirebaseMessagingServiceTest.kt b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/VectorFirebaseMessagingServiceTest.kt index 6973c9d828..d640e3c858 100644 --- a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/VectorFirebaseMessagingServiceTest.kt +++ b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/VectorFirebaseMessagingServiceTest.kt @@ -171,22 +171,22 @@ class VectorFirebaseMessagingServiceTest : RobolectricTest() { fun `test new token is forwarded to the handler`() = runTest { val lambda = lambdaRecorder { } val vectorFirebaseMessagingService = createVectorFirebaseMessagingService( - firebaseNewTokenHandler = FakeFirebaseNewTokenHandler(handleResult = lambda) + firebaseNewInstallationIdHandler = FakeFirebaseNewInstallationIdHandler(handleResult = lambda) ) - vectorFirebaseMessagingService.onNewToken("aToken") + vectorFirebaseMessagingService.onRegistered("installationId") advanceUntilIdle() lambda.assertions() .isCalledOnce() - .with(value("aToken")) + .with(value("installationId")) } private fun TestScope.createVectorFirebaseMessagingService( - firebaseNewTokenHandler: FirebaseNewTokenHandler = FakeFirebaseNewTokenHandler(), + firebaseNewInstallationIdHandler: FirebaseNewInstallationIdHandler = FakeFirebaseNewInstallationIdHandler(), pushHandler: PushHandler = FakePushHandler(), pushHandlingWakeLock: FakeFetchPushForegroundServiceManager = FakeFetchPushForegroundServiceManager(), ): VectorFirebaseMessagingService { return VectorFirebaseMessagingService().apply { - this.firebaseNewTokenHandler = firebaseNewTokenHandler + this.firebaseNewInstallationIdHandler = firebaseNewInstallationIdHandler this.pushParser = FirebasePushParser() this.pushHandler = pushHandler this.coroutineScope = this@createVectorFirebaseMessagingService diff --git a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTestTest.kt b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTestTest.kt index 8f9721a17d..963a4352ec 100644 --- a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTestTest.kt +++ b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTestTest.kt @@ -45,7 +45,7 @@ class FirebaseTokenTestTest { firebaseStore = firebaseStore, firebaseTroubleshooter = FakeFirebaseTroubleshooter( troubleShootResult = { - firebaseStore.storeFcmToken(FAKE_TOKEN) + firebaseStore.storeInstallationId(FAKE_TOKEN) Result.success(Unit) } ), @@ -70,7 +70,7 @@ class FirebaseTokenTestTest { firebaseStore = firebaseStore, firebaseTroubleshooter = FakeFirebaseTroubleshooter( troubleShootResult = { - firebaseStore.storeFcmToken(FAKE_TOKEN) + firebaseStore.storeInstallationId(FAKE_TOKEN) Result.success(Unit) } ),