From 9456789047f96d0fd2aa2313a2e2ff97a96f91da Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Mon, 28 Nov 2022 11:38:06 +0100 Subject: [PATCH 01/16] Adding changelog entry --- changelog.d/7653.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7653.bugfix diff --git a/changelog.d/7653.bugfix b/changelog.d/7653.bugfix new file mode 100644 index 0000000000..ae49c4ed4e --- /dev/null +++ b/changelog.d/7653.bugfix @@ -0,0 +1 @@ +ANR when asking to select the notification method From 4dbca7858cbb49d95135a34e2fc4f84330d75460 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 29 Nov 2022 15:07:26 +0100 Subject: [PATCH 02/16] Adding new use cases to handle the Unified push registration --- .../im/vector/app/push/fcm/FdroidFcmHelper.kt | 3 +- .../im/vector/app/push/fcm/GoogleFcmHelper.kt | 13 ++-- .../EnsureFcmTokenIsRetrievedUseCase.kt | 44 ++++++++++++ .../im/vector/app/core/pushers/FcmHelper.kt | 3 +- .../pushers/RegisterUnifiedPushUseCase.kt | 70 +++++++++++++++++++ .../app/core/pushers/UnifiedPushHelper.kt | 60 ++++++++++++++-- .../pushers/UnregisterUnifiedPushUseCase.kt | 51 ++++++++++++++ .../vector/app/features/home/HomeActivity.kt | 29 ++------ .../features/home/HomeActivityViewActions.kt | 1 + .../features/home/HomeActivityViewEvents.kt | 3 + .../features/home/HomeActivityViewModel.kt | 53 +++++++------- .../features/home/HomeActivityViewState.kt | 1 - ...leNotificationsForCurrentSessionUseCase.kt | 12 ++-- ...rSettingsNotificationPreferenceFragment.kt | 10 +-- 14 files changed, 271 insertions(+), 82 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/core/pushers/EnsureFcmTokenIsRetrievedUseCase.kt create mode 100644 vector/src/main/java/im/vector/app/core/pushers/RegisterUnifiedPushUseCase.kt create mode 100644 vector/src/main/java/im/vector/app/core/pushers/UnregisterUnifiedPushUseCase.kt diff --git a/vector-app/src/fdroid/java/im/vector/app/push/fcm/FdroidFcmHelper.kt b/vector-app/src/fdroid/java/im/vector/app/push/fcm/FdroidFcmHelper.kt index 5b83769116..44fd92953e 100755 --- a/vector-app/src/fdroid/java/im/vector/app/push/fcm/FdroidFcmHelper.kt +++ b/vector-app/src/fdroid/java/im/vector/app/push/fcm/FdroidFcmHelper.kt @@ -17,7 +17,6 @@ package im.vector.app.push.fcm -import android.app.Activity import android.content.Context import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.pushers.FcmHelper @@ -44,7 +43,7 @@ class FdroidFcmHelper @Inject constructor( // No op } - override fun ensureFcmTokenIsRetrieved(activity: Activity, pushersManager: PushersManager, registerPusher: Boolean) { + override fun ensureFcmTokenIsRetrieved(pushersManager: PushersManager, registerPusher: Boolean) { // No op } diff --git a/vector-app/src/gplay/java/im/vector/app/push/fcm/GoogleFcmHelper.kt b/vector-app/src/gplay/java/im/vector/app/push/fcm/GoogleFcmHelper.kt index 7cf90cf874..53e65f88b4 100755 --- a/vector-app/src/gplay/java/im/vector/app/push/fcm/GoogleFcmHelper.kt +++ b/vector-app/src/gplay/java/im/vector/app/push/fcm/GoogleFcmHelper.kt @@ -15,7 +15,6 @@ */ package im.vector.app.push.fcm -import android.app.Activity import android.content.Context import android.content.SharedPreferences import android.widget.Toast @@ -23,6 +22,7 @@ import androidx.core.content.edit import com.google.android.gms.common.ConnectionResult import com.google.android.gms.common.GoogleApiAvailability import com.google.firebase.messaging.FirebaseMessaging +import dagger.hilt.android.qualifiers.ApplicationContext import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.DefaultPreferences @@ -36,8 +36,8 @@ import javax.inject.Inject * It has an alter ego in the fdroid variant. */ class GoogleFcmHelper @Inject constructor( - @DefaultPreferences - private val sharedPrefs: SharedPreferences, + @ApplicationContext private val context: Context, + @DefaultPreferences private val sharedPrefs: SharedPreferences, ) : FcmHelper { companion object { private const val PREFS_KEY_FCM_TOKEN = "FCM_TOKEN" @@ -56,10 +56,9 @@ class GoogleFcmHelper @Inject constructor( } } - override fun ensureFcmTokenIsRetrieved(activity: Activity, pushersManager: PushersManager, registerPusher: Boolean) { - // if (TextUtils.isEmpty(getFcmToken(activity))) { + 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(activity)) { + if (checkPlayServices(context)) { try { FirebaseMessaging.getInstance().token .addOnSuccessListener { token -> @@ -75,7 +74,7 @@ class GoogleFcmHelper @Inject constructor( Timber.e(e, "## ensureFcmTokenIsRetrieved() : failed") } } else { - Toast.makeText(activity, R.string.no_valid_google_play_services_apk, Toast.LENGTH_SHORT).show() + Toast.makeText(context, R.string.no_valid_google_play_services_apk, Toast.LENGTH_SHORT).show() Timber.e("No valid Google Play Services found. Cannot use FCM.") } } diff --git a/vector/src/main/java/im/vector/app/core/pushers/EnsureFcmTokenIsRetrievedUseCase.kt b/vector/src/main/java/im/vector/app/core/pushers/EnsureFcmTokenIsRetrievedUseCase.kt new file mode 100644 index 0000000000..4a8ff5fb39 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/pushers/EnsureFcmTokenIsRetrievedUseCase.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2022 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 im.vector.app.core.pushers + +import im.vector.app.core.di.ActiveSessionHolder +import timber.log.Timber +import javax.inject.Inject + +class EnsureFcmTokenIsRetrievedUseCase @Inject constructor( + private val unifiedPushHelper: UnifiedPushHelper, + private val fcmHelper: FcmHelper, + private val activeSessionHolder: ActiveSessionHolder, +) { + + // TODO add unit tests + fun execute(pushersManager: PushersManager, registerPusher: Boolean) { + if (unifiedPushHelper.isEmbeddedDistributor()) { + Timber.d("ensureFcmTokenIsRetrieved") + fcmHelper.ensureFcmTokenIsRetrieved(pushersManager, shouldAddHttpPusher(registerPusher)) + } + } + + private fun shouldAddHttpPusher(registerPusher: Boolean) = if (registerPusher) { + val currentSession = activeSessionHolder.getActiveSession() + val currentPushers = currentSession.pushersService().getPushers() + currentPushers.none { it.deviceId == currentSession.sessionParams.deviceId } + } else { + false + } +} diff --git a/vector/src/main/java/im/vector/app/core/pushers/FcmHelper.kt b/vector/src/main/java/im/vector/app/core/pushers/FcmHelper.kt index 7b2c5e3959..0cc251ce31 100644 --- a/vector/src/main/java/im/vector/app/core/pushers/FcmHelper.kt +++ b/vector/src/main/java/im/vector/app/core/pushers/FcmHelper.kt @@ -39,11 +39,10 @@ interface FcmHelper { /** * onNewToken may not be called on application upgrade, so ensure my shared pref is set. * - * @param activity the first launch Activity. * @param pushersManager the instance to register the pusher on. * @param registerPusher whether the pusher should be registered. */ - fun ensureFcmTokenIsRetrieved(activity: Activity, pushersManager: PushersManager, registerPusher: Boolean) + fun ensureFcmTokenIsRetrieved(pushersManager: PushersManager, registerPusher: Boolean) fun onEnterForeground(activeSessionHolder: ActiveSessionHolder) diff --git a/vector/src/main/java/im/vector/app/core/pushers/RegisterUnifiedPushUseCase.kt b/vector/src/main/java/im/vector/app/core/pushers/RegisterUnifiedPushUseCase.kt new file mode 100644 index 0000000000..7aafa07348 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/pushers/RegisterUnifiedPushUseCase.kt @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2022 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 im.vector.app.core.pushers + +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import im.vector.app.features.VectorFeatures +import org.unifiedpush.android.connector.UnifiedPush +import javax.inject.Inject + +class RegisterUnifiedPushUseCase @Inject constructor( + @ApplicationContext private val context: Context, + private val vectorFeatures: VectorFeatures, +) { + + sealed interface RegisterUnifiedPushResult { + object Success : RegisterUnifiedPushResult + data class NeedToAskUserForDistributor(val distributors: List) : RegisterUnifiedPushResult + } + + // TODO add unit tests + fun execute(distributor: String = ""): RegisterUnifiedPushResult { + if(distributor.isNotEmpty()) { + saveAndRegisterApp(distributor) + return RegisterUnifiedPushResult.Success + } + + if (!vectorFeatures.allowExternalUnifiedPushDistributors()) { + saveAndRegisterApp(context.packageName) + return RegisterUnifiedPushResult.Success + } + + if (UnifiedPush.getDistributor(context).isNotEmpty()) { + registerApp() + return RegisterUnifiedPushResult.Success + } + + val distributors = UnifiedPush.getDistributors(context) + + return if (distributors.size == 1) { + saveAndRegisterApp(distributors.first()) + RegisterUnifiedPushResult.Success + } else { + RegisterUnifiedPushResult.NeedToAskUserForDistributor(distributors) + } + } + + private fun saveAndRegisterApp(distributor: String) { + UnifiedPush.saveDistributor(context, distributor) + registerApp() + } + + private fun registerApp() { + UnifiedPush.registerApp(context) + } +} diff --git a/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushHelper.kt b/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushHelper.kt index aab94eca93..64d2a79494 100644 --- a/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushHelper.kt +++ b/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushHelper.kt @@ -17,6 +17,7 @@ package im.vector.app.core.pushers import android.content.Context +import androidx.annotation.MainThread import androidx.fragment.app.FragmentActivity import androidx.lifecycle.lifecycleScope import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -28,7 +29,9 @@ import im.vector.app.core.utils.getApplicationLabel import im.vector.app.features.VectorFeatures import im.vector.app.features.settings.BackgroundSyncMode import im.vector.app.features.settings.VectorPreferences +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.Matrix import org.matrix.android.sdk.api.cache.CacheStrategy import org.matrix.android.sdk.api.util.MatrixJsonParser @@ -49,6 +52,7 @@ class UnifiedPushHelper @Inject constructor( // Called when the home activity starts // or when notifications are enabled + // TODO remove and replace by use case fun register( activity: FragmentActivity, onDoneRunnable: Runnable? = null, @@ -66,10 +70,11 @@ class UnifiedPushHelper @Inject constructor( // The registration is forced in 2 cases : // * in the settings // * in the troubleshoot list (doFix) + // TODO remove and replace by use case fun forceRegister( activity: FragmentActivity, pushersManager: PushersManager, - onDoneRunnable: Runnable? = null + @MainThread onDoneRunnable: Runnable? = null ) { registerInternal( activity, @@ -79,17 +84,21 @@ class UnifiedPushHelper @Inject constructor( ) } + // TODO remove private fun registerInternal( activity: FragmentActivity, force: Boolean = false, pushersManager: PushersManager? = null, onDoneRunnable: Runnable? = null ) { - activity.lifecycleScope.launch { + activity.lifecycleScope.launch(Dispatchers.IO) { + Timber.d("registerInternal force=$force, $activity on thread ${Thread.currentThread()}") if (!vectorFeatures.allowExternalUnifiedPushDistributors()) { UnifiedPush.saveDistributor(context, context.packageName) UnifiedPush.registerApp(context) - onDoneRunnable?.run() + withContext(Dispatchers.Main) { + onDoneRunnable?.run() + } return@launch } if (force) { @@ -99,7 +108,9 @@ class UnifiedPushHelper @Inject constructor( // the !force should not be needed if (!force && UnifiedPush.getDistributor(context).isNotEmpty()) { UnifiedPush.registerApp(context) - onDoneRunnable?.run() + withContext(Dispatchers.Main) { + onDoneRunnable?.run() + } return@launch } @@ -108,7 +119,9 @@ class UnifiedPushHelper @Inject constructor( if (!force && distributors.size == 1) { UnifiedPush.saveDistributor(context, distributors.first()) UnifiedPush.registerApp(context) - onDoneRunnable?.run() + withContext(Dispatchers.Main) { + onDoneRunnable?.run() + } } else { openDistributorDialogInternal( activity = activity, @@ -164,6 +177,43 @@ class UnifiedPushHelper @Inject constructor( .show() } + @MainThread + fun showSelectDistributorDialog( + context: Context, + distributors: List, + onDistributorSelected: (String) -> Unit, + ) { + val internalDistributorName = stringProvider.getString( + if (fcmHelper.isFirebaseAvailable()) { + R.string.unifiedpush_distributor_fcm_fallback + } else { + R.string.unifiedpush_distributor_background_sync + } + ) + + val distributorsName = distributors.map { + if (it == context.packageName) { + internalDistributorName + } else { + context.getApplicationLabel(it) + } + } + + MaterialAlertDialogBuilder(context) + .setTitle(stringProvider.getString(R.string.unifiedpush_getdistributors_dialog_title)) + .setItems(distributorsName.toTypedArray()) { _, which -> + val distributor = distributors[which] + onDistributorSelected(distributor) + } + .setOnCancelListener { + // By default, use internal solution (fcm/background sync) + onDistributorSelected(context.packageName) + } + .setCancelable(true) + .show() + } + + // TODO remove and replace by use case suspend fun unregister(pushersManager: PushersManager? = null) { val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME vectorPreferences.setFdroidSyncBackgroundMode(mode) diff --git a/vector/src/main/java/im/vector/app/core/pushers/UnregisterUnifiedPushUseCase.kt b/vector/src/main/java/im/vector/app/core/pushers/UnregisterUnifiedPushUseCase.kt new file mode 100644 index 0000000000..d81581679e --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/pushers/UnregisterUnifiedPushUseCase.kt @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2022 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 im.vector.app.core.pushers + +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import im.vector.app.features.settings.BackgroundSyncMode +import im.vector.app.features.settings.VectorPreferences +import org.unifiedpush.android.connector.UnifiedPush +import timber.log.Timber +import javax.inject.Inject + +class UnregisterUnifiedPushUseCase @Inject constructor( + @ApplicationContext private val context: Context, + private val pushersManager: PushersManager, + private val vectorPreferences: VectorPreferences, + private val unifiedPushStore: UnifiedPushStore, + private val unifiedPushHelper: UnifiedPushHelper, +) { + + // TODO add unit tests + suspend fun execute() { + val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME + vectorPreferences.setFdroidSyncBackgroundMode(mode) + try { + unifiedPushHelper.getEndpointOrToken()?.let { + Timber.d("Removing $it") + pushersManager.unregisterPusher(it) + } + } catch (e: Exception) { + Timber.d(e, "Probably unregistering a non existing pusher") + } + unifiedPushStore.storeUpEndpoint(null) + unifiedPushStore.storePushGateway(null) + UnifiedPush.unregisterApp(context) + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt index 2df94fecad..14157a1de8 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt @@ -44,8 +44,6 @@ import im.vector.app.core.extensions.restart import im.vector.app.core.extensions.validateBackPressed import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorMenuProvider -import im.vector.app.core.pushers.FcmHelper -import im.vector.app.core.pushers.PushersManager import im.vector.app.core.pushers.UnifiedPushHelper import im.vector.app.core.utils.registerForPermissionsResult import im.vector.app.core.utils.startSharePlainTextIntent @@ -128,7 +126,6 @@ class HomeActivity : private val serverBackupStatusViewModel: ServerBackupStatusViewModel by viewModel() @Inject lateinit var vectorUncaughtExceptionHandler: VectorUncaughtExceptionHandler - @Inject lateinit var pushersManager: PushersManager @Inject lateinit var notificationDrawerManager: NotificationDrawerManager @Inject lateinit var popupAlertManager: PopupAlertManager @Inject lateinit var shortcutsHandler: ShortcutsHandler @@ -137,7 +134,6 @@ class HomeActivity : @Inject lateinit var initSyncStepFormatter: InitSyncStepFormatter @Inject lateinit var spaceStateHandler: SpaceStateHandler @Inject lateinit var unifiedPushHelper: UnifiedPushHelper - @Inject lateinit var fcmHelper: FcmHelper @Inject lateinit var nightlyProxy: NightlyProxy @Inject lateinit var disclaimerDialog: DisclaimerDialog @Inject lateinit var notificationPermissionManager: NotificationPermissionManager @@ -209,16 +205,6 @@ class HomeActivity : isNewAppLayoutEnabled = vectorPreferences.isNewAppLayoutEnabled() analyticsScreenName = MobileScreen.ScreenName.Home supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, false) - unifiedPushHelper.register(this) { - if (unifiedPushHelper.isEmbeddedDistributor()) { - fcmHelper.ensureFcmTokenIsRetrieved( - this, - pushersManager, - homeActivityViewModel.shouldAddHttpPusher() - ) - } - } - sharedActionViewModel = viewModelProvider[HomeSharedActionViewModel::class.java] roomListSharedActionViewModel = viewModelProvider[RoomListSharedActionViewModel::class.java] views.drawerLayout.addDrawerListener(drawerListener) @@ -280,6 +266,7 @@ class HomeActivity : HomeActivityViewEvents.ShowReleaseNotes -> handleShowReleaseNotes() HomeActivityViewEvents.NotifyUserForThreadsMigration -> handleNotifyUserForThreadsMigration() is HomeActivityViewEvents.MigrateThreads -> migrateThreadsIfNeeded(it.checkSession) + is HomeActivityViewEvents.AskUserForPushDistributor -> askUserToSelectPushDistributor(it.distributors) } } homeActivityViewModel.onEach { renderState(it) } @@ -292,6 +279,12 @@ class HomeActivity : homeActivityViewModel.handle(HomeActivityViewActions.ViewStarted) } + private fun askUserToSelectPushDistributor(distributors: List) { + unifiedPushHelper.showSelectDistributorDialog(this, distributors) { selection -> + homeActivityViewModel.handle(HomeActivityViewActions.RegisterPushDistributor(selection)) + } + } + private fun handleShowNotificationDialog() { notificationPermissionManager.eventuallyRequestPermission(this, postPermissionLauncher) } @@ -415,14 +408,6 @@ class HomeActivity : } private fun renderState(state: HomeActivityViewState) { - lifecycleScope.launch { - if (state.areNotificationsSilenced) { - unifiedPushHelper.unregister(pushersManager) - } else { - unifiedPushHelper.register(this@HomeActivity) - } - } - when (val status = state.syncRequestState) { is SyncRequestState.InitialSyncProgressing -> { val initSyncStepStr = initSyncStepFormatter.format(status.initialSyncStep) diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewActions.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewActions.kt index 5f89c89bc9..54392d5f56 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewActions.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewActions.kt @@ -21,4 +21,5 @@ import im.vector.app.core.platform.VectorViewModelAction sealed interface HomeActivityViewActions : VectorViewModelAction { object ViewStarted : HomeActivityViewActions object PushPromptHasBeenReviewed : HomeActivityViewActions + data class RegisterPushDistributor(val distributor: String) : HomeActivityViewActions } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt index e548fdb2f3..6fdf441d1d 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt @@ -25,9 +25,11 @@ sealed interface HomeActivityViewEvents : VectorViewEvents { val userItem: MatrixItem.UserItem, val waitForIncomingRequest: Boolean = true, ) : HomeActivityViewEvents + data class CurrentSessionCannotBeVerified( val userItem: MatrixItem.UserItem, ) : HomeActivityViewEvents + data class OnCrossSignedInvalidated(val userItem: MatrixItem.UserItem) : HomeActivityViewEvents object PromptToEnableSessionPush : HomeActivityViewEvents object ShowAnalyticsOptIn : HomeActivityViewEvents @@ -37,4 +39,5 @@ sealed interface HomeActivityViewEvents : VectorViewEvents { data class MigrateThreads(val checkSession: Boolean) : HomeActivityViewEvents object StartRecoverySetupFlow : HomeActivityViewEvents data class ForceVerification(val sendRequest: Boolean) : HomeActivityViewEvents + data class AskUserForPushDistributor(val distributors: List) : HomeActivityViewEvents } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt index 49f2079625..3fd555bbea 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt @@ -16,7 +16,6 @@ package im.vector.app.features.home -import androidx.lifecycle.asFlow import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext @@ -27,7 +26,9 @@ import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel -import im.vector.app.features.VectorFeatures +import im.vector.app.core.pushers.EnsureFcmTokenIsRetrievedUseCase +import im.vector.app.core.pushers.PushersManager +import im.vector.app.core.pushers.RegisterUnifiedPushUseCase import im.vector.app.features.analytics.AnalyticsConfig import im.vector.app.features.analytics.AnalyticsTracker import im.vector.app.features.analytics.extensions.toAnalyticsType @@ -48,12 +49,10 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.takeWhile import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.account.LocalNotificationSettingsContent import org.matrix.android.sdk.api.auth.UIABaseAuth import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.auth.UserPasswordAuth @@ -62,11 +61,9 @@ import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse import org.matrix.android.sdk.api.auth.registration.nextUncompletedStage import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.raw.RawService -import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap -import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.getUserOrDefault import org.matrix.android.sdk.api.session.pushrules.RuleIds import org.matrix.android.sdk.api.session.room.model.Membership @@ -92,8 +89,10 @@ class HomeActivityViewModel @AssistedInject constructor( private val analyticsTracker: AnalyticsTracker, private val analyticsConfig: AnalyticsConfig, private val releaseNotesPreferencesStore: ReleaseNotesPreferencesStore, - private val vectorFeatures: VectorFeatures, private val stopOngoingVoiceBroadcastUseCase: StopOngoingVoiceBroadcastUseCase, + private val pushersManager: PushersManager, + private val registerUnifiedPushUseCase: RegisterUnifiedPushUseCase, + private val ensureFcmTokenIsRetrievedUseCase: EnsureFcmTokenIsRetrievedUseCase, ) : VectorViewModel(initialState) { @AssistedFactory @@ -117,17 +116,32 @@ class HomeActivityViewModel @AssistedInject constructor( private fun initialize() { if (isInitialized) return isInitialized = true + registerUnifiedPush(distributor = "") cleanupFiles() observeInitialSync() checkSessionPushIsOn() observeCrossSigningReset() observeAnalytics() observeReleaseNotes() - observeLocalNotificationsSilenced() initThreadsMigration() viewModelScope.launch { stopOngoingVoiceBroadcastUseCase.execute() } } + private fun registerUnifiedPush(distributor: String) { + viewModelScope.launch { + when (val result = registerUnifiedPushUseCase.execute(distributor = distributor)) { + is RegisterUnifiedPushUseCase.RegisterUnifiedPushResult.NeedToAskUserForDistributor -> { + Timber.d("registerUnifiedPush $distributor need to ask user") + _viewEvents.post(HomeActivityViewEvents.AskUserForPushDistributor(result.distributors)) + } + RegisterUnifiedPushUseCase.RegisterUnifiedPushResult.Success -> { + Timber.d("registerUnifiedPush $distributor success") + ensureFcmTokenIsRetrievedUseCase.execute(pushersManager, registerPusher = vectorPreferences.areNotificationEnabledForDevice()) + } + } + } + } + private fun observeReleaseNotes() = withState { state -> if (vectorPreferences.isNewAppLayoutEnabled()) { // we don't want to show release notes for new users or after relogin @@ -146,26 +160,6 @@ class HomeActivityViewModel @AssistedInject constructor( } } - fun shouldAddHttpPusher() = if (vectorPreferences.areNotificationEnabledForDevice()) { - val currentSession = activeSessionHolder.getActiveSession() - val currentPushers = currentSession.pushersService().getPushers() - currentPushers.none { it.deviceId == currentSession.sessionParams.deviceId } - } else { - false - } - - fun observeLocalNotificationsSilenced() { - val currentSession = activeSessionHolder.getActiveSession() - val deviceId = currentSession.cryptoService().getMyDevice().deviceId - viewModelScope.launch { - currentSession.accountDataService() - .getLiveUserAccountDataEvent(UserAccountDataTypes.TYPE_LOCAL_NOTIFICATION_SETTINGS + deviceId) - .asFlow() - .map { it.getOrNull()?.content?.toModel()?.isSilenced ?: false } - .onEach { setState { copy(areNotificationsSilenced = it) } } - } - } - private fun observeAnalytics() { if (analyticsConfig.isEnabled) { analyticsStore.didAskUserConsentFlow @@ -501,6 +495,9 @@ class HomeActivityViewModel @AssistedInject constructor( HomeActivityViewActions.ViewStarted -> { initialize() } + is HomeActivityViewActions.RegisterPushDistributor -> { + registerUnifiedPush(distributor = action.distributor) + } } } } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewState.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewState.kt index 4df2957cbc..f9c1b37ed5 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewState.kt @@ -23,5 +23,4 @@ import org.matrix.android.sdk.api.session.sync.SyncRequestState data class HomeActivityViewState( val syncRequestState: SyncRequestState = SyncRequestState.Idle, val authenticationDescription: AuthenticationDescription? = null, - val areNotificationsSilenced: Boolean = false, ) : MavericksState diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/EnableNotificationsForCurrentSessionUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/EnableNotificationsForCurrentSessionUseCase.kt index 180627a15f..91974787bd 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/EnableNotificationsForCurrentSessionUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/EnableNotificationsForCurrentSessionUseCase.kt @@ -18,6 +18,7 @@ package im.vector.app.features.settings.notifications import androidx.fragment.app.FragmentActivity import im.vector.app.core.di.ActiveSessionHolder +import im.vector.app.core.pushers.EnsureFcmTokenIsRetrievedUseCase import im.vector.app.core.pushers.FcmHelper import im.vector.app.core.pushers.PushersManager import im.vector.app.core.pushers.UnifiedPushHelper @@ -32,11 +33,12 @@ class EnableNotificationsForCurrentSessionUseCase @Inject constructor( private val activeSessionHolder: ActiveSessionHolder, private val unifiedPushHelper: UnifiedPushHelper, private val pushersManager: PushersManager, - private val fcmHelper: FcmHelper, private val checkIfCanTogglePushNotificationsViaPusherUseCase: CheckIfCanTogglePushNotificationsViaPusherUseCase, private val togglePushNotificationUseCase: TogglePushNotificationUseCase, + private val ensureFcmTokenIsRetrievedUseCase: EnsureFcmTokenIsRetrievedUseCase, ) { + // TODO update unit tests suspend fun execute(fragmentActivity: FragmentActivity) { val pusherForCurrentSession = pushersManager.getPusherForCurrentSession() if (pusherForCurrentSession == null) { @@ -54,13 +56,7 @@ class EnableNotificationsForCurrentSessionUseCase @Inject constructor( suspendCoroutine { continuation -> try { unifiedPushHelper.register(fragmentActivity) { - if (unifiedPushHelper.isEmbeddedDistributor()) { - fcmHelper.ensureFcmTokenIsRetrieved( - fragmentActivity, - pushersManager, - registerPusher = true - ) - } + ensureFcmTokenIsRetrievedUseCase.execute(pushersManager, registerPusher = true) continuation.resume(Unit) } } catch (error: Exception) { diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt index 58f86bc949..f8c1a9ad44 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt @@ -37,6 +37,7 @@ import im.vector.app.core.preference.VectorEditTextPreference import im.vector.app.core.preference.VectorPreference import im.vector.app.core.preference.VectorPreferenceCategory import im.vector.app.core.preference.VectorSwitchPreference +import im.vector.app.core.pushers.EnsureFcmTokenIsRetrievedUseCase import im.vector.app.core.pushers.FcmHelper import im.vector.app.core.pushers.PushersManager import im.vector.app.core.pushers.UnifiedPushHelper @@ -82,6 +83,7 @@ class VectorSettingsNotificationPreferenceFragment : @Inject lateinit var notificationPermissionManager: NotificationPermissionManager @Inject lateinit var disableNotificationsForCurrentSessionUseCase: DisableNotificationsForCurrentSessionUseCase @Inject lateinit var enableNotificationsForCurrentSessionUseCase: EnableNotificationsForCurrentSessionUseCase + @Inject lateinit var ensureFcmTokenIsRetrievedUseCase: EnsureFcmTokenIsRetrievedUseCase override var titleRes: Int = R.string.settings_notifications override val preferenceXmlRes = R.xml.vector_settings_notifications @@ -183,13 +185,7 @@ class VectorSettingsNotificationPreferenceFragment : it.summary = unifiedPushHelper.getCurrentDistributorName() it.onPreferenceClickListener = Preference.OnPreferenceClickListener { unifiedPushHelper.forceRegister(requireActivity(), pushersManager) { - if (unifiedPushHelper.isEmbeddedDistributor()) { - fcmHelper.ensureFcmTokenIsRetrieved( - requireActivity(), - pushersManager, - vectorPreferences.areNotificationEnabledForDevice() - ) - } + ensureFcmTokenIsRetrievedUseCase.execute(pushersManager, registerPusher = vectorPreferences.areNotificationEnabledForDevice()) it.summary = unifiedPushHelper.getCurrentDistributorName() session.pushersService().refreshPushers() refreshBackgroundSyncPrefs() From 2890f41f30d675b9988d12e62bcd94916da04e3c Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 29 Nov 2022 17:00:44 +0100 Subject: [PATCH 03/16] Replacing unregister method by usecase --- .../main/java/im/vector/app/core/di/ActiveSessionHolder.kt | 6 +++--- .../java/im/vector/app/core/pushers/UnifiedPushHelper.kt | 2 +- .../app/core/pushers/UnregisterUnifiedPushUseCase.kt | 5 ++--- .../DisableNotificationsForCurrentSessionUseCase.kt | 7 ++++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/di/ActiveSessionHolder.kt b/vector/src/main/java/im/vector/app/core/di/ActiveSessionHolder.kt index f1863cfa23..fead1e15b1 100644 --- a/vector/src/main/java/im/vector/app/core/di/ActiveSessionHolder.kt +++ b/vector/src/main/java/im/vector/app/core/di/ActiveSessionHolder.kt @@ -19,7 +19,7 @@ package im.vector.app.core.di import android.content.Context import im.vector.app.ActiveSessionDataSource import im.vector.app.core.extensions.startSyncing -import im.vector.app.core.pushers.UnifiedPushHelper +import im.vector.app.core.pushers.UnregisterUnifiedPushUseCase import im.vector.app.core.services.GuardServiceStarter import im.vector.app.core.session.ConfigureAndStartSessionUseCase import im.vector.app.features.call.webrtc.WebRtcCallManager @@ -46,12 +46,12 @@ class ActiveSessionHolder @Inject constructor( private val pushRuleTriggerListener: PushRuleTriggerListener, private val sessionListener: SessionListener, private val imageManager: ImageManager, - private val unifiedPushHelper: UnifiedPushHelper, private val guardServiceStarter: GuardServiceStarter, private val sessionInitializer: SessionInitializer, private val applicationContext: Context, private val authenticationService: AuthenticationService, private val configureAndStartSessionUseCase: ConfigureAndStartSessionUseCase, + private val unregisterUnifiedPushUseCase: UnregisterUnifiedPushUseCase, ) { private var activeSessionReference: AtomicReference = AtomicReference() @@ -85,7 +85,7 @@ class ActiveSessionHolder @Inject constructor( incomingVerificationRequestHandler.stop() pushRuleTriggerListener.stop() // No need to unregister the pusher, the sign out will (should?) do it server side. - unifiedPushHelper.unregister(pushersManager = null) + unregisterUnifiedPushUseCase.execute(pushersManager = null) guardServiceStarter.stop() } diff --git a/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushHelper.kt b/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushHelper.kt index 64d2a79494..34ba254250 100644 --- a/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushHelper.kt +++ b/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushHelper.kt @@ -213,7 +213,7 @@ class UnifiedPushHelper @Inject constructor( .show() } - // TODO remove and replace by use case + // TODO remove suspend fun unregister(pushersManager: PushersManager? = null) { val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME vectorPreferences.setFdroidSyncBackgroundMode(mode) diff --git a/vector/src/main/java/im/vector/app/core/pushers/UnregisterUnifiedPushUseCase.kt b/vector/src/main/java/im/vector/app/core/pushers/UnregisterUnifiedPushUseCase.kt index d81581679e..71b1a9c033 100644 --- a/vector/src/main/java/im/vector/app/core/pushers/UnregisterUnifiedPushUseCase.kt +++ b/vector/src/main/java/im/vector/app/core/pushers/UnregisterUnifiedPushUseCase.kt @@ -26,20 +26,19 @@ import javax.inject.Inject class UnregisterUnifiedPushUseCase @Inject constructor( @ApplicationContext private val context: Context, - private val pushersManager: PushersManager, private val vectorPreferences: VectorPreferences, private val unifiedPushStore: UnifiedPushStore, private val unifiedPushHelper: UnifiedPushHelper, ) { // TODO add unit tests - suspend fun execute() { + suspend fun execute(pushersManager: PushersManager?) { val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME vectorPreferences.setFdroidSyncBackgroundMode(mode) try { unifiedPushHelper.getEndpointOrToken()?.let { Timber.d("Removing $it") - pushersManager.unregisterPusher(it) + pushersManager?.unregisterPusher(it) } } catch (e: Exception) { Timber.d(e, "Probably unregistering a non existing pusher") diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/DisableNotificationsForCurrentSessionUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/DisableNotificationsForCurrentSessionUseCase.kt index 61c884f0bc..2ce2254f2e 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/DisableNotificationsForCurrentSessionUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/DisableNotificationsForCurrentSessionUseCase.kt @@ -18,26 +18,27 @@ package im.vector.app.features.settings.notifications import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.pushers.PushersManager -import im.vector.app.core.pushers.UnifiedPushHelper +import im.vector.app.core.pushers.UnregisterUnifiedPushUseCase import im.vector.app.features.settings.devices.v2.notification.CheckIfCanTogglePushNotificationsViaPusherUseCase import im.vector.app.features.settings.devices.v2.notification.TogglePushNotificationUseCase import javax.inject.Inject class DisableNotificationsForCurrentSessionUseCase @Inject constructor( private val activeSessionHolder: ActiveSessionHolder, - private val unifiedPushHelper: UnifiedPushHelper, private val pushersManager: PushersManager, private val checkIfCanTogglePushNotificationsViaPusherUseCase: CheckIfCanTogglePushNotificationsViaPusherUseCase, private val togglePushNotificationUseCase: TogglePushNotificationUseCase, + private val unregisterUnifiedPushUseCase: UnregisterUnifiedPushUseCase, ) { + // TODO update unit tests suspend fun execute() { val session = activeSessionHolder.getSafeActiveSession() ?: return val deviceId = session.sessionParams.deviceId ?: return if (checkIfCanTogglePushNotificationsViaPusherUseCase.execute(session)) { togglePushNotificationUseCase.execute(deviceId, enabled = false) } else { - unifiedPushHelper.unregister(pushersManager) + unregisterUnifiedPushUseCase.execute(pushersManager) } } } From 58efe90f7dfa23ffd17110eedec21ef480022fcf Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 29 Nov 2022 17:10:20 +0100 Subject: [PATCH 04/16] Removing some debug logs --- .../vector/app/core/pushers/EnsureFcmTokenIsRetrievedUseCase.kt | 2 -- .../java/im/vector/app/features/home/HomeActivityViewModel.kt | 2 -- 2 files changed, 4 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/pushers/EnsureFcmTokenIsRetrievedUseCase.kt b/vector/src/main/java/im/vector/app/core/pushers/EnsureFcmTokenIsRetrievedUseCase.kt index 4a8ff5fb39..e55d0426ba 100644 --- a/vector/src/main/java/im/vector/app/core/pushers/EnsureFcmTokenIsRetrievedUseCase.kt +++ b/vector/src/main/java/im/vector/app/core/pushers/EnsureFcmTokenIsRetrievedUseCase.kt @@ -17,7 +17,6 @@ package im.vector.app.core.pushers import im.vector.app.core.di.ActiveSessionHolder -import timber.log.Timber import javax.inject.Inject class EnsureFcmTokenIsRetrievedUseCase @Inject constructor( @@ -29,7 +28,6 @@ class EnsureFcmTokenIsRetrievedUseCase @Inject constructor( // TODO add unit tests fun execute(pushersManager: PushersManager, registerPusher: Boolean) { if (unifiedPushHelper.isEmbeddedDistributor()) { - Timber.d("ensureFcmTokenIsRetrieved") fcmHelper.ensureFcmTokenIsRetrieved(pushersManager, shouldAddHttpPusher(registerPusher)) } } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt index 3fd555bbea..2905decddf 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt @@ -131,11 +131,9 @@ class HomeActivityViewModel @AssistedInject constructor( viewModelScope.launch { when (val result = registerUnifiedPushUseCase.execute(distributor = distributor)) { is RegisterUnifiedPushUseCase.RegisterUnifiedPushResult.NeedToAskUserForDistributor -> { - Timber.d("registerUnifiedPush $distributor need to ask user") _viewEvents.post(HomeActivityViewEvents.AskUserForPushDistributor(result.distributors)) } RegisterUnifiedPushUseCase.RegisterUnifiedPushResult.Success -> { - Timber.d("registerUnifiedPush $distributor success") ensureFcmTokenIsRetrievedUseCase.execute(pushersManager, registerPusher = vectorPreferences.areNotificationEnabledForDevice()) } } From b29191e892bbc9d0b2f15fa106c1524da7c4f180 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 29 Nov 2022 17:28:48 +0100 Subject: [PATCH 05/16] Using use cases inside component for endpoint testing --- .../TestEndpointAsTokenRegistration.kt | 63 ++++++++++++++----- 1 file changed, 47 insertions(+), 16 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestEndpointAsTokenRegistration.kt b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestEndpointAsTokenRegistration.kt index 3bbff0f2fe..e6cb78d185 100644 --- a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestEndpointAsTokenRegistration.kt +++ b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestEndpointAsTokenRegistration.kt @@ -17,14 +17,17 @@ package im.vector.app.features.settings.troubleshoot import androidx.fragment.app.FragmentActivity -import androidx.lifecycle.Observer import androidx.work.WorkInfo import androidx.work.WorkManager import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.pushers.PushersManager +import im.vector.app.core.pushers.RegisterUnifiedPushUseCase import im.vector.app.core.pushers.UnifiedPushHelper +import im.vector.app.core.pushers.UnregisterUnifiedPushUseCase import im.vector.app.core.resources.StringProvider +import im.vector.app.features.session.coroutineScope +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.pushers.PusherState import javax.inject.Inject @@ -34,6 +37,8 @@ class TestEndpointAsTokenRegistration @Inject constructor( private val pushersManager: PushersManager, private val activeSessionHolder: ActiveSessionHolder, private val unifiedPushHelper: UnifiedPushHelper, + private val registerUnifiedPushUseCase: RegisterUnifiedPushUseCase, + private val unregisterUnifiedPushUseCase: UnregisterUnifiedPushUseCase, ) : TroubleshootTest(R.string.settings_troubleshoot_test_endpoint_registration_title) { override fun perform(testParameters: TestParameters) { @@ -56,27 +61,53 @@ class TestEndpointAsTokenRegistration @Inject constructor( ) quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_endpoint_registration_quick_fix) { override fun doFix() { - unifiedPushHelper.forceRegister( - context, - pushersManager - ) - val workId = pushersManager.enqueueRegisterPusherWithFcmKey(endpoint) - WorkManager.getInstance(context).getWorkInfoByIdLiveData(workId).observe(context, Observer { workInfo -> - if (workInfo != null) { - if (workInfo.state == WorkInfo.State.SUCCEEDED) { - manager?.retry(testParameters) - } else if (workInfo.state == WorkInfo.State.FAILED) { - manager?.retry(testParameters) - } - } - }) + unregisterThenRegister(testParameters, endpoint) } } - status = TestStatus.FAILED } else { description = stringProvider.getString(R.string.settings_troubleshoot_test_endpoint_registration_success) status = TestStatus.SUCCESS } } + + private fun unregisterThenRegister(testParameters: TestParameters, pushKey: String) { + activeSessionHolder.getSafeActiveSession()?.coroutineScope?.launch { + unregisterUnifiedPushUseCase.execute(pushersManager) + registerUnifiedPush(distributor = "", testParameters, pushKey) + } + } + + private fun registerUnifiedPush( + distributor: String, + testParameters: TestParameters, + pushKey: String, + ) { + when (val result = registerUnifiedPushUseCase.execute(distributor)) { + is RegisterUnifiedPushUseCase.RegisterUnifiedPushResult.NeedToAskUserForDistributor -> + askUserForDistributor(result.distributors, testParameters, pushKey) + RegisterUnifiedPushUseCase.RegisterUnifiedPushResult.Success -> { + val workId = pushersManager.enqueueRegisterPusherWithFcmKey(pushKey) + WorkManager.getInstance(context).getWorkInfoByIdLiveData(workId).observe(context) { workInfo -> + if (workInfo != null) { + if (workInfo.state == WorkInfo.State.SUCCEEDED) { + manager?.retry(testParameters) + } else if (workInfo.state == WorkInfo.State.FAILED) { + manager?.retry(testParameters) + } + } + } + } + } + } + + private fun askUserForDistributor( + distributors: List, + testParameters: TestParameters, + pushKey: String, + ) { + unifiedPushHelper.showSelectDistributorDialog(context, distributors) { selection -> + registerUnifiedPush(distributor = selection, testParameters, pushKey) + } + } } From 3f944e9d36c892e88676e3e64fb061afd98b158b Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 30 Nov 2022 11:05:46 +0100 Subject: [PATCH 06/16] Extracting the logic to toggle notifications for device into a ViewModel --- .../app/core/di/MavericksViewModelModule.kt | 6 ++ .../settings/VectorSettingsBaseFragment.kt | 17 +++- ...leNotificationsForCurrentSessionUseCase.kt | 49 +++++------- ...rSettingsNotificationPreferenceFragment.kt | 73 ++++++++++++++---- ...ettingsNotificationPreferenceViewAction.kt | 25 ++++++ ...SettingsNotificationPreferenceViewEvent.kt | 26 +++++++ ...SettingsNotificationPreferenceViewModel.kt | 77 +++++++++++++++++++ 7 files changed, 227 insertions(+), 46 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceViewAction.kt create mode 100644 vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceViewEvent.kt create mode 100644 vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceViewModel.kt diff --git a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt index 2242abb7aa..ad3e361775 100644 --- a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt @@ -105,6 +105,7 @@ import im.vector.app.features.settings.ignored.IgnoredUsersViewModel import im.vector.app.features.settings.labs.VectorSettingsLabsViewModel import im.vector.app.features.settings.legals.LegalsViewModel import im.vector.app.features.settings.locale.LocalePickerViewModel +import im.vector.app.features.settings.notifications.VectorSettingsNotificationPreferenceViewModel import im.vector.app.features.settings.push.PushGatewaysViewModel import im.vector.app.features.settings.threepids.ThreePidsSettingsViewModel import im.vector.app.features.share.IncomingShareViewModel @@ -683,4 +684,9 @@ interface MavericksViewModelModule { @IntoMap @MavericksViewModelKey(AttachmentTypeSelectorViewModel::class) fun attachmentTypeSelectorViewModelFactory(factory: AttachmentTypeSelectorViewModel.Factory): MavericksAssistedViewModelFactory<*, *> + + @Binds + @IntoMap + @MavericksViewModelKey(VectorSettingsNotificationPreferenceViewModel::class) + fun vectorSettingsNotificationPreferenceViewModelFactory(factory: VectorSettingsNotificationPreferenceViewModel.Factory): MavericksAssistedViewModelFactory<*, *> } diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt index 176909b48d..38ba949a49 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt @@ -28,6 +28,8 @@ import im.vector.app.R import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.extensions.singletonEntryPoint import im.vector.app.core.platform.VectorBaseActivity +import im.vector.app.core.platform.VectorViewEvents +import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.utils.toast import im.vector.app.features.analytics.AnalyticsTracker import im.vector.app.features.analytics.plan.MobileScreen @@ -60,6 +62,19 @@ abstract class VectorSettingsBaseFragment : PreferenceFragmentCompat(), Maverick protected lateinit var session: Session protected lateinit var errorFormatter: ErrorFormatter + /* ========================================================================================== + * ViewEvents + * ========================================================================================== */ + + protected fun VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) { + viewEvents + .stream() + .onEach { + observer(it) + } + .launchIn(viewLifecycleOwner.lifecycleScope) + } + /* ========================================================================================== * Views * ========================================================================================== */ @@ -148,7 +163,7 @@ abstract class VectorSettingsBaseFragment : PreferenceFragmentCompat(), Maverick } } - protected fun displayErrorDialog(throwable: Throwable) { + protected fun displayErrorDialog(throwable: Throwable?) { displayErrorDialog(errorFormatter.toHumanReadable(throwable)) } diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/EnableNotificationsForCurrentSessionUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/EnableNotificationsForCurrentSessionUseCase.kt index 91974787bd..2f8bdd4d0d 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/EnableNotificationsForCurrentSessionUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/EnableNotificationsForCurrentSessionUseCase.kt @@ -16,52 +16,45 @@ package im.vector.app.features.settings.notifications -import androidx.fragment.app.FragmentActivity import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.pushers.EnsureFcmTokenIsRetrievedUseCase -import im.vector.app.core.pushers.FcmHelper import im.vector.app.core.pushers.PushersManager -import im.vector.app.core.pushers.UnifiedPushHelper -import im.vector.app.features.settings.devices.v2.notification.CheckIfCanTogglePushNotificationsViaPusherUseCase +import im.vector.app.core.pushers.RegisterUnifiedPushUseCase import im.vector.app.features.settings.devices.v2.notification.TogglePushNotificationUseCase import javax.inject.Inject -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException -import kotlin.coroutines.suspendCoroutine class EnableNotificationsForCurrentSessionUseCase @Inject constructor( private val activeSessionHolder: ActiveSessionHolder, - private val unifiedPushHelper: UnifiedPushHelper, private val pushersManager: PushersManager, - private val checkIfCanTogglePushNotificationsViaPusherUseCase: CheckIfCanTogglePushNotificationsViaPusherUseCase, private val togglePushNotificationUseCase: TogglePushNotificationUseCase, + private val registerUnifiedPushUseCase: RegisterUnifiedPushUseCase, private val ensureFcmTokenIsRetrievedUseCase: EnsureFcmTokenIsRetrievedUseCase, ) { + sealed interface EnableNotificationsResult { + object Success : EnableNotificationsResult + object Failure : EnableNotificationsResult + data class NeedToAskUserForDistributor(val distributors: List) : EnableNotificationsResult + } + // TODO update unit tests - suspend fun execute(fragmentActivity: FragmentActivity) { + suspend fun execute(distributor: String = ""): EnableNotificationsResult { val pusherForCurrentSession = pushersManager.getPusherForCurrentSession() if (pusherForCurrentSession == null) { - registerPusher(fragmentActivity) - } - - val session = activeSessionHolder.getSafeActiveSession() ?: return - if (checkIfCanTogglePushNotificationsViaPusherUseCase.execute(session)) { - val deviceId = session.sessionParams.deviceId ?: return - togglePushNotificationUseCase.execute(deviceId, enabled = true) - } - } - - private suspend fun registerPusher(fragmentActivity: FragmentActivity) { - suspendCoroutine { continuation -> - try { - unifiedPushHelper.register(fragmentActivity) { - ensureFcmTokenIsRetrievedUseCase.execute(pushersManager, registerPusher = true) - continuation.resume(Unit) + when (val result = registerUnifiedPushUseCase.execute(distributor)) { + is RegisterUnifiedPushUseCase.RegisterUnifiedPushResult.NeedToAskUserForDistributor -> { + return EnableNotificationsResult.NeedToAskUserForDistributor(result.distributors) + } + RegisterUnifiedPushUseCase.RegisterUnifiedPushResult.Success -> { + ensureFcmTokenIsRetrievedUseCase.execute(pushersManager, registerPusher = true) } - } catch (error: Exception) { - continuation.resumeWithException(error) } } + + val session = activeSessionHolder.getSafeActiveSession() ?: return EnableNotificationsResult.Failure + val deviceId = session.sessionParams.deviceId ?: return EnableNotificationsResult.Failure + togglePushNotificationUseCase.execute(deviceId, enabled = true) + + return EnableNotificationsResult.Success } } diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt index f8c1a9ad44..ae00b3864c 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt @@ -22,6 +22,7 @@ import android.content.Intent import android.media.RingtoneManager import android.net.Uri import android.os.Bundle +import android.view.View import android.widget.Toast import androidx.lifecycle.LiveData import androidx.lifecycle.distinctUntilChanged @@ -29,6 +30,7 @@ import androidx.lifecycle.lifecycleScope import androidx.lifecycle.map import androidx.preference.Preference import androidx.preference.SwitchPreference +import com.airbnb.mvrx.fragmentViewModel import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder @@ -81,8 +83,6 @@ class VectorSettingsNotificationPreferenceFragment : @Inject lateinit var guardServiceStarter: GuardServiceStarter @Inject lateinit var vectorFeatures: VectorFeatures @Inject lateinit var notificationPermissionManager: NotificationPermissionManager - @Inject lateinit var disableNotificationsForCurrentSessionUseCase: DisableNotificationsForCurrentSessionUseCase - @Inject lateinit var enableNotificationsForCurrentSessionUseCase: EnableNotificationsForCurrentSessionUseCase @Inject lateinit var ensureFcmTokenIsRetrievedUseCase: EnsureFcmTokenIsRetrievedUseCase override var titleRes: Int = R.string.settings_notifications @@ -90,6 +90,8 @@ class VectorSettingsNotificationPreferenceFragment : private var interactionListener: VectorSettingsFragmentInteractionListener? = null + private val viewModel: VectorSettingsNotificationPreferenceViewModel by fragmentViewModel() + private val notificationStartForActivityResult = registerStartForActivityResult { _ -> // No op } @@ -106,6 +108,22 @@ class VectorSettingsNotificationPreferenceFragment : analyticsScreenName = MobileScreen.ScreenName.SettingsNotifications } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + observeViewEvents() + } + + private fun observeViewEvents() { + viewModel.observeViewEvents { + when (it) { + VectorSettingsNotificationPreferenceViewEvent.NotificationForDeviceEnabled -> onNotificationsForDeviceEnabled() + VectorSettingsNotificationPreferenceViewEvent.NotificationForDeviceDisabled -> onNotificationsForDeviceDisabled() + is VectorSettingsNotificationPreferenceViewEvent.AskUserForPushDistributor -> askUserToSelectPushDistributor(it.distributors) + VectorSettingsNotificationPreferenceViewEvent.EnableNotificationForDeviceFailure -> displayErrorDialog(throwable = null) + } + } + } + override fun bindPref() { findPreference(VectorPreferences.SETTINGS_ENABLE_ALL_NOTIF_PREFERENCE_KEY)!!.let { pref -> val pushRuleService = session.pushRuleService() @@ -123,23 +141,15 @@ class VectorSettingsNotificationPreferenceFragment : } findPreference(VectorPreferences.SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY) - ?.setTransactionalSwitchChangeListener(lifecycleScope) { isChecked -> - if (isChecked) { - enableNotificationsForCurrentSessionUseCase.execute(requireActivity()) - - findPreference(VectorPreferences.SETTINGS_NOTIFICATION_METHOD_KEY) - ?.summary = unifiedPushHelper.getCurrentDistributorName() - - notificationPermissionManager.eventuallyRequestPermission( - requireActivity(), - postPermissionLauncher, - showRationale = false, - ignorePreference = true - ) + ?.setOnPreferenceChangeListener { _, isChecked -> + val action = if (isChecked as Boolean) { + VectorSettingsNotificationPreferenceViewAction.EnableNotificationsForDevice(pushDistributor = "") } else { - disableNotificationsForCurrentSessionUseCase.execute() - notificationPermissionManager.eventuallyRevokePermission(requireActivity()) + VectorSettingsNotificationPreferenceViewAction.DisableNotificationsForDevice } + viewModel.handle(action) + // preference will be updated on ViewEvent reception + false } findPreference(VectorPreferences.SETTINGS_FDROID_BACKGROUND_SYNC_MODE)?.let { @@ -184,6 +194,8 @@ class VectorSettingsNotificationPreferenceFragment : if (vectorFeatures.allowExternalUnifiedPushDistributors()) { it.summary = unifiedPushHelper.getCurrentDistributorName() it.onPreferenceClickListener = Preference.OnPreferenceClickListener { + // TODO show dialog to pick a distributor + // TODO call unregister then register only when a new distributor has been selected => use UnifiedPushHelper method unifiedPushHelper.forceRegister(requireActivity(), pushersManager) { ensureFcmTokenIsRetrievedUseCase.execute(pushersManager, registerPusher = vectorPreferences.areNotificationEnabledForDevice()) it.summary = unifiedPushHelper.getCurrentDistributorName() @@ -203,6 +215,33 @@ class VectorSettingsNotificationPreferenceFragment : handleSystemPreference() } + private fun onNotificationsForDeviceEnabled() { + findPreference(VectorPreferences.SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY) + ?.isChecked = true + findPreference(VectorPreferences.SETTINGS_NOTIFICATION_METHOD_KEY) + ?.summary = unifiedPushHelper.getCurrentDistributorName() + + notificationPermissionManager.eventuallyRequestPermission( + requireActivity(), + postPermissionLauncher, + showRationale = false, + ignorePreference = true + ) + } + + private fun onNotificationsForDeviceDisabled() { + findPreference(VectorPreferences.SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY) + ?.isChecked = false + notificationPermissionManager.eventuallyRevokePermission(requireActivity()) + } + + // TODO add an argument to know if unregister should be called + private fun askUserToSelectPushDistributor(distributors: List) { + unifiedPushHelper.showSelectDistributorDialog(requireContext(), distributors) { selection -> + viewModel.handle(VectorSettingsNotificationPreferenceViewAction.RegisterPushDistributor(selection)) + } + } + private fun bindEmailNotifications() { val initialEmails = session.getEmailsWithPushInformation() bindEmailNotificationCategory(initialEmails) diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceViewAction.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceViewAction.kt new file mode 100644 index 0000000000..949dc99993 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceViewAction.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2022 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 im.vector.app.features.settings.notifications + +import im.vector.app.core.platform.VectorViewModelAction + +sealed interface VectorSettingsNotificationPreferenceViewAction : VectorViewModelAction { + data class EnableNotificationsForDevice(val pushDistributor: String) : VectorSettingsNotificationPreferenceViewAction + object DisableNotificationsForDevice : VectorSettingsNotificationPreferenceViewAction + data class RegisterPushDistributor(val pushDistributor: String) : VectorSettingsNotificationPreferenceViewAction +} diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceViewEvent.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceViewEvent.kt new file mode 100644 index 0000000000..4948ad6e58 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceViewEvent.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2022 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 im.vector.app.features.settings.notifications + +import im.vector.app.core.platform.VectorViewEvents + +sealed interface VectorSettingsNotificationPreferenceViewEvent : VectorViewEvents { + object NotificationForDeviceEnabled : VectorSettingsNotificationPreferenceViewEvent + object EnableNotificationForDeviceFailure : VectorSettingsNotificationPreferenceViewEvent + object NotificationForDeviceDisabled : VectorSettingsNotificationPreferenceViewEvent + data class AskUserForPushDistributor(val distributors: List) : VectorSettingsNotificationPreferenceViewEvent +} diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceViewModel.kt new file mode 100644 index 0000000000..0173f4846f --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceViewModel.kt @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2022 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 im.vector.app.features.settings.notifications + +import com.airbnb.mvrx.MavericksViewModelFactory +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import im.vector.app.core.di.MavericksAssistedViewModelFactory +import im.vector.app.core.di.hiltMavericksViewModelFactory +import im.vector.app.core.platform.VectorDummyViewState +import im.vector.app.core.platform.VectorViewModel +import kotlinx.coroutines.launch + +class VectorSettingsNotificationPreferenceViewModel @AssistedInject constructor( + @Assisted initialState: VectorDummyViewState, + private val enableNotificationsForCurrentSessionUseCase: EnableNotificationsForCurrentSessionUseCase, + private val disableNotificationsForCurrentSessionUseCase: DisableNotificationsForCurrentSessionUseCase, +) : VectorViewModel(initialState) { + + @AssistedFactory + interface Factory : MavericksAssistedViewModelFactory { + override fun create(initialState: VectorDummyViewState): VectorSettingsNotificationPreferenceViewModel + } + + companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() + + // TODO add unit tests + override fun handle(action: VectorSettingsNotificationPreferenceViewAction) { + when (action) { + VectorSettingsNotificationPreferenceViewAction.DisableNotificationsForDevice -> handleDisableNotificationsForDevice() + is VectorSettingsNotificationPreferenceViewAction.EnableNotificationsForDevice -> handleEnableNotificationsForDevice(action.pushDistributor) + is VectorSettingsNotificationPreferenceViewAction.RegisterPushDistributor -> handleRegisterPushDistributor(action.pushDistributor) + } + } + + private fun handleDisableNotificationsForDevice() { + viewModelScope.launch { + disableNotificationsForCurrentSessionUseCase.execute() + _viewEvents.post(VectorSettingsNotificationPreferenceViewEvent.NotificationForDeviceDisabled) + } + } + + private fun handleEnableNotificationsForDevice(distributor: String) { + viewModelScope.launch { + when (val result = enableNotificationsForCurrentSessionUseCase.execute(distributor)) { + EnableNotificationsForCurrentSessionUseCase.EnableNotificationsResult.Failure -> { + _viewEvents.post(VectorSettingsNotificationPreferenceViewEvent.EnableNotificationForDeviceFailure) + } + is EnableNotificationsForCurrentSessionUseCase.EnableNotificationsResult.NeedToAskUserForDistributor -> { + _viewEvents.post(VectorSettingsNotificationPreferenceViewEvent.AskUserForPushDistributor(result.distributors)) + } + EnableNotificationsForCurrentSessionUseCase.EnableNotificationsResult.Success -> { + _viewEvents.post(VectorSettingsNotificationPreferenceViewEvent.NotificationForDeviceEnabled) + } + } + } + } + + private fun handleRegisterPushDistributor(distributor: String) { + handleEnableNotificationsForDevice(distributor) + } +} From 95556d25515d08b6f9fcc8154be65908110f17ef Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 30 Nov 2022 11:06:24 +0100 Subject: [PATCH 07/16] Change the distributor in dialog cancellation only if there is no existing one --- .../java/im/vector/app/core/pushers/UnifiedPushHelper.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushHelper.kt b/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushHelper.kt index 34ba254250..87231d1d67 100644 --- a/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushHelper.kt +++ b/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushHelper.kt @@ -206,8 +206,11 @@ class UnifiedPushHelper @Inject constructor( onDistributorSelected(distributor) } .setOnCancelListener { - // By default, use internal solution (fcm/background sync) - onDistributorSelected(context.packageName) + // we do not want to change the distributor on behalf of the user + if (UnifiedPush.getDistributor(context).isEmpty()) { + // By default, use internal solution (fcm/background sync) + onDistributorSelected(context.packageName) + } } .setCancelable(true) .show() From 2673979ef8f7feddd60dcd6058a3790435729db0 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 30 Nov 2022 12:04:52 +0100 Subject: [PATCH 08/16] Handling change of notification method --- .../pushers/RegisterUnifiedPushUseCase.kt | 6 ++-- .../app/core/pushers/UnifiedPushHelper.kt | 7 ++-- .../vector/app/features/home/HomeActivity.kt | 6 ++-- .../features/home/HomeActivityViewEvents.kt | 2 +- .../features/home/HomeActivityViewModel.kt | 4 +-- ...leNotificationsForCurrentSessionUseCase.kt | 6 ++-- ...rSettingsNotificationPreferenceFragment.kt | 33 ++++++++++--------- ...SettingsNotificationPreferenceViewEvent.kt | 7 ++-- ...SettingsNotificationPreferenceViewModel.kt | 31 ++++++++++++++--- .../TestEndpointAsTokenRegistration.kt | 7 ++-- 10 files changed, 67 insertions(+), 42 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/pushers/RegisterUnifiedPushUseCase.kt b/vector/src/main/java/im/vector/app/core/pushers/RegisterUnifiedPushUseCase.kt index 7aafa07348..58bf0f5050 100644 --- a/vector/src/main/java/im/vector/app/core/pushers/RegisterUnifiedPushUseCase.kt +++ b/vector/src/main/java/im/vector/app/core/pushers/RegisterUnifiedPushUseCase.kt @@ -29,12 +29,12 @@ class RegisterUnifiedPushUseCase @Inject constructor( sealed interface RegisterUnifiedPushResult { object Success : RegisterUnifiedPushResult - data class NeedToAskUserForDistributor(val distributors: List) : RegisterUnifiedPushResult + object NeedToAskUserForDistributor : RegisterUnifiedPushResult } // TODO add unit tests fun execute(distributor: String = ""): RegisterUnifiedPushResult { - if(distributor.isNotEmpty()) { + if (distributor.isNotEmpty()) { saveAndRegisterApp(distributor) return RegisterUnifiedPushResult.Success } @@ -55,7 +55,7 @@ class RegisterUnifiedPushUseCase @Inject constructor( saveAndRegisterApp(distributors.first()) RegisterUnifiedPushResult.Success } else { - RegisterUnifiedPushResult.NeedToAskUserForDistributor(distributors) + RegisterUnifiedPushResult.NeedToAskUserForDistributor } } diff --git a/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushHelper.kt b/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushHelper.kt index 87231d1d67..efa396a980 100644 --- a/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushHelper.kt +++ b/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushHelper.kt @@ -52,7 +52,7 @@ class UnifiedPushHelper @Inject constructor( // Called when the home activity starts // or when notifications are enabled - // TODO remove and replace by use case + // TODO remove fun register( activity: FragmentActivity, onDoneRunnable: Runnable? = null, @@ -70,7 +70,7 @@ class UnifiedPushHelper @Inject constructor( // The registration is forced in 2 cases : // * in the settings // * in the troubleshoot list (doFix) - // TODO remove and replace by use case + // TODO remove fun forceRegister( activity: FragmentActivity, pushersManager: PushersManager, @@ -132,6 +132,7 @@ class UnifiedPushHelper @Inject constructor( } } + // TODO remove // There is no case where this function is called // with a saved distributor and/or a pusher private fun openDistributorDialogInternal( @@ -180,7 +181,6 @@ class UnifiedPushHelper @Inject constructor( @MainThread fun showSelectDistributorDialog( context: Context, - distributors: List, onDistributorSelected: (String) -> Unit, ) { val internalDistributorName = stringProvider.getString( @@ -191,6 +191,7 @@ class UnifiedPushHelper @Inject constructor( } ) + val distributors = UnifiedPush.getDistributors(context) val distributorsName = distributors.map { if (it == context.packageName) { internalDistributorName diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt index 14157a1de8..8c6daae95a 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt @@ -266,7 +266,7 @@ class HomeActivity : HomeActivityViewEvents.ShowReleaseNotes -> handleShowReleaseNotes() HomeActivityViewEvents.NotifyUserForThreadsMigration -> handleNotifyUserForThreadsMigration() is HomeActivityViewEvents.MigrateThreads -> migrateThreadsIfNeeded(it.checkSession) - is HomeActivityViewEvents.AskUserForPushDistributor -> askUserToSelectPushDistributor(it.distributors) + is HomeActivityViewEvents.AskUserForPushDistributor -> askUserToSelectPushDistributor() } } homeActivityViewModel.onEach { renderState(it) } @@ -279,8 +279,8 @@ class HomeActivity : homeActivityViewModel.handle(HomeActivityViewActions.ViewStarted) } - private fun askUserToSelectPushDistributor(distributors: List) { - unifiedPushHelper.showSelectDistributorDialog(this, distributors) { selection -> + private fun askUserToSelectPushDistributor() { + unifiedPushHelper.showSelectDistributorDialog(this) { selection -> homeActivityViewModel.handle(HomeActivityViewActions.RegisterPushDistributor(selection)) } } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt index 6fdf441d1d..be5aa7def0 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt @@ -39,5 +39,5 @@ sealed interface HomeActivityViewEvents : VectorViewEvents { data class MigrateThreads(val checkSession: Boolean) : HomeActivityViewEvents object StartRecoverySetupFlow : HomeActivityViewEvents data class ForceVerification(val sendRequest: Boolean) : HomeActivityViewEvents - data class AskUserForPushDistributor(val distributors: List) : HomeActivityViewEvents + object AskUserForPushDistributor : HomeActivityViewEvents } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt index 2905decddf..7ffc46218c 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt @@ -129,9 +129,9 @@ class HomeActivityViewModel @AssistedInject constructor( private fun registerUnifiedPush(distributor: String) { viewModelScope.launch { - when (val result = registerUnifiedPushUseCase.execute(distributor = distributor)) { + when (registerUnifiedPushUseCase.execute(distributor = distributor)) { is RegisterUnifiedPushUseCase.RegisterUnifiedPushResult.NeedToAskUserForDistributor -> { - _viewEvents.post(HomeActivityViewEvents.AskUserForPushDistributor(result.distributors)) + _viewEvents.post(HomeActivityViewEvents.AskUserForPushDistributor) } RegisterUnifiedPushUseCase.RegisterUnifiedPushResult.Success -> { ensureFcmTokenIsRetrievedUseCase.execute(pushersManager, registerPusher = vectorPreferences.areNotificationEnabledForDevice()) diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/EnableNotificationsForCurrentSessionUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/EnableNotificationsForCurrentSessionUseCase.kt index 2f8bdd4d0d..e0b0a872f8 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/EnableNotificationsForCurrentSessionUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/EnableNotificationsForCurrentSessionUseCase.kt @@ -34,16 +34,16 @@ class EnableNotificationsForCurrentSessionUseCase @Inject constructor( sealed interface EnableNotificationsResult { object Success : EnableNotificationsResult object Failure : EnableNotificationsResult - data class NeedToAskUserForDistributor(val distributors: List) : EnableNotificationsResult + object NeedToAskUserForDistributor : EnableNotificationsResult } // TODO update unit tests suspend fun execute(distributor: String = ""): EnableNotificationsResult { val pusherForCurrentSession = pushersManager.getPusherForCurrentSession() if (pusherForCurrentSession == null) { - when (val result = registerUnifiedPushUseCase.execute(distributor)) { + when (registerUnifiedPushUseCase.execute(distributor)) { is RegisterUnifiedPushUseCase.RegisterUnifiedPushResult.NeedToAskUserForDistributor -> { - return EnableNotificationsResult.NeedToAskUserForDistributor(result.distributors) + return EnableNotificationsResult.NeedToAskUserForDistributor } RegisterUnifiedPushUseCase.RegisterUnifiedPushResult.Success -> { ensureFcmTokenIsRetrievedUseCase.execute(pushersManager, registerPusher = true) diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt index ae00b3864c..238ed4218c 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt @@ -116,10 +116,11 @@ class VectorSettingsNotificationPreferenceFragment : private fun observeViewEvents() { viewModel.observeViewEvents { when (it) { - VectorSettingsNotificationPreferenceViewEvent.NotificationForDeviceEnabled -> onNotificationsForDeviceEnabled() - VectorSettingsNotificationPreferenceViewEvent.NotificationForDeviceDisabled -> onNotificationsForDeviceDisabled() - is VectorSettingsNotificationPreferenceViewEvent.AskUserForPushDistributor -> askUserToSelectPushDistributor(it.distributors) + VectorSettingsNotificationPreferenceViewEvent.NotificationsForDeviceEnabled -> onNotificationsForDeviceEnabled() + VectorSettingsNotificationPreferenceViewEvent.NotificationsForDeviceDisabled -> onNotificationsForDeviceDisabled() + is VectorSettingsNotificationPreferenceViewEvent.AskUserForPushDistributor -> askUserToSelectPushDistributor() VectorSettingsNotificationPreferenceViewEvent.EnableNotificationForDeviceFailure -> displayErrorDialog(throwable = null) + VectorSettingsNotificationPreferenceViewEvent.NotificationMethodChanged -> onNotificationMethodChanged() } } } @@ -194,14 +195,7 @@ class VectorSettingsNotificationPreferenceFragment : if (vectorFeatures.allowExternalUnifiedPushDistributors()) { it.summary = unifiedPushHelper.getCurrentDistributorName() it.onPreferenceClickListener = Preference.OnPreferenceClickListener { - // TODO show dialog to pick a distributor - // TODO call unregister then register only when a new distributor has been selected => use UnifiedPushHelper method - unifiedPushHelper.forceRegister(requireActivity(), pushersManager) { - ensureFcmTokenIsRetrievedUseCase.execute(pushersManager, registerPusher = vectorPreferences.areNotificationEnabledForDevice()) - it.summary = unifiedPushHelper.getCurrentDistributorName() - session.pushersService().refreshPushers() - refreshBackgroundSyncPrefs() - } + askUserToSelectPushDistributor(withUnregister = true) true } } else { @@ -235,13 +229,22 @@ class VectorSettingsNotificationPreferenceFragment : notificationPermissionManager.eventuallyRevokePermission(requireActivity()) } - // TODO add an argument to know if unregister should be called - private fun askUserToSelectPushDistributor(distributors: List) { - unifiedPushHelper.showSelectDistributorDialog(requireContext(), distributors) { selection -> - viewModel.handle(VectorSettingsNotificationPreferenceViewAction.RegisterPushDistributor(selection)) + private fun askUserToSelectPushDistributor(withUnregister: Boolean = false) { + unifiedPushHelper.showSelectDistributorDialog(requireContext()) { selection -> + if (withUnregister) { + viewModel.handle(VectorSettingsNotificationPreferenceViewAction.RegisterPushDistributor(selection)) + } else { + viewModel.handle(VectorSettingsNotificationPreferenceViewAction.EnableNotificationsForDevice(selection)) + } } } + private fun onNotificationMethodChanged() { + findPreference(VectorPreferences.SETTINGS_NOTIFICATION_METHOD_KEY)?.summary = unifiedPushHelper.getCurrentDistributorName() + session.pushersService().refreshPushers() + refreshBackgroundSyncPrefs() + } + private fun bindEmailNotifications() { val initialEmails = session.getEmailsWithPushInformation() bindEmailNotificationCategory(initialEmails) diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceViewEvent.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceViewEvent.kt index 4948ad6e58..e4cf8e1973 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceViewEvent.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceViewEvent.kt @@ -19,8 +19,9 @@ package im.vector.app.features.settings.notifications import im.vector.app.core.platform.VectorViewEvents sealed interface VectorSettingsNotificationPreferenceViewEvent : VectorViewEvents { - object NotificationForDeviceEnabled : VectorSettingsNotificationPreferenceViewEvent + object NotificationsForDeviceEnabled : VectorSettingsNotificationPreferenceViewEvent object EnableNotificationForDeviceFailure : VectorSettingsNotificationPreferenceViewEvent - object NotificationForDeviceDisabled : VectorSettingsNotificationPreferenceViewEvent - data class AskUserForPushDistributor(val distributors: List) : VectorSettingsNotificationPreferenceViewEvent + object NotificationsForDeviceDisabled : VectorSettingsNotificationPreferenceViewEvent + object AskUserForPushDistributor : VectorSettingsNotificationPreferenceViewEvent + object NotificationMethodChanged : VectorSettingsNotificationPreferenceViewEvent } diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceViewModel.kt index 0173f4846f..59c26749c9 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceViewModel.kt @@ -24,12 +24,22 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorDummyViewState import im.vector.app.core.platform.VectorViewModel +import im.vector.app.core.pushers.EnsureFcmTokenIsRetrievedUseCase +import im.vector.app.core.pushers.PushersManager +import im.vector.app.core.pushers.RegisterUnifiedPushUseCase +import im.vector.app.core.pushers.UnregisterUnifiedPushUseCase +import im.vector.app.features.settings.VectorPreferences import kotlinx.coroutines.launch class VectorSettingsNotificationPreferenceViewModel @AssistedInject constructor( @Assisted initialState: VectorDummyViewState, + private val pushersManager: PushersManager, + private val vectorPreferences: VectorPreferences, private val enableNotificationsForCurrentSessionUseCase: EnableNotificationsForCurrentSessionUseCase, private val disableNotificationsForCurrentSessionUseCase: DisableNotificationsForCurrentSessionUseCase, + private val unregisterUnifiedPushUseCase: UnregisterUnifiedPushUseCase, + private val registerUnifiedPushUseCase: RegisterUnifiedPushUseCase, + private val ensureFcmTokenIsRetrievedUseCase: EnsureFcmTokenIsRetrievedUseCase, ) : VectorViewModel(initialState) { @AssistedFactory @@ -51,27 +61,38 @@ class VectorSettingsNotificationPreferenceViewModel @AssistedInject constructor( private fun handleDisableNotificationsForDevice() { viewModelScope.launch { disableNotificationsForCurrentSessionUseCase.execute() - _viewEvents.post(VectorSettingsNotificationPreferenceViewEvent.NotificationForDeviceDisabled) + _viewEvents.post(VectorSettingsNotificationPreferenceViewEvent.NotificationsForDeviceDisabled) } } private fun handleEnableNotificationsForDevice(distributor: String) { viewModelScope.launch { - when (val result = enableNotificationsForCurrentSessionUseCase.execute(distributor)) { + when (enableNotificationsForCurrentSessionUseCase.execute(distributor)) { EnableNotificationsForCurrentSessionUseCase.EnableNotificationsResult.Failure -> { _viewEvents.post(VectorSettingsNotificationPreferenceViewEvent.EnableNotificationForDeviceFailure) } is EnableNotificationsForCurrentSessionUseCase.EnableNotificationsResult.NeedToAskUserForDistributor -> { - _viewEvents.post(VectorSettingsNotificationPreferenceViewEvent.AskUserForPushDistributor(result.distributors)) + _viewEvents.post(VectorSettingsNotificationPreferenceViewEvent.AskUserForPushDistributor) } EnableNotificationsForCurrentSessionUseCase.EnableNotificationsResult.Success -> { - _viewEvents.post(VectorSettingsNotificationPreferenceViewEvent.NotificationForDeviceEnabled) + _viewEvents.post(VectorSettingsNotificationPreferenceViewEvent.NotificationsForDeviceEnabled) } } } } private fun handleRegisterPushDistributor(distributor: String) { - handleEnableNotificationsForDevice(distributor) + viewModelScope.launch { + unregisterUnifiedPushUseCase.execute(pushersManager) + when (registerUnifiedPushUseCase.execute(distributor)) { + RegisterUnifiedPushUseCase.RegisterUnifiedPushResult.NeedToAskUserForDistributor -> { + _viewEvents.post(VectorSettingsNotificationPreferenceViewEvent.AskUserForPushDistributor) + } + RegisterUnifiedPushUseCase.RegisterUnifiedPushResult.Success -> { + ensureFcmTokenIsRetrievedUseCase.execute(pushersManager, registerPusher = vectorPreferences.areNotificationEnabledForDevice()) + _viewEvents.post(VectorSettingsNotificationPreferenceViewEvent.NotificationMethodChanged) + } + } + } } } diff --git a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestEndpointAsTokenRegistration.kt b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestEndpointAsTokenRegistration.kt index e6cb78d185..b355b55903 100644 --- a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestEndpointAsTokenRegistration.kt +++ b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestEndpointAsTokenRegistration.kt @@ -83,9 +83,9 @@ class TestEndpointAsTokenRegistration @Inject constructor( testParameters: TestParameters, pushKey: String, ) { - when (val result = registerUnifiedPushUseCase.execute(distributor)) { + when (registerUnifiedPushUseCase.execute(distributor)) { is RegisterUnifiedPushUseCase.RegisterUnifiedPushResult.NeedToAskUserForDistributor -> - askUserForDistributor(result.distributors, testParameters, pushKey) + askUserForDistributor(testParameters, pushKey) RegisterUnifiedPushUseCase.RegisterUnifiedPushResult.Success -> { val workId = pushersManager.enqueueRegisterPusherWithFcmKey(pushKey) WorkManager.getInstance(context).getWorkInfoByIdLiveData(workId).observe(context) { workInfo -> @@ -102,11 +102,10 @@ class TestEndpointAsTokenRegistration @Inject constructor( } private fun askUserForDistributor( - distributors: List, testParameters: TestParameters, pushKey: String, ) { - unifiedPushHelper.showSelectDistributorDialog(context, distributors) { selection -> + unifiedPushHelper.showSelectDistributorDialog(context) { selection -> registerUnifiedPush(distributor = selection, testParameters, pushKey) } } From 740ed8963892ff76a4482c43c090996ca7579f76 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 30 Nov 2022 14:27:18 +0100 Subject: [PATCH 09/16] Removing the old methods from helper --- .../app/core/pushers/UnifiedPushHelper.kt | 155 ------------------ 1 file changed, 155 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushHelper.kt b/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushHelper.kt index efa396a980..9f96f13ee7 100644 --- a/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushHelper.kt +++ b/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushHelper.kt @@ -18,20 +18,12 @@ package im.vector.app.core.pushers import android.content.Context import androidx.annotation.MainThread -import androidx.fragment.app.FragmentActivity -import androidx.lifecycle.lifecycleScope import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import im.vector.app.R import im.vector.app.core.resources.StringProvider import im.vector.app.core.utils.getApplicationLabel -import im.vector.app.features.VectorFeatures -import im.vector.app.features.settings.BackgroundSyncMode -import im.vector.app.features.settings.VectorPreferences -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.Matrix import org.matrix.android.sdk.api.cache.CacheStrategy import org.matrix.android.sdk.api.util.MatrixJsonParser @@ -44,140 +36,10 @@ class UnifiedPushHelper @Inject constructor( private val context: Context, private val unifiedPushStore: UnifiedPushStore, private val stringProvider: StringProvider, - private val vectorPreferences: VectorPreferences, private val matrix: Matrix, - private val vectorFeatures: VectorFeatures, private val fcmHelper: FcmHelper, ) { - // Called when the home activity starts - // or when notifications are enabled - // TODO remove - fun register( - activity: FragmentActivity, - onDoneRunnable: Runnable? = null, - ) { - registerInternal( - activity, - onDoneRunnable = onDoneRunnable - ) - } - - // If registration is forced: - // * the current distributor (if any) is removed - // * The dialog is opened - // - // The registration is forced in 2 cases : - // * in the settings - // * in the troubleshoot list (doFix) - // TODO remove - fun forceRegister( - activity: FragmentActivity, - pushersManager: PushersManager, - @MainThread onDoneRunnable: Runnable? = null - ) { - registerInternal( - activity, - force = true, - pushersManager = pushersManager, - onDoneRunnable = onDoneRunnable - ) - } - - // TODO remove - private fun registerInternal( - activity: FragmentActivity, - force: Boolean = false, - pushersManager: PushersManager? = null, - onDoneRunnable: Runnable? = null - ) { - activity.lifecycleScope.launch(Dispatchers.IO) { - Timber.d("registerInternal force=$force, $activity on thread ${Thread.currentThread()}") - if (!vectorFeatures.allowExternalUnifiedPushDistributors()) { - UnifiedPush.saveDistributor(context, context.packageName) - UnifiedPush.registerApp(context) - withContext(Dispatchers.Main) { - onDoneRunnable?.run() - } - return@launch - } - if (force) { - // Un-register first - unregister(pushersManager) - } - // the !force should not be needed - if (!force && UnifiedPush.getDistributor(context).isNotEmpty()) { - UnifiedPush.registerApp(context) - withContext(Dispatchers.Main) { - onDoneRunnable?.run() - } - return@launch - } - - val distributors = UnifiedPush.getDistributors(context) - - if (!force && distributors.size == 1) { - UnifiedPush.saveDistributor(context, distributors.first()) - UnifiedPush.registerApp(context) - withContext(Dispatchers.Main) { - onDoneRunnable?.run() - } - } else { - openDistributorDialogInternal( - activity = activity, - onDoneRunnable = onDoneRunnable, - distributors = distributors - ) - } - } - } - - // TODO remove - // There is no case where this function is called - // with a saved distributor and/or a pusher - private fun openDistributorDialogInternal( - activity: FragmentActivity, - onDoneRunnable: Runnable?, - distributors: List - ) { - val internalDistributorName = stringProvider.getString( - if (fcmHelper.isFirebaseAvailable()) { - R.string.unifiedpush_distributor_fcm_fallback - } else { - R.string.unifiedpush_distributor_background_sync - } - ) - - val distributorsName = distributors.map { - if (it == context.packageName) { - internalDistributorName - } else { - context.getApplicationLabel(it) - } - } - - MaterialAlertDialogBuilder(activity) - .setTitle(stringProvider.getString(R.string.unifiedpush_getdistributors_dialog_title)) - .setItems(distributorsName.toTypedArray()) { _, which -> - val distributor = distributors[which] - - activity.lifecycleScope.launch { - UnifiedPush.saveDistributor(context, distributor) - Timber.i("Saving distributor: $distributor") - UnifiedPush.registerApp(context) - onDoneRunnable?.run() - } - } - .setOnCancelListener { - // By default, use internal solution (fcm/background sync) - UnifiedPush.saveDistributor(context, context.packageName) - UnifiedPush.registerApp(context) - onDoneRunnable?.run() - } - .setCancelable(true) - .show() - } - @MainThread fun showSelectDistributorDialog( context: Context, @@ -217,23 +79,6 @@ class UnifiedPushHelper @Inject constructor( .show() } - // TODO remove - suspend fun unregister(pushersManager: PushersManager? = null) { - val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME - vectorPreferences.setFdroidSyncBackgroundMode(mode) - try { - getEndpointOrToken()?.let { - Timber.d("Removing $it") - pushersManager?.unregisterPusher(it) - } - } catch (e: Exception) { - Timber.d(e, "Probably unregistering a non existing pusher") - } - unifiedPushStore.storeUpEndpoint(null) - unifiedPushStore.storePushGateway(null) - UnifiedPush.unregisterApp(context) - } - @JsonClass(generateAdapter = true) internal data class DiscoveryResponse( @Json(name = "unifiedpush") val unifiedpush: DiscoveryUnifiedPush = DiscoveryUnifiedPush() From aa3a808d2cc670301b5d1920d532b30b85be5b48 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 30 Nov 2022 14:42:56 +0100 Subject: [PATCH 10/16] Do not ask to select push distributor in home if notifications are disabled --- .../im/vector/app/features/home/HomeActivityViewModel.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt index 7ffc46218c..cf4bce12f0 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt @@ -116,7 +116,7 @@ class HomeActivityViewModel @AssistedInject constructor( private fun initialize() { if (isInitialized) return isInitialized = true - registerUnifiedPush(distributor = "") + registerUnifiedPushIfNeeded() cleanupFiles() observeInitialSync() checkSessionPushIsOn() @@ -127,6 +127,12 @@ class HomeActivityViewModel @AssistedInject constructor( viewModelScope.launch { stopOngoingVoiceBroadcastUseCase.execute() } } + private fun registerUnifiedPushIfNeeded() { + if(vectorPreferences.areNotificationEnabledForDevice()) { + registerUnifiedPush(distributor = "") + } + } + private fun registerUnifiedPush(distributor: String) { viewModelScope.launch { when (registerUnifiedPushUseCase.execute(distributor = distributor)) { From a3815d70128e35639b2b5b020d96db8927306267 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 30 Nov 2022 15:12:07 +0100 Subject: [PATCH 11/16] Update unit tests --- ...leNotificationsForCurrentSessionUseCase.kt | 1 - ...leNotificationsForCurrentSessionUseCase.kt | 1 - ...tificationsForCurrentSessionUseCaseTest.kt | 12 +-- ...tificationsForCurrentSessionUseCaseTest.kt | 88 +++++++++++-------- .../im/vector/app/test/fakes/FakeFcmHelper.kt | 11 +-- .../app/test/fakes/FakeUnifiedPushHelper.kt | 23 ----- 6 files changed, 63 insertions(+), 73 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/DisableNotificationsForCurrentSessionUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/DisableNotificationsForCurrentSessionUseCase.kt index 2ce2254f2e..84d92c4291 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/DisableNotificationsForCurrentSessionUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/DisableNotificationsForCurrentSessionUseCase.kt @@ -31,7 +31,6 @@ class DisableNotificationsForCurrentSessionUseCase @Inject constructor( private val unregisterUnifiedPushUseCase: UnregisterUnifiedPushUseCase, ) { - // TODO update unit tests suspend fun execute() { val session = activeSessionHolder.getSafeActiveSession() ?: return val deviceId = session.sessionParams.deviceId ?: return diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/EnableNotificationsForCurrentSessionUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/EnableNotificationsForCurrentSessionUseCase.kt index e0b0a872f8..99fb249384 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/EnableNotificationsForCurrentSessionUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/EnableNotificationsForCurrentSessionUseCase.kt @@ -37,7 +37,6 @@ class EnableNotificationsForCurrentSessionUseCase @Inject constructor( object NeedToAskUserForDistributor : EnableNotificationsResult } - // TODO update unit tests suspend fun execute(distributor: String = ""): EnableNotificationsResult { val pusherForCurrentSession = pushersManager.getPusherForCurrentSession() if (pusherForCurrentSession == null) { diff --git a/vector/src/test/java/im/vector/app/features/settings/notifications/DisableNotificationsForCurrentSessionUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/notifications/DisableNotificationsForCurrentSessionUseCaseTest.kt index e460413a39..386e68ee3a 100644 --- a/vector/src/test/java/im/vector/app/features/settings/notifications/DisableNotificationsForCurrentSessionUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/settings/notifications/DisableNotificationsForCurrentSessionUseCaseTest.kt @@ -16,11 +16,11 @@ package im.vector.app.features.settings.notifications +import im.vector.app.core.pushers.UnregisterUnifiedPushUseCase import im.vector.app.features.settings.devices.v2.notification.CheckIfCanTogglePushNotificationsViaPusherUseCase import im.vector.app.features.settings.devices.v2.notification.TogglePushNotificationUseCase import im.vector.app.test.fakes.FakeActiveSessionHolder import im.vector.app.test.fakes.FakePushersManager -import im.vector.app.test.fakes.FakeUnifiedPushHelper import io.mockk.coJustRun import io.mockk.coVerify import io.mockk.every @@ -33,17 +33,17 @@ private const val A_SESSION_ID = "session-id" class DisableNotificationsForCurrentSessionUseCaseTest { private val fakeActiveSessionHolder = FakeActiveSessionHolder() - private val fakeUnifiedPushHelper = FakeUnifiedPushHelper() private val fakePushersManager = FakePushersManager() private val fakeCheckIfCanTogglePushNotificationsViaPusherUseCase = mockk() private val fakeTogglePushNotificationUseCase = mockk() + private val fakeUnregisterUnifiedPushUseCase = mockk() private val disableNotificationsForCurrentSessionUseCase = DisableNotificationsForCurrentSessionUseCase( activeSessionHolder = fakeActiveSessionHolder.instance, - unifiedPushHelper = fakeUnifiedPushHelper.instance, pushersManager = fakePushersManager.instance, checkIfCanTogglePushNotificationsViaPusherUseCase = fakeCheckIfCanTogglePushNotificationsViaPusherUseCase, togglePushNotificationUseCase = fakeTogglePushNotificationUseCase, + unregisterUnifiedPushUseCase = fakeUnregisterUnifiedPushUseCase, ) @Test @@ -67,12 +67,14 @@ class DisableNotificationsForCurrentSessionUseCaseTest { val fakeSession = fakeActiveSessionHolder.fakeSession fakeSession.givenSessionId(A_SESSION_ID) every { fakeCheckIfCanTogglePushNotificationsViaPusherUseCase.execute(fakeSession) } returns false - fakeUnifiedPushHelper.givenUnregister(fakePushersManager.instance) + coJustRun { fakeUnregisterUnifiedPushUseCase.execute(any()) } // When disableNotificationsForCurrentSessionUseCase.execute() // Then - fakeUnifiedPushHelper.verifyUnregister(fakePushersManager.instance) + coVerify { + fakeUnregisterUnifiedPushUseCase.execute(fakePushersManager.instance) + } } } diff --git a/vector/src/test/java/im/vector/app/features/settings/notifications/EnableNotificationsForCurrentSessionUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/notifications/EnableNotificationsForCurrentSessionUseCaseTest.kt index eb6629cb13..c923f0c7d6 100644 --- a/vector/src/test/java/im/vector/app/features/settings/notifications/EnableNotificationsForCurrentSessionUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/settings/notifications/EnableNotificationsForCurrentSessionUseCaseTest.kt @@ -16,18 +16,18 @@ package im.vector.app.features.settings.notifications -import androidx.fragment.app.FragmentActivity -import im.vector.app.features.settings.devices.v2.notification.CheckIfCanTogglePushNotificationsViaPusherUseCase +import im.vector.app.core.pushers.EnsureFcmTokenIsRetrievedUseCase +import im.vector.app.core.pushers.RegisterUnifiedPushUseCase import im.vector.app.features.settings.devices.v2.notification.TogglePushNotificationUseCase import im.vector.app.test.fakes.FakeActiveSessionHolder -import im.vector.app.test.fakes.FakeFcmHelper import im.vector.app.test.fakes.FakePushersManager -import im.vector.app.test.fakes.FakeUnifiedPushHelper import io.mockk.coJustRun -import io.mockk.coVerify import io.mockk.every +import io.mockk.justRun import io.mockk.mockk +import io.mockk.verify import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBe import org.junit.Test private const val A_SESSION_ID = "session-id" @@ -35,53 +35,71 @@ private const val A_SESSION_ID = "session-id" class EnableNotificationsForCurrentSessionUseCaseTest { private val fakeActiveSessionHolder = FakeActiveSessionHolder() - private val fakeUnifiedPushHelper = FakeUnifiedPushHelper() private val fakePushersManager = FakePushersManager() - private val fakeFcmHelper = FakeFcmHelper() - private val fakeCheckIfCanTogglePushNotificationsViaPusherUseCase = mockk() private val fakeTogglePushNotificationUseCase = mockk() + private val fakeRegisterUnifiedPushUseCase = mockk() + private val fakeEnsureFcmTokenIsRetrievedUseCase = mockk() private val enableNotificationsForCurrentSessionUseCase = EnableNotificationsForCurrentSessionUseCase( activeSessionHolder = fakeActiveSessionHolder.instance, - unifiedPushHelper = fakeUnifiedPushHelper.instance, pushersManager = fakePushersManager.instance, - fcmHelper = fakeFcmHelper.instance, - checkIfCanTogglePushNotificationsViaPusherUseCase = fakeCheckIfCanTogglePushNotificationsViaPusherUseCase, togglePushNotificationUseCase = fakeTogglePushNotificationUseCase, + registerUnifiedPushUseCase = fakeRegisterUnifiedPushUseCase, + ensureFcmTokenIsRetrievedUseCase = fakeEnsureFcmTokenIsRetrievedUseCase, ) @Test - fun `given no existing pusher for current session when execute then a new pusher is registered`() = runTest { + fun `given no existing pusher and a registered distributor when execute then a new pusher is registered and result is success`() = runTest { // Given - val fragmentActivity = mockk() - fakePushersManager.givenGetPusherForCurrentSessionReturns(null) - fakeUnifiedPushHelper.givenRegister(fragmentActivity) - fakeUnifiedPushHelper.givenIsEmbeddedDistributorReturns(true) - fakeFcmHelper.givenEnsureFcmTokenIsRetrieved(fragmentActivity, fakePushersManager.instance) - every { fakeCheckIfCanTogglePushNotificationsViaPusherUseCase.execute(fakeActiveSessionHolder.fakeSession) } returns false - - // When - enableNotificationsForCurrentSessionUseCase.execute(fragmentActivity) - - // Then - fakeUnifiedPushHelper.verifyRegister(fragmentActivity) - fakeFcmHelper.verifyEnsureFcmTokenIsRetrieved(fragmentActivity, fakePushersManager.instance, registerPusher = true) - } - - @Test - fun `given toggle via Pusher is possible when execute then current pusher is toggled to true`() = runTest { - // Given - val fragmentActivity = mockk() - fakePushersManager.givenGetPusherForCurrentSessionReturns(mockk()) + val aDistributor = "distributor" val fakeSession = fakeActiveSessionHolder.fakeSession fakeSession.givenSessionId(A_SESSION_ID) - every { fakeCheckIfCanTogglePushNotificationsViaPusherUseCase.execute(fakeActiveSessionHolder.fakeSession) } returns true + fakePushersManager.givenGetPusherForCurrentSessionReturns(null) + every { fakeRegisterUnifiedPushUseCase.execute(any()) } returns RegisterUnifiedPushUseCase.RegisterUnifiedPushResult.Success + justRun { fakeEnsureFcmTokenIsRetrievedUseCase.execute(any(), any()) } coJustRun { fakeTogglePushNotificationUseCase.execute(A_SESSION_ID, any()) } // When - enableNotificationsForCurrentSessionUseCase.execute(fragmentActivity) + val result = enableNotificationsForCurrentSessionUseCase.execute(aDistributor) // Then - coVerify { fakeTogglePushNotificationUseCase.execute(A_SESSION_ID, true) } + result shouldBe EnableNotificationsForCurrentSessionUseCase.EnableNotificationsResult.Success + verify { + fakeRegisterUnifiedPushUseCase.execute(aDistributor) + fakeEnsureFcmTokenIsRetrievedUseCase.execute(fakePushersManager.instance, registerPusher = true) + } + } + + @Test + fun `given no existing pusher and a no registered distributor when execute then result is need to ask user for distributor`() = runTest { + // Given + val aDistributor = "distributor" + fakePushersManager.givenGetPusherForCurrentSessionReturns(null) + every { fakeRegisterUnifiedPushUseCase.execute(any()) } returns RegisterUnifiedPushUseCase.RegisterUnifiedPushResult.NeedToAskUserForDistributor + + // When + val result = enableNotificationsForCurrentSessionUseCase.execute(aDistributor) + + // Then + result shouldBe EnableNotificationsForCurrentSessionUseCase.EnableNotificationsResult.NeedToAskUserForDistributor + verify { + fakeRegisterUnifiedPushUseCase.execute(aDistributor) + } + } + + @Test + fun `given no deviceId for current session when execute then result is failure`() = runTest { + // Given + val aDistributor = "distributor" + val fakeSession = fakeActiveSessionHolder.fakeSession + fakeSession.givenSessionId(null) + fakePushersManager.givenGetPusherForCurrentSessionReturns(mockk()) + every { fakeRegisterUnifiedPushUseCase.execute(any()) } returns RegisterUnifiedPushUseCase.RegisterUnifiedPushResult.NeedToAskUserForDistributor + + // When + val result = enableNotificationsForCurrentSessionUseCase.execute(aDistributor) + + // Then + result shouldBe EnableNotificationsForCurrentSessionUseCase.EnableNotificationsResult.Failure } } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeFcmHelper.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeFcmHelper.kt index 11abf18794..07eef36dc1 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeFcmHelper.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeFcmHelper.kt @@ -16,7 +16,6 @@ package im.vector.app.test.fakes -import androidx.fragment.app.FragmentActivity import im.vector.app.core.pushers.FcmHelper import im.vector.app.core.pushers.PushersManager import io.mockk.justRun @@ -27,18 +26,14 @@ class FakeFcmHelper { val instance = mockk() - fun givenEnsureFcmTokenIsRetrieved( - fragmentActivity: FragmentActivity, - pushersManager: PushersManager, - ) { - justRun { instance.ensureFcmTokenIsRetrieved(fragmentActivity, pushersManager, any()) } + fun givenEnsureFcmTokenIsRetrieved(pushersManager: PushersManager) { + justRun { instance.ensureFcmTokenIsRetrieved(pushersManager, any()) } } fun verifyEnsureFcmTokenIsRetrieved( - fragmentActivity: FragmentActivity, pushersManager: PushersManager, registerPusher: Boolean, ) { - verify { instance.ensureFcmTokenIsRetrieved(fragmentActivity, pushersManager, registerPusher) } + verify { instance.ensureFcmTokenIsRetrieved(pushersManager, registerPusher) } } } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeUnifiedPushHelper.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeUnifiedPushHelper.kt index 1f2cc8a1ce..5bc57ead07 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeUnifiedPushHelper.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeUnifiedPushHelper.kt @@ -16,37 +16,14 @@ package im.vector.app.test.fakes -import androidx.fragment.app.FragmentActivity -import im.vector.app.core.pushers.PushersManager import im.vector.app.core.pushers.UnifiedPushHelper -import io.mockk.coJustRun -import io.mockk.coVerify import io.mockk.every import io.mockk.mockk -import io.mockk.verify class FakeUnifiedPushHelper { val instance = mockk() - fun givenRegister(fragmentActivity: FragmentActivity) { - every { instance.register(fragmentActivity, any()) } answers { - secondArg().run() - } - } - - fun verifyRegister(fragmentActivity: FragmentActivity) { - verify { instance.register(fragmentActivity, any()) } - } - - fun givenUnregister(pushersManager: PushersManager) { - coJustRun { instance.unregister(pushersManager) } - } - - fun verifyUnregister(pushersManager: PushersManager) { - coVerify { instance.unregister(pushersManager) } - } - fun givenIsEmbeddedDistributorReturns(isEmbedded: Boolean) { every { instance.isEmbeddedDistributor() } returns isEmbedded } From 46ccf4d73fea7909b64999f7d0786e2684b33e4c Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 30 Nov 2022 16:02:55 +0100 Subject: [PATCH 12/16] Adding unit tests for register and unregister use cases --- .../pushers/RegisterUnifiedPushUseCase.kt | 1 - .../pushers/UnregisterUnifiedPushUseCase.kt | 1 - .../pushers/RegisterUnifiedPushUseCaseTest.kt | 158 ++++++++++++++++++ .../UnregisterUnifiedPushUseCaseTest.kt | 83 +++++++++ .../im/vector/app/test/fakes/FakeContext.kt | 4 + .../app/test/fakes/FakePushersManager.kt | 10 ++ .../app/test/fakes/FakeUnifiedPushHelper.kt | 4 + .../app/test/fakes/FakeUnifiedPushStore.kt | 43 +++++ .../app/test/fakes/FakeVectorFeatures.kt | 4 + .../app/test/fakes/FakeVectorPreferences.kt | 9 + 10 files changed, 315 insertions(+), 2 deletions(-) create mode 100644 vector/src/test/java/im/vector/app/core/pushers/RegisterUnifiedPushUseCaseTest.kt create mode 100644 vector/src/test/java/im/vector/app/core/pushers/UnregisterUnifiedPushUseCaseTest.kt create mode 100644 vector/src/test/java/im/vector/app/test/fakes/FakeUnifiedPushStore.kt diff --git a/vector/src/main/java/im/vector/app/core/pushers/RegisterUnifiedPushUseCase.kt b/vector/src/main/java/im/vector/app/core/pushers/RegisterUnifiedPushUseCase.kt index 58bf0f5050..aa3652a54f 100644 --- a/vector/src/main/java/im/vector/app/core/pushers/RegisterUnifiedPushUseCase.kt +++ b/vector/src/main/java/im/vector/app/core/pushers/RegisterUnifiedPushUseCase.kt @@ -32,7 +32,6 @@ class RegisterUnifiedPushUseCase @Inject constructor( object NeedToAskUserForDistributor : RegisterUnifiedPushResult } - // TODO add unit tests fun execute(distributor: String = ""): RegisterUnifiedPushResult { if (distributor.isNotEmpty()) { saveAndRegisterApp(distributor) diff --git a/vector/src/main/java/im/vector/app/core/pushers/UnregisterUnifiedPushUseCase.kt b/vector/src/main/java/im/vector/app/core/pushers/UnregisterUnifiedPushUseCase.kt index 71b1a9c033..acad3e649f 100644 --- a/vector/src/main/java/im/vector/app/core/pushers/UnregisterUnifiedPushUseCase.kt +++ b/vector/src/main/java/im/vector/app/core/pushers/UnregisterUnifiedPushUseCase.kt @@ -31,7 +31,6 @@ class UnregisterUnifiedPushUseCase @Inject constructor( private val unifiedPushHelper: UnifiedPushHelper, ) { - // TODO add unit tests suspend fun execute(pushersManager: PushersManager?) { val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME vectorPreferences.setFdroidSyncBackgroundMode(mode) diff --git a/vector/src/test/java/im/vector/app/core/pushers/RegisterUnifiedPushUseCaseTest.kt b/vector/src/test/java/im/vector/app/core/pushers/RegisterUnifiedPushUseCaseTest.kt new file mode 100644 index 0000000000..c72c519172 --- /dev/null +++ b/vector/src/test/java/im/vector/app/core/pushers/RegisterUnifiedPushUseCaseTest.kt @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2022 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 im.vector.app.core.pushers + +import im.vector.app.test.fakes.FakeContext +import im.vector.app.test.fakes.FakeVectorFeatures +import io.mockk.every +import io.mockk.justRun +import io.mockk.mockkStatic +import io.mockk.unmockkAll +import io.mockk.verify +import io.mockk.verifyAll +import io.mockk.verifyOrder +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBe +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.unifiedpush.android.connector.UnifiedPush + +class RegisterUnifiedPushUseCaseTest { + + private val fakeContext = FakeContext() + private val fakeVectorFeatures = FakeVectorFeatures() + + private val registerUnifiedPushUseCase = RegisterUnifiedPushUseCase( + context = fakeContext.instance, + vectorFeatures = fakeVectorFeatures, + ) + + @Before + fun setup() { + mockkStatic(UnifiedPush::class) + } + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `given non empty distributor when execute then distributor is saved and app is registered`() = runTest { + // Given + val aDistributor = "distributor" + justRun { UnifiedPush.registerApp(any()) } + justRun { UnifiedPush.saveDistributor(any(), any()) } + + // When + val result = registerUnifiedPushUseCase.execute(aDistributor) + + // Then + result shouldBe RegisterUnifiedPushUseCase.RegisterUnifiedPushResult.Success + verifyOrder { + UnifiedPush.saveDistributor(fakeContext.instance, aDistributor) + UnifiedPush.registerApp(fakeContext.instance) + } + } + + @Test + fun `given external distributors are not allowed when execute then internal distributor is saved and app is registered`() = runTest { + // Given + val aPackageName = "packageName" + fakeContext.givenPackageName(aPackageName) + justRun { UnifiedPush.registerApp(any()) } + justRun { UnifiedPush.saveDistributor(any(), any()) } + fakeVectorFeatures.givenExternalDistributorsAreAllowed(false) + + // When + val result = registerUnifiedPushUseCase.execute() + + // Then + result shouldBe RegisterUnifiedPushUseCase.RegisterUnifiedPushResult.Success + verifyOrder { + UnifiedPush.saveDistributor(fakeContext.instance, aPackageName) + UnifiedPush.registerApp(fakeContext.instance) + } + } + + @Test + fun `given a saved distributor and external distributors are allowed when execute then app is registered`() = runTest { + // Given + justRun { UnifiedPush.registerApp(any()) } + val aDistributor = "distributor" + every { UnifiedPush.getDistributor(any()) } returns aDistributor + fakeVectorFeatures.givenExternalDistributorsAreAllowed(true) + + // When + val result = registerUnifiedPushUseCase.execute() + + // Then + result shouldBe RegisterUnifiedPushUseCase.RegisterUnifiedPushResult.Success + verifyAll { + UnifiedPush.getDistributor(fakeContext.instance) + UnifiedPush.registerApp(fakeContext.instance) + } + } + + @Test + fun `given no saved distributor and a unique distributor available when execute then the distributor is saved and app is registered`() = runTest { + // Given + justRun { UnifiedPush.registerApp(any()) } + justRun { UnifiedPush.saveDistributor(any(), any()) } + every { UnifiedPush.getDistributor(any()) } returns "" + fakeVectorFeatures.givenExternalDistributorsAreAllowed(true) + val aDistributor = "distributor" + every { UnifiedPush.getDistributors(any()) } returns listOf(aDistributor) + + // When + val result = registerUnifiedPushUseCase.execute() + + // Then + result shouldBe RegisterUnifiedPushUseCase.RegisterUnifiedPushResult.Success + verifyOrder { + UnifiedPush.getDistributor(fakeContext.instance) + UnifiedPush.getDistributors(fakeContext.instance) + UnifiedPush.saveDistributor(fakeContext.instance, aDistributor) + UnifiedPush.registerApp(fakeContext.instance) + } + } + + @Test + fun `given no saved distributor and multiple distributors available when execute then result is to ask user`() = runTest { + // Given + every { UnifiedPush.getDistributor(any()) } returns "" + fakeVectorFeatures.givenExternalDistributorsAreAllowed(true) + val aDistributor1 = "distributor1" + val aDistributor2 = "distributor2" + every { UnifiedPush.getDistributors(any()) } returns listOf(aDistributor1, aDistributor2) + + // When + val result = registerUnifiedPushUseCase.execute() + + // Then + result shouldBe RegisterUnifiedPushUseCase.RegisterUnifiedPushResult.NeedToAskUserForDistributor + verifyOrder { + UnifiedPush.getDistributor(fakeContext.instance) + UnifiedPush.getDistributors(fakeContext.instance) + } + verify(inverse = true) { + UnifiedPush.saveDistributor(any(), any()) + UnifiedPush.registerApp(any()) + } + } +} diff --git a/vector/src/test/java/im/vector/app/core/pushers/UnregisterUnifiedPushUseCaseTest.kt b/vector/src/test/java/im/vector/app/core/pushers/UnregisterUnifiedPushUseCaseTest.kt new file mode 100644 index 0000000000..bee545b3e1 --- /dev/null +++ b/vector/src/test/java/im/vector/app/core/pushers/UnregisterUnifiedPushUseCaseTest.kt @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2022 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 im.vector.app.core.pushers + +import im.vector.app.features.settings.BackgroundSyncMode +import im.vector.app.test.fakes.FakeContext +import im.vector.app.test.fakes.FakePushersManager +import im.vector.app.test.fakes.FakeUnifiedPushHelper +import im.vector.app.test.fakes.FakeUnifiedPushStore +import im.vector.app.test.fakes.FakeVectorPreferences +import io.mockk.justRun +import io.mockk.mockkStatic +import io.mockk.unmockkAll +import io.mockk.verifyAll +import kotlinx.coroutines.test.runTest +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.unifiedpush.android.connector.UnifiedPush + +class UnregisterUnifiedPushUseCaseTest { + + private val fakeContext = FakeContext() + private val fakeVectorPreferences = FakeVectorPreferences() + private val fakeUnifiedPushStore = FakeUnifiedPushStore() + private val fakeUnifiedPushHelper = FakeUnifiedPushHelper() + + private val unregisterUnifiedPushUseCase = UnregisterUnifiedPushUseCase( + context = fakeContext.instance, + vectorPreferences = fakeVectorPreferences.instance, + unifiedPushStore = fakeUnifiedPushStore.instance, + unifiedPushHelper = fakeUnifiedPushHelper.instance, + ) + + @Before + fun setup() { + mockkStatic(UnifiedPush::class) + } + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `given pushersManager when execute then unregister and clean everything which is needed`() = runTest { + // Given + val aEndpoint = "endpoint" + fakeUnifiedPushHelper.givenGetEndpointOrTokenReturns(aEndpoint) + val aPushersManager = FakePushersManager() + aPushersManager.givenUnregisterPusher(aEndpoint) + justRun { UnifiedPush.unregisterApp(any()) } + fakeVectorPreferences.givenSetFdroidSyncBackgroundMode(BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME) + fakeUnifiedPushStore.givenStorePushGateway(null) + fakeUnifiedPushStore.givenStoreUpEndpoint(null) + + // When + unregisterUnifiedPushUseCase.execute(aPushersManager.instance) + + // Then + fakeVectorPreferences.verifySetFdroidSyncBackgroundMode(BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME) + aPushersManager.verifyUnregisterPusher(aEndpoint) + verifyAll { + UnifiedPush.unregisterApp(fakeContext.instance) + } + fakeUnifiedPushStore.verifyStorePushGateway(null) + fakeUnifiedPushStore.verifyStoreUpEndpoint(null) + } +} diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeContext.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeContext.kt index 9a94313fec..f8c568e908 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeContext.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeContext.kt @@ -81,4 +81,8 @@ class FakeContext( givenService(Context.CLIPBOARD_SERVICE, ClipboardManager::class.java, fakeClipboardManager.instance) return fakeClipboardManager } + + fun givenPackageName(name: String) { + every { instance.packageName } returns name + } } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakePushersManager.kt b/vector/src/test/java/im/vector/app/test/fakes/FakePushersManager.kt index 46d852f4f8..3dd3854a18 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakePushersManager.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakePushersManager.kt @@ -17,6 +17,8 @@ package im.vector.app.test.fakes import im.vector.app.core.pushers.PushersManager +import io.mockk.coJustRun +import io.mockk.coVerify import io.mockk.every import io.mockk.mockk import org.matrix.android.sdk.api.session.pushers.Pusher @@ -28,4 +30,12 @@ class FakePushersManager { fun givenGetPusherForCurrentSessionReturns(pusher: Pusher?) { every { instance.getPusherForCurrentSession() } returns pusher } + + fun givenUnregisterPusher(pushKey: String) { + coJustRun { instance.unregisterPusher(pushKey) } + } + + fun verifyUnregisterPusher(pushKey: String) { + coVerify { instance.unregisterPusher(pushKey) } + } } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeUnifiedPushHelper.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeUnifiedPushHelper.kt index 5bc57ead07..99b5b75874 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeUnifiedPushHelper.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeUnifiedPushHelper.kt @@ -27,4 +27,8 @@ class FakeUnifiedPushHelper { fun givenIsEmbeddedDistributorReturns(isEmbedded: Boolean) { every { instance.isEmbeddedDistributor() } returns isEmbedded } + + fun givenGetEndpointOrTokenReturns(endpoint: String?) { + every { instance.getEndpointOrToken() } returns endpoint + } } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeUnifiedPushStore.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeUnifiedPushStore.kt new file mode 100644 index 0000000000..9b09bec688 --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeUnifiedPushStore.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2022 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 im.vector.app.test.fakes + +import im.vector.app.core.pushers.UnifiedPushStore +import io.mockk.justRun +import io.mockk.mockk +import io.mockk.verify + +class FakeUnifiedPushStore { + + val instance = mockk() + + fun givenStoreUpEndpoint(endpoint: String?) { + justRun { instance.storeUpEndpoint(endpoint) } + } + + fun verifyStoreUpEndpoint(endpoint: String?) { + verify { instance.storeUpEndpoint(endpoint) } + } + + fun givenStorePushGateway(gateway: String?) { + justRun { instance.storePushGateway(gateway) } + } + + fun verifyStorePushGateway(gateway: String?) { + verify { instance.storePushGateway(gateway) } + } +} diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeVectorFeatures.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeVectorFeatures.kt index c3c2fa684f..b399f0baa4 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeVectorFeatures.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeVectorFeatures.kt @@ -54,4 +54,8 @@ class FakeVectorFeatures : VectorFeatures by spyk() { fun givenUnverifiedSessionsAlertEnabled(isEnabled: Boolean) { every { isUnverifiedSessionsAlertEnabled() } returns isEnabled } + + fun givenExternalDistributorsAreAllowed(allowed: Boolean) { + every { allowExternalUnifiedPushDistributors() } returns allowed + } } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeVectorPreferences.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeVectorPreferences.kt index 77df3ffc7a..7970c14e90 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeVectorPreferences.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeVectorPreferences.kt @@ -16,6 +16,7 @@ package im.vector.app.test.fakes +import im.vector.app.features.settings.BackgroundSyncMode import im.vector.app.features.settings.VectorPreferences import io.mockk.every import io.mockk.justRun @@ -60,4 +61,12 @@ class FakeVectorPreferences { fun givenUnverifiedSessionsAlertLastShownMillis(lastShownMillis: Long) { every { instance.getUnverifiedSessionsAlertLastShownMillis(any()) } returns lastShownMillis } + + fun givenSetFdroidSyncBackgroundMode(mode: BackgroundSyncMode) { + justRun { instance.setFdroidSyncBackgroundMode(mode) } + } + + fun verifySetFdroidSyncBackgroundMode(mode: BackgroundSyncMode) { + verify { instance.setFdroidSyncBackgroundMode(mode) } + } } From 2a8c72bdcf60409b78e51fad4af53c49bcacc5d4 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 30 Nov 2022 16:05:25 +0100 Subject: [PATCH 13/16] Fixing code style issues --- .../java/im/vector/app/core/di/MavericksViewModelModule.kt | 4 +++- vector/src/main/java/im/vector/app/core/pushers/FcmHelper.kt | 1 - .../java/im/vector/app/features/home/HomeActivityViewModel.kt | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt index ad3e361775..b58d584dad 100644 --- a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt @@ -688,5 +688,7 @@ interface MavericksViewModelModule { @Binds @IntoMap @MavericksViewModelKey(VectorSettingsNotificationPreferenceViewModel::class) - fun vectorSettingsNotificationPreferenceViewModelFactory(factory: VectorSettingsNotificationPreferenceViewModel.Factory): MavericksAssistedViewModelFactory<*, *> + fun vectorSettingsNotificationPreferenceViewModelFactory( + factory: VectorSettingsNotificationPreferenceViewModel.Factory + ): MavericksAssistedViewModelFactory<*, *> } diff --git a/vector/src/main/java/im/vector/app/core/pushers/FcmHelper.kt b/vector/src/main/java/im/vector/app/core/pushers/FcmHelper.kt index 0cc251ce31..381348638d 100644 --- a/vector/src/main/java/im/vector/app/core/pushers/FcmHelper.kt +++ b/vector/src/main/java/im/vector/app/core/pushers/FcmHelper.kt @@ -16,7 +16,6 @@ package im.vector.app.core.pushers -import android.app.Activity import im.vector.app.core.di.ActiveSessionHolder interface FcmHelper { diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt index cf4bce12f0..26034fc09c 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt @@ -128,7 +128,7 @@ class HomeActivityViewModel @AssistedInject constructor( } private fun registerUnifiedPushIfNeeded() { - if(vectorPreferences.areNotificationEnabledForDevice()) { + if (vectorPreferences.areNotificationEnabledForDevice()) { registerUnifiedPush(distributor = "") } } From e78e19285344a9b68db8542226dcacac56c110de Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 30 Nov 2022 16:37:15 +0100 Subject: [PATCH 14/16] Adding unit tests for FCM token retrieval --- .../EnsureFcmTokenIsRetrievedUseCase.kt | 1 - .../EnsureFcmTokenIsRetrievedUseCaseTest.kt | 106 ++++++++++++++++++ .../im/vector/app/test/fakes/FakeFcmHelper.kt | 3 +- 3 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 vector/src/test/java/im/vector/app/core/pushers/EnsureFcmTokenIsRetrievedUseCaseTest.kt diff --git a/vector/src/main/java/im/vector/app/core/pushers/EnsureFcmTokenIsRetrievedUseCase.kt b/vector/src/main/java/im/vector/app/core/pushers/EnsureFcmTokenIsRetrievedUseCase.kt index e55d0426ba..cb955e01f7 100644 --- a/vector/src/main/java/im/vector/app/core/pushers/EnsureFcmTokenIsRetrievedUseCase.kt +++ b/vector/src/main/java/im/vector/app/core/pushers/EnsureFcmTokenIsRetrievedUseCase.kt @@ -25,7 +25,6 @@ class EnsureFcmTokenIsRetrievedUseCase @Inject constructor( private val activeSessionHolder: ActiveSessionHolder, ) { - // TODO add unit tests fun execute(pushersManager: PushersManager, registerPusher: Boolean) { if (unifiedPushHelper.isEmbeddedDistributor()) { fcmHelper.ensureFcmTokenIsRetrieved(pushersManager, shouldAddHttpPusher(registerPusher)) diff --git a/vector/src/test/java/im/vector/app/core/pushers/EnsureFcmTokenIsRetrievedUseCaseTest.kt b/vector/src/test/java/im/vector/app/core/pushers/EnsureFcmTokenIsRetrievedUseCaseTest.kt new file mode 100644 index 0000000000..03a43a5b55 --- /dev/null +++ b/vector/src/test/java/im/vector/app/core/pushers/EnsureFcmTokenIsRetrievedUseCaseTest.kt @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2022 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 im.vector.app.core.pushers + +import im.vector.app.test.fakes.FakeActiveSessionHolder +import im.vector.app.test.fakes.FakeFcmHelper +import im.vector.app.test.fakes.FakePushersManager +import im.vector.app.test.fakes.FakeUnifiedPushHelper +import im.vector.app.test.fixtures.PusherFixture +import io.mockk.verify +import org.junit.Test + +class EnsureFcmTokenIsRetrievedUseCaseTest { + + private val fakeUnifiedPushHelper = FakeUnifiedPushHelper() + private val fakeFcmHelper = FakeFcmHelper() + private val fakeActiveSessionHolder = FakeActiveSessionHolder() + + private val ensureFcmTokenIsRetrievedUseCase = EnsureFcmTokenIsRetrievedUseCase( + unifiedPushHelper = fakeUnifiedPushHelper.instance, + fcmHelper = fakeFcmHelper.instance, + activeSessionHolder = fakeActiveSessionHolder.instance, + ) + + @Test + fun `given no registered pusher and distributor as embedded when execute then ensure the FCM token is retrieved with register pusher option`() { + // Given + val aPushersManager = FakePushersManager() + fakeUnifiedPushHelper.givenIsEmbeddedDistributorReturns(true) + fakeFcmHelper.givenEnsureFcmTokenIsRetrieved(aPushersManager.instance) + val aSessionId = "aSessionId" + fakeActiveSessionHolder.fakeSession.givenSessionId(aSessionId) + val expectedPusher = PusherFixture.aPusher(deviceId = "") + fakeActiveSessionHolder.fakeSession.fakePushersService.givenGetPushers(listOf(expectedPusher)) + + // When + ensureFcmTokenIsRetrievedUseCase.execute(aPushersManager.instance, registerPusher = true) + + // Then + fakeFcmHelper.verifyEnsureFcmTokenIsRetrieved(aPushersManager.instance, registerPusher = true) + } + + @Test + fun `given a registered pusher and distributor as embedded when execute then ensure the FCM token is retrieved without register pusher option`() { + // Given + val aPushersManager = FakePushersManager() + fakeUnifiedPushHelper.givenIsEmbeddedDistributorReturns(true) + fakeFcmHelper.givenEnsureFcmTokenIsRetrieved(aPushersManager.instance) + val aSessionId = "aSessionId" + fakeActiveSessionHolder.fakeSession.givenSessionId(aSessionId) + val expectedPusher = PusherFixture.aPusher(deviceId = aSessionId) + fakeActiveSessionHolder.fakeSession.fakePushersService.givenGetPushers(listOf(expectedPusher)) + + // When + ensureFcmTokenIsRetrievedUseCase.execute(aPushersManager.instance, registerPusher = true) + + // Then + fakeFcmHelper.verifyEnsureFcmTokenIsRetrieved(aPushersManager.instance, registerPusher = false) + } + + @Test + fun `given no registering asked and distributor as embedded when execute then ensure the FCM token is retrieved without register pusher option`() { + // Given + val aPushersManager = FakePushersManager() + fakeUnifiedPushHelper.givenIsEmbeddedDistributorReturns(true) + fakeFcmHelper.givenEnsureFcmTokenIsRetrieved(aPushersManager.instance) + val aSessionId = "aSessionId" + fakeActiveSessionHolder.fakeSession.givenSessionId(aSessionId) + val expectedPusher = PusherFixture.aPusher(deviceId = aSessionId) + fakeActiveSessionHolder.fakeSession.fakePushersService.givenGetPushers(listOf(expectedPusher)) + + // When + ensureFcmTokenIsRetrievedUseCase.execute(aPushersManager.instance, registerPusher = false) + + // Then + fakeFcmHelper.verifyEnsureFcmTokenIsRetrieved(aPushersManager.instance, registerPusher = false) + } + + @Test + fun `given distributor as not embedded when execute then nothing is done`() { + // Given + val aPushersManager = FakePushersManager() + fakeUnifiedPushHelper.givenIsEmbeddedDistributorReturns(false) + + // When + ensureFcmTokenIsRetrievedUseCase.execute(aPushersManager.instance, registerPusher = true) + + // Then + fakeFcmHelper.verifyEnsureFcmTokenIsRetrieved(aPushersManager.instance, registerPusher = true, inverse = true) + fakeFcmHelper.verifyEnsureFcmTokenIsRetrieved(aPushersManager.instance, registerPusher = false, inverse = true) + } +} diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeFcmHelper.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeFcmHelper.kt index 07eef36dc1..4c210215ec 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeFcmHelper.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeFcmHelper.kt @@ -33,7 +33,8 @@ class FakeFcmHelper { fun verifyEnsureFcmTokenIsRetrieved( pushersManager: PushersManager, registerPusher: Boolean, + inverse: Boolean = false, ) { - verify { instance.ensureFcmTokenIsRetrieved(pushersManager, registerPusher) } + verify(inverse = inverse) { instance.ensureFcmTokenIsRetrieved(pushersManager, registerPusher) } } } From d31652e91007e67f6ea5a8065c6f032af9607e6f Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 30 Nov 2022 17:21:48 +0100 Subject: [PATCH 15/16] Adding unit tests for settings ViewModel --- ...SettingsNotificationPreferenceViewModel.kt | 1 - ...ingsNotificationPreferenceViewModelTest.kt | 202 ++++++++++++++++++ .../app/test/fakes/FakeVectorPreferences.kt | 4 + 3 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 vector/src/test/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceViewModelTest.kt diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceViewModel.kt index 59c26749c9..d6a9c621f2 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceViewModel.kt @@ -49,7 +49,6 @@ class VectorSettingsNotificationPreferenceViewModel @AssistedInject constructor( companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() - // TODO add unit tests override fun handle(action: VectorSettingsNotificationPreferenceViewAction) { when (action) { VectorSettingsNotificationPreferenceViewAction.DisableNotificationsForDevice -> handleDisableNotificationsForDevice() diff --git a/vector/src/test/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceViewModelTest.kt b/vector/src/test/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceViewModelTest.kt new file mode 100644 index 0000000000..f9d7527316 --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceViewModelTest.kt @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2022 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 im.vector.app.features.settings.notifications + +import com.airbnb.mvrx.test.MavericksTestRule +import im.vector.app.core.platform.VectorDummyViewState +import im.vector.app.core.pushers.EnsureFcmTokenIsRetrievedUseCase +import im.vector.app.core.pushers.RegisterUnifiedPushUseCase +import im.vector.app.core.pushers.UnregisterUnifiedPushUseCase +import im.vector.app.test.fakes.FakePushersManager +import im.vector.app.test.fakes.FakeVectorPreferences +import im.vector.app.test.test +import im.vector.app.test.testDispatcher +import io.mockk.coEvery +import io.mockk.coJustRun +import io.mockk.coVerify +import io.mockk.coVerifyOrder +import io.mockk.justRun +import io.mockk.mockk +import org.junit.Rule +import org.junit.Test + +class VectorSettingsNotificationPreferenceViewModelTest { + + @get:Rule + val mavericksTestRule = MavericksTestRule(testDispatcher = testDispatcher) + + private val fakePushersManager = FakePushersManager() + private val fakeVectorPreferences = FakeVectorPreferences() + private val fakeEnableNotificationsForCurrentSessionUseCase = mockk() + private val fakeDisableNotificationsForCurrentSessionUseCase = mockk() + private val fakeUnregisterUnifiedPushUseCase = mockk() + private val fakeRegisterUnifiedPushUseCase = mockk() + private val fakeEnsureFcmTokenIsRetrievedUseCase = mockk() + + private fun createViewModel() = VectorSettingsNotificationPreferenceViewModel( + initialState = VectorDummyViewState(), + pushersManager = fakePushersManager.instance, + vectorPreferences = fakeVectorPreferences.instance, + enableNotificationsForCurrentSessionUseCase = fakeEnableNotificationsForCurrentSessionUseCase, + disableNotificationsForCurrentSessionUseCase = fakeDisableNotificationsForCurrentSessionUseCase, + unregisterUnifiedPushUseCase = fakeUnregisterUnifiedPushUseCase, + registerUnifiedPushUseCase = fakeRegisterUnifiedPushUseCase, + ensureFcmTokenIsRetrievedUseCase = fakeEnsureFcmTokenIsRetrievedUseCase, + ) + + @Test + fun `given DisableNotificationsForDevice action when handling action then disable use case is called`() { + // Given + val viewModel = createViewModel() + val action = VectorSettingsNotificationPreferenceViewAction.DisableNotificationsForDevice + coJustRun { fakeDisableNotificationsForCurrentSessionUseCase.execute() } + val expectedEvent = VectorSettingsNotificationPreferenceViewEvent.NotificationsForDeviceDisabled + + // When + val viewModelTest = viewModel.test() + viewModel.handle(action) + + // Then + viewModelTest + .assertEvent { event -> event == expectedEvent } + .finish() + coVerify { + fakeDisableNotificationsForCurrentSessionUseCase.execute() + } + } + + @Test + fun `given EnableNotificationsForDevice action and enable success when handling action then enable use case is called`() { + // Given + val viewModel = createViewModel() + val aDistributor = "aDistributor" + val action = VectorSettingsNotificationPreferenceViewAction.EnableNotificationsForDevice(aDistributor) + coEvery { fakeEnableNotificationsForCurrentSessionUseCase.execute(any()) } returns + EnableNotificationsForCurrentSessionUseCase.EnableNotificationsResult.Success + val expectedEvent = VectorSettingsNotificationPreferenceViewEvent.NotificationsForDeviceEnabled + + // When + val viewModelTest = viewModel.test() + viewModel.handle(action) + + // Then + viewModelTest + .assertEvent { event -> event == expectedEvent } + .finish() + coVerify { + fakeEnableNotificationsForCurrentSessionUseCase.execute(aDistributor) + } + } + + @Test + fun `given EnableNotificationsForDevice action and enable needs user choice when handling action then enable use case is called`() { + // Given + val viewModel = createViewModel() + val aDistributor = "aDistributor" + val action = VectorSettingsNotificationPreferenceViewAction.EnableNotificationsForDevice(aDistributor) + coEvery { fakeEnableNotificationsForCurrentSessionUseCase.execute(any()) } returns + EnableNotificationsForCurrentSessionUseCase.EnableNotificationsResult.NeedToAskUserForDistributor + val expectedEvent = VectorSettingsNotificationPreferenceViewEvent.AskUserForPushDistributor + + // When + val viewModelTest = viewModel.test() + viewModel.handle(action) + + // Then + viewModelTest + .assertEvent { event -> event == expectedEvent } + .finish() + coVerify { + fakeEnableNotificationsForCurrentSessionUseCase.execute(aDistributor) + } + } + + @Test + fun `given EnableNotificationsForDevice action and enable failure when handling action then enable use case is called`() { + // Given + val viewModel = createViewModel() + val aDistributor = "aDistributor" + val action = VectorSettingsNotificationPreferenceViewAction.EnableNotificationsForDevice(aDistributor) + coEvery { fakeEnableNotificationsForCurrentSessionUseCase.execute(any()) } returns + EnableNotificationsForCurrentSessionUseCase.EnableNotificationsResult.Failure + val expectedEvent = VectorSettingsNotificationPreferenceViewEvent.EnableNotificationForDeviceFailure + + // When + val viewModelTest = viewModel.test() + viewModel.handle(action) + + // Then + viewModelTest + .assertEvent { event -> event == expectedEvent } + .finish() + coVerify { + fakeEnableNotificationsForCurrentSessionUseCase.execute(aDistributor) + } + } + + @Test + fun `given RegisterPushDistributor action and register success when handling action then register use case is called`() { + // Given + val viewModel = createViewModel() + val aDistributor = "aDistributor" + val action = VectorSettingsNotificationPreferenceViewAction.RegisterPushDistributor(aDistributor) + coEvery { fakeRegisterUnifiedPushUseCase.execute(any()) } returns RegisterUnifiedPushUseCase.RegisterUnifiedPushResult.Success + coJustRun { fakeUnregisterUnifiedPushUseCase.execute(any()) } + val areNotificationsEnabled = true + fakeVectorPreferences.givenAreNotificationsEnabledForDevice(areNotificationsEnabled) + justRun { fakeEnsureFcmTokenIsRetrievedUseCase.execute(any(), any()) } + val expectedEvent = VectorSettingsNotificationPreferenceViewEvent.NotificationMethodChanged + + // When + val viewModelTest = viewModel.test() + viewModel.handle(action) + + // Then + viewModelTest + .assertEvent { event -> event == expectedEvent } + .finish() + coVerifyOrder { + fakeUnregisterUnifiedPushUseCase.execute(fakePushersManager.instance) + fakeRegisterUnifiedPushUseCase.execute(aDistributor) + fakeEnsureFcmTokenIsRetrievedUseCase.execute(fakePushersManager.instance, registerPusher = areNotificationsEnabled) + } + } + + @Test + fun `given RegisterPushDistributor action and register needs user choice when handling action then register use case is called`() { + // Given + val viewModel = createViewModel() + val aDistributor = "aDistributor" + val action = VectorSettingsNotificationPreferenceViewAction.RegisterPushDistributor(aDistributor) + coEvery { fakeRegisterUnifiedPushUseCase.execute(any()) } returns RegisterUnifiedPushUseCase.RegisterUnifiedPushResult.NeedToAskUserForDistributor + coJustRun { fakeUnregisterUnifiedPushUseCase.execute(any()) } + val expectedEvent = VectorSettingsNotificationPreferenceViewEvent.AskUserForPushDistributor + + // When + val viewModelTest = viewModel.test() + viewModel.handle(action) + + // Then + viewModelTest + .assertEvent { event -> event == expectedEvent } + .finish() + coVerifyOrder { + fakeUnregisterUnifiedPushUseCase.execute(fakePushersManager.instance) + fakeRegisterUnifiedPushUseCase.execute(aDistributor) + } + } +} diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeVectorPreferences.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeVectorPreferences.kt index 7970c14e90..06efca1bf7 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeVectorPreferences.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeVectorPreferences.kt @@ -69,4 +69,8 @@ class FakeVectorPreferences { fun verifySetFdroidSyncBackgroundMode(mode: BackgroundSyncMode) { verify { instance.setFdroidSyncBackgroundMode(mode) } } + + fun givenAreNotificationsEnabledForDevice(notificationsEnabled: Boolean) { + every { instance.areNotificationEnabledForDevice() } returns notificationsEnabled + } } From f8c59f6b0c82608484463d1f3157c1203102bccc Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Thu, 1 Dec 2022 10:22:09 +0100 Subject: [PATCH 16/16] Removing unused import --- .../app/core/pushers/EnsureFcmTokenIsRetrievedUseCaseTest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/vector/src/test/java/im/vector/app/core/pushers/EnsureFcmTokenIsRetrievedUseCaseTest.kt b/vector/src/test/java/im/vector/app/core/pushers/EnsureFcmTokenIsRetrievedUseCaseTest.kt index 03a43a5b55..fca49adc9b 100644 --- a/vector/src/test/java/im/vector/app/core/pushers/EnsureFcmTokenIsRetrievedUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/core/pushers/EnsureFcmTokenIsRetrievedUseCaseTest.kt @@ -21,7 +21,6 @@ import im.vector.app.test.fakes.FakeFcmHelper import im.vector.app.test.fakes.FakePushersManager import im.vector.app.test.fakes.FakeUnifiedPushHelper import im.vector.app.test.fixtures.PusherFixture -import io.mockk.verify import org.junit.Test class EnsureFcmTokenIsRetrievedUseCaseTest {