diff --git a/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/push/providers/api/PushProvider.kt b/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/push/providers/api/PushProvider.kt index 6476e4f815..854ae43522 100644 --- a/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/push/providers/api/PushProvider.kt +++ b/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/push/providers/api/PushProvider.kt @@ -28,4 +28,9 @@ interface PushProvider { val index: Int fun getDistributorNames(): List suspend fun registerWith(matrixClient: MatrixClient, distributorName: String) + + /** + * Attempt to troubleshoot the push provider + */ + suspend fun troubleshoot(): Result } diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/FirebaseSetPusher.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/FirebaseNewTokenHandler.kt similarity index 84% rename from libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/FirebaseSetPusher.kt rename to libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/FirebaseNewTokenHandler.kt index ea6937551a..01b93f3d95 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/FirebaseSetPusher.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/FirebaseNewTokenHandler.kt @@ -26,16 +26,20 @@ import io.element.android.libraries.sessionstorage.api.toUserList import timber.log.Timber import javax.inject.Inject -private val loggerTag = LoggerTag("FirebaseSetPusher") +private val loggerTag = LoggerTag("FirebaseNewTokenHandler") -// TODO Rename -class FirebaseSetPusher @Inject constructor( +/** + * Handle new token receive from Firebase. Will update all the sessions which are using Firebase as a push provider. + */ +class FirebaseNewTokenHandler @Inject constructor( private val pusherSubscriber: PusherSubscriber, private val sessionStore: SessionStore, private val userPushStoreFactory: UserPushStoreFactory, private val matrixAuthenticationService: MatrixAuthenticationService, + private val firebaseStore: FirebaseStore, ) { - suspend fun onNewFirebaseToken(firebaseToken: String) { + suspend fun handle(firebaseToken: String) { + firebaseStore.storeFcmToken(firebaseToken) // Register the pusher for all the sessions sessionStore.getAllSessions().toUserList().forEach { userId -> val userDataStore = userPushStoreFactory.create(userId) diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/FirebasePushProvider.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/FirebasePushProvider.kt index 0e96fc4a26..86042ae4e3 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/FirebasePushProvider.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/FirebasePushProvider.kt @@ -26,7 +26,8 @@ import javax.inject.Inject private val loggerTag = LoggerTag("FirebasePushProvider") class FirebasePushProvider @Inject constructor( - private val googleFcmHelper: GoogleFcmHelper, + private val firebaseStore: FirebaseStore, + private val firebaseTroubleshooter: FirebaseTroubleshooter, private val pusherSubscriber: PusherSubscriber, ) : PushProvider { override val index = 0 @@ -37,9 +38,13 @@ class FirebasePushProvider @Inject constructor( } override suspend fun registerWith(matrixClient: MatrixClient, distributorName: String) { - val pushKey = googleFcmHelper.getFcmToken() ?: return Unit.also { + val pushKey = firebaseStore.getFcmToken() ?: return Unit.also { Timber.tag(loggerTag.value).w("Unable to register pusher, Firebase token is not known.") } pusherSubscriber.registerPusher(matrixClient, pushKey, FirebaseConfig.pusher_http_url) } + + override suspend fun troubleshoot(): Result { + return firebaseTroubleshooter.troubleshoot() + } } diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/FirebaseStore.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/FirebaseStore.kt new file mode 100644 index 0000000000..f25ce08bc7 --- /dev/null +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/FirebaseStore.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.push.providers.firebase + +import android.content.SharedPreferences +import androidx.core.content.edit +import io.element.android.libraries.di.DefaultPreferences +import javax.inject.Inject + +/** + * This class store the Firebase token in SharedPrefs. + */ +class FirebaseStore @Inject constructor( + @DefaultPreferences private val sharedPrefs: SharedPreferences, +) { + fun getFcmToken(): String? { + return sharedPrefs.getString(PREFS_KEY_FCM_TOKEN, null) + } + + fun storeFcmToken(token: String?) { + sharedPrefs.edit { + putString(PREFS_KEY_FCM_TOKEN, token) + } + } + + companion object { + private const val PREFS_KEY_FCM_TOKEN = "FCM_TOKEN" + } +} diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/FirebaseTroubleshooter.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/FirebaseTroubleshooter.kt new file mode 100644 index 0000000000..9fb9b5708d --- /dev/null +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/FirebaseTroubleshooter.kt @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.push.providers.firebase + +import android.content.Context +import com.google.android.gms.common.ConnectionResult +import com.google.android.gms.common.GoogleApiAvailability +import com.google.firebase.messaging.FirebaseMessaging +import io.element.android.libraries.di.ApplicationContext +import timber.log.Timber +import javax.inject.Inject +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine + +/** + * This class force retrieving and storage of the Firebase token. + */ +class FirebaseTroubleshooter @Inject constructor( + @ApplicationContext private val context: Context, + private val newTokenHandler: FirebaseNewTokenHandler, +) { + suspend fun troubleshoot(): Result { + return runCatching { + val token = retrievedFirebaseToken() + newTokenHandler.handle(token) + } + } + + private suspend fun retrievedFirebaseToken(): String { + return suspendCoroutine { continuation -> + // 'app should always check the device for a compatible Google Play services APK before accessing Google Play services features' + if (checkPlayServices(context)) { + 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) + } + } else { + val e = Exception("No valid Google Play Services found. Cannot use FCM.") + Timber.e(e) + continuation.resumeWithException(e) + } + } + } + + /** + * Check the device to make sure it has the Google Play Services APK. If + * it doesn't, display a dialog that allows users to download the APK from + * the Google Play Store or enable it in the device's system settings. + */ + private fun checkPlayServices(context: Context): Boolean { + val apiAvailability = GoogleApiAvailability.getInstance() + val resultCode = apiAvailability.isGooglePlayServicesAvailable(context) + return resultCode == ConnectionResult.SUCCESS + } +} diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/GoogleFcmHelper.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/GoogleFcmHelper.kt deleted file mode 100644 index 0772ac0603..0000000000 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/GoogleFcmHelper.kt +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) 2023 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.element.android.libraries.push.providers.firebase - -import android.content.Context -import android.content.SharedPreferences -import androidx.core.content.edit -import com.google.android.gms.common.ConnectionResult -import com.google.android.gms.common.GoogleApiAvailability -import io.element.android.libraries.di.ApplicationContext -import io.element.android.libraries.di.DefaultPreferences -import javax.inject.Inject - -/** - * This class store the FCM token in SharedPrefs and ensure this token is retrieved. - * It has an alter ego in the fdroid variant. - */ -// TODO Rename to store? -class GoogleFcmHelper @Inject constructor( - @ApplicationContext private val context: Context, - @DefaultPreferences private val sharedPrefs: SharedPreferences, -) { - fun getFcmToken(): String? { - return sharedPrefs.getString(PREFS_KEY_FCM_TOKEN, null) - } - - fun storeFcmToken(token: String?) { - sharedPrefs.edit { - putString(PREFS_KEY_FCM_TOKEN, token) - } - } - - /* - override fun ensureFcmTokenIsRetrieved(pushersManager: PushersManager, registerPusher: Boolean) { - // 'app should always check the device for a compatible Google Play services APK before accessing Google Play services features' - if (checkPlayServices(context)) { - try { - FirebaseMessaging.getInstance().token - .addOnSuccessListener { token -> - storeFcmToken(token) - if (registerPusher) { - runBlocking {// TODO - pushersManager.enqueueRegisterPusherWithFcmKey(token) - } - } - } - .addOnFailureListener { e -> - Timber.e(e, "## ensureFcmTokenIsRetrieved() : failed") - } - } catch (e: Throwable) { - Timber.e(e, "## ensureFcmTokenIsRetrieved() : failed") - } - } else { - Toast.makeText(context, R.string.push_no_valid_google_play_services_apk_android, Toast.LENGTH_SHORT).show() - Timber.e("No valid Google Play Services found. Cannot use FCM.") - } - } - */ - - /** - * Check the device to make sure it has the Google Play Services APK. If - * it doesn't, display a dialog that allows users to download the APK from - * the Google Play Store or enable it in the device's system settings. - */ - private fun checkPlayServices(context: Context): Boolean { - val apiAvailability = GoogleApiAvailability.getInstance() - val resultCode = apiAvailability.isGooglePlayServicesAvailable(context) - return resultCode == ConnectionResult.SUCCESS - } - - /* - override fun onEnterForeground(activeSessionHolder: ActiveSessionHolder) { - // No op - } - - override fun onEnterBackground(activeSessionHolder: ActiveSessionHolder) { - // No op - } - */ - - companion object { - private const val PREFS_KEY_FCM_TOKEN = "FCM_TOKEN" - } -} diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/VectorFirebaseMessagingService.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/VectorFirebaseMessagingService.kt index 02bb84c541..35434ceb2e 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/VectorFirebaseMessagingService.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/push/providers/firebase/VectorFirebaseMessagingService.kt @@ -30,10 +30,9 @@ import javax.inject.Inject private val loggerTag = LoggerTag("Firebase") class VectorFirebaseMessagingService : FirebaseMessagingService() { - @Inject lateinit var firebaseSetPusher: FirebaseSetPusher + @Inject lateinit var firebaseNewTokenHandler: FirebaseNewTokenHandler @Inject lateinit var pushParser: FirebasePushParser @Inject lateinit var pushHandler: PushHandler - @Inject lateinit var googleFcmHelper: GoogleFcmHelper private val coroutineScope = CoroutineScope(SupervisorJob()) @@ -44,9 +43,8 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { override fun onNewToken(token: String) { Timber.tag(loggerTag.value).d("New Firebase token") - googleFcmHelper.storeFcmToken(token) coroutineScope.launch { - firebaseSetPusher.onNewFirebaseToken(token) + firebaseNewTokenHandler.handle(token) } } diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/UnifiedPushProvider.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/UnifiedPushProvider.kt index dcf629fe98..e6d402b8d2 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/UnifiedPushProvider.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/push/providers/unifiedpush/UnifiedPushProvider.kt @@ -30,4 +30,8 @@ class UnifiedPushProvider @Inject constructor() : PushProvider { override suspend fun registerWith(matrixClient: MatrixClient, distributorName: String) { TODO("Not yet implemented") } + + override suspend fun troubleshoot(): Result { + TODO("Not yet implemented") + } }