Merge pull request #7675 from vector-im/fix/mna/unified-push-selection

ANR when asking to select the notification method
This commit is contained in:
Maxime NATUREL 2022-12-05 16:58:09 +01:00 committed by GitHub
commit a2f8fed63c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 1226 additions and 335 deletions

1
changelog.d/7653.bugfix Normal file
View File

@ -0,0 +1 @@
ANR when asking to select the notification method

View File

@ -17,7 +17,6 @@
package im.vector.app.push.fcm package im.vector.app.push.fcm
import android.app.Activity
import android.content.Context import android.content.Context
import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.pushers.FcmHelper import im.vector.app.core.pushers.FcmHelper
@ -44,7 +43,7 @@ class FdroidFcmHelper @Inject constructor(
// No op // No op
} }
override fun ensureFcmTokenIsRetrieved(activity: Activity, pushersManager: PushersManager, registerPusher: Boolean) { override fun ensureFcmTokenIsRetrieved(pushersManager: PushersManager, registerPusher: Boolean) {
// No op // No op
} }

View File

@ -15,7 +15,6 @@
*/ */
package im.vector.app.push.fcm package im.vector.app.push.fcm
import android.app.Activity
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.widget.Toast 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.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailability import com.google.android.gms.common.GoogleApiAvailability
import com.google.firebase.messaging.FirebaseMessaging import com.google.firebase.messaging.FirebaseMessaging
import dagger.hilt.android.qualifiers.ApplicationContext
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.di.DefaultPreferences import im.vector.app.core.di.DefaultPreferences
@ -36,8 +36,8 @@ import javax.inject.Inject
* It has an alter ego in the fdroid variant. * It has an alter ego in the fdroid variant.
*/ */
class GoogleFcmHelper @Inject constructor( class GoogleFcmHelper @Inject constructor(
@DefaultPreferences @ApplicationContext private val context: Context,
private val sharedPrefs: SharedPreferences, @DefaultPreferences private val sharedPrefs: SharedPreferences,
) : FcmHelper { ) : FcmHelper {
companion object { companion object {
private const val PREFS_KEY_FCM_TOKEN = "FCM_TOKEN" 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) { override fun ensureFcmTokenIsRetrieved(pushersManager: PushersManager, registerPusher: Boolean) {
// if (TextUtils.isEmpty(getFcmToken(activity))) {
// 'app should always check the device for a compatible Google Play services APK before accessing Google Play services features' // '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 { try {
FirebaseMessaging.getInstance().token FirebaseMessaging.getInstance().token
.addOnSuccessListener { token -> .addOnSuccessListener { token ->
@ -75,7 +74,7 @@ class GoogleFcmHelper @Inject constructor(
Timber.e(e, "## ensureFcmTokenIsRetrieved() : failed") Timber.e(e, "## ensureFcmTokenIsRetrieved() : failed")
} }
} else { } 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.") Timber.e("No valid Google Play Services found. Cannot use FCM.")
} }
} }

View File

@ -19,7 +19,7 @@ package im.vector.app.core.di
import android.content.Context import android.content.Context
import im.vector.app.ActiveSessionDataSource import im.vector.app.ActiveSessionDataSource
import im.vector.app.core.extensions.startSyncing 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.services.GuardServiceStarter
import im.vector.app.core.session.ConfigureAndStartSessionUseCase import im.vector.app.core.session.ConfigureAndStartSessionUseCase
import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.call.webrtc.WebRtcCallManager
@ -46,12 +46,12 @@ class ActiveSessionHolder @Inject constructor(
private val pushRuleTriggerListener: PushRuleTriggerListener, private val pushRuleTriggerListener: PushRuleTriggerListener,
private val sessionListener: SessionListener, private val sessionListener: SessionListener,
private val imageManager: ImageManager, private val imageManager: ImageManager,
private val unifiedPushHelper: UnifiedPushHelper,
private val guardServiceStarter: GuardServiceStarter, private val guardServiceStarter: GuardServiceStarter,
private val sessionInitializer: SessionInitializer, private val sessionInitializer: SessionInitializer,
private val applicationContext: Context, private val applicationContext: Context,
private val authenticationService: AuthenticationService, private val authenticationService: AuthenticationService,
private val configureAndStartSessionUseCase: ConfigureAndStartSessionUseCase, private val configureAndStartSessionUseCase: ConfigureAndStartSessionUseCase,
private val unregisterUnifiedPushUseCase: UnregisterUnifiedPushUseCase,
) { ) {
private var activeSessionReference: AtomicReference<Session?> = AtomicReference() private var activeSessionReference: AtomicReference<Session?> = AtomicReference()
@ -85,7 +85,7 @@ class ActiveSessionHolder @Inject constructor(
incomingVerificationRequestHandler.stop() incomingVerificationRequestHandler.stop()
pushRuleTriggerListener.stop() pushRuleTriggerListener.stop()
// No need to unregister the pusher, the sign out will (should?) do it server side. // 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() guardServiceStarter.stop()
} }

View File

@ -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.labs.VectorSettingsLabsViewModel
import im.vector.app.features.settings.legals.LegalsViewModel import im.vector.app.features.settings.legals.LegalsViewModel
import im.vector.app.features.settings.locale.LocalePickerViewModel 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.push.PushGatewaysViewModel
import im.vector.app.features.settings.threepids.ThreePidsSettingsViewModel import im.vector.app.features.settings.threepids.ThreePidsSettingsViewModel
import im.vector.app.features.share.IncomingShareViewModel import im.vector.app.features.share.IncomingShareViewModel
@ -683,4 +684,11 @@ interface MavericksViewModelModule {
@IntoMap @IntoMap
@MavericksViewModelKey(AttachmentTypeSelectorViewModel::class) @MavericksViewModelKey(AttachmentTypeSelectorViewModel::class)
fun attachmentTypeSelectorViewModelFactory(factory: AttachmentTypeSelectorViewModel.Factory): MavericksAssistedViewModelFactory<*, *> fun attachmentTypeSelectorViewModelFactory(factory: AttachmentTypeSelectorViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
@Binds
@IntoMap
@MavericksViewModelKey(VectorSettingsNotificationPreferenceViewModel::class)
fun vectorSettingsNotificationPreferenceViewModelFactory(
factory: VectorSettingsNotificationPreferenceViewModel.Factory
): MavericksAssistedViewModelFactory<*, *>
} }

View File

@ -0,0 +1,41 @@
/*
* 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 javax.inject.Inject
class EnsureFcmTokenIsRetrievedUseCase @Inject constructor(
private val unifiedPushHelper: UnifiedPushHelper,
private val fcmHelper: FcmHelper,
private val activeSessionHolder: ActiveSessionHolder,
) {
fun execute(pushersManager: PushersManager, registerPusher: Boolean) {
if (unifiedPushHelper.isEmbeddedDistributor()) {
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
}
}

View File

@ -16,7 +16,6 @@
package im.vector.app.core.pushers package im.vector.app.core.pushers
import android.app.Activity
import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActiveSessionHolder
interface FcmHelper { interface FcmHelper {
@ -39,11 +38,10 @@ interface FcmHelper {
/** /**
* onNewToken may not be called on application upgrade, so ensure my shared pref is set. * 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 pushersManager the instance to register the pusher on.
* @param registerPusher whether the pusher should be registered. * @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) fun onEnterForeground(activeSessionHolder: ActiveSessionHolder)

View File

@ -0,0 +1,69 @@
/*
* 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
object NeedToAskUserForDistributor : RegisterUnifiedPushResult
}
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
}
}
private fun saveAndRegisterApp(distributor: String) {
UnifiedPush.saveDistributor(context, distributor)
registerApp()
}
private fun registerApp() {
UnifiedPush.registerApp(context)
}
}

View File

@ -17,18 +17,13 @@
package im.vector.app.core.pushers package im.vector.app.core.pushers
import android.content.Context import android.content.Context
import androidx.fragment.app.FragmentActivity import androidx.annotation.MainThread
import androidx.lifecycle.lifecycleScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.core.utils.getApplicationLabel 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.launch
import org.matrix.android.sdk.api.Matrix import org.matrix.android.sdk.api.Matrix
import org.matrix.android.sdk.api.cache.CacheStrategy import org.matrix.android.sdk.api.cache.CacheStrategy
import org.matrix.android.sdk.api.util.MatrixJsonParser import org.matrix.android.sdk.api.util.MatrixJsonParser
@ -41,90 +36,14 @@ class UnifiedPushHelper @Inject constructor(
private val context: Context, private val context: Context,
private val unifiedPushStore: UnifiedPushStore, private val unifiedPushStore: UnifiedPushStore,
private val stringProvider: StringProvider, private val stringProvider: StringProvider,
private val vectorPreferences: VectorPreferences,
private val matrix: Matrix, private val matrix: Matrix,
private val vectorFeatures: VectorFeatures,
private val fcmHelper: FcmHelper, private val fcmHelper: FcmHelper,
) { ) {
// Called when the home activity starts @MainThread
// or when notifications are enabled fun showSelectDistributorDialog(
fun register( context: Context,
activity: FragmentActivity, onDistributorSelected: (String) -> Unit,
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)
fun forceRegister(
activity: FragmentActivity,
pushersManager: PushersManager,
onDoneRunnable: Runnable? = null
) {
registerInternal(
activity,
force = true,
pushersManager = pushersManager,
onDoneRunnable = onDoneRunnable
)
}
private fun registerInternal(
activity: FragmentActivity,
force: Boolean = false,
pushersManager: PushersManager? = null,
onDoneRunnable: Runnable? = null
) {
activity.lifecycleScope.launch {
if (!vectorFeatures.allowExternalUnifiedPushDistributors()) {
UnifiedPush.saveDistributor(context, context.packageName)
UnifiedPush.registerApp(context)
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)
onDoneRunnable?.run()
return@launch
}
val distributors = UnifiedPush.getDistributors(context)
if (!force && distributors.size == 1) {
UnifiedPush.saveDistributor(context, distributors.first())
UnifiedPush.registerApp(context)
onDoneRunnable?.run()
} else {
openDistributorDialogInternal(
activity = activity,
onDoneRunnable = onDoneRunnable,
distributors = distributors
)
}
}
}
// 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<String>
) { ) {
val internalDistributorName = stringProvider.getString( val internalDistributorName = stringProvider.getString(
if (fcmHelper.isFirebaseAvailable()) { if (fcmHelper.isFirebaseAvailable()) {
@ -134,6 +53,7 @@ class UnifiedPushHelper @Inject constructor(
} }
) )
val distributors = UnifiedPush.getDistributors(context)
val distributorsName = distributors.map { val distributorsName = distributors.map {
if (it == context.packageName) { if (it == context.packageName) {
internalDistributorName internalDistributorName
@ -142,44 +62,23 @@ class UnifiedPushHelper @Inject constructor(
} }
} }
MaterialAlertDialogBuilder(activity) MaterialAlertDialogBuilder(context)
.setTitle(stringProvider.getString(R.string.unifiedpush_getdistributors_dialog_title)) .setTitle(stringProvider.getString(R.string.unifiedpush_getdistributors_dialog_title))
.setItems(distributorsName.toTypedArray()) { _, which -> .setItems(distributorsName.toTypedArray()) { _, which ->
val distributor = distributors[which] val distributor = distributors[which]
onDistributorSelected(distributor)
activity.lifecycleScope.launch {
UnifiedPush.saveDistributor(context, distributor)
Timber.i("Saving distributor: $distributor")
UnifiedPush.registerApp(context)
onDoneRunnable?.run()
}
} }
.setOnCancelListener { .setOnCancelListener {
// 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) // By default, use internal solution (fcm/background sync)
UnifiedPush.saveDistributor(context, context.packageName) onDistributorSelected(context.packageName)
UnifiedPush.registerApp(context) }
onDoneRunnable?.run()
} }
.setCancelable(true) .setCancelable(true)
.show() .show()
} }
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) @JsonClass(generateAdapter = true)
internal data class DiscoveryResponse( internal data class DiscoveryResponse(
@Json(name = "unifiedpush") val unifiedpush: DiscoveryUnifiedPush = DiscoveryUnifiedPush() @Json(name = "unifiedpush") val unifiedpush: DiscoveryUnifiedPush = DiscoveryUnifiedPush()

View File

@ -0,0 +1,49 @@
/*
* 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 vectorPreferences: VectorPreferences,
private val unifiedPushStore: UnifiedPushStore,
private val unifiedPushHelper: UnifiedPushHelper,
) {
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)
}
} catch (e: Exception) {
Timber.d(e, "Probably unregistering a non existing pusher")
}
unifiedPushStore.storeUpEndpoint(null)
unifiedPushStore.storePushGateway(null)
UnifiedPush.unregisterApp(context)
}
}

View File

@ -44,8 +44,6 @@ import im.vector.app.core.extensions.restart
import im.vector.app.core.extensions.validateBackPressed import im.vector.app.core.extensions.validateBackPressed
import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.platform.VectorMenuProvider 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.pushers.UnifiedPushHelper
import im.vector.app.core.utils.registerForPermissionsResult import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.core.utils.startSharePlainTextIntent import im.vector.app.core.utils.startSharePlainTextIntent
@ -128,7 +126,6 @@ class HomeActivity :
private val serverBackupStatusViewModel: ServerBackupStatusViewModel by viewModel() private val serverBackupStatusViewModel: ServerBackupStatusViewModel by viewModel()
@Inject lateinit var vectorUncaughtExceptionHandler: VectorUncaughtExceptionHandler @Inject lateinit var vectorUncaughtExceptionHandler: VectorUncaughtExceptionHandler
@Inject lateinit var pushersManager: PushersManager
@Inject lateinit var notificationDrawerManager: NotificationDrawerManager @Inject lateinit var notificationDrawerManager: NotificationDrawerManager
@Inject lateinit var popupAlertManager: PopupAlertManager @Inject lateinit var popupAlertManager: PopupAlertManager
@Inject lateinit var shortcutsHandler: ShortcutsHandler @Inject lateinit var shortcutsHandler: ShortcutsHandler
@ -137,7 +134,6 @@ class HomeActivity :
@Inject lateinit var initSyncStepFormatter: InitSyncStepFormatter @Inject lateinit var initSyncStepFormatter: InitSyncStepFormatter
@Inject lateinit var spaceStateHandler: SpaceStateHandler @Inject lateinit var spaceStateHandler: SpaceStateHandler
@Inject lateinit var unifiedPushHelper: UnifiedPushHelper @Inject lateinit var unifiedPushHelper: UnifiedPushHelper
@Inject lateinit var fcmHelper: FcmHelper
@Inject lateinit var nightlyProxy: NightlyProxy @Inject lateinit var nightlyProxy: NightlyProxy
@Inject lateinit var disclaimerDialog: DisclaimerDialog @Inject lateinit var disclaimerDialog: DisclaimerDialog
@Inject lateinit var notificationPermissionManager: NotificationPermissionManager @Inject lateinit var notificationPermissionManager: NotificationPermissionManager
@ -209,16 +205,6 @@ class HomeActivity :
isNewAppLayoutEnabled = vectorPreferences.isNewAppLayoutEnabled() isNewAppLayoutEnabled = vectorPreferences.isNewAppLayoutEnabled()
analyticsScreenName = MobileScreen.ScreenName.Home analyticsScreenName = MobileScreen.ScreenName.Home
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, false) supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, false)
unifiedPushHelper.register(this) {
if (unifiedPushHelper.isEmbeddedDistributor()) {
fcmHelper.ensureFcmTokenIsRetrieved(
this,
pushersManager,
homeActivityViewModel.shouldAddHttpPusher()
)
}
}
sharedActionViewModel = viewModelProvider[HomeSharedActionViewModel::class.java] sharedActionViewModel = viewModelProvider[HomeSharedActionViewModel::class.java]
roomListSharedActionViewModel = viewModelProvider[RoomListSharedActionViewModel::class.java] roomListSharedActionViewModel = viewModelProvider[RoomListSharedActionViewModel::class.java]
views.drawerLayout.addDrawerListener(drawerListener) views.drawerLayout.addDrawerListener(drawerListener)
@ -280,6 +266,7 @@ class HomeActivity :
HomeActivityViewEvents.ShowReleaseNotes -> handleShowReleaseNotes() HomeActivityViewEvents.ShowReleaseNotes -> handleShowReleaseNotes()
HomeActivityViewEvents.NotifyUserForThreadsMigration -> handleNotifyUserForThreadsMigration() HomeActivityViewEvents.NotifyUserForThreadsMigration -> handleNotifyUserForThreadsMigration()
is HomeActivityViewEvents.MigrateThreads -> migrateThreadsIfNeeded(it.checkSession) is HomeActivityViewEvents.MigrateThreads -> migrateThreadsIfNeeded(it.checkSession)
is HomeActivityViewEvents.AskUserForPushDistributor -> askUserToSelectPushDistributor()
} }
} }
homeActivityViewModel.onEach { renderState(it) } homeActivityViewModel.onEach { renderState(it) }
@ -292,6 +279,12 @@ class HomeActivity :
homeActivityViewModel.handle(HomeActivityViewActions.ViewStarted) homeActivityViewModel.handle(HomeActivityViewActions.ViewStarted)
} }
private fun askUserToSelectPushDistributor() {
unifiedPushHelper.showSelectDistributorDialog(this) { selection ->
homeActivityViewModel.handle(HomeActivityViewActions.RegisterPushDistributor(selection))
}
}
private fun handleShowNotificationDialog() { private fun handleShowNotificationDialog() {
notificationPermissionManager.eventuallyRequestPermission(this, postPermissionLauncher) notificationPermissionManager.eventuallyRequestPermission(this, postPermissionLauncher)
} }
@ -415,14 +408,6 @@ class HomeActivity :
} }
private fun renderState(state: HomeActivityViewState) { private fun renderState(state: HomeActivityViewState) {
lifecycleScope.launch {
if (state.areNotificationsSilenced) {
unifiedPushHelper.unregister(pushersManager)
} else {
unifiedPushHelper.register(this@HomeActivity)
}
}
when (val status = state.syncRequestState) { when (val status = state.syncRequestState) {
is SyncRequestState.InitialSyncProgressing -> { is SyncRequestState.InitialSyncProgressing -> {
val initSyncStepStr = initSyncStepFormatter.format(status.initialSyncStep) val initSyncStepStr = initSyncStepFormatter.format(status.initialSyncStep)

View File

@ -21,4 +21,5 @@ import im.vector.app.core.platform.VectorViewModelAction
sealed interface HomeActivityViewActions : VectorViewModelAction { sealed interface HomeActivityViewActions : VectorViewModelAction {
object ViewStarted : HomeActivityViewActions object ViewStarted : HomeActivityViewActions
object PushPromptHasBeenReviewed : HomeActivityViewActions object PushPromptHasBeenReviewed : HomeActivityViewActions
data class RegisterPushDistributor(val distributor: String) : HomeActivityViewActions
} }

View File

@ -25,9 +25,11 @@ sealed interface HomeActivityViewEvents : VectorViewEvents {
val userItem: MatrixItem.UserItem, val userItem: MatrixItem.UserItem,
val waitForIncomingRequest: Boolean = true, val waitForIncomingRequest: Boolean = true,
) : HomeActivityViewEvents ) : HomeActivityViewEvents
data class CurrentSessionCannotBeVerified( data class CurrentSessionCannotBeVerified(
val userItem: MatrixItem.UserItem, val userItem: MatrixItem.UserItem,
) : HomeActivityViewEvents ) : HomeActivityViewEvents
data class OnCrossSignedInvalidated(val userItem: MatrixItem.UserItem) : HomeActivityViewEvents data class OnCrossSignedInvalidated(val userItem: MatrixItem.UserItem) : HomeActivityViewEvents
object PromptToEnableSessionPush : HomeActivityViewEvents object PromptToEnableSessionPush : HomeActivityViewEvents
object ShowAnalyticsOptIn : HomeActivityViewEvents object ShowAnalyticsOptIn : HomeActivityViewEvents
@ -37,4 +39,5 @@ sealed interface HomeActivityViewEvents : VectorViewEvents {
data class MigrateThreads(val checkSession: Boolean) : HomeActivityViewEvents data class MigrateThreads(val checkSession: Boolean) : HomeActivityViewEvents
object StartRecoverySetupFlow : HomeActivityViewEvents object StartRecoverySetupFlow : HomeActivityViewEvents
data class ForceVerification(val sendRequest: Boolean) : HomeActivityViewEvents data class ForceVerification(val sendRequest: Boolean) : HomeActivityViewEvents
object AskUserForPushDistributor : HomeActivityViewEvents
} }

View File

@ -16,7 +16,6 @@
package im.vector.app.features.home package im.vector.app.features.home
import androidx.lifecycle.asFlow
import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.Mavericks
import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.MavericksViewModelFactory
import com.airbnb.mvrx.ViewModelContext 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.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.platform.VectorViewModel 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.AnalyticsConfig
import im.vector.app.features.analytics.AnalyticsTracker import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.analytics.extensions.toAnalyticsType import im.vector.app.features.analytics.extensions.toAnalyticsType
@ -48,12 +49,10 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.takeWhile import kotlinx.coroutines.flow.takeWhile
import kotlinx.coroutines.launch 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.UIABaseAuth
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
import org.matrix.android.sdk.api.auth.UserPasswordAuth 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.auth.registration.nextUncompletedStage
import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.raw.RawService 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.crosssigning.CrossSigningService
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo 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.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.getUserOrDefault
import org.matrix.android.sdk.api.session.pushrules.RuleIds import org.matrix.android.sdk.api.session.pushrules.RuleIds
import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.Membership
@ -92,8 +89,10 @@ class HomeActivityViewModel @AssistedInject constructor(
private val analyticsTracker: AnalyticsTracker, private val analyticsTracker: AnalyticsTracker,
private val analyticsConfig: AnalyticsConfig, private val analyticsConfig: AnalyticsConfig,
private val releaseNotesPreferencesStore: ReleaseNotesPreferencesStore, private val releaseNotesPreferencesStore: ReleaseNotesPreferencesStore,
private val vectorFeatures: VectorFeatures,
private val stopOngoingVoiceBroadcastUseCase: StopOngoingVoiceBroadcastUseCase, private val stopOngoingVoiceBroadcastUseCase: StopOngoingVoiceBroadcastUseCase,
private val pushersManager: PushersManager,
private val registerUnifiedPushUseCase: RegisterUnifiedPushUseCase,
private val ensureFcmTokenIsRetrievedUseCase: EnsureFcmTokenIsRetrievedUseCase,
) : VectorViewModel<HomeActivityViewState, HomeActivityViewActions, HomeActivityViewEvents>(initialState) { ) : VectorViewModel<HomeActivityViewState, HomeActivityViewActions, HomeActivityViewEvents>(initialState) {
@AssistedFactory @AssistedFactory
@ -117,17 +116,36 @@ class HomeActivityViewModel @AssistedInject constructor(
private fun initialize() { private fun initialize() {
if (isInitialized) return if (isInitialized) return
isInitialized = true isInitialized = true
registerUnifiedPushIfNeeded()
cleanupFiles() cleanupFiles()
observeInitialSync() observeInitialSync()
checkSessionPushIsOn() checkSessionPushIsOn()
observeCrossSigningReset() observeCrossSigningReset()
observeAnalytics() observeAnalytics()
observeReleaseNotes() observeReleaseNotes()
observeLocalNotificationsSilenced()
initThreadsMigration() initThreadsMigration()
viewModelScope.launch { stopOngoingVoiceBroadcastUseCase.execute() } viewModelScope.launch { stopOngoingVoiceBroadcastUseCase.execute() }
} }
private fun registerUnifiedPushIfNeeded() {
if (vectorPreferences.areNotificationEnabledForDevice()) {
registerUnifiedPush(distributor = "")
}
}
private fun registerUnifiedPush(distributor: String) {
viewModelScope.launch {
when (registerUnifiedPushUseCase.execute(distributor = distributor)) {
is RegisterUnifiedPushUseCase.RegisterUnifiedPushResult.NeedToAskUserForDistributor -> {
_viewEvents.post(HomeActivityViewEvents.AskUserForPushDistributor)
}
RegisterUnifiedPushUseCase.RegisterUnifiedPushResult.Success -> {
ensureFcmTokenIsRetrievedUseCase.execute(pushersManager, registerPusher = vectorPreferences.areNotificationEnabledForDevice())
}
}
}
}
private fun observeReleaseNotes() = withState { state -> private fun observeReleaseNotes() = withState { state ->
if (vectorPreferences.isNewAppLayoutEnabled()) { if (vectorPreferences.isNewAppLayoutEnabled()) {
// we don't want to show release notes for new users or after relogin // we don't want to show release notes for new users or after relogin
@ -146,26 +164,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<LocalNotificationSettingsContent>()?.isSilenced ?: false }
.onEach { setState { copy(areNotificationsSilenced = it) } }
}
}
private fun observeAnalytics() { private fun observeAnalytics() {
if (analyticsConfig.isEnabled) { if (analyticsConfig.isEnabled) {
analyticsStore.didAskUserConsentFlow analyticsStore.didAskUserConsentFlow
@ -501,6 +499,9 @@ class HomeActivityViewModel @AssistedInject constructor(
HomeActivityViewActions.ViewStarted -> { HomeActivityViewActions.ViewStarted -> {
initialize() initialize()
} }
is HomeActivityViewActions.RegisterPushDistributor -> {
registerUnifiedPush(distributor = action.distributor)
}
} }
} }
} }

View File

@ -23,5 +23,4 @@ import org.matrix.android.sdk.api.session.sync.SyncRequestState
data class HomeActivityViewState( data class HomeActivityViewState(
val syncRequestState: SyncRequestState = SyncRequestState.Idle, val syncRequestState: SyncRequestState = SyncRequestState.Idle,
val authenticationDescription: AuthenticationDescription? = null, val authenticationDescription: AuthenticationDescription? = null,
val areNotificationsSilenced: Boolean = false,
) : MavericksState ) : MavericksState

View File

@ -28,6 +28,8 @@ import im.vector.app.R
import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.extensions.singletonEntryPoint import im.vector.app.core.extensions.singletonEntryPoint
import im.vector.app.core.platform.VectorBaseActivity 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.core.utils.toast
import im.vector.app.features.analytics.AnalyticsTracker import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.analytics.plan.MobileScreen 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 session: Session
protected lateinit var errorFormatter: ErrorFormatter protected lateinit var errorFormatter: ErrorFormatter
/* ==========================================================================================
* ViewEvents
* ========================================================================================== */
protected fun <T : VectorViewEvents> VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) {
viewEvents
.stream()
.onEach {
observer(it)
}
.launchIn(viewLifecycleOwner.lifecycleScope)
}
/* ========================================================================================== /* ==========================================================================================
* Views * Views
* ========================================================================================== */ * ========================================================================================== */
@ -148,7 +163,7 @@ abstract class VectorSettingsBaseFragment : PreferenceFragmentCompat(), Maverick
} }
} }
protected fun displayErrorDialog(throwable: Throwable) { protected fun displayErrorDialog(throwable: Throwable?) {
displayErrorDialog(errorFormatter.toHumanReadable(throwable)) displayErrorDialog(errorFormatter.toHumanReadable(throwable))
} }

View File

@ -18,17 +18,17 @@ package im.vector.app.features.settings.notifications
import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.pushers.PushersManager 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.CheckIfCanTogglePushNotificationsViaPusherUseCase
import im.vector.app.features.settings.devices.v2.notification.TogglePushNotificationUseCase import im.vector.app.features.settings.devices.v2.notification.TogglePushNotificationUseCase
import javax.inject.Inject import javax.inject.Inject
class DisableNotificationsForCurrentSessionUseCase @Inject constructor( class DisableNotificationsForCurrentSessionUseCase @Inject constructor(
private val activeSessionHolder: ActiveSessionHolder, private val activeSessionHolder: ActiveSessionHolder,
private val unifiedPushHelper: UnifiedPushHelper,
private val pushersManager: PushersManager, private val pushersManager: PushersManager,
private val checkIfCanTogglePushNotificationsViaPusherUseCase: CheckIfCanTogglePushNotificationsViaPusherUseCase, private val checkIfCanTogglePushNotificationsViaPusherUseCase: CheckIfCanTogglePushNotificationsViaPusherUseCase,
private val togglePushNotificationUseCase: TogglePushNotificationUseCase, private val togglePushNotificationUseCase: TogglePushNotificationUseCase,
private val unregisterUnifiedPushUseCase: UnregisterUnifiedPushUseCase,
) { ) {
suspend fun execute() { suspend fun execute() {
@ -37,7 +37,7 @@ class DisableNotificationsForCurrentSessionUseCase @Inject constructor(
if (checkIfCanTogglePushNotificationsViaPusherUseCase.execute(session)) { if (checkIfCanTogglePushNotificationsViaPusherUseCase.execute(session)) {
togglePushNotificationUseCase.execute(deviceId, enabled = false) togglePushNotificationUseCase.execute(deviceId, enabled = false)
} else { } else {
unifiedPushHelper.unregister(pushersManager) unregisterUnifiedPushUseCase.execute(pushersManager)
} }
} }
} }

View File

@ -16,56 +16,44 @@
package im.vector.app.features.settings.notifications package im.vector.app.features.settings.notifications
import androidx.fragment.app.FragmentActivity
import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.pushers.FcmHelper import im.vector.app.core.pushers.EnsureFcmTokenIsRetrievedUseCase
import im.vector.app.core.pushers.PushersManager import im.vector.app.core.pushers.PushersManager
import im.vector.app.core.pushers.UnifiedPushHelper import im.vector.app.core.pushers.RegisterUnifiedPushUseCase
import im.vector.app.features.settings.devices.v2.notification.CheckIfCanTogglePushNotificationsViaPusherUseCase
import im.vector.app.features.settings.devices.v2.notification.TogglePushNotificationUseCase import im.vector.app.features.settings.devices.v2.notification.TogglePushNotificationUseCase
import javax.inject.Inject import javax.inject.Inject
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
class EnableNotificationsForCurrentSessionUseCase @Inject constructor( class EnableNotificationsForCurrentSessionUseCase @Inject constructor(
private val activeSessionHolder: ActiveSessionHolder, private val activeSessionHolder: ActiveSessionHolder,
private val unifiedPushHelper: UnifiedPushHelper,
private val pushersManager: PushersManager, private val pushersManager: PushersManager,
private val fcmHelper: FcmHelper,
private val checkIfCanTogglePushNotificationsViaPusherUseCase: CheckIfCanTogglePushNotificationsViaPusherUseCase,
private val togglePushNotificationUseCase: TogglePushNotificationUseCase, private val togglePushNotificationUseCase: TogglePushNotificationUseCase,
private val registerUnifiedPushUseCase: RegisterUnifiedPushUseCase,
private val ensureFcmTokenIsRetrievedUseCase: EnsureFcmTokenIsRetrievedUseCase,
) { ) {
suspend fun execute(fragmentActivity: FragmentActivity) { sealed interface EnableNotificationsResult {
object Success : EnableNotificationsResult
object Failure : EnableNotificationsResult
object NeedToAskUserForDistributor : EnableNotificationsResult
}
suspend fun execute(distributor: String = ""): EnableNotificationsResult {
val pusherForCurrentSession = pushersManager.getPusherForCurrentSession() val pusherForCurrentSession = pushersManager.getPusherForCurrentSession()
if (pusherForCurrentSession == null) { if (pusherForCurrentSession == null) {
registerPusher(fragmentActivity) when (registerUnifiedPushUseCase.execute(distributor)) {
is RegisterUnifiedPushUseCase.RegisterUnifiedPushResult.NeedToAskUserForDistributor -> {
return EnableNotificationsResult.NeedToAskUserForDistributor
}
RegisterUnifiedPushUseCase.RegisterUnifiedPushResult.Success -> {
ensureFcmTokenIsRetrievedUseCase.execute(pushersManager, registerPusher = true)
}
}
} }
val session = activeSessionHolder.getSafeActiveSession() ?: return val session = activeSessionHolder.getSafeActiveSession() ?: return EnableNotificationsResult.Failure
if (checkIfCanTogglePushNotificationsViaPusherUseCase.execute(session)) { val deviceId = session.sessionParams.deviceId ?: return EnableNotificationsResult.Failure
val deviceId = session.sessionParams.deviceId ?: return
togglePushNotificationUseCase.execute(deviceId, enabled = true) togglePushNotificationUseCase.execute(deviceId, enabled = true)
}
}
private suspend fun registerPusher(fragmentActivity: FragmentActivity) { return EnableNotificationsResult.Success
suspendCoroutine { continuation ->
try {
unifiedPushHelper.register(fragmentActivity) {
if (unifiedPushHelper.isEmbeddedDistributor()) {
fcmHelper.ensureFcmTokenIsRetrieved(
fragmentActivity,
pushersManager,
registerPusher = true
)
}
continuation.resume(Unit)
}
} catch (error: Exception) {
continuation.resumeWithException(error)
}
}
} }
} }

View File

@ -22,6 +22,7 @@ import android.content.Intent
import android.media.RingtoneManager import android.media.RingtoneManager
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.view.View
import android.widget.Toast import android.widget.Toast
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.distinctUntilChanged import androidx.lifecycle.distinctUntilChanged
@ -29,6 +30,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.map import androidx.lifecycle.map
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.SwitchPreference import androidx.preference.SwitchPreference
import com.airbnb.mvrx.fragmentViewModel
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActiveSessionHolder
@ -37,6 +39,7 @@ import im.vector.app.core.preference.VectorEditTextPreference
import im.vector.app.core.preference.VectorPreference import im.vector.app.core.preference.VectorPreference
import im.vector.app.core.preference.VectorPreferenceCategory import im.vector.app.core.preference.VectorPreferenceCategory
import im.vector.app.core.preference.VectorSwitchPreference 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.FcmHelper
import im.vector.app.core.pushers.PushersManager import im.vector.app.core.pushers.PushersManager
import im.vector.app.core.pushers.UnifiedPushHelper import im.vector.app.core.pushers.UnifiedPushHelper
@ -80,14 +83,15 @@ class VectorSettingsNotificationPreferenceFragment :
@Inject lateinit var guardServiceStarter: GuardServiceStarter @Inject lateinit var guardServiceStarter: GuardServiceStarter
@Inject lateinit var vectorFeatures: VectorFeatures @Inject lateinit var vectorFeatures: VectorFeatures
@Inject lateinit var notificationPermissionManager: NotificationPermissionManager @Inject lateinit var notificationPermissionManager: NotificationPermissionManager
@Inject lateinit var disableNotificationsForCurrentSessionUseCase: DisableNotificationsForCurrentSessionUseCase @Inject lateinit var ensureFcmTokenIsRetrievedUseCase: EnsureFcmTokenIsRetrievedUseCase
@Inject lateinit var enableNotificationsForCurrentSessionUseCase: EnableNotificationsForCurrentSessionUseCase
override var titleRes: Int = R.string.settings_notifications override var titleRes: Int = R.string.settings_notifications
override val preferenceXmlRes = R.xml.vector_settings_notifications override val preferenceXmlRes = R.xml.vector_settings_notifications
private var interactionListener: VectorSettingsFragmentInteractionListener? = null private var interactionListener: VectorSettingsFragmentInteractionListener? = null
private val viewModel: VectorSettingsNotificationPreferenceViewModel by fragmentViewModel()
private val notificationStartForActivityResult = registerStartForActivityResult { _ -> private val notificationStartForActivityResult = registerStartForActivityResult { _ ->
// No op // No op
} }
@ -104,6 +108,23 @@ class VectorSettingsNotificationPreferenceFragment :
analyticsScreenName = MobileScreen.ScreenName.SettingsNotifications analyticsScreenName = MobileScreen.ScreenName.SettingsNotifications
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
observeViewEvents()
}
private fun observeViewEvents() {
viewModel.observeViewEvents {
when (it) {
VectorSettingsNotificationPreferenceViewEvent.NotificationsForDeviceEnabled -> onNotificationsForDeviceEnabled()
VectorSettingsNotificationPreferenceViewEvent.NotificationsForDeviceDisabled -> onNotificationsForDeviceDisabled()
is VectorSettingsNotificationPreferenceViewEvent.AskUserForPushDistributor -> askUserToSelectPushDistributor()
VectorSettingsNotificationPreferenceViewEvent.EnableNotificationForDeviceFailure -> displayErrorDialog(throwable = null)
VectorSettingsNotificationPreferenceViewEvent.NotificationMethodChanged -> onNotificationMethodChanged()
}
}
}
override fun bindPref() { override fun bindPref() {
findPreference<VectorSwitchPreference>(VectorPreferences.SETTINGS_ENABLE_ALL_NOTIF_PREFERENCE_KEY)!!.let { pref -> findPreference<VectorSwitchPreference>(VectorPreferences.SETTINGS_ENABLE_ALL_NOTIF_PREFERENCE_KEY)!!.let { pref ->
val pushRuleService = session.pushRuleService() val pushRuleService = session.pushRuleService()
@ -121,23 +142,15 @@ class VectorSettingsNotificationPreferenceFragment :
} }
findPreference<SwitchPreference>(VectorPreferences.SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY) findPreference<SwitchPreference>(VectorPreferences.SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY)
?.setTransactionalSwitchChangeListener(lifecycleScope) { isChecked -> ?.setOnPreferenceChangeListener { _, isChecked ->
if (isChecked) { val action = if (isChecked as Boolean) {
enableNotificationsForCurrentSessionUseCase.execute(requireActivity()) VectorSettingsNotificationPreferenceViewAction.EnableNotificationsForDevice(pushDistributor = "")
findPreference<VectorPreference>(VectorPreferences.SETTINGS_NOTIFICATION_METHOD_KEY)
?.summary = unifiedPushHelper.getCurrentDistributorName()
notificationPermissionManager.eventuallyRequestPermission(
requireActivity(),
postPermissionLauncher,
showRationale = false,
ignorePreference = true
)
} else { } else {
disableNotificationsForCurrentSessionUseCase.execute() VectorSettingsNotificationPreferenceViewAction.DisableNotificationsForDevice
notificationPermissionManager.eventuallyRevokePermission(requireActivity())
} }
viewModel.handle(action)
// preference will be updated on ViewEvent reception
false
} }
findPreference<VectorPreference>(VectorPreferences.SETTINGS_FDROID_BACKGROUND_SYNC_MODE)?.let { findPreference<VectorPreference>(VectorPreferences.SETTINGS_FDROID_BACKGROUND_SYNC_MODE)?.let {
@ -182,18 +195,7 @@ class VectorSettingsNotificationPreferenceFragment :
if (vectorFeatures.allowExternalUnifiedPushDistributors()) { if (vectorFeatures.allowExternalUnifiedPushDistributors()) {
it.summary = unifiedPushHelper.getCurrentDistributorName() it.summary = unifiedPushHelper.getCurrentDistributorName()
it.onPreferenceClickListener = Preference.OnPreferenceClickListener { it.onPreferenceClickListener = Preference.OnPreferenceClickListener {
unifiedPushHelper.forceRegister(requireActivity(), pushersManager) { askUserToSelectPushDistributor(withUnregister = true)
if (unifiedPushHelper.isEmbeddedDistributor()) {
fcmHelper.ensureFcmTokenIsRetrieved(
requireActivity(),
pushersManager,
vectorPreferences.areNotificationEnabledForDevice()
)
}
it.summary = unifiedPushHelper.getCurrentDistributorName()
session.pushersService().refreshPushers()
refreshBackgroundSyncPrefs()
}
true true
} }
} else { } else {
@ -207,6 +209,42 @@ class VectorSettingsNotificationPreferenceFragment :
handleSystemPreference() handleSystemPreference()
} }
private fun onNotificationsForDeviceEnabled() {
findPreference<SwitchPreference>(VectorPreferences.SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY)
?.isChecked = true
findPreference<VectorPreference>(VectorPreferences.SETTINGS_NOTIFICATION_METHOD_KEY)
?.summary = unifiedPushHelper.getCurrentDistributorName()
notificationPermissionManager.eventuallyRequestPermission(
requireActivity(),
postPermissionLauncher,
showRationale = false,
ignorePreference = true
)
}
private fun onNotificationsForDeviceDisabled() {
findPreference<SwitchPreference>(VectorPreferences.SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY)
?.isChecked = false
notificationPermissionManager.eventuallyRevokePermission(requireActivity())
}
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<VectorPreference>(VectorPreferences.SETTINGS_NOTIFICATION_METHOD_KEY)?.summary = unifiedPushHelper.getCurrentDistributorName()
session.pushersService().refreshPushers()
refreshBackgroundSyncPrefs()
}
private fun bindEmailNotifications() { private fun bindEmailNotifications() {
val initialEmails = session.getEmailsWithPushInformation() val initialEmails = session.getEmailsWithPushInformation()
bindEmailNotificationCategory(initialEmails) bindEmailNotificationCategory(initialEmails)

View File

@ -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
}

View File

@ -0,0 +1,27 @@
/*
* 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 NotificationsForDeviceEnabled : VectorSettingsNotificationPreferenceViewEvent
object EnableNotificationForDeviceFailure : VectorSettingsNotificationPreferenceViewEvent
object NotificationsForDeviceDisabled : VectorSettingsNotificationPreferenceViewEvent
object AskUserForPushDistributor : VectorSettingsNotificationPreferenceViewEvent
object NotificationMethodChanged : VectorSettingsNotificationPreferenceViewEvent
}

View File

@ -0,0 +1,97 @@
/*
* 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 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<VectorDummyViewState, VectorSettingsNotificationPreferenceViewAction, VectorSettingsNotificationPreferenceViewEvent>(initialState) {
@AssistedFactory
interface Factory : MavericksAssistedViewModelFactory<VectorSettingsNotificationPreferenceViewModel, VectorDummyViewState> {
override fun create(initialState: VectorDummyViewState): VectorSettingsNotificationPreferenceViewModel
}
companion object : MavericksViewModelFactory<VectorSettingsNotificationPreferenceViewModel, VectorDummyViewState> by hiltMavericksViewModelFactory()
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.NotificationsForDeviceDisabled)
}
}
private fun handleEnableNotificationsForDevice(distributor: String) {
viewModelScope.launch {
when (enableNotificationsForCurrentSessionUseCase.execute(distributor)) {
EnableNotificationsForCurrentSessionUseCase.EnableNotificationsResult.Failure -> {
_viewEvents.post(VectorSettingsNotificationPreferenceViewEvent.EnableNotificationForDeviceFailure)
}
is EnableNotificationsForCurrentSessionUseCase.EnableNotificationsResult.NeedToAskUserForDistributor -> {
_viewEvents.post(VectorSettingsNotificationPreferenceViewEvent.AskUserForPushDistributor)
}
EnableNotificationsForCurrentSessionUseCase.EnableNotificationsResult.Success -> {
_viewEvents.post(VectorSettingsNotificationPreferenceViewEvent.NotificationsForDeviceEnabled)
}
}
}
}
private fun handleRegisterPushDistributor(distributor: String) {
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)
}
}
}
}
}

View File

@ -17,14 +17,17 @@
package im.vector.app.features.settings.troubleshoot package im.vector.app.features.settings.troubleshoot
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.Observer
import androidx.work.WorkInfo import androidx.work.WorkInfo
import androidx.work.WorkManager import androidx.work.WorkManager
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.pushers.PushersManager 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.UnifiedPushHelper
import im.vector.app.core.pushers.UnregisterUnifiedPushUseCase
import im.vector.app.core.resources.StringProvider 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 org.matrix.android.sdk.api.session.pushers.PusherState
import javax.inject.Inject import javax.inject.Inject
@ -34,6 +37,8 @@ class TestEndpointAsTokenRegistration @Inject constructor(
private val pushersManager: PushersManager, private val pushersManager: PushersManager,
private val activeSessionHolder: ActiveSessionHolder, private val activeSessionHolder: ActiveSessionHolder,
private val unifiedPushHelper: UnifiedPushHelper, private val unifiedPushHelper: UnifiedPushHelper,
private val registerUnifiedPushUseCase: RegisterUnifiedPushUseCase,
private val unregisterUnifiedPushUseCase: UnregisterUnifiedPushUseCase,
) : TroubleshootTest(R.string.settings_troubleshoot_test_endpoint_registration_title) { ) : TroubleshootTest(R.string.settings_troubleshoot_test_endpoint_registration_title) {
override fun perform(testParameters: TestParameters) { override fun perform(testParameters: TestParameters) {
@ -56,12 +61,34 @@ class TestEndpointAsTokenRegistration @Inject constructor(
) )
quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_endpoint_registration_quick_fix) { quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_endpoint_registration_quick_fix) {
override fun doFix() { override fun doFix() {
unifiedPushHelper.forceRegister( unregisterThenRegister(testParameters, endpoint)
context, }
pushersManager }
) status = TestStatus.FAILED
val workId = pushersManager.enqueueRegisterPusherWithFcmKey(endpoint) } else {
WorkManager.getInstance(context).getWorkInfoByIdLiveData(workId).observe(context, Observer { workInfo -> 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 (registerUnifiedPushUseCase.execute(distributor)) {
is RegisterUnifiedPushUseCase.RegisterUnifiedPushResult.NeedToAskUserForDistributor ->
askUserForDistributor(testParameters, pushKey)
RegisterUnifiedPushUseCase.RegisterUnifiedPushResult.Success -> {
val workId = pushersManager.enqueueRegisterPusherWithFcmKey(pushKey)
WorkManager.getInstance(context).getWorkInfoByIdLiveData(workId).observe(context) { workInfo ->
if (workInfo != null) { if (workInfo != null) {
if (workInfo.state == WorkInfo.State.SUCCEEDED) { if (workInfo.state == WorkInfo.State.SUCCEEDED) {
manager?.retry(testParameters) manager?.retry(testParameters)
@ -69,14 +96,17 @@ class TestEndpointAsTokenRegistration @Inject constructor(
manager?.retry(testParameters) manager?.retry(testParameters)
} }
} }
}) }
}
} }
} }
status = TestStatus.FAILED private fun askUserForDistributor(
} else { testParameters: TestParameters,
description = stringProvider.getString(R.string.settings_troubleshoot_test_endpoint_registration_success) pushKey: String,
status = TestStatus.SUCCESS ) {
unifiedPushHelper.showSelectDistributorDialog(context) { selection ->
registerUnifiedPush(distributor = selection, testParameters, pushKey)
} }
} }
} }

View File

@ -0,0 +1,105 @@
/*
* 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 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)
}
}

View File

@ -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())
}
}
}

View File

@ -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)
}
}

View File

@ -16,11 +16,11 @@
package im.vector.app.features.settings.notifications 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.CheckIfCanTogglePushNotificationsViaPusherUseCase
import im.vector.app.features.settings.devices.v2.notification.TogglePushNotificationUseCase import im.vector.app.features.settings.devices.v2.notification.TogglePushNotificationUseCase
import im.vector.app.test.fakes.FakeActiveSessionHolder import im.vector.app.test.fakes.FakeActiveSessionHolder
import im.vector.app.test.fakes.FakePushersManager import im.vector.app.test.fakes.FakePushersManager
import im.vector.app.test.fakes.FakeUnifiedPushHelper
import io.mockk.coJustRun import io.mockk.coJustRun
import io.mockk.coVerify import io.mockk.coVerify
import io.mockk.every import io.mockk.every
@ -33,17 +33,17 @@ private const val A_SESSION_ID = "session-id"
class DisableNotificationsForCurrentSessionUseCaseTest { class DisableNotificationsForCurrentSessionUseCaseTest {
private val fakeActiveSessionHolder = FakeActiveSessionHolder() private val fakeActiveSessionHolder = FakeActiveSessionHolder()
private val fakeUnifiedPushHelper = FakeUnifiedPushHelper()
private val fakePushersManager = FakePushersManager() private val fakePushersManager = FakePushersManager()
private val fakeCheckIfCanTogglePushNotificationsViaPusherUseCase = mockk<CheckIfCanTogglePushNotificationsViaPusherUseCase>() private val fakeCheckIfCanTogglePushNotificationsViaPusherUseCase = mockk<CheckIfCanTogglePushNotificationsViaPusherUseCase>()
private val fakeTogglePushNotificationUseCase = mockk<TogglePushNotificationUseCase>() private val fakeTogglePushNotificationUseCase = mockk<TogglePushNotificationUseCase>()
private val fakeUnregisterUnifiedPushUseCase = mockk<UnregisterUnifiedPushUseCase>()
private val disableNotificationsForCurrentSessionUseCase = DisableNotificationsForCurrentSessionUseCase( private val disableNotificationsForCurrentSessionUseCase = DisableNotificationsForCurrentSessionUseCase(
activeSessionHolder = fakeActiveSessionHolder.instance, activeSessionHolder = fakeActiveSessionHolder.instance,
unifiedPushHelper = fakeUnifiedPushHelper.instance,
pushersManager = fakePushersManager.instance, pushersManager = fakePushersManager.instance,
checkIfCanTogglePushNotificationsViaPusherUseCase = fakeCheckIfCanTogglePushNotificationsViaPusherUseCase, checkIfCanTogglePushNotificationsViaPusherUseCase = fakeCheckIfCanTogglePushNotificationsViaPusherUseCase,
togglePushNotificationUseCase = fakeTogglePushNotificationUseCase, togglePushNotificationUseCase = fakeTogglePushNotificationUseCase,
unregisterUnifiedPushUseCase = fakeUnregisterUnifiedPushUseCase,
) )
@Test @Test
@ -67,12 +67,14 @@ class DisableNotificationsForCurrentSessionUseCaseTest {
val fakeSession = fakeActiveSessionHolder.fakeSession val fakeSession = fakeActiveSessionHolder.fakeSession
fakeSession.givenSessionId(A_SESSION_ID) fakeSession.givenSessionId(A_SESSION_ID)
every { fakeCheckIfCanTogglePushNotificationsViaPusherUseCase.execute(fakeSession) } returns false every { fakeCheckIfCanTogglePushNotificationsViaPusherUseCase.execute(fakeSession) } returns false
fakeUnifiedPushHelper.givenUnregister(fakePushersManager.instance) coJustRun { fakeUnregisterUnifiedPushUseCase.execute(any()) }
// When // When
disableNotificationsForCurrentSessionUseCase.execute() disableNotificationsForCurrentSessionUseCase.execute()
// Then // Then
fakeUnifiedPushHelper.verifyUnregister(fakePushersManager.instance) coVerify {
fakeUnregisterUnifiedPushUseCase.execute(fakePushersManager.instance)
}
} }
} }

View File

@ -16,18 +16,18 @@
package im.vector.app.features.settings.notifications package im.vector.app.features.settings.notifications
import androidx.fragment.app.FragmentActivity import im.vector.app.core.pushers.EnsureFcmTokenIsRetrievedUseCase
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 im.vector.app.features.settings.devices.v2.notification.TogglePushNotificationUseCase
import im.vector.app.test.fakes.FakeActiveSessionHolder 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.FakePushersManager
import im.vector.app.test.fakes.FakeUnifiedPushHelper
import io.mockk.coJustRun import io.mockk.coJustRun
import io.mockk.coVerify
import io.mockk.every import io.mockk.every
import io.mockk.justRun
import io.mockk.mockk import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.amshove.kluent.shouldBe
import org.junit.Test import org.junit.Test
private const val A_SESSION_ID = "session-id" private const val A_SESSION_ID = "session-id"
@ -35,53 +35,71 @@ private const val A_SESSION_ID = "session-id"
class EnableNotificationsForCurrentSessionUseCaseTest { class EnableNotificationsForCurrentSessionUseCaseTest {
private val fakeActiveSessionHolder = FakeActiveSessionHolder() private val fakeActiveSessionHolder = FakeActiveSessionHolder()
private val fakeUnifiedPushHelper = FakeUnifiedPushHelper()
private val fakePushersManager = FakePushersManager() private val fakePushersManager = FakePushersManager()
private val fakeFcmHelper = FakeFcmHelper()
private val fakeCheckIfCanTogglePushNotificationsViaPusherUseCase = mockk<CheckIfCanTogglePushNotificationsViaPusherUseCase>()
private val fakeTogglePushNotificationUseCase = mockk<TogglePushNotificationUseCase>() private val fakeTogglePushNotificationUseCase = mockk<TogglePushNotificationUseCase>()
private val fakeRegisterUnifiedPushUseCase = mockk<RegisterUnifiedPushUseCase>()
private val fakeEnsureFcmTokenIsRetrievedUseCase = mockk<EnsureFcmTokenIsRetrievedUseCase>()
private val enableNotificationsForCurrentSessionUseCase = EnableNotificationsForCurrentSessionUseCase( private val enableNotificationsForCurrentSessionUseCase = EnableNotificationsForCurrentSessionUseCase(
activeSessionHolder = fakeActiveSessionHolder.instance, activeSessionHolder = fakeActiveSessionHolder.instance,
unifiedPushHelper = fakeUnifiedPushHelper.instance,
pushersManager = fakePushersManager.instance, pushersManager = fakePushersManager.instance,
fcmHelper = fakeFcmHelper.instance,
checkIfCanTogglePushNotificationsViaPusherUseCase = fakeCheckIfCanTogglePushNotificationsViaPusherUseCase,
togglePushNotificationUseCase = fakeTogglePushNotificationUseCase, togglePushNotificationUseCase = fakeTogglePushNotificationUseCase,
registerUnifiedPushUseCase = fakeRegisterUnifiedPushUseCase,
ensureFcmTokenIsRetrievedUseCase = fakeEnsureFcmTokenIsRetrievedUseCase,
) )
@Test @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 // Given
val fragmentActivity = mockk<FragmentActivity>() val aDistributor = "distributor"
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<FragmentActivity>()
fakePushersManager.givenGetPusherForCurrentSessionReturns(mockk())
val fakeSession = fakeActiveSessionHolder.fakeSession val fakeSession = fakeActiveSessionHolder.fakeSession
fakeSession.givenSessionId(A_SESSION_ID) 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()) } coJustRun { fakeTogglePushNotificationUseCase.execute(A_SESSION_ID, any()) }
// When // When
enableNotificationsForCurrentSessionUseCase.execute(fragmentActivity) val result = enableNotificationsForCurrentSessionUseCase.execute(aDistributor)
// Then // 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
} }
} }

View File

@ -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<EnableNotificationsForCurrentSessionUseCase>()
private val fakeDisableNotificationsForCurrentSessionUseCase = mockk<DisableNotificationsForCurrentSessionUseCase>()
private val fakeUnregisterUnifiedPushUseCase = mockk<UnregisterUnifiedPushUseCase>()
private val fakeRegisterUnifiedPushUseCase = mockk<RegisterUnifiedPushUseCase>()
private val fakeEnsureFcmTokenIsRetrievedUseCase = mockk<EnsureFcmTokenIsRetrievedUseCase>()
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)
}
}
}

View File

@ -81,4 +81,8 @@ class FakeContext(
givenService(Context.CLIPBOARD_SERVICE, ClipboardManager::class.java, fakeClipboardManager.instance) givenService(Context.CLIPBOARD_SERVICE, ClipboardManager::class.java, fakeClipboardManager.instance)
return fakeClipboardManager return fakeClipboardManager
} }
fun givenPackageName(name: String) {
every { instance.packageName } returns name
}
} }

View File

@ -16,7 +16,6 @@
package im.vector.app.test.fakes package im.vector.app.test.fakes
import androidx.fragment.app.FragmentActivity
import im.vector.app.core.pushers.FcmHelper import im.vector.app.core.pushers.FcmHelper
import im.vector.app.core.pushers.PushersManager import im.vector.app.core.pushers.PushersManager
import io.mockk.justRun import io.mockk.justRun
@ -27,18 +26,15 @@ class FakeFcmHelper {
val instance = mockk<FcmHelper>() val instance = mockk<FcmHelper>()
fun givenEnsureFcmTokenIsRetrieved( fun givenEnsureFcmTokenIsRetrieved(pushersManager: PushersManager) {
fragmentActivity: FragmentActivity, justRun { instance.ensureFcmTokenIsRetrieved(pushersManager, any()) }
pushersManager: PushersManager,
) {
justRun { instance.ensureFcmTokenIsRetrieved(fragmentActivity, pushersManager, any()) }
} }
fun verifyEnsureFcmTokenIsRetrieved( fun verifyEnsureFcmTokenIsRetrieved(
fragmentActivity: FragmentActivity,
pushersManager: PushersManager, pushersManager: PushersManager,
registerPusher: Boolean, registerPusher: Boolean,
inverse: Boolean = false,
) { ) {
verify { instance.ensureFcmTokenIsRetrieved(fragmentActivity, pushersManager, registerPusher) } verify(inverse = inverse) { instance.ensureFcmTokenIsRetrieved(pushersManager, registerPusher) }
} }
} }

View File

@ -17,6 +17,8 @@
package im.vector.app.test.fakes package im.vector.app.test.fakes
import im.vector.app.core.pushers.PushersManager import im.vector.app.core.pushers.PushersManager
import io.mockk.coJustRun
import io.mockk.coVerify
import io.mockk.every import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
import org.matrix.android.sdk.api.session.pushers.Pusher import org.matrix.android.sdk.api.session.pushers.Pusher
@ -28,4 +30,12 @@ class FakePushersManager {
fun givenGetPusherForCurrentSessionReturns(pusher: Pusher?) { fun givenGetPusherForCurrentSessionReturns(pusher: Pusher?) {
every { instance.getPusherForCurrentSession() } returns pusher every { instance.getPusherForCurrentSession() } returns pusher
} }
fun givenUnregisterPusher(pushKey: String) {
coJustRun { instance.unregisterPusher(pushKey) }
}
fun verifyUnregisterPusher(pushKey: String) {
coVerify { instance.unregisterPusher(pushKey) }
}
} }

View File

@ -16,38 +16,19 @@
package im.vector.app.test.fakes 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 im.vector.app.core.pushers.UnifiedPushHelper
import io.mockk.coJustRun
import io.mockk.coVerify
import io.mockk.every import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
import io.mockk.verify
class FakeUnifiedPushHelper { class FakeUnifiedPushHelper {
val instance = mockk<UnifiedPushHelper>() val instance = mockk<UnifiedPushHelper>()
fun givenRegister(fragmentActivity: FragmentActivity) {
every { instance.register(fragmentActivity, any()) } answers {
secondArg<Runnable>().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) { fun givenIsEmbeddedDistributorReturns(isEmbedded: Boolean) {
every { instance.isEmbeddedDistributor() } returns isEmbedded every { instance.isEmbeddedDistributor() } returns isEmbedded
} }
fun givenGetEndpointOrTokenReturns(endpoint: String?) {
every { instance.getEndpointOrToken() } returns endpoint
}
} }

View File

@ -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<UnifiedPushStore>()
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) }
}
}

View File

@ -54,4 +54,8 @@ class FakeVectorFeatures : VectorFeatures by spyk<DefaultVectorFeatures>() {
fun givenUnverifiedSessionsAlertEnabled(isEnabled: Boolean) { fun givenUnverifiedSessionsAlertEnabled(isEnabled: Boolean) {
every { isUnverifiedSessionsAlertEnabled() } returns isEnabled every { isUnverifiedSessionsAlertEnabled() } returns isEnabled
} }
fun givenExternalDistributorsAreAllowed(allowed: Boolean) {
every { allowExternalUnifiedPushDistributors() } returns allowed
}
} }

View File

@ -16,6 +16,7 @@
package im.vector.app.test.fakes package im.vector.app.test.fakes
import im.vector.app.features.settings.BackgroundSyncMode
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
import io.mockk.every import io.mockk.every
import io.mockk.justRun import io.mockk.justRun
@ -60,4 +61,16 @@ class FakeVectorPreferences {
fun givenUnverifiedSessionsAlertLastShownMillis(lastShownMillis: Long) { fun givenUnverifiedSessionsAlertLastShownMillis(lastShownMillis: Long) {
every { instance.getUnverifiedSessionsAlertLastShownMillis(any()) } returns lastShownMillis every { instance.getUnverifiedSessionsAlertLastShownMillis(any()) } returns lastShownMillis
} }
fun givenSetFdroidSyncBackgroundMode(mode: BackgroundSyncMode) {
justRun { instance.setFdroidSyncBackgroundMode(mode) }
}
fun verifySetFdroidSyncBackgroundMode(mode: BackgroundSyncMode) {
verify { instance.setFdroidSyncBackgroundMode(mode) }
}
fun givenAreNotificationsEnabledForDevice(notificationsEnabled: Boolean) {
every { instance.areNotificationEnabledForDevice() } returns notificationsEnabled
}
} }