First Commit

This commit is contained in:
2025-12-18 16:28:50 +07:00
commit 8c3e4f491f
9974 changed files with 396488 additions and 0 deletions
@@ -0,0 +1,7 @@
# Firebase
## Configuration
In order to make this module only know about Firebase, the plugin `com.google.gms.google-services` has been disabled from the `app` module.
To be able to change the values set to `google_app_id` in the file `build.gradle.kts` of this module, you should enable the plugin `com.google.gms.google-services` again, copy the file `google-services.json` to the folder `/app/src/main`, build the project, and check the generated file `app/build/generated/res/google-services/<buildtype>/values/values.xml` to import the generated values into the `build.gradle.kts` files.
@@ -0,0 +1,82 @@
/*
* 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.
*/
@file:Suppress("UnstableApiUsage")
import config.BuildTimeConfig
import extension.setupDependencyInjection
import extension.testCommonDependencies
plugins {
id("io.element.android-library")
}
android {
namespace = "io.element.android.libraries.pushproviders.firebase"
buildTypes {
getByName("release") {
consumerProguardFiles("consumer-proguard-rules.pro")
resValue(
type = "string",
name = "google_app_id",
value = BuildTimeConfig.GOOGLE_APP_ID_RELEASE,
)
}
getByName("debug") {
resValue(
type = "string",
name = "google_app_id",
value = BuildTimeConfig.GOOGLE_APP_ID_DEBUG,
)
}
register("nightly") {
consumerProguardFiles("consumer-proguard-rules.pro")
matchingFallbacks += listOf("release")
resValue(
type = "string",
name = "google_app_id",
value = BuildTimeConfig.GOOGLE_APP_ID_NIGHTLY,
)
}
}
}
setupDependencyInjection()
dependencies {
implementation(libs.androidx.corektx)
implementation(projects.features.enterprise.api)
implementation(projects.libraries.architecture)
implementation(projects.libraries.core)
implementation(projects.libraries.di)
implementation(projects.libraries.matrix.api)
implementation(projects.libraries.uiStrings)
implementation(projects.libraries.troubleshoot.api)
implementation(projects.services.toolbox.api)
implementation(projects.libraries.pushstore.api)
implementation(projects.libraries.pushproviders.api)
api(platform(libs.google.firebase.bom))
api("com.google.firebase:firebase-messaging") {
exclude(group = "com.google.firebase", module = "firebase-core")
exclude(group = "com.google.firebase", module = "firebase-analytics")
exclude(group = "com.google.firebase", module = "firebase-measurement-connector")
}
testCommonDependencies(libs)
testImplementation(libs.kotlinx.collections.immutable)
testImplementation(projects.features.enterprise.test)
testImplementation(projects.libraries.matrix.test)
testImplementation(projects.libraries.push.test)
testImplementation(projects.libraries.pushstore.test)
testImplementation(projects.libraries.sessionStorage.test)
testImplementation(projects.libraries.troubleshoot.test)
testImplementation(projects.services.toolbox.test)
}
@@ -0,0 +1,4 @@
# Fix this error:
# ERROR: Missing classes detected while running R8. Please add the missing classes or apply additional keep rules that are generated in /Users/bmarty/workspaces/element-x-android/app/build/outputs/mapping/nightly/missing_rules.txt.
# ERROR: R8: Missing class com.google.firebase.analytics.connector.AnalyticsConnector (referenced from: void com.google.firebase.messaging.MessagingAnalytics.logToScion(java.lang.String, android.os.Bundle) and 1 other context)
-dontwarn com.google.firebase.analytics.connector.AnalyticsConnector
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (c) 2025 Element Creations Ltd.
~ Copyright 2023 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<!-- Firebase components -->
<meta-data
android:name="firebase_analytics_collection_deactivated"
android:value="true" />
<service
android:name="io.element.android.libraries.pushproviders.firebase.VectorFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
</application>
</manifest>
@@ -0,0 +1,20 @@
/*
* 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
object FirebaseConfig {
/**
* It is the push gateway for firebase.
* Note: pusher_http_url should have path '/_matrix/push/v1/notify' -->
*/
const val PUSHER_HTTP_URL: String = "https://matrix.org/_matrix/push/v1/notify"
const val INDEX = 0
const val NAME = "Firebase"
}
@@ -0,0 +1,26 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 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.features.enterprise.api.EnterpriseService
interface FirebaseGatewayProvider {
fun getFirebaseGateway(): String
}
@ContributesBinding(AppScope::class)
class DefaultFirebaseGatewayProvider(
private val enterpriseService: EnterpriseService,
) : FirebaseGatewayProvider {
override fun getFirebaseGateway(): String {
return enterpriseService.firebasePushGateway() ?: FirebaseConfig.PUSHER_HTTP_URL
}
}
@@ -0,0 +1,70 @@
/*
* 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 dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesBinding
import io.element.android.libraries.core.extensions.flatMap
import io.element.android.libraries.core.log.logger.LoggerTag
import io.element.android.libraries.matrix.api.MatrixClientProvider
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.pushproviders.api.PusherSubscriber
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
import io.element.android.libraries.sessionstorage.api.SessionStore
import io.element.android.libraries.sessionstorage.api.toUserList
import timber.log.Timber
private val loggerTag = LoggerTag("FirebaseNewTokenHandler", LoggerTag.PushLoggerTag)
/**
* Handle new token receive from Firebase. Will update all the sessions which are using Firebase as a push provider.
*/
interface FirebaseNewTokenHandler {
suspend fun handle(firebaseToken: String)
}
@ContributesBinding(AppScope::class)
class DefaultFirebaseNewTokenHandler(
private val pusherSubscriber: PusherSubscriber,
private val sessionStore: SessionStore,
private val userPushStoreFactory: UserPushStoreFactory,
private val matrixClientProvider: MatrixClientProvider,
private val firebaseStore: FirebaseStore,
private val firebaseGatewayProvider: FirebaseGatewayProvider,
) : FirebaseNewTokenHandler {
override suspend fun handle(firebaseToken: String) {
firebaseStore.storeFcmToken(firebaseToken)
// Register the pusher for all the sessions
sessionStore.getAllSessions().toUserList()
.map { SessionId(it) }
.forEach { sessionId ->
val userDataStore = userPushStoreFactory.getOrCreate(sessionId)
if (userDataStore.getPushProviderName() == FirebaseConfig.NAME) {
matrixClientProvider
.getOrRestore(sessionId)
.onFailure {
Timber.tag(loggerTag.value).e(it, "Failed to restore session $sessionId")
}
.flatMap { client ->
pusherSubscriber
.registerPusher(
matrixClient = client,
pushKey = firebaseToken,
gateway = firebaseGatewayProvider.getFirebaseGateway(),
)
.onFailure {
Timber.tag(loggerTag.value).e(it, "Failed to register pusher for session $sessionId")
}
}
} else {
Timber.tag(loggerTag.value).d("This session is not using Firebase pusher")
}
}
}
}
@@ -0,0 +1,25 @@
/*
* 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 dev.zacsweers.metro.Inject
import io.element.android.libraries.pushproviders.api.PushData
@Inject
class FirebasePushParser {
fun parse(message: Map<String, String?>): PushData? {
val pushDataFirebase = PushDataFirebase(
eventId = message["event_id"],
roomId = message["room_id"],
unread = message["unread"]?.toIntOrNull(),
clientSecret = message["cs"],
)
return pushDataFirebase.toPushData()
}
}
@@ -0,0 +1,96 @@
/*
* 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 dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesIntoSet
import dev.zacsweers.metro.Inject
import io.element.android.libraries.core.log.logger.LoggerTag
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.pushproviders.api.Config
import io.element.android.libraries.pushproviders.api.Distributor
import io.element.android.libraries.pushproviders.api.PushProvider
import io.element.android.libraries.pushproviders.api.PusherSubscriber
import timber.log.Timber
private val loggerTag = LoggerTag("FirebasePushProvider", LoggerTag.PushLoggerTag)
@ContributesIntoSet(AppScope::class)
@Inject
class FirebasePushProvider(
private val firebaseStore: FirebaseStore,
private val pusherSubscriber: PusherSubscriber,
private val isPlayServiceAvailable: IsPlayServiceAvailable,
private val firebaseTokenRotator: FirebaseTokenRotator,
private val firebaseGatewayProvider: FirebaseGatewayProvider,
) : PushProvider {
override val index = FirebaseConfig.INDEX
override val name = FirebaseConfig.NAME
override val supportMultipleDistributors = false
override fun getDistributors(): List<Distributor> {
return listOfNotNull(
firebaseDistributor.takeIf { isPlayServiceAvailable.isAvailable() }
)
}
override suspend fun registerWith(matrixClient: MatrixClient, distributor: Distributor): Result<Unit> {
val pushKey = firebaseStore.getFcmToken() ?: return Result.failure<Unit>(
IllegalStateException(
"Unable to register pusher, Firebase token is not known."
)
).also {
Timber.tag(loggerTag.value).w("Unable to register pusher, Firebase token is not known.")
}
return pusherSubscriber.registerPusher(
matrixClient = matrixClient,
pushKey = pushKey,
gateway = firebaseGatewayProvider.getFirebaseGateway(),
)
}
override suspend fun getCurrentDistributorValue(sessionId: SessionId): String = firebaseDistributor.value
override suspend fun getCurrentDistributor(sessionId: SessionId) = firebaseDistributor
override suspend fun unregister(matrixClient: MatrixClient): Result<Unit> {
val pushKey = firebaseStore.getFcmToken()
return if (pushKey == null) {
Timber.tag(loggerTag.value).w("Unable to unregister pusher, Firebase token is not known.")
Result.success(Unit)
} else {
pusherSubscriber.unregisterPusher(matrixClient, pushKey, firebaseGatewayProvider.getFirebaseGateway())
}
}
/**
* Nothing to clean up here.
*/
override suspend fun onSessionDeleted(sessionId: SessionId) = Unit
override suspend fun getPushConfig(sessionId: SessionId): Config? {
return firebaseStore.getFcmToken()?.let { fcmToken ->
Config(
url = firebaseGatewayProvider.getFirebaseGateway(),
pushKey = fcmToken
)
}
}
override fun canRotateToken(): Boolean = true
override suspend fun rotateToken(): Result<Unit> {
return firebaseTokenRotator.rotate()
}
companion object {
private val firebaseDistributor = Distributor("Firebase", "Firebase")
}
}
@@ -0,0 +1,62 @@
/*
* 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 android.content.SharedPreferences
import androidx.core.content.edit
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesBinding
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onStart
/**
* This class store the Firebase token in SharedPrefs.
*/
interface FirebaseStore {
fun getFcmToken(): String?
fun fcmTokenFlow(): Flow<String?>
fun storeFcmToken(token: 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 fcmTokenFlow(): Flow<String?> {
val flow = MutableStateFlow(getFcmToken())
val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, k ->
if (k == PREFS_KEY_FCM_TOKEN) {
try {
flow.value = getFcmToken()
} catch (e: Exception) {
flow.value = null
}
}
}
return flow
.onStart { sharedPreferences.registerOnSharedPreferenceChangeListener(listener) }
.onCompletion { sharedPreferences.unregisterOnSharedPreferenceChangeListener(listener) }
}
override fun storeFcmToken(token: String?) {
sharedPreferences.edit {
putString(PREFS_KEY_FCM_TOKEN, token)
}
}
companion object {
private const val PREFS_KEY_FCM_TOKEN = "FCM_TOKEN"
}
}
@@ -0,0 +1,49 @@
/*
* 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)
}
}
}
}
@@ -0,0 +1,50 @@
/*
* 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)
}
}
}
}
@@ -0,0 +1,33 @@
/*
* 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<Unit>
}
/**
* 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<Unit> {
return runCatchingExceptions {
firebaseTokenDeleter.delete()
firebaseTokenGetter.get()
}
}
}
@@ -0,0 +1,33 @@
/*
* 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 dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesBinding
import io.element.android.libraries.core.extensions.runCatchingExceptions
interface FirebaseTroubleshooter {
suspend fun troubleshoot(): Result<Unit>
}
/**
* This class force retrieving and storage of the Firebase token.
*/
@ContributesBinding(AppScope::class)
class DefaultFirebaseTroubleshooter(
private val newTokenHandler: FirebaseNewTokenHandler,
private val firebaseTokenGetter: FirebaseTokenGetter,
) : FirebaseTroubleshooter {
override suspend fun troubleshoot(): Result<Unit> {
return runCatchingExceptions {
val token = firebaseTokenGetter.get()
newTokenHandler.handle(token)
}
}
}
@@ -0,0 +1,44 @@
/*
* 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 android.content.Context
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailabilityLight
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesBinding
import io.element.android.libraries.di.annotations.ApplicationContext
import timber.log.Timber
interface IsPlayServiceAvailable {
fun isAvailable(): Boolean
}
fun IsPlayServiceAvailable.checkAvailableOrThrow() {
if (!isAvailable()) {
throw Exception("No valid Google Play Services found. Cannot use FCM.").also(Timber::e)
}
}
@ContributesBinding(AppScope::class)
class DefaultIsPlayServiceAvailable(
@ApplicationContext private val context: Context,
) : IsPlayServiceAvailable {
override fun isAvailable(): Boolean {
val apiAvailability = GoogleApiAvailabilityLight.getInstance()
val resultCode = apiAvailability.isGooglePlayServicesAvailable(context)
return if (resultCode == ConnectionResult.SUCCESS) {
Timber.d("Google Play Services is available")
true
} else {
Timber.w("Google Play Services is not available")
false
}
}
}
@@ -0,0 +1,45 @@
/*
* 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 io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.pushproviders.api.PushData
/**
* In this case, the format is:
* <pre>
* {
* "event_id":"$anEventId",
* "room_id":"!aRoomId",
* "unread":"1",
* "prio":"high",
* "cs":"<client_secret>"
* }
* </pre>
* .
*/
data class PushDataFirebase(
val eventId: String?,
val roomId: String?,
val unread: Int?,
val clientSecret: String?
)
fun PushDataFirebase.toPushData(): PushData? {
val safeEventId = eventId?.let(::EventId) ?: return null
val safeRoomId = roomId?.let(::RoomId) ?: return null
val safeClientSecret = clientSecret ?: return null
return PushData(
eventId = safeEventId,
roomId = safeRoomId,
unread = unread,
clientSecret = safeClientSecret,
)
}
@@ -0,0 +1,63 @@
/*
* 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.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import dev.zacsweers.metro.Inject
import io.element.android.libraries.architecture.bindings
import io.element.android.libraries.core.log.logger.LoggerTag
import io.element.android.libraries.di.annotations.AppCoroutineScope
import io.element.android.libraries.pushproviders.api.PushHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import timber.log.Timber
private val loggerTag = LoggerTag("VectorFirebaseMessagingService", LoggerTag.PushLoggerTag)
class VectorFirebaseMessagingService : FirebaseMessagingService() {
@Inject lateinit var firebaseNewTokenHandler: FirebaseNewTokenHandler
@Inject lateinit var pushParser: FirebasePushParser
@Inject lateinit var pushHandler: PushHandler
@AppCoroutineScope
@Inject lateinit var coroutineScope: CoroutineScope
override fun onCreate() {
super.onCreate()
bindings<VectorFirebaseMessagingServiceBindings>().inject(this)
}
override fun onNewToken(token: String) {
Timber.tag(loggerTag.value).w("New Firebase token")
coroutineScope.launch {
firebaseNewTokenHandler.handle(token)
}
}
override fun onMessageReceived(message: RemoteMessage) {
Timber.tag(loggerTag.value).w("New Firebase message. Priority: ${message.priority}/${message.originalPriority}")
coroutineScope.launch {
val pushData = pushParser.parse(message.data)
if (pushData == null) {
Timber.tag(loggerTag.value).w("Invalid data received from Firebase")
pushHandler.handleInvalid(
providerInfo = FirebaseConfig.NAME,
data = message.data.keys.joinToString("\n") {
"$it: ${message.data[it]}"
},
)
} else {
pushHandler.handle(
pushData = pushData,
providerInfo = FirebaseConfig.NAME,
)
}
}
}
}
@@ -0,0 +1,17 @@
/*
* 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 dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesTo
@ContributesTo(AppScope::class)
interface VectorFirebaseMessagingServiceBindings {
fun inject(service: VectorFirebaseMessagingService)
}
@@ -0,0 +1,61 @@
/*
* 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.troubleshoot
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesIntoSet
import dev.zacsweers.metro.Inject
import io.element.android.libraries.pushproviders.firebase.FirebaseConfig
import io.element.android.libraries.pushproviders.firebase.IsPlayServiceAvailable
import io.element.android.libraries.pushproviders.firebase.R
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestDelegate
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState
import io.element.android.libraries.troubleshoot.api.test.TestFilterData
import io.element.android.services.toolbox.api.strings.StringProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.StateFlow
@ContributesIntoSet(AppScope::class)
@Inject
class FirebaseAvailabilityTest(
private val isPlayServiceAvailable: IsPlayServiceAvailable,
private val stringProvider: StringProvider,
) : NotificationTroubleshootTest {
override val order = 300
private val delegate = NotificationTroubleshootTestDelegate(
defaultName = stringProvider.getString(R.string.troubleshoot_notifications_test_firebase_availability_title),
defaultDescription = stringProvider.getString(R.string.troubleshoot_notifications_test_firebase_availability_description),
visibleWhenIdle = false,
fakeDelay = NotificationTroubleshootTestDelegate.LONG_DELAY,
)
override val state: StateFlow<NotificationTroubleshootTestState> = delegate.state
override fun isRelevant(data: TestFilterData): Boolean {
return data.currentPushProviderName == FirebaseConfig.NAME
}
override suspend fun run(coroutineScope: CoroutineScope) {
delegate.start()
val result = isPlayServiceAvailable.isAvailable()
if (result) {
delegate.updateState(
description = stringProvider.getString(R.string.troubleshoot_notifications_test_firebase_availability_success),
status = NotificationTroubleshootTestState.Status.Success
)
} else {
delegate.updateState(
description = stringProvider.getString(R.string.troubleshoot_notifications_test_firebase_availability_failure),
status = NotificationTroubleshootTestState.Status.Failure()
)
}
}
override suspend fun reset() = delegate.reset()
}
@@ -0,0 +1,84 @@
/*
* 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.troubleshoot
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesIntoSet
import dev.zacsweers.metro.Inject
import io.element.android.libraries.pushproviders.firebase.FirebaseConfig
import io.element.android.libraries.pushproviders.firebase.FirebaseStore
import io.element.android.libraries.pushproviders.firebase.FirebaseTroubleshooter
import io.element.android.libraries.pushproviders.firebase.R
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootNavigator
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestDelegate
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState
import io.element.android.libraries.troubleshoot.api.test.TestFilterData
import io.element.android.services.toolbox.api.strings.StringProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@ContributesIntoSet(AppScope::class)
@Inject
class FirebaseTokenTest(
private val firebaseStore: FirebaseStore,
private val firebaseTroubleshooter: FirebaseTroubleshooter,
private val stringProvider: StringProvider,
) : NotificationTroubleshootTest {
override val order = 310
private val delegate = NotificationTroubleshootTestDelegate(
defaultName = stringProvider.getString(R.string.troubleshoot_notifications_test_firebase_token_title),
defaultDescription = stringProvider.getString(R.string.troubleshoot_notifications_test_firebase_token_description),
visibleWhenIdle = false,
fakeDelay = NotificationTroubleshootTestDelegate.LONG_DELAY,
)
override val state: StateFlow<NotificationTroubleshootTestState> = delegate.state
override fun isRelevant(data: TestFilterData): Boolean {
return data.currentPushProviderName == FirebaseConfig.NAME
}
private var currentJob: Job? = null
override suspend fun run(coroutineScope: CoroutineScope) {
currentJob?.cancel()
delegate.start()
currentJob = firebaseStore.fcmTokenFlow()
.onEach { token ->
if (token != null) {
delegate.updateState(
description = stringProvider.getString(
R.string.troubleshoot_notifications_test_firebase_token_success,
"*****${token.takeLast(8)}"
),
status = NotificationTroubleshootTestState.Status.Success
)
} else {
delegate.updateState(
description = stringProvider.getString(R.string.troubleshoot_notifications_test_firebase_token_failure),
status = NotificationTroubleshootTestState.Status.Failure(hasQuickFix = true)
)
}
}
.launchIn(coroutineScope)
}
override suspend fun reset() = delegate.reset()
override suspend fun quickFix(
coroutineScope: CoroutineScope,
navigator: NotificationTroubleshootNavigator,
) {
delegate.start()
firebaseTroubleshooter.troubleshoot()
run(coroutineScope)
}
}
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="troubleshoot_notifications_test_firebase_availability_description">"Пераканайцеся, што Firebase даступны."</string>
<string name="troubleshoot_notifications_test_firebase_availability_failure">"Firebase недаступны."</string>
<string name="troubleshoot_notifications_test_firebase_availability_success">"Firebase даступны."</string>
<string name="troubleshoot_notifications_test_firebase_availability_title">"Праверыць Firebase"</string>
<string name="troubleshoot_notifications_test_firebase_token_description">"Пераканайцеся, што маркер Firebase даступны."</string>
<string name="troubleshoot_notifications_test_firebase_token_failure">"Маркер Firebase невядомы."</string>
<string name="troubleshoot_notifications_test_firebase_token_success">"Маркер Firebase: %1$s."</string>
<string name="troubleshoot_notifications_test_firebase_token_title">"Праверце маркер Firebase"</string>
</resources>
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="troubleshoot_notifications_test_firebase_availability_description">"Уверете се, че Firebase е наличен."</string>
<string name="troubleshoot_notifications_test_firebase_availability_failure">"Firebase не е наличен."</string>
<string name="troubleshoot_notifications_test_firebase_availability_success">"Firebase е наличен."</string>
<string name="troubleshoot_notifications_test_firebase_availability_title">"Проверка на Firebase"</string>
</resources>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="troubleshoot_notifications_test_firebase_availability_description">"Ujistěte se, že je k dispozici Firebase."</string>
<string name="troubleshoot_notifications_test_firebase_availability_failure">"Firebase není k dispozici."</string>
<string name="troubleshoot_notifications_test_firebase_availability_success">"Firebase je k dispozici."</string>
<string name="troubleshoot_notifications_test_firebase_availability_title">"Zkontrolovat Firebase"</string>
<string name="troubleshoot_notifications_test_firebase_token_description">"Ujistěte se, že je k dispozici Firebase token."</string>
<string name="troubleshoot_notifications_test_firebase_token_failure">"Firebase token není znám."</string>
<string name="troubleshoot_notifications_test_firebase_token_success">"Firebase token: %1$s."</string>
<string name="troubleshoot_notifications_test_firebase_token_title">"Zkontrolovat Firebase token"</string>
</resources>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="troubleshoot_notifications_test_firebase_availability_description">"Gwnewch yn siwr fod Firebase ar gael."</string>
<string name="troubleshoot_notifications_test_firebase_availability_failure">"Nid yw Firebase ar gael."</string>
<string name="troubleshoot_notifications_test_firebase_availability_success">"Mae Firebase ar gael."</string>
<string name="troubleshoot_notifications_test_firebase_availability_title">"Gwiriwch Firebase"</string>
<string name="troubleshoot_notifications_test_firebase_token_description">"Gwnewch yn siŵr fod tocyn Firebase ar gael."</string>
<string name="troubleshoot_notifications_test_firebase_token_failure">"Nid yw tocyn Firebase yn hysbys."</string>
<string name="troubleshoot_notifications_test_firebase_token_success">"Tocyn Firebase: %1$s."</string>
<string name="troubleshoot_notifications_test_firebase_token_title">"Gwiriwch tocyn Firebase"</string>
</resources>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="troubleshoot_notifications_test_firebase_availability_description">"Sørg for, at Firebase er tilgængelig."</string>
<string name="troubleshoot_notifications_test_firebase_availability_failure">"Firebase er ikke tilgængelig."</string>
<string name="troubleshoot_notifications_test_firebase_availability_success">"Firebase er tilgængelig."</string>
<string name="troubleshoot_notifications_test_firebase_availability_title">"Tjek Firebase"</string>
<string name="troubleshoot_notifications_test_firebase_token_description">"Sørg for, at Firebase-tokenet er tilgængeligt."</string>
<string name="troubleshoot_notifications_test_firebase_token_failure">"Firebase-tokenet er ikke kendt."</string>
<string name="troubleshoot_notifications_test_firebase_token_success">"Firebase-token: %1$s."</string>
<string name="troubleshoot_notifications_test_firebase_token_title">"Tjek Firebase-token"</string>
</resources>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="troubleshoot_notifications_test_firebase_availability_description">"Stelle sicher, dass Firebase verfügbar ist."</string>
<string name="troubleshoot_notifications_test_firebase_availability_failure">"Firebase ist nicht verfügbar."</string>
<string name="troubleshoot_notifications_test_firebase_availability_success">"Firebase ist verfügbar."</string>
<string name="troubleshoot_notifications_test_firebase_availability_title">"Überprüfe Firebase"</string>
<string name="troubleshoot_notifications_test_firebase_token_description">"Stelle sicher, dass der Firebase Token verfügbar ist."</string>
<string name="troubleshoot_notifications_test_firebase_token_failure">"Firebase Token ist nicht bekannt."</string>
<string name="troubleshoot_notifications_test_firebase_token_success">"Firebase Token: %1$s."</string>
<string name="troubleshoot_notifications_test_firebase_token_title">"Prüfe Firebase Token"</string>
</resources>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="troubleshoot_notifications_test_firebase_availability_description">"Βεβαιώσου ότι το Firebase είναι διαθέσιμο."</string>
<string name="troubleshoot_notifications_test_firebase_availability_failure">"Το Firebase δεν είναι διαθέσιμο."</string>
<string name="troubleshoot_notifications_test_firebase_availability_success">"Το Firebase είναι διαθέσιμο."</string>
<string name="troubleshoot_notifications_test_firebase_availability_title">"Έλεγχος Firebase"</string>
<string name="troubleshoot_notifications_test_firebase_token_description">"Βεβαιώσου ότι το διακριτικό του Firebase είναι διαθέσιμο."</string>
<string name="troubleshoot_notifications_test_firebase_token_failure">"Το διακριτικό Firebase δεν είναι γνωστό."</string>
<string name="troubleshoot_notifications_test_firebase_token_success">"Διακριτικό Firebase: %1$s."</string>
<string name="troubleshoot_notifications_test_firebase_token_title">"Έλεγξε το διακριτικό του Firebase"</string>
</resources>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="troubleshoot_notifications_test_firebase_availability_description">"Asegurarse de que Firebase esté disponible."</string>
<string name="troubleshoot_notifications_test_firebase_availability_failure">"Firebase no está disponible."</string>
<string name="troubleshoot_notifications_test_firebase_availability_success">"Firebase está disponible."</string>
<string name="troubleshoot_notifications_test_firebase_availability_title">"Verificar Firebase"</string>
<string name="troubleshoot_notifications_test_firebase_token_description">"Asegurarse de que el token de Firebase esté disponible."</string>
<string name="troubleshoot_notifications_test_firebase_token_failure">"Se desconoce el token de Firebase."</string>
<string name="troubleshoot_notifications_test_firebase_token_success">"Token de Firebase: %1$s."</string>
<string name="troubleshoot_notifications_test_firebase_token_title">"Verificar token de Firebase"</string>
</resources>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="troubleshoot_notifications_test_firebase_availability_description">"Palun veendu, et Firebase oleks saadaval."</string>
<string name="troubleshoot_notifications_test_firebase_availability_failure">"Firebase pole saadaval."</string>
<string name="troubleshoot_notifications_test_firebase_availability_success">"Firebase on saadaval."</string>
<string name="troubleshoot_notifications_test_firebase_availability_title">"Kontrolli Firebase\'i"</string>
<string name="troubleshoot_notifications_test_firebase_token_description">"Palun veendu, et Firebase\'i pääsuluba oleks saadaval."</string>
<string name="troubleshoot_notifications_test_firebase_token_failure">"Firebase\'i pääsuluba pole teada."</string>
<string name="troubleshoot_notifications_test_firebase_token_success">"Firebase\'i pääsuluba: %1$s."</string>
<string name="troubleshoot_notifications_test_firebase_token_title">"Kontrolli Firebase\'i pääsuluba"</string>
</resources>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="troubleshoot_notifications_test_firebase_availability_description">"Ziurtatu Firebase erabilgarri dagoela."</string>
<string name="troubleshoot_notifications_test_firebase_availability_failure">"Firebase ez dago erabilgarri."</string>
<string name="troubleshoot_notifications_test_firebase_availability_success">"Firebase eskuragarri dago."</string>
<string name="troubleshoot_notifications_test_firebase_availability_title">"Egiaztatu Firebase"</string>
<string name="troubleshoot_notifications_test_firebase_token_description">"Ziurtatu Firebaseren tokena erabilgarri dagoela."</string>
<string name="troubleshoot_notifications_test_firebase_token_failure">"Ez da Firebaseren tokena ezagutzen."</string>
<string name="troubleshoot_notifications_test_firebase_token_success">"Firebaseren tokena: %1$s."</string>
<string name="troubleshoot_notifications_test_firebase_token_title">"Egiaztatu Firebaseren tokena"</string>
</resources>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="troubleshoot_notifications_test_firebase_availability_description">"Varmistaa, että Firebase on käytettävissä."</string>
<string name="troubleshoot_notifications_test_firebase_availability_failure">"Firebase ei ole saatavilla."</string>
<string name="troubleshoot_notifications_test_firebase_availability_success">"Firebase on saatavilla."</string>
<string name="troubleshoot_notifications_test_firebase_availability_title">"Firebasen tarkistus"</string>
<string name="troubleshoot_notifications_test_firebase_token_description">"Varmistaa, että Firebase token on käytettävissä."</string>
<string name="troubleshoot_notifications_test_firebase_token_failure">"Firebase token ei ole tiedossa."</string>
<string name="troubleshoot_notifications_test_firebase_token_success">"Firebase token: %1$s."</string>
<string name="troubleshoot_notifications_test_firebase_token_title">"Firebase tokenin tarkistus"</string>
</resources>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="troubleshoot_notifications_test_firebase_availability_description">"Vérification que Firebase est disponible."</string>
<string name="troubleshoot_notifications_test_firebase_availability_failure">"Firebase nest pas disponible."</string>
<string name="troubleshoot_notifications_test_firebase_availability_success">"Firebase est disponible."</string>
<string name="troubleshoot_notifications_test_firebase_availability_title">"Vérification de Firebase"</string>
<string name="troubleshoot_notifications_test_firebase_token_description">"Vérifier que le jeton Firebase est disponible."</string>
<string name="troubleshoot_notifications_test_firebase_token_failure">"Le jeton Firebase nest pas connu."</string>
<string name="troubleshoot_notifications_test_firebase_token_success">"Jeton Firebase :%1$s."</string>
<string name="troubleshoot_notifications_test_firebase_token_title">"Vérifier le jeton Firebase"</string>
</resources>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="troubleshoot_notifications_test_firebase_availability_description">"Győződjön meg arról, hogy a Firebase elérhető-e."</string>
<string name="troubleshoot_notifications_test_firebase_availability_failure">"A Firebase nem érhető el."</string>
<string name="troubleshoot_notifications_test_firebase_availability_success">"A Firebase elérhető."</string>
<string name="troubleshoot_notifications_test_firebase_availability_title">"Ellenőrizze a Firebase-t"</string>
<string name="troubleshoot_notifications_test_firebase_token_description">"Győződjön meg arról, hogy a Firebase-token elérhető."</string>
<string name="troubleshoot_notifications_test_firebase_token_failure">"A Firebase-token nem ismert."</string>
<string name="troubleshoot_notifications_test_firebase_token_success">"Firebase-token: %1$s."</string>
<string name="troubleshoot_notifications_test_firebase_token_title">"Ellenőrizze a Firebase-tokent"</string>
</resources>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="troubleshoot_notifications_test_firebase_availability_description">"Pastikan bahwa Firebase tersedia."</string>
<string name="troubleshoot_notifications_test_firebase_availability_failure">"Firebase tidak tersedia."</string>
<string name="troubleshoot_notifications_test_firebase_availability_success">"Firebase tersedia."</string>
<string name="troubleshoot_notifications_test_firebase_availability_title">"Periksa Firebase"</string>
<string name="troubleshoot_notifications_test_firebase_token_description">"Pastikan token Firebase tersedia."</string>
<string name="troubleshoot_notifications_test_firebase_token_failure">"Token Firebase tidak diketahui."</string>
<string name="troubleshoot_notifications_test_firebase_token_success">"Token Firebase: %1$s."</string>
<string name="troubleshoot_notifications_test_firebase_token_title">"Periksa token Firebase"</string>
</resources>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="troubleshoot_notifications_test_firebase_availability_description">"Assicurati che Firebase sia disponibile."</string>
<string name="troubleshoot_notifications_test_firebase_availability_failure">"Firebase non è disponibile."</string>
<string name="troubleshoot_notifications_test_firebase_availability_success">"Firebase è disponibile."</string>
<string name="troubleshoot_notifications_test_firebase_availability_title">"Controlla Firebase"</string>
<string name="troubleshoot_notifications_test_firebase_token_description">"Assicurati che il token di Firebase sia disponibile."</string>
<string name="troubleshoot_notifications_test_firebase_token_failure">"Il token di Firebase non è noto."</string>
<string name="troubleshoot_notifications_test_firebase_token_success">"Token Firebase: %1$s."</string>
<string name="troubleshoot_notifications_test_firebase_token_title">"Verifica il token di Firebase"</string>
</resources>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="troubleshoot_notifications_test_firebase_availability_description">"დარწმუნდით რომ Firebase ხელმისაწვდომია."</string>
<string name="troubleshoot_notifications_test_firebase_availability_failure">"Firebase არაა ხელმისაწვდომი."</string>
<string name="troubleshoot_notifications_test_firebase_availability_success">"Firebase ხელმისაწვდომია."</string>
<string name="troubleshoot_notifications_test_firebase_availability_title">"Firebase-ის შემოწმება"</string>
<string name="troubleshoot_notifications_test_firebase_token_description">"დარწმუნდით რომ Firebase ტოკენი ხელმისაწვდომია."</string>
<string name="troubleshoot_notifications_test_firebase_token_failure">"Firebase ტოკენი უცნობია."</string>
<string name="troubleshoot_notifications_test_firebase_token_success">"Firebase ტოკენი: %1$s."</string>
<string name="troubleshoot_notifications_test_firebase_token_title">"შეამოწმეთ Firebase ტოკენი"</string>
</resources>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="troubleshoot_notifications_test_firebase_availability_description">"Firebase를 사용할 수 있는지 확인하세요."</string>
<string name="troubleshoot_notifications_test_firebase_availability_failure">"Firebase는 사용할 수 없습니다."</string>
<string name="troubleshoot_notifications_test_firebase_availability_success">"Firebase를 사용할 수 있습니다."</string>
<string name="troubleshoot_notifications_test_firebase_availability_title">"Firebase 확인"</string>
<string name="troubleshoot_notifications_test_firebase_token_description">"Firebase 토큰을 사용할 수 있는지 확인하세요."</string>
<string name="troubleshoot_notifications_test_firebase_token_failure">"Firebase 토큰이 인식되지 않았습니다."</string>
<string name="troubleshoot_notifications_test_firebase_token_success">"Firebase 토큰: %1$s."</string>
<string name="troubleshoot_notifications_test_firebase_token_title">"Firebase 토큰 확인"</string>
</resources>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="troubleshoot_notifications_test_firebase_availability_description">"Sørg for at Firebase er tilgjengelig."</string>
<string name="troubleshoot_notifications_test_firebase_availability_failure">"Firebase er ikke tilgjengelig."</string>
<string name="troubleshoot_notifications_test_firebase_availability_success">"Firebase er tilgjengelig."</string>
<string name="troubleshoot_notifications_test_firebase_availability_title">"Sjekk Firebase"</string>
<string name="troubleshoot_notifications_test_firebase_token_description">"Sørg for at Firebase-token er tilgjengelig."</string>
<string name="troubleshoot_notifications_test_firebase_token_failure">"Firebase-token er ikke kjent."</string>
<string name="troubleshoot_notifications_test_firebase_token_success">"Firebase-token: %1$s."</string>
<string name="troubleshoot_notifications_test_firebase_token_title">"Sjekk Firebase-token"</string>
</resources>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="troubleshoot_notifications_test_firebase_availability_description">"Zorg ervoor dat Firebase beschikbaar is."</string>
<string name="troubleshoot_notifications_test_firebase_availability_failure">"Firebase is niet beschikbaar."</string>
<string name="troubleshoot_notifications_test_firebase_availability_success">"Firebase is beschikbaar."</string>
<string name="troubleshoot_notifications_test_firebase_availability_title">"Firebase controleren"</string>
<string name="troubleshoot_notifications_test_firebase_token_description">"Zorg ervoor dat de Firebase-token beschikbaar is."</string>
<string name="troubleshoot_notifications_test_firebase_token_failure">"Firebase-token is niet bekend."</string>
<string name="troubleshoot_notifications_test_firebase_token_success">"Firebase-token: %1$s."</string>
<string name="troubleshoot_notifications_test_firebase_token_title">"Firebase-token controleren"</string>
</resources>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="troubleshoot_notifications_test_firebase_availability_description">"Upewnij się, że Firebase jest dostępny."</string>
<string name="troubleshoot_notifications_test_firebase_availability_failure">"Baza Firebase jest niedostępna."</string>
<string name="troubleshoot_notifications_test_firebase_availability_success">"Baza Firebase jest dostępna."</string>
<string name="troubleshoot_notifications_test_firebase_availability_title">"Sprawdź Firebase"</string>
<string name="troubleshoot_notifications_test_firebase_token_description">"Upewnij się, że token Firebase jest dostępny."</string>
<string name="troubleshoot_notifications_test_firebase_token_failure">"Token Firebase nie jest znany."</string>
<string name="troubleshoot_notifications_test_firebase_token_success">"Token Firebase: %1$s."</string>
<string name="troubleshoot_notifications_test_firebase_token_title">"Sprawdź token Firebase"</string>
</resources>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="troubleshoot_notifications_test_firebase_availability_description">"Certifique-se de que o Firebase esteja disponível."</string>
<string name="troubleshoot_notifications_test_firebase_availability_failure">"O Firebase não está disponível."</string>
<string name="troubleshoot_notifications_test_firebase_availability_success">"O Firebase está disponível."</string>
<string name="troubleshoot_notifications_test_firebase_availability_title">"Verificar o Firebase"</string>
<string name="troubleshoot_notifications_test_firebase_token_description">"Certifique-se de que o token do Firebase esteja disponível."</string>
<string name="troubleshoot_notifications_test_firebase_token_failure">"O token do Firebase não é conhecido."</string>
<string name="troubleshoot_notifications_test_firebase_token_success">"Token do Firebase: %1$s."</string>
<string name="troubleshoot_notifications_test_firebase_token_title">"Verificar o token do Firebase"</string>
</resources>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="troubleshoot_notifications_test_firebase_availability_description">"Certifica que a Firebase está disponível"</string>
<string name="troubleshoot_notifications_test_firebase_availability_failure">"Firebase indisponível."</string>
<string name="troubleshoot_notifications_test_firebase_availability_success">"Firebase disponível."</string>
<string name="troubleshoot_notifications_test_firebase_availability_title">"Verificar a Firebase"</string>
<string name="troubleshoot_notifications_test_firebase_token_description">"Certifica que o \"token\" da Firebase está disponível."</string>
<string name="troubleshoot_notifications_test_firebase_token_failure">"\"Token\" da Firebase desconhecido."</string>
<string name="troubleshoot_notifications_test_firebase_token_success">"\"Token\" da Firebase: %1$s."</string>
<string name="troubleshoot_notifications_test_firebase_token_title">"Verificar \"token\" da Firebase"</string>
</resources>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="troubleshoot_notifications_test_firebase_availability_description">"Asigurați-vă că Firebase este disponibil."</string>
<string name="troubleshoot_notifications_test_firebase_availability_failure">"Firebase nu este disponibil."</string>
<string name="troubleshoot_notifications_test_firebase_availability_success">"Firebase este disponibil."</string>
<string name="troubleshoot_notifications_test_firebase_availability_title">"Verificați Firebase"</string>
<string name="troubleshoot_notifications_test_firebase_token_description">"Asigurați-vă că tokenul Firebase este disponibil."</string>
<string name="troubleshoot_notifications_test_firebase_token_failure">"Tokenul Firebase nu este cunoscut."</string>
<string name="troubleshoot_notifications_test_firebase_token_success">"Token Firebase: %1$s."</string>
<string name="troubleshoot_notifications_test_firebase_token_title">"Verificați token-ul Firebase"</string>
</resources>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="troubleshoot_notifications_test_firebase_availability_description">"Убедитесь, что Firebase доступен."</string>
<string name="troubleshoot_notifications_test_firebase_availability_failure">"Firebase недоступен."</string>
<string name="troubleshoot_notifications_test_firebase_availability_success">"Firebase доступен."</string>
<string name="troubleshoot_notifications_test_firebase_availability_title">"Проверить Firebase"</string>
<string name="troubleshoot_notifications_test_firebase_token_description">"Убедитесь, что токен Firebase доступен."</string>
<string name="troubleshoot_notifications_test_firebase_token_failure">"Токен Firebase неизвестен."</string>
<string name="troubleshoot_notifications_test_firebase_token_success">"Токен Firebase: %1$s."</string>
<string name="troubleshoot_notifications_test_firebase_token_title">"Проверить токен Firebase"</string>
</resources>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="troubleshoot_notifications_test_firebase_availability_description">"Uistite sa, že Firebase je k dispozícii."</string>
<string name="troubleshoot_notifications_test_firebase_availability_failure">"Firebase nie je k dispozícii."</string>
<string name="troubleshoot_notifications_test_firebase_availability_success">"Firebase je k dispozícii."</string>
<string name="troubleshoot_notifications_test_firebase_availability_title">"Skontrolovať Firebase"</string>
<string name="troubleshoot_notifications_test_firebase_token_description">"Uistite sa, že je k dispozícii token Firebase."</string>
<string name="troubleshoot_notifications_test_firebase_token_failure">"Token Firebase nie je známy."</string>
<string name="troubleshoot_notifications_test_firebase_token_success">"Token Firebase: %1$s."</string>
<string name="troubleshoot_notifications_test_firebase_token_title">"Skontrolovať token Firebase"</string>
</resources>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="troubleshoot_notifications_test_firebase_availability_description">"Se till att Firebase är tillgängligt."</string>
<string name="troubleshoot_notifications_test_firebase_availability_failure">"Firebase är inte tillgängligt."</string>
<string name="troubleshoot_notifications_test_firebase_availability_success">"Firebase är tillgänglig."</string>
<string name="troubleshoot_notifications_test_firebase_availability_title">"Kontrollera Firebase"</string>
<string name="troubleshoot_notifications_test_firebase_token_description">"Se till att Firebase-token är tillgänglig."</string>
<string name="troubleshoot_notifications_test_firebase_token_failure">"Firebase-token är inte känd."</string>
<string name="troubleshoot_notifications_test_firebase_token_success">"Firebase-token: %1$s."</string>
<string name="troubleshoot_notifications_test_firebase_token_title">"Kontrollera Firebase-token"</string>
</resources>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="troubleshoot_notifications_test_firebase_availability_description">"Firebase\'in kullanılabilir olduğundan emin olun."</string>
<string name="troubleshoot_notifications_test_firebase_availability_failure">"Firebase kullanılamıyor."</string>
<string name="troubleshoot_notifications_test_firebase_availability_success">"Firebase kullanılabilir."</string>
<string name="troubleshoot_notifications_test_firebase_availability_title">"Firebase\'i kontrol et"</string>
<string name="troubleshoot_notifications_test_firebase_token_description">"Firebase belirtecinin mevcut olduğundan emin olun."</string>
<string name="troubleshoot_notifications_test_firebase_token_failure">"Firebase belirteci bilinmiyor."</string>
<string name="troubleshoot_notifications_test_firebase_token_success">"Firebase belirteci: %1$s."</string>
<string name="troubleshoot_notifications_test_firebase_token_title">"Firebase jetonunu kontrol edin"</string>
</resources>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="troubleshoot_notifications_test_firebase_availability_description">"Переконується, що Firebase доступний."</string>
<string name="troubleshoot_notifications_test_firebase_availability_failure">"Firebase недоступний."</string>
<string name="troubleshoot_notifications_test_firebase_availability_success">"Firebase доступний."</string>
<string name="troubleshoot_notifications_test_firebase_availability_title">"Перевірка Firebase"</string>
<string name="troubleshoot_notifications_test_firebase_token_description">"Переконується, що токен Firebase доступний."</string>
<string name="troubleshoot_notifications_test_firebase_token_failure">"Токен Firebase невідомий."</string>
<string name="troubleshoot_notifications_test_firebase_token_success">"Токен Firebase: %1$s."</string>
<string name="troubleshoot_notifications_test_firebase_token_title">"Перевірка токена Firebase"</string>
</resources>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="troubleshoot_notifications_test_firebase_availability_description">"یقینی بنائیں کہ Firebase دستیاب ہے۔"</string>
<string name="troubleshoot_notifications_test_firebase_availability_failure">"Firebase دستیاب نہیں ہے۔"</string>
<string name="troubleshoot_notifications_test_firebase_availability_success">"Firebase دستیاب ہے۔"</string>
<string name="troubleshoot_notifications_test_firebase_availability_title">"Firebase کی پڑتال کریں"</string>
<string name="troubleshoot_notifications_test_firebase_token_description">"یقینی بنائیں کہ Firebase رمزِ ممیز دستیاب ہے۔"</string>
<string name="troubleshoot_notifications_test_firebase_token_failure">"Firebase رمزِ ممیز معلوم نہیں ہے۔"</string>
<string name="troubleshoot_notifications_test_firebase_token_success">"Firebase رمزِ ممیز:%1$s۔"</string>
<string name="troubleshoot_notifications_test_firebase_token_title">"Firebase رمزِ ممیز کی پڑتال کریں"</string>
</resources>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="troubleshoot_notifications_test_firebase_availability_description">"Firebase mavjudligiga ishonch hosil qiling."</string>
<string name="troubleshoot_notifications_test_firebase_availability_failure">"Firebase mavjud emas."</string>
<string name="troubleshoot_notifications_test_firebase_availability_success">"Firebase mavjud."</string>
<string name="troubleshoot_notifications_test_firebase_availability_title">"Firebase-ni tekshiring"</string>
<string name="troubleshoot_notifications_test_firebase_token_description">"Firebase tokeni mavjudligiga ishonch hosil qiling."</string>
<string name="troubleshoot_notifications_test_firebase_token_failure">"Firebase mavjudligiga ishonch hosil qiling."</string>
<string name="troubleshoot_notifications_test_firebase_token_success">"Firebase tokeni: %1$s ."</string>
<string name="troubleshoot_notifications_test_firebase_token_title">"Firebase tokenini tekshiring"</string>
</resources>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="troubleshoot_notifications_test_firebase_availability_description">"確保 Firebase 可用。"</string>
<string name="troubleshoot_notifications_test_firebase_availability_failure">"Firebase 不可用。"</string>
<string name="troubleshoot_notifications_test_firebase_availability_success">"Firebase 可用。"</string>
<string name="troubleshoot_notifications_test_firebase_availability_title">"檢查 Firebase"</string>
<string name="troubleshoot_notifications_test_firebase_token_description">"確保 Firebase 權杖可用。"</string>
<string name="troubleshoot_notifications_test_firebase_token_failure">"Firebase 權杖未知。"</string>
<string name="troubleshoot_notifications_test_firebase_token_success">"Firebase 權杖:%1$s。"</string>
<string name="troubleshoot_notifications_test_firebase_token_title">"檢查 Firebase 權杖"</string>
</resources>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="troubleshoot_notifications_test_firebase_availability_description">"确保 Firebase 可用。"</string>
<string name="troubleshoot_notifications_test_firebase_availability_failure">"Firebase 不可用。"</string>
<string name="troubleshoot_notifications_test_firebase_availability_success">"Firebase 可用。"</string>
<string name="troubleshoot_notifications_test_firebase_availability_title">"检查 Firebase"</string>
<string name="troubleshoot_notifications_test_firebase_token_description">"确保 Firebase 令牌可用。"</string>
<string name="troubleshoot_notifications_test_firebase_token_failure">"Firebase 令牌未知。"</string>
<string name="troubleshoot_notifications_test_firebase_token_success">"Firebase 令牌:%1$s 。"</string>
<string name="troubleshoot_notifications_test_firebase_token_title">"检查 Firebase 令牌"</string>
</resources>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="default_web_client_id" translatable="false">912726360885-e87n3jva9uoj4vbidvijq78ebg02asv2.apps.googleusercontent.com</string>
<string name="firebase_database_url" translatable="false">https://vector-alpha.firebaseio.com</string>
<string name="gcm_defaultSenderId" translatable="false">912726360885</string>
<string name="google_api_key" translatable="false">AIzaSyAFZX8IhIfgzdOZvxDP_ISO5WYoU7jmQ5c</string>
<string name="google_crash_reporting_api_key" translatable="false">AIzaSyAFZX8IhIfgzdOZvxDP_ISO5WYoU7jmQ5c</string>
<string name="google_storage_bucket" translatable="false">vector-alpha.appspot.com</string>
<string name="project_id" translatable="false" tools:ignore="UnusedResources">vector-alpha</string>
</resources>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="troubleshoot_notifications_test_firebase_availability_description">"Ensure that Firebase is available."</string>
<string name="troubleshoot_notifications_test_firebase_availability_failure">"Firebase is not available."</string>
<string name="troubleshoot_notifications_test_firebase_availability_success">"Firebase is available."</string>
<string name="troubleshoot_notifications_test_firebase_availability_title">"Check Firebase"</string>
<string name="troubleshoot_notifications_test_firebase_token_description">"Ensure that Firebase token is available."</string>
<string name="troubleshoot_notifications_test_firebase_token_failure">"Firebase token is not known."</string>
<string name="troubleshoot_notifications_test_firebase_token_success">"Firebase token: %1$s."</string>
<string name="troubleshoot_notifications_test_firebase_token_title">"Check Firebase token"</string>
</resources>
@@ -0,0 +1,155 @@
/*
* 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 com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.MatrixClientProvider
import io.element.android.libraries.matrix.test.AN_EXCEPTION
import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.A_USER_ID_2
import io.element.android.libraries.matrix.test.A_USER_ID_3
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
import io.element.android.libraries.push.test.FakePusherSubscriber
import io.element.android.libraries.pushproviders.api.PusherSubscriber
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
import io.element.android.libraries.pushstore.test.userpushstore.FakeUserPushStore
import io.element.android.libraries.pushstore.test.userpushstore.FakeUserPushStoreFactory
import io.element.android.libraries.sessionstorage.api.SessionStore
import io.element.android.libraries.sessionstorage.test.InMemorySessionStore
import io.element.android.libraries.sessionstorage.test.aSessionData
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value
import kotlinx.coroutines.test.runTest
import org.junit.Test
class DefaultFirebaseNewTokenHandlerTest {
@Test
fun `when a new token is received it is stored in the firebase store`() = runTest {
val firebaseStore = InMemoryFirebaseStore()
assertThat(firebaseStore.getFcmToken()).isNull()
val firebaseNewTokenHandler = createDefaultFirebaseNewTokenHandler(
firebaseStore = firebaseStore,
)
firebaseNewTokenHandler.handle("aToken")
assertThat(firebaseStore.getFcmToken()).isEqualTo("aToken")
}
@Test
fun `when a new token is received, the handler registers the pusher again to all sessions using Firebase`() = runTest {
val aMatrixClient1 = FakeMatrixClient(A_USER_ID)
val aMatrixClient2 = FakeMatrixClient(A_USER_ID_2)
val aMatrixClient3 = FakeMatrixClient(A_USER_ID_3)
val registerPusherResult = lambdaRecorder<MatrixClient, String, String, Result<Unit>> { _, _, _ -> Result.success(Unit) }
val pusherSubscriber = FakePusherSubscriber(registerPusherResult = registerPusherResult)
val firebaseNewTokenHandler = createDefaultFirebaseNewTokenHandler(
sessionStore = InMemorySessionStore(
initialList = listOf(
aSessionData(A_USER_ID.value),
aSessionData(A_USER_ID_2.value),
aSessionData(A_USER_ID_3.value),
)
),
matrixClientProvider = FakeMatrixClientProvider { sessionId ->
when (sessionId) {
A_USER_ID -> Result.success(aMatrixClient1)
A_USER_ID_2 -> Result.success(aMatrixClient2)
A_USER_ID_3 -> Result.success(aMatrixClient3)
else -> Result.failure(IllegalStateException())
}
},
userPushStoreFactory = FakeUserPushStoreFactory(
userPushStore = { sessionId ->
when (sessionId) {
A_USER_ID -> FakeUserPushStore(pushProviderName = FirebaseConfig.NAME)
A_USER_ID_2 -> FakeUserPushStore(pushProviderName = "Other")
A_USER_ID_3 -> FakeUserPushStore(pushProviderName = FirebaseConfig.NAME)
else -> error("Unexpected sessionId: $sessionId")
}
}
),
pusherSubscriber = pusherSubscriber,
)
firebaseNewTokenHandler.handle("aToken")
registerPusherResult.assertions()
.isCalledExactly(2)
.withSequence(
listOf(value(aMatrixClient1), value("aToken"), value(A_FIREBASE_GATEWAY)),
listOf(value(aMatrixClient3), value("aToken"), value(A_FIREBASE_GATEWAY)),
)
}
@Test
fun `when a new token is received, if the session cannot be restore, nothing happen`() = runTest {
val registerPusherResult = lambdaRecorder<MatrixClient, String, String, Result<Unit>> { _, _, _ -> Result.success(Unit) }
val pusherSubscriber = FakePusherSubscriber(registerPusherResult = registerPusherResult)
val firebaseNewTokenHandler = createDefaultFirebaseNewTokenHandler(
sessionStore = InMemorySessionStore(
initialList = listOf(aSessionData(A_USER_ID.value))
),
matrixClientProvider = FakeMatrixClientProvider {
Result.failure(IllegalStateException())
},
userPushStoreFactory = FakeUserPushStoreFactory(
userPushStore = { _ ->
FakeUserPushStore(pushProviderName = FirebaseConfig.NAME)
}
),
pusherSubscriber = pusherSubscriber,
)
firebaseNewTokenHandler.handle("aToken")
registerPusherResult.assertions()
.isNeverCalled()
}
@Test
fun `when a new token is received, error when registering the pusher is ignored`() = runTest {
val aMatrixClient1 = FakeMatrixClient(A_USER_ID)
val registerPusherResult = lambdaRecorder<MatrixClient, String, String, Result<Unit>> { _, _, _ -> Result.failure(AN_EXCEPTION) }
val pusherSubscriber = FakePusherSubscriber(registerPusherResult = registerPusherResult)
val firebaseNewTokenHandler = createDefaultFirebaseNewTokenHandler(
sessionStore = InMemorySessionStore(
initialList = listOf(aSessionData(A_USER_ID.value))
),
matrixClientProvider = FakeMatrixClientProvider {
Result.success(aMatrixClient1)
},
userPushStoreFactory = FakeUserPushStoreFactory(
userPushStore = { _ ->
FakeUserPushStore(pushProviderName = FirebaseConfig.NAME)
}
),
pusherSubscriber = pusherSubscriber,
)
firebaseNewTokenHandler.handle("aToken")
registerPusherResult.assertions()
registerPusherResult.assertions()
.isCalledOnce()
.with(value(aMatrixClient1), value("aToken"), value(A_FIREBASE_GATEWAY))
}
private fun createDefaultFirebaseNewTokenHandler(
pusherSubscriber: PusherSubscriber = FakePusherSubscriber(),
sessionStore: SessionStore = InMemorySessionStore(),
userPushStoreFactory: UserPushStoreFactory = FakeUserPushStoreFactory(),
matrixClientProvider: MatrixClientProvider = FakeMatrixClientProvider(),
firebaseStore: FirebaseStore = InMemoryFirebaseStore(),
firebaseGatewayProvider: FirebaseGatewayProvider = FakeFirebaseGatewayProvider(),
): FirebaseNewTokenHandler {
return DefaultFirebaseNewTokenHandler(
pusherSubscriber = pusherSubscriber,
sessionStore = sessionStore,
userPushStoreFactory = userPushStoreFactory,
matrixClientProvider = matrixClientProvider,
firebaseStore = firebaseStore,
firebaseGatewayProvider = firebaseGatewayProvider,
)
}
}
@@ -0,0 +1,17 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 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
const val A_FIREBASE_GATEWAY = "aGateway"
class FakeFirebaseGatewayProvider(
private val firebaseGatewayResult: () -> String = { A_FIREBASE_GATEWAY }
) : FirebaseGatewayProvider {
override fun getFirebaseGateway() = firebaseGatewayResult()
}
@@ -0,0 +1,19 @@
/*
* 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 io.element.android.tests.testutils.lambda.lambdaError
class FakeFirebaseNewTokenHandler(
private val handleResult: (String) -> Unit = { lambdaError() }
) : FirebaseNewTokenHandler {
override suspend fun handle(firebaseToken: String) {
handleResult(firebaseToken)
}
}
@@ -0,0 +1,19 @@
/*
* 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 io.element.android.tests.testutils.lambda.lambdaError
class FakeFirebaseTokenRotator(
private val rotateWithResult: () -> Result<Unit> = { lambdaError() }
) : FirebaseTokenRotator {
override suspend fun rotate(): Result<Unit> {
return rotateWithResult()
}
}
@@ -0,0 +1,19 @@
/*
* 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 io.element.android.tests.testutils.simulateLongTask
class FakeFirebaseTroubleshooter(
private val troubleShootResult: () -> Result<Unit> = { Result.success(Unit) }
) : FirebaseTroubleshooter {
override suspend fun troubleshoot(): Result<Unit> = simulateLongTask {
troubleShootResult()
}
}
@@ -0,0 +1,15 @@
/*
* 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
class FakeIsPlayServiceAvailable(
private val isAvailable: Boolean,
) : IsPlayServiceAvailable {
override fun isAvailable() = isAvailable
}
@@ -0,0 +1,88 @@
/*
* 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.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.pushproviders.api.PushData
import io.element.android.tests.testutils.assertThrowsInDebug
import org.junit.Test
class FirebasePushParserTest {
private val validData = PushData(
eventId = AN_EVENT_ID,
roomId = A_ROOM_ID,
unread = 1,
clientSecret = "a-secret"
)
@Test
fun `test edge cases Firebase`() {
val pushParser = FirebasePushParser()
// Empty Json
assertThat(pushParser.parse(emptyMap())).isNull()
// Bad Json
assertThat(pushParser.parse(FIREBASE_PUSH_DATA.mutate("unread", "str"))).isEqualTo(validData.copy(unread = null))
// Extra data
assertThat(pushParser.parse(FIREBASE_PUSH_DATA.mutate("extra", "5"))).isEqualTo(validData)
}
@Test
fun `test Firebase format`() {
val pushParser = FirebasePushParser()
assertThat(pushParser.parse(FIREBASE_PUSH_DATA)).isEqualTo(validData)
}
@Test
fun `test empty roomId`() {
val pushParser = FirebasePushParser()
assertThat(pushParser.parse(FIREBASE_PUSH_DATA.mutate("room_id", null))).isNull()
assertThrowsInDebug { pushParser.parse(FIREBASE_PUSH_DATA.mutate("room_id", "")) }
}
@Test
fun `test invalid roomId`() {
val pushParser = FirebasePushParser()
assertThrowsInDebug { pushParser.parse(FIREBASE_PUSH_DATA.mutate("room_id", "aRoomId:domain")) }
}
@Test
fun `test empty eventId`() {
val pushParser = FirebasePushParser()
assertThat(pushParser.parse(FIREBASE_PUSH_DATA.mutate("event_id", null))).isNull()
assertThrowsInDebug { pushParser.parse(FIREBASE_PUSH_DATA.mutate("event_id", "")) }
}
@Test
fun `test empty client secret`() {
val pushParser = FirebasePushParser()
assertThat(pushParser.parse(FIREBASE_PUSH_DATA.mutate("cs", null))).isNull()
}
@Test
fun `test invalid eventId`() {
val pushParser = FirebasePushParser()
assertThrowsInDebug { pushParser.parse(FIREBASE_PUSH_DATA.mutate("event_id", "anEventId")) }
}
companion object {
private val FIREBASE_PUSH_DATA = mapOf(
"event_id" to AN_EVENT_ID.value,
"room_id" to A_ROOM_ID.value,
"unread" to "1",
"prio" to "high",
"cs" to "a-secret",
)
}
}
private fun Map<String, String?>.mutate(key: String, value: String?): Map<String, String?> {
return toMutableMap().apply { put(key, value) }
}
@@ -0,0 +1,208 @@
/*
* 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 com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.test.AN_EXCEPTION
import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.push.test.FakePusherSubscriber
import io.element.android.libraries.pushproviders.api.Config
import io.element.android.libraries.pushproviders.api.Distributor
import io.element.android.libraries.pushproviders.api.PusherSubscriber
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value
import kotlinx.coroutines.test.runTest
import org.junit.Test
class FirebasePushProviderTest {
@Test
fun `test index and name`() {
val firebasePushProvider = createFirebasePushProvider()
assertThat(firebasePushProvider.name).isEqualTo(FirebaseConfig.NAME)
assertThat(firebasePushProvider.index).isEqualTo(FirebaseConfig.INDEX)
}
@Test
fun `getDistributors return the unique distributor if available`() {
val firebasePushProvider = createFirebasePushProvider(
isPlayServiceAvailable = FakeIsPlayServiceAvailable(isAvailable = true)
)
val result = firebasePushProvider.getDistributors()
assertThat(result).containsExactly(Distributor("Firebase", "Firebase"))
}
@Test
fun `getDistributors return empty list if service is not available`() {
val firebasePushProvider = createFirebasePushProvider(
isPlayServiceAvailable = FakeIsPlayServiceAvailable(isAvailable = false)
)
val result = firebasePushProvider.getDistributors()
assertThat(result).isEmpty()
}
@Test
fun `getCurrentDistributor always returns the unique distributor`() = runTest {
val firebasePushProvider = createFirebasePushProvider()
val result = firebasePushProvider.getCurrentDistributor(A_SESSION_ID)
assertThat(result).isEqualTo(Distributor("Firebase", "Firebase"))
}
@Test
fun `register ok`() = runTest {
val matrixClient = FakeMatrixClient()
val registerPusherResultLambda = lambdaRecorder<MatrixClient, String, String, Result<Unit>> { _, _, _ -> Result.success(Unit) }
val firebasePushProvider = createFirebasePushProvider(
firebaseStore = InMemoryFirebaseStore(
token = "aToken"
),
pusherSubscriber = FakePusherSubscriber(
registerPusherResult = registerPusherResultLambda
)
)
val result = firebasePushProvider.registerWith(matrixClient, Distributor("value", "Name"))
assertThat(result).isEqualTo(Result.success(Unit))
registerPusherResultLambda.assertions()
.isCalledOnce()
.with(value(matrixClient), value("aToken"), value(A_FIREBASE_GATEWAY))
}
@Test
fun `register ko no token`() = runTest {
val firebasePushProvider = createFirebasePushProvider(
firebaseStore = InMemoryFirebaseStore(
token = null
),
pusherSubscriber = FakePusherSubscriber(
registerPusherResult = { _, _, _ -> Result.success(Unit) }
)
)
val result = firebasePushProvider.registerWith(FakeMatrixClient(), Distributor("value", "Name"))
assertThat(result.isFailure).isTrue()
}
@Test
fun `register ko error`() = runTest {
val firebasePushProvider = createFirebasePushProvider(
firebaseStore = InMemoryFirebaseStore(
token = "aToken"
),
pusherSubscriber = FakePusherSubscriber(
registerPusherResult = { _, _, _ -> Result.failure(AN_EXCEPTION) }
)
)
val result = firebasePushProvider.registerWith(FakeMatrixClient(), Distributor("value", "Name"))
assertThat(result.isFailure).isTrue()
}
@Test
fun `unregister ok`() = runTest {
val matrixClient = FakeMatrixClient()
val unregisterPusherResultLambda = lambdaRecorder<MatrixClient, String, String, Result<Unit>> { _, _, _ -> Result.success(Unit) }
val firebasePushProvider = createFirebasePushProvider(
firebaseStore = InMemoryFirebaseStore(
token = "aToken"
),
pusherSubscriber = FakePusherSubscriber(
unregisterPusherResult = unregisterPusherResultLambda
)
)
val result = firebasePushProvider.unregister(matrixClient)
assertThat(result).isEqualTo(Result.success(Unit))
unregisterPusherResultLambda.assertions()
.isCalledOnce()
.with(value(matrixClient), value("aToken"), value(A_FIREBASE_GATEWAY))
}
@Test
fun `unregister no token - in this case, the error is ignored`() = runTest {
val firebasePushProvider = createFirebasePushProvider(
firebaseStore = InMemoryFirebaseStore(
token = null
),
)
val result = firebasePushProvider.unregister(FakeMatrixClient())
assertThat(result.isSuccess).isTrue()
}
@Test
fun `unregister ko error`() = runTest {
val firebasePushProvider = createFirebasePushProvider(
firebaseStore = InMemoryFirebaseStore(
token = "aToken"
),
pusherSubscriber = FakePusherSubscriber(
unregisterPusherResult = { _, _, _ -> Result.failure(AN_EXCEPTION) }
)
)
val result = firebasePushProvider.unregister(FakeMatrixClient())
assertThat(result.isFailure).isTrue()
}
@Test
fun `getCurrentUserPushConfig no push ket`() = runTest {
val firebasePushProvider = createFirebasePushProvider(
firebaseStore = InMemoryFirebaseStore(
token = null
)
)
val result = firebasePushProvider.getPushConfig(A_SESSION_ID)
assertThat(result).isNull()
}
@Test
fun `getCurrentUserPushConfig ok`() = runTest {
val firebasePushProvider = createFirebasePushProvider(
firebaseStore = InMemoryFirebaseStore(
token = "aToken"
),
)
val result = firebasePushProvider.getPushConfig(A_SESSION_ID)
assertThat(result).isEqualTo(Config(A_FIREBASE_GATEWAY, "aToken"))
}
@Test
fun `rotateToken invokes the FirebaseTokenRotator`() = runTest {
val lambda = lambdaRecorder<Result<Unit>> { Result.success(Unit) }
val firebasePushProvider = createFirebasePushProvider(
firebaseTokenRotator = FakeFirebaseTokenRotator(lambda),
)
firebasePushProvider.rotateToken()
lambda.assertions().isCalledOnce()
}
@Test
fun `canRotateToken should return true`() = runTest {
val firebasePushProvider = createFirebasePushProvider()
assertThat(firebasePushProvider.canRotateToken()).isTrue()
}
@Test
fun `onSessionDeleted should be noop`() = runTest {
val firebasePushProvider = createFirebasePushProvider()
firebasePushProvider.onSessionDeleted(A_SESSION_ID)
}
private fun createFirebasePushProvider(
firebaseStore: FirebaseStore = InMemoryFirebaseStore(),
pusherSubscriber: PusherSubscriber = FakePusherSubscriber(),
isPlayServiceAvailable: IsPlayServiceAvailable = FakeIsPlayServiceAvailable(false),
firebaseTokenRotator: FirebaseTokenRotator = FakeFirebaseTokenRotator(),
firebaseGatewayProvider: FirebaseGatewayProvider = FakeFirebaseGatewayProvider()
): FirebasePushProvider {
return FirebasePushProvider(
firebaseStore = firebaseStore,
pusherSubscriber = pusherSubscriber,
isPlayServiceAvailable = isPlayServiceAvailable,
firebaseTokenRotator = firebaseTokenRotator,
firebaseGatewayProvider = firebaseGatewayProvider,
)
}
}
@@ -0,0 +1,24 @@
/*
* 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 kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
class InMemoryFirebaseStore(
private var token: String? = null
) : FirebaseStore {
override fun getFcmToken(): String? = token
override fun fcmTokenFlow(): Flow<String?> = flowOf(token)
override fun storeFcmToken(token: String?) {
this.token = token
}
}
@@ -0,0 +1,104 @@
/*
* 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.
*/
@file:OptIn(ExperimentalCoroutinesApi::class)
package io.element.android.libraries.pushproviders.firebase
import android.os.Bundle
import com.google.firebase.messaging.RemoteMessage
import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_SECRET
import io.element.android.libraries.push.test.test.FakePushHandler
import io.element.android.libraries.pushproviders.api.PushData
import io.element.android.libraries.pushproviders.api.PushHandler
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
@RunWith(RobolectricTestRunner::class)
class VectorFirebaseMessagingServiceTest {
@Test
fun `test receiving invalid data`() = runTest {
val lambda = lambdaRecorder<String, String, Unit> { _, _ -> }
val vectorFirebaseMessagingService = createVectorFirebaseMessagingService(
pushHandler = FakePushHandler(handleInvalidResult = lambda)
)
vectorFirebaseMessagingService.onMessageReceived(
message = RemoteMessage(
Bundle().apply {
putString("a", "A")
putString("b", "B")
}
)
)
runCurrent()
lambda.assertions().isCalledOnce()
.with(
value(FirebaseConfig.NAME),
value("a: A\nb: B"),
)
}
@Test
fun `test receiving valid data`() = runTest {
val lambda = lambdaRecorder<PushData, String, Unit> { _, _ -> }
val vectorFirebaseMessagingService = createVectorFirebaseMessagingService(
pushHandler = FakePushHandler(handleResult = lambda)
)
vectorFirebaseMessagingService.onMessageReceived(
message = RemoteMessage(
Bundle().apply {
putString("event_id", AN_EVENT_ID.value)
putString("room_id", A_ROOM_ID.value)
putString("cs", A_SECRET)
},
)
)
advanceUntilIdle()
lambda.assertions()
.isCalledOnce()
.with(
value(PushData(AN_EVENT_ID, A_ROOM_ID, null, A_SECRET)),
value(FirebaseConfig.NAME)
)
}
@Test
fun `test new token is forwarded to the handler`() = runTest {
val lambda = lambdaRecorder<String, Unit> { }
val vectorFirebaseMessagingService = createVectorFirebaseMessagingService(
firebaseNewTokenHandler = FakeFirebaseNewTokenHandler(handleResult = lambda)
)
vectorFirebaseMessagingService.onNewToken("aToken")
advanceUntilIdle()
lambda.assertions()
.isCalledOnce()
.with(value("aToken"))
}
private fun TestScope.createVectorFirebaseMessagingService(
firebaseNewTokenHandler: FirebaseNewTokenHandler = FakeFirebaseNewTokenHandler(),
pushHandler: PushHandler = FakePushHandler(),
): VectorFirebaseMessagingService {
return VectorFirebaseMessagingService().apply {
this.firebaseNewTokenHandler = firebaseNewTokenHandler
this.pushParser = FirebasePushParser()
this.pushHandler = pushHandler
this.coroutineScope = this@createVectorFirebaseMessagingService
}
}
}
@@ -0,0 +1,61 @@
/*
* 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.troubleshoot
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.pushproviders.firebase.FakeIsPlayServiceAvailable
import io.element.android.libraries.pushproviders.firebase.FirebaseConfig
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState
import io.element.android.libraries.troubleshoot.api.test.TestFilterData
import io.element.android.libraries.troubleshoot.test.runAndTestState
import io.element.android.services.toolbox.test.strings.FakeStringProvider
import kotlinx.coroutines.test.runTest
import org.junit.Test
class FirebaseAvailabilityTestTest {
@Test
fun `test FirebaseAvailabilityTest success`() = runTest {
val sut = FirebaseAvailabilityTest(
isPlayServiceAvailable = FakeIsPlayServiceAvailable(true),
stringProvider = FakeStringProvider(),
)
sut.runAndTestState {
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(false))
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
val lastItem = awaitItem()
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Success)
}
}
@Test
fun `test FirebaseAvailabilityTest failure`() = runTest {
val sut = FirebaseAvailabilityTest(
isPlayServiceAvailable = FakeIsPlayServiceAvailable(false),
stringProvider = FakeStringProvider(),
)
sut.runAndTestState {
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(false))
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
val lastItem = awaitItem()
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure())
sut.reset()
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(false))
}
}
@Test
fun `test FirebaseAvailabilityTest isRelevant`() {
val sut = FirebaseAvailabilityTest(
isPlayServiceAvailable = FakeIsPlayServiceAvailable(false),
stringProvider = FakeStringProvider(),
)
assertThat(sut.isRelevant(TestFilterData(currentPushProviderName = "unknown"))).isFalse()
assertThat(sut.isRelevant(TestFilterData(currentPushProviderName = FirebaseConfig.NAME))).isTrue()
}
}
@@ -0,0 +1,102 @@
/*
* 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.troubleshoot
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.pushproviders.firebase.FakeFirebaseTroubleshooter
import io.element.android.libraries.pushproviders.firebase.FirebaseConfig
import io.element.android.libraries.pushproviders.firebase.InMemoryFirebaseStore
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState
import io.element.android.libraries.troubleshoot.api.test.TestFilterData
import io.element.android.libraries.troubleshoot.test.FakeNotificationTroubleshootNavigator
import io.element.android.libraries.troubleshoot.test.runAndTestState
import io.element.android.services.toolbox.test.strings.FakeStringProvider
import kotlinx.coroutines.test.runTest
import org.junit.Test
class FirebaseTokenTestTest {
@Test
fun `test FirebaseTokenTest success`() = runTest {
val sut = FirebaseTokenTest(
firebaseStore = InMemoryFirebaseStore(FAKE_TOKEN),
firebaseTroubleshooter = FakeFirebaseTroubleshooter(),
stringProvider = FakeStringProvider(),
)
sut.runAndTestState {
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(false))
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
val lastItem = awaitItem()
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Success)
assertThat(lastItem.description).contains(FAKE_TOKEN.takeLast(8))
assertThat(lastItem.description).doesNotContain(FAKE_TOKEN)
}
}
@Test
fun `test FirebaseTokenTest error`() = runTest {
val firebaseStore = InMemoryFirebaseStore(null)
val sut = FirebaseTokenTest(
firebaseStore = firebaseStore,
firebaseTroubleshooter = FakeFirebaseTroubleshooter(
troubleShootResult = {
firebaseStore.storeFcmToken(FAKE_TOKEN)
Result.success(Unit)
}
),
stringProvider = FakeStringProvider(),
)
sut.runAndTestState {
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(false))
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
val lastItem = awaitItem()
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(hasQuickFix = true))
// Quick fix
sut.quickFix(this, FakeNotificationTroubleshootNavigator())
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Success)
}
}
@Test
fun `test FirebaseTokenTest error and reset`() = runTest {
val firebaseStore = InMemoryFirebaseStore(null)
val sut = FirebaseTokenTest(
firebaseStore = firebaseStore,
firebaseTroubleshooter = FakeFirebaseTroubleshooter(
troubleShootResult = {
firebaseStore.storeFcmToken(FAKE_TOKEN)
Result.success(Unit)
}
),
stringProvider = FakeStringProvider(),
)
sut.runAndTestState {
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(false))
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(hasQuickFix = true))
sut.reset()
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(false))
}
}
@Test
fun `test FirebaseTokenTest isRelevant`() {
val sut = FirebaseTokenTest(
firebaseStore = InMemoryFirebaseStore(null),
firebaseTroubleshooter = FakeFirebaseTroubleshooter(),
stringProvider = FakeStringProvider(),
)
assertThat(sut.isRelevant(TestFilterData(currentPushProviderName = "unknown"))).isFalse()
assertThat(sut.isRelevant(TestFilterData(currentPushProviderName = FirebaseConfig.NAME))).isTrue()
}
companion object {
private const val FAKE_TOKEN = "abcdefghijk"
}
}