Adding new use cases to handle the Unified push registration

This commit is contained in:
Maxime NATUREL 2022-11-29 15:07:26 +01:00
parent 9456789047
commit 4dbca7858c
14 changed files with 271 additions and 82 deletions

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

@ -0,0 +1,44 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.core.pushers
import im.vector.app.core.di.ActiveSessionHolder
import timber.log.Timber
import javax.inject.Inject
class EnsureFcmTokenIsRetrievedUseCase @Inject constructor(
private val unifiedPushHelper: UnifiedPushHelper,
private val fcmHelper: FcmHelper,
private val activeSessionHolder: ActiveSessionHolder,
) {
// TODO add unit tests
fun execute(pushersManager: PushersManager, registerPusher: Boolean) {
if (unifiedPushHelper.isEmbeddedDistributor()) {
Timber.d("ensureFcmTokenIsRetrieved")
fcmHelper.ensureFcmTokenIsRetrieved(pushersManager, shouldAddHttpPusher(registerPusher))
}
}
private fun shouldAddHttpPusher(registerPusher: Boolean) = if (registerPusher) {
val currentSession = activeSessionHolder.getActiveSession()
val currentPushers = currentSession.pushersService().getPushers()
currentPushers.none { it.deviceId == currentSession.sessionParams.deviceId }
} else {
false
}
}

View File

@ -39,11 +39,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,70 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.core.pushers
import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext
import im.vector.app.features.VectorFeatures
import org.unifiedpush.android.connector.UnifiedPush
import javax.inject.Inject
class RegisterUnifiedPushUseCase @Inject constructor(
@ApplicationContext private val context: Context,
private val vectorFeatures: VectorFeatures,
) {
sealed interface RegisterUnifiedPushResult {
object Success : RegisterUnifiedPushResult
data class NeedToAskUserForDistributor(val distributors: List<String>) : RegisterUnifiedPushResult
}
// TODO add unit tests
fun execute(distributor: String = ""): RegisterUnifiedPushResult {
if(distributor.isNotEmpty()) {
saveAndRegisterApp(distributor)
return RegisterUnifiedPushResult.Success
}
if (!vectorFeatures.allowExternalUnifiedPushDistributors()) {
saveAndRegisterApp(context.packageName)
return RegisterUnifiedPushResult.Success
}
if (UnifiedPush.getDistributor(context).isNotEmpty()) {
registerApp()
return RegisterUnifiedPushResult.Success
}
val distributors = UnifiedPush.getDistributors(context)
return if (distributors.size == 1) {
saveAndRegisterApp(distributors.first())
RegisterUnifiedPushResult.Success
} else {
RegisterUnifiedPushResult.NeedToAskUserForDistributor(distributors)
}
}
private fun saveAndRegisterApp(distributor: String) {
UnifiedPush.saveDistributor(context, distributor)
registerApp()
}
private fun registerApp() {
UnifiedPush.registerApp(context)
}
}

View File

@ -17,6 +17,7 @@
package im.vector.app.core.pushers package im.vector.app.core.pushers
import android.content.Context import android.content.Context
import androidx.annotation.MainThread
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
@ -28,7 +29,9 @@ import im.vector.app.core.utils.getApplicationLabel
import im.vector.app.features.VectorFeatures import im.vector.app.features.VectorFeatures
import im.vector.app.features.settings.BackgroundSyncMode import im.vector.app.features.settings.BackgroundSyncMode
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
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
@ -49,6 +52,7 @@ class UnifiedPushHelper @Inject constructor(
// Called when the home activity starts // Called when the home activity starts
// or when notifications are enabled // or when notifications are enabled
// TODO remove and replace by use case
fun register( fun register(
activity: FragmentActivity, activity: FragmentActivity,
onDoneRunnable: Runnable? = null, onDoneRunnable: Runnable? = null,
@ -66,10 +70,11 @@ class UnifiedPushHelper @Inject constructor(
// The registration is forced in 2 cases : // The registration is forced in 2 cases :
// * in the settings // * in the settings
// * in the troubleshoot list (doFix) // * in the troubleshoot list (doFix)
// TODO remove and replace by use case
fun forceRegister( fun forceRegister(
activity: FragmentActivity, activity: FragmentActivity,
pushersManager: PushersManager, pushersManager: PushersManager,
onDoneRunnable: Runnable? = null @MainThread onDoneRunnable: Runnable? = null
) { ) {
registerInternal( registerInternal(
activity, activity,
@ -79,17 +84,21 @@ class UnifiedPushHelper @Inject constructor(
) )
} }
// TODO remove
private fun registerInternal( private fun registerInternal(
activity: FragmentActivity, activity: FragmentActivity,
force: Boolean = false, force: Boolean = false,
pushersManager: PushersManager? = null, pushersManager: PushersManager? = null,
onDoneRunnable: Runnable? = null onDoneRunnable: Runnable? = null
) { ) {
activity.lifecycleScope.launch { activity.lifecycleScope.launch(Dispatchers.IO) {
Timber.d("registerInternal force=$force, $activity on thread ${Thread.currentThread()}")
if (!vectorFeatures.allowExternalUnifiedPushDistributors()) { if (!vectorFeatures.allowExternalUnifiedPushDistributors()) {
UnifiedPush.saveDistributor(context, context.packageName) UnifiedPush.saveDistributor(context, context.packageName)
UnifiedPush.registerApp(context) UnifiedPush.registerApp(context)
onDoneRunnable?.run() withContext(Dispatchers.Main) {
onDoneRunnable?.run()
}
return@launch return@launch
} }
if (force) { if (force) {
@ -99,7 +108,9 @@ class UnifiedPushHelper @Inject constructor(
// the !force should not be needed // the !force should not be needed
if (!force && UnifiedPush.getDistributor(context).isNotEmpty()) { if (!force && UnifiedPush.getDistributor(context).isNotEmpty()) {
UnifiedPush.registerApp(context) UnifiedPush.registerApp(context)
onDoneRunnable?.run() withContext(Dispatchers.Main) {
onDoneRunnable?.run()
}
return@launch return@launch
} }
@ -108,7 +119,9 @@ class UnifiedPushHelper @Inject constructor(
if (!force && distributors.size == 1) { if (!force && distributors.size == 1) {
UnifiedPush.saveDistributor(context, distributors.first()) UnifiedPush.saveDistributor(context, distributors.first())
UnifiedPush.registerApp(context) UnifiedPush.registerApp(context)
onDoneRunnable?.run() withContext(Dispatchers.Main) {
onDoneRunnable?.run()
}
} else { } else {
openDistributorDialogInternal( openDistributorDialogInternal(
activity = activity, activity = activity,
@ -164,6 +177,43 @@ class UnifiedPushHelper @Inject constructor(
.show() .show()
} }
@MainThread
fun showSelectDistributorDialog(
context: Context,
distributors: List<String>,
onDistributorSelected: (String) -> Unit,
) {
val internalDistributorName = stringProvider.getString(
if (fcmHelper.isFirebaseAvailable()) {
R.string.unifiedpush_distributor_fcm_fallback
} else {
R.string.unifiedpush_distributor_background_sync
}
)
val distributorsName = distributors.map {
if (it == context.packageName) {
internalDistributorName
} else {
context.getApplicationLabel(it)
}
}
MaterialAlertDialogBuilder(context)
.setTitle(stringProvider.getString(R.string.unifiedpush_getdistributors_dialog_title))
.setItems(distributorsName.toTypedArray()) { _, which ->
val distributor = distributors[which]
onDistributorSelected(distributor)
}
.setOnCancelListener {
// By default, use internal solution (fcm/background sync)
onDistributorSelected(context.packageName)
}
.setCancelable(true)
.show()
}
// TODO remove and replace by use case
suspend fun unregister(pushersManager: PushersManager? = null) { suspend fun unregister(pushersManager: PushersManager? = null) {
val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME
vectorPreferences.setFdroidSyncBackgroundMode(mode) vectorPreferences.setFdroidSyncBackgroundMode(mode)

View File

@ -0,0 +1,51 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.core.pushers
import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext
import im.vector.app.features.settings.BackgroundSyncMode
import im.vector.app.features.settings.VectorPreferences
import org.unifiedpush.android.connector.UnifiedPush
import timber.log.Timber
import javax.inject.Inject
class UnregisterUnifiedPushUseCase @Inject constructor(
@ApplicationContext private val context: Context,
private val pushersManager: PushersManager,
private val vectorPreferences: VectorPreferences,
private val unifiedPushStore: UnifiedPushStore,
private val unifiedPushHelper: UnifiedPushHelper,
) {
// TODO add unit tests
suspend fun execute() {
val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME
vectorPreferences.setFdroidSyncBackgroundMode(mode)
try {
unifiedPushHelper.getEndpointOrToken()?.let {
Timber.d("Removing $it")
pushersManager.unregisterPusher(it)
}
} catch (e: Exception) {
Timber.d(e, "Probably unregistering a non existing pusher")
}
unifiedPushStore.storeUpEndpoint(null)
unifiedPushStore.storePushGateway(null)
UnifiedPush.unregisterApp(context)
}
}

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(it.distributors)
} }
} }
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(distributors: List<String>) {
unifiedPushHelper.showSelectDistributorDialog(this, distributors) { 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
data class AskUserForPushDistributor(val distributors: List<String>) : 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,32 @@ class HomeActivityViewModel @AssistedInject constructor(
private fun initialize() { private fun initialize() {
if (isInitialized) return if (isInitialized) return
isInitialized = true isInitialized = true
registerUnifiedPush(distributor = "")
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 registerUnifiedPush(distributor: String) {
viewModelScope.launch {
when (val result = registerUnifiedPushUseCase.execute(distributor = distributor)) {
is RegisterUnifiedPushUseCase.RegisterUnifiedPushResult.NeedToAskUserForDistributor -> {
Timber.d("registerUnifiedPush $distributor need to ask user")
_viewEvents.post(HomeActivityViewEvents.AskUserForPushDistributor(result.distributors))
}
RegisterUnifiedPushUseCase.RegisterUnifiedPushResult.Success -> {
Timber.d("registerUnifiedPush $distributor success")
ensureFcmTokenIsRetrievedUseCase.execute(pushersManager, registerPusher = vectorPreferences.areNotificationEnabledForDevice())
}
}
}
}
private fun observeReleaseNotes() = withState { state -> 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 +160,6 @@ class HomeActivityViewModel @AssistedInject constructor(
} }
} }
fun shouldAddHttpPusher() = if (vectorPreferences.areNotificationEnabledForDevice()) {
val currentSession = activeSessionHolder.getActiveSession()
val currentPushers = currentSession.pushersService().getPushers()
currentPushers.none { it.deviceId == currentSession.sessionParams.deviceId }
} else {
false
}
fun observeLocalNotificationsSilenced() {
val currentSession = activeSessionHolder.getActiveSession()
val deviceId = currentSession.cryptoService().getMyDevice().deviceId
viewModelScope.launch {
currentSession.accountDataService()
.getLiveUserAccountDataEvent(UserAccountDataTypes.TYPE_LOCAL_NOTIFICATION_SETTINGS + deviceId)
.asFlow()
.map { it.getOrNull()?.content?.toModel<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 +495,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

@ -18,6 +18,7 @@ package im.vector.app.features.settings.notifications
import androidx.fragment.app.FragmentActivity 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.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
@ -32,11 +33,12 @@ class EnableNotificationsForCurrentSessionUseCase @Inject constructor(
private val activeSessionHolder: ActiveSessionHolder, private val activeSessionHolder: ActiveSessionHolder,
private val unifiedPushHelper: UnifiedPushHelper, private val unifiedPushHelper: UnifiedPushHelper,
private val pushersManager: PushersManager, private val pushersManager: PushersManager,
private val fcmHelper: FcmHelper,
private val checkIfCanTogglePushNotificationsViaPusherUseCase: CheckIfCanTogglePushNotificationsViaPusherUseCase, private val checkIfCanTogglePushNotificationsViaPusherUseCase: CheckIfCanTogglePushNotificationsViaPusherUseCase,
private val togglePushNotificationUseCase: TogglePushNotificationUseCase, private val togglePushNotificationUseCase: TogglePushNotificationUseCase,
private val ensureFcmTokenIsRetrievedUseCase: EnsureFcmTokenIsRetrievedUseCase,
) { ) {
// TODO update unit tests
suspend fun execute(fragmentActivity: FragmentActivity) { suspend fun execute(fragmentActivity: FragmentActivity) {
val pusherForCurrentSession = pushersManager.getPusherForCurrentSession() val pusherForCurrentSession = pushersManager.getPusherForCurrentSession()
if (pusherForCurrentSession == null) { if (pusherForCurrentSession == null) {
@ -54,13 +56,7 @@ class EnableNotificationsForCurrentSessionUseCase @Inject constructor(
suspendCoroutine { continuation -> suspendCoroutine { continuation ->
try { try {
unifiedPushHelper.register(fragmentActivity) { unifiedPushHelper.register(fragmentActivity) {
if (unifiedPushHelper.isEmbeddedDistributor()) { ensureFcmTokenIsRetrievedUseCase.execute(pushersManager, registerPusher = true)
fcmHelper.ensureFcmTokenIsRetrieved(
fragmentActivity,
pushersManager,
registerPusher = true
)
}
continuation.resume(Unit) continuation.resume(Unit)
} }
} catch (error: Exception) { } catch (error: Exception) {

View File

@ -37,6 +37,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
@ -82,6 +83,7 @@ class VectorSettingsNotificationPreferenceFragment :
@Inject lateinit var notificationPermissionManager: NotificationPermissionManager @Inject lateinit var notificationPermissionManager: NotificationPermissionManager
@Inject lateinit var disableNotificationsForCurrentSessionUseCase: DisableNotificationsForCurrentSessionUseCase @Inject lateinit var disableNotificationsForCurrentSessionUseCase: DisableNotificationsForCurrentSessionUseCase
@Inject lateinit var enableNotificationsForCurrentSessionUseCase: EnableNotificationsForCurrentSessionUseCase @Inject lateinit var enableNotificationsForCurrentSessionUseCase: EnableNotificationsForCurrentSessionUseCase
@Inject lateinit var ensureFcmTokenIsRetrievedUseCase: EnsureFcmTokenIsRetrievedUseCase
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
@ -183,13 +185,7 @@ class VectorSettingsNotificationPreferenceFragment :
it.summary = unifiedPushHelper.getCurrentDistributorName() it.summary = unifiedPushHelper.getCurrentDistributorName()
it.onPreferenceClickListener = Preference.OnPreferenceClickListener { it.onPreferenceClickListener = Preference.OnPreferenceClickListener {
unifiedPushHelper.forceRegister(requireActivity(), pushersManager) { unifiedPushHelper.forceRegister(requireActivity(), pushersManager) {
if (unifiedPushHelper.isEmbeddedDistributor()) { ensureFcmTokenIsRetrievedUseCase.execute(pushersManager, registerPusher = vectorPreferences.areNotificationEnabledForDevice())
fcmHelper.ensureFcmTokenIsRetrieved(
requireActivity(),
pushersManager,
vectorPreferences.areNotificationEnabledForDevice()
)
}
it.summary = unifiedPushHelper.getCurrentDistributorName() it.summary = unifiedPushHelper.getCurrentDistributorName()
session.pushersService().refreshPushers() session.pushersService().refreshPushers()
refreshBackgroundSyncPrefs() refreshBackgroundSyncPrefs()