From 4a723c54c86bc2b64edbc86b787f1a0f148d2936 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Saleniuk?= <30429749+saleniuk@users.noreply.github.com> Date: Fri, 12 Jan 2024 11:53:27 +0100 Subject: [PATCH] fix: missing ServerConfig crashes after session expired / logout [WPB-5960] (#2570) --- .../wire/android/GlobalObserversManager.kt | 25 ++++++ .../com/wire/android/di/CoreLogicModule.kt | 6 ++ .../android/feature/AccountSwitchUseCase.kt | 25 ++++-- .../com/wire/android/ui/WireActivity.kt | 11 +-- .../wire/android/ui/WireActivityDialogs.kt | 2 + .../wire/android/ui/WireActivityViewModel.kt | 21 +++-- .../appLock/set/SetLockScreenViewModel.kt | 2 +- .../sync/FeatureFlagNotificationViewModel.kt | 87 ++++++++++--------- .../android/ui/sharing/ImportMediaScreen.kt | 2 - .../self/SelfUserProfileViewModel.kt | 2 +- .../android/GlobalObserversManagerTest.kt | 7 +- .../android/ui/WireActivityViewModelTest.kt | 22 ++++- .../FeatureFlagNotificationViewModelTest.kt | 46 ++++------ kalium | 2 +- 14 files changed, 160 insertions(+), 100 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/GlobalObserversManager.kt b/app/src/main/kotlin/com/wire/android/GlobalObserversManager.kt index 8da0269e3a..00956ef2d1 100644 --- a/app/src/main/kotlin/com/wire/android/GlobalObserversManager.kt +++ b/app/src/main/kotlin/com/wire/android/GlobalObserversManager.kt @@ -20,18 +20,25 @@ package com.wire.android +import com.wire.android.datastore.UserDataStoreProvider import com.wire.android.di.KaliumCoreLogic import com.wire.android.notification.NotificationChannelsManager import com.wire.android.notification.WireNotificationManager import com.wire.android.util.dispatchers.DispatcherProvider import com.wire.kalium.logic.CoreLogic +import com.wire.kalium.logic.data.logout.LogoutReason +import com.wire.kalium.logic.data.user.UserId +import com.wire.kalium.logic.feature.auth.LogoutCallback import com.wire.kalium.logic.feature.user.webSocketStatus.ObservePersistentWebSocketConnectionStatusUseCase import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.launch import javax.inject.Inject import javax.inject.Singleton @@ -46,6 +53,7 @@ class GlobalObserversManager @Inject constructor( @KaliumCoreLogic private val coreLogic: CoreLogic, private val notificationManager: WireNotificationManager, private val notificationChannelsManager: NotificationChannelsManager, + private val userDataStoreProvider: UserDataStoreProvider, ) { private val scope = CoroutineScope(SupervisorJob() + dispatcherProvider.io()) @@ -58,6 +66,7 @@ class GlobalObserversManager @Inject constructor( } } } + scope.handleLogouts() } private suspend fun setUpNotifications() { @@ -91,4 +100,20 @@ class GlobalObserversManager @Inject constructor( // but we can't start PersistentWebSocketService here, to avoid ForegroundServiceStartNotAllowedException } } + + private fun CoroutineScope.handleLogouts() { + callbackFlow { + val callback: LogoutCallback = object : LogoutCallback { + override suspend fun invoke(userId: UserId, reason: LogoutReason) { + notificationManager.stopObservingOnLogout(userId) + notificationChannelsManager.deleteChannelGroup(userId) + if (reason != LogoutReason.SELF_SOFT_LOGOUT) { + userDataStoreProvider.getOrCreate(userId).clear() + } + } + } + coreLogic.getGlobalScope().logoutCallbackManager.register(callback) + awaitClose { coreLogic.getGlobalScope().logoutCallbackManager.unregister(callback) } + }.launchIn(this) + } } diff --git a/app/src/main/kotlin/com/wire/android/di/CoreLogicModule.kt b/app/src/main/kotlin/com/wire/android/di/CoreLogicModule.kt index 0835d6ad8f..454d5b57eb 100644 --- a/app/src/main/kotlin/com/wire/android/di/CoreLogicModule.kt +++ b/app/src/main/kotlin/com/wire/android/di/CoreLogicModule.kt @@ -41,6 +41,7 @@ import com.wire.kalium.logic.feature.selfDeletingMessages.ObserveTeamSettingsSel import com.wire.kalium.logic.feature.selfDeletingMessages.PersistNewSelfDeletionTimerUseCase import com.wire.kalium.logic.feature.server.ServerConfigForAccountUseCase import com.wire.kalium.logic.feature.session.CurrentSessionResult +import com.wire.kalium.logic.feature.session.DoesValidSessionExistUseCase import com.wire.kalium.logic.feature.session.GetSessionsUseCase import com.wire.kalium.logic.feature.session.UpdateCurrentSessionUseCase import com.wire.kalium.logic.feature.user.MarkFileSharingChangeAsNotifiedUseCase @@ -310,6 +311,11 @@ class UseCaseModule { fun provideObserveValidAccountsUseCase(@KaliumCoreLogic coreLogic: CoreLogic): ObserveValidAccountsUseCase = coreLogic.getGlobalScope().observeValidAccounts + @ViewModelScoped + @Provides + fun provideDoesValidSessionExistsUseCase(@KaliumCoreLogic coreLogic: CoreLogic): DoesValidSessionExistUseCase = + coreLogic.getGlobalScope().doesValidSessionExist + @ViewModelScoped @Provides fun observeSecurityClassificationLabelUseCase( diff --git a/app/src/main/kotlin/com/wire/android/feature/AccountSwitchUseCase.kt b/app/src/main/kotlin/com/wire/android/feature/AccountSwitchUseCase.kt index 9fe12938ed..c539a61134 100644 --- a/app/src/main/kotlin/com/wire/android/feature/AccountSwitchUseCase.kt +++ b/app/src/main/kotlin/com/wire/android/feature/AccountSwitchUseCase.kt @@ -20,6 +20,7 @@ package com.wire.android.feature +import com.wire.android.appLogger import com.wire.android.di.ApplicationScope import com.wire.android.di.AuthServerConfigProvider import com.wire.android.navigation.BackStackMode @@ -40,6 +41,8 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.async import kotlinx.coroutines.launch import kotlinx.coroutines.withTimeout +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json import javax.inject.Inject import javax.inject.Singleton @@ -64,11 +67,12 @@ class AccountSwitchUseCase @Inject constructor( } suspend operator fun invoke(params: SwitchAccountParam): SwitchAccountResult { - val current = currentAccount + val current = currentAccount.await() + appLogger.i("$TAG Switching account invoked: ${params.toLogString()}, current account: ${current?.userId?.toLogString() ?: "-"}") return when (params) { - is SwitchAccountParam.SwitchToAccount -> switch(params.userId, current.await()) - SwitchAccountParam.TryToSwitchToNextAccount -> getNextAccountIfPossibleAndSwitch(current.await()) - SwitchAccountParam.Clear -> switch(null, current.await()) + is SwitchAccountParam.SwitchToAccount -> switch(params.userId, current) + SwitchAccountParam.TryToSwitchToNextAccount -> getNextAccountIfPossibleAndSwitch(current) + SwitchAccountParam.Clear -> switch(null, current) } } @@ -83,6 +87,8 @@ class AccountSwitchUseCase @Inject constructor( }?.userId } } + if (nextSessionId == null) appLogger.i("$TAG No next account to switch to") + else appLogger.i("$TAG Switching to next account: ${nextSessionId.toLogString()}") return switch(nextSessionId, current) } @@ -104,6 +110,7 @@ class AccountSwitchUseCase @Inject constructor( } private suspend fun updateAuthServer(current: UserId) { + appLogger.i("$TAG Updating auth server config for account: ${current.toLogString()}") serverConfigForAccountUseCase(current).let { when (it) { is ServerConfigForAccountUseCase.Result.Success -> authServerConfigProvider.updateAuthServer(it.config) @@ -126,6 +133,7 @@ class AccountSwitchUseCase @Inject constructor( } private suspend fun handleInvalidSession(invalidAccount: AccountInfo.Invalid) { + appLogger.i("$TAG Handling invalid account: ${invalidAccount.userId.toLogString()}") when (invalidAccount.logoutReason) { LogoutReason.SELF_SOFT_LOGOUT, LogoutReason.SELF_HARD_LOGOUT -> { deleteSession(invalidAccount.userId) @@ -137,14 +145,21 @@ class AccountSwitchUseCase @Inject constructor( } private companion object { + const val TAG = "AccountSwitch" const val DELETE_USER_SESSION_TIMEOUT = 3000L } } sealed class SwitchAccountParam { - object TryToSwitchToNextAccount : SwitchAccountParam() + data object TryToSwitchToNextAccount : SwitchAccountParam() data class SwitchToAccount(val userId: UserId) : SwitchAccountParam() data object Clear : SwitchAccountParam() + private fun toLogMap(): Map = when (this) { + is Clear -> mutableMapOf("value" to "CLEAR") + is SwitchToAccount -> mutableMapOf("value" to "SWITCH_TO_ACCOUNT", "userId" to userId.toLogString()) + is TryToSwitchToNextAccount -> mutableMapOf("value" to "TRY_TO_SWITCH_TO_NEXT_ACCOUNT") + } + fun toLogString(): String = Json.encodeToString(toLogMap()) } sealed class SwitchAccountResult { diff --git a/app/src/main/kotlin/com/wire/android/ui/WireActivity.kt b/app/src/main/kotlin/com/wire/android/ui/WireActivity.kt index 84428d9c4d..464cdee367 100644 --- a/app/src/main/kotlin/com/wire/android/ui/WireActivity.kt +++ b/app/src/main/kotlin/com/wire/android/ui/WireActivity.kt @@ -90,7 +90,6 @@ import com.wire.android.util.debug.FeatureVisibilityFlags import com.wire.android.util.debug.LocalFeatureVisibilityFlags import com.wire.android.util.deeplink.DeepLinkResult import com.wire.android.util.ui.updateScreenSettings -import com.wire.kalium.logic.data.user.UserId import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.collectLatest @@ -196,10 +195,7 @@ class WireActivity : AppCompatActivity() { // and if any NavigationCommand is executed before the graph is fully built, it will cause a NullPointerException. setUpNavigation(navigator.navController, onComplete) handleScreenshotCensoring() - handleDialogs( - navigator::navigate, - viewModel.currentUserId.value - ) + handleDialogs(navigator::navigate) } } } @@ -253,10 +249,7 @@ class WireActivity : AppCompatActivity() { } @Composable - private fun handleDialogs(navigate: (NavigationCommand) -> Unit, userId: UserId?) { - LaunchedEffect(userId) { - featureFlagNotificationViewModel.loadInitialSync() - } + private fun handleDialogs(navigate: (NavigationCommand) -> Unit) { with(featureFlagNotificationViewModel.featureFlagState) { if (shouldShowTeamAppLockDialog) { TeamAppLockFeatureFlagDialog( diff --git a/app/src/main/kotlin/com/wire/android/ui/WireActivityDialogs.kt b/app/src/main/kotlin/com/wire/android/ui/WireActivityDialogs.kt index a1c9c24773..a2f333d156 100644 --- a/app/src/main/kotlin/com/wire/android/ui/WireActivityDialogs.kt +++ b/app/src/main/kotlin/com/wire/android/ui/WireActivityDialogs.kt @@ -26,6 +26,7 @@ import androidx.annotation.StringRes import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.res.stringResource +import androidx.compose.ui.window.DialogProperties import com.wire.android.BuildConfig import com.wire.android.R import com.wire.android.appLogger @@ -296,6 +297,7 @@ private fun accountLoggedOutDialog(reason: CurrentSessionErrorState, navigateAwa title = stringResource(id = title), text = text, onDismiss = remember { { } }, + properties = DialogProperties(dismissOnBackPress = false, dismissOnClickOutside = false, usePlatformDefaultWidth = false), optionButton1Properties = WireDialogButtonProperties( text = stringResource(R.string.label_ok), onClick = navigateAway, diff --git a/app/src/main/kotlin/com/wire/android/ui/WireActivityViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/WireActivityViewModel.kt index eed6339bcf..4a11ca6119 100644 --- a/app/src/main/kotlin/com/wire/android/ui/WireActivityViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/WireActivityViewModel.kt @@ -21,7 +21,6 @@ package com.wire.android.ui import android.content.Intent -import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue @@ -66,6 +65,8 @@ import com.wire.kalium.logic.feature.server.GetServerConfigResult import com.wire.kalium.logic.feature.server.GetServerConfigUseCase import com.wire.kalium.logic.feature.session.CurrentSessionFlowUseCase import com.wire.kalium.logic.feature.session.CurrentSessionResult +import com.wire.kalium.logic.feature.session.DoesValidSessionExistResult +import com.wire.kalium.logic.feature.session.DoesValidSessionExistUseCase import com.wire.kalium.logic.feature.session.GetAllSessionsResult import com.wire.kalium.logic.feature.session.GetSessionsUseCase import com.wire.kalium.logic.feature.user.screenshotCensoring.ObserveScreenshotCensoringConfigResult @@ -96,6 +97,7 @@ class WireActivityViewModel @Inject constructor( @KaliumCoreLogic private val coreLogic: CoreLogic, private val dispatchers: DispatcherProvider, private val currentSessionFlow: CurrentSessionFlowUseCase, + private val doesValidSessionExist: DoesValidSessionExistUseCase, private val getServerConfigUseCase: GetServerConfigUseCase, private val deepLinkProcessor: DeepLinkProcessor, private val authServerConfigProvider: AuthServerConfigProvider, @@ -116,12 +118,12 @@ class WireActivityViewModel @Inject constructor( private set private val observeUserId = currentSessionFlow() + .distinctUntilChanged() .onEach { if (it is CurrentSessionResult.Success) { if (it.accountInfo.isValid().not()) { handleInvalidSession((it.accountInfo as AccountInfo.Invalid).logoutReason) } - currentUserId.value = it.accountInfo.userId } } .map { result -> @@ -134,13 +136,14 @@ class WireActivityViewModel @Inject constructor( } else { null } - }.distinctUntilChanged().flowOn(dispatchers.io()).shareIn(viewModelScope, SharingStarted.WhileSubscribed(), 1) + } + .distinctUntilChanged() + .flowOn(dispatchers.io()) + .shareIn(viewModelScope, SharingStarted.WhileSubscribed(), 1) private val _observeSyncFlowState: MutableStateFlow = MutableStateFlow(null) val observeSyncFlowState: StateFlow = _observeSyncFlowState - val currentUserId: MutableState = mutableStateOf(null) - init { observeSyncState() observeUpdateAppState() @@ -290,7 +293,13 @@ class WireActivityViewModel @Inject constructor( fun dismissNewClientsDialog(userId: UserId) { globalAppState = globalAppState.copy(newClientDialog = null) - viewModelScope.launch { clearNewClientsForUser(userId) } + viewModelScope.launch { + doesValidSessionExist(userId).let { + if (it is DoesValidSessionExistResult.Success && it.doesValidSessionExist) { + clearNewClientsForUser(userId) + } + } + } } fun switchAccount(userId: UserId, actions: SwitchAccountActions, onComplete: () -> Unit) { diff --git a/app/src/main/kotlin/com/wire/android/ui/home/appLock/set/SetLockScreenViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/home/appLock/set/SetLockScreenViewModel.kt index 66c1a55dd4..e6ca58aa42 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/appLock/set/SetLockScreenViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/appLock/set/SetLockScreenViewModel.kt @@ -56,7 +56,7 @@ class SetLockScreenViewModel @Inject constructor( observeAppLockConfig(), observeIsAppLockEditable() ) { config, isEditable -> - SetLockCodeViewState( + state.copy( timeout = config.timeout, isEditable = isEditable ) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/sync/FeatureFlagNotificationViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/home/sync/FeatureFlagNotificationViewModel.kt index 82d481e547..c096d3e244 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/sync/FeatureFlagNotificationViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/sync/FeatureFlagNotificationViewModel.kt @@ -38,10 +38,11 @@ import com.wire.kalium.logic.configuration.FileSharingStatus import com.wire.kalium.logic.data.message.TeamSelfDeleteTimer import com.wire.kalium.logic.data.sync.SyncState import com.wire.kalium.logic.data.user.UserId +import com.wire.kalium.logic.feature.session.CurrentSessionFlowUseCase import com.wire.kalium.logic.feature.session.CurrentSessionResult -import com.wire.kalium.logic.feature.session.CurrentSessionUseCase import com.wire.kalium.logic.feature.user.E2EIRequiredResult import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.firstOrNull @@ -52,7 +53,7 @@ import javax.inject.Inject @HiltViewModel class FeatureFlagNotificationViewModel @Inject constructor( @KaliumCoreLogic private val coreLogic: CoreLogic, - private val currentSessionUseCase: CurrentSessionUseCase, + private val currentSessionFlow: CurrentSessionFlowUseCase, private val globalDataStore: GlobalDataStore, private val disableAppLockUseCase: DisableAppLockUseCase ) : ViewModel() { @@ -62,6 +63,10 @@ class FeatureFlagNotificationViewModel @Inject constructor( private var currentUserId by mutableStateOf(null) + init { + viewModelScope.launch { initialSync() } + } + /** * The FeatureFlagNotificationViewModel is an attempt to encapsulate the logic regarding the different user feature flags, like for * example the file sharing one. This means that this VM could be invoked as an extension from outside the general app lifecycle (for @@ -71,37 +76,44 @@ class FeatureFlagNotificationViewModel @Inject constructor( * it until the sync state is live. Once the sync state is live, it sets whether the file sharing feature is enabled or not on the VM * state. */ - fun loadInitialSync() { - viewModelScope.launch { initialSync() } - } - - suspend fun initialSync() { - currentSessionUseCase().let { currentSessionResult -> - when (currentSessionResult) { - is CurrentSessionResult.Failure -> { - appLogger.e("Failure while getting current session from FeatureFlagNotificationViewModel") - featureFlagState = - featureFlagState.copy(fileSharingRestrictedState = FeatureFlagState.SharingRestrictedState.NO_USER) - } + private suspend fun initialSync() { + currentSessionFlow() + .distinctUntilChanged() + .collectLatest { currentSessionResult -> + when (currentSessionResult) { + is CurrentSessionResult.Failure -> { + currentUserId = null + appLogger.e("Failure while getting current session from FeatureFlagNotificationViewModel") + featureFlagState = FeatureFlagState( // no session, clear feature flag state to default and set NO_USER + fileSharingRestrictedState = FeatureFlagState.SharingRestrictedState.NO_USER + ) + } - is CurrentSessionResult.Success -> { - val userId = currentSessionResult.accountInfo.userId - coreLogic.getSessionScope(userId).observeSyncState() - .firstOrNull { it == SyncState.Live }?.let { - currentUserId = userId - setFileSharingState(userId) - observeTeamSettingsSelfDeletionStatus(userId) - setGuestRoomLinkFeatureFlag(userId) - setE2EIRequiredState(userId) - setTeamAppLockFeatureFlag(userId) - observeCallEndedBecauseOfConversationDegraded(userId) - } + is CurrentSessionResult.Success -> { + featureFlagState = FeatureFlagState() // new session, clear feature flag state to default and wait until synced + val userId = currentSessionResult.accountInfo.userId + currentUserId = userId + coreLogic.getSessionScope(userId).observeSyncState() + .firstOrNull { it == SyncState.Live }?.let { + observeStatesAfterInitialSync(userId) + } + } } } + } + + private suspend fun observeStatesAfterInitialSync(userId: UserId) { + coroutineScope { + launch { setFileSharingState(userId) } + launch { observeTeamSettingsSelfDeletionStatus(userId) } + launch { setGuestRoomLinkFeatureFlag(userId) } + launch { setE2EIRequiredState(userId) } + launch { setTeamAppLockFeatureFlag(userId) } + launch { observeCallEndedBecauseOfConversationDegraded(userId) } } } - private fun setFileSharingState(userId: UserId) = viewModelScope.launch { + private suspend fun setFileSharingState(userId: UserId) { coreLogic.getSessionScope(userId).observeFileSharingStatus().collect { fileSharingStatus -> fileSharingStatus.state?.let { // TODO: handle restriction when sending assets @@ -122,8 +134,7 @@ class FeatureFlagNotificationViewModel @Inject constructor( } } - private fun setGuestRoomLinkFeatureFlag(userId: UserId) { - viewModelScope.launch { + private suspend fun setGuestRoomLinkFeatureFlag(userId: UserId) { coreLogic.getSessionScope(userId).observeGuestRoomLinkFeatureFlag() .collect { guestRoomLinkStatus -> guestRoomLinkStatus.isGuestRoomLinkEnabled?.let { @@ -134,10 +145,8 @@ class FeatureFlagNotificationViewModel @Inject constructor( } } } - } - private fun setTeamAppLockFeatureFlag(userId: UserId) { - viewModelScope.launch { + private suspend fun setTeamAppLockFeatureFlag(userId: UserId) { coreLogic.getSessionScope(userId).appLockTeamFeatureConfigObserver() .distinctUntilChanged() .collectLatest { appLockConfig -> @@ -155,10 +164,8 @@ class FeatureFlagNotificationViewModel @Inject constructor( } } } - } - private fun observeTeamSettingsSelfDeletionStatus(userId: UserId) { - viewModelScope.launch { + private suspend fun observeTeamSettingsSelfDeletionStatus(userId: UserId) { coreLogic.getSessionScope(userId).observeTeamSettingsSelfDeletionStatus() .collect { teamSettingsSelfDeletingStatus -> val areSelfDeletedMessagesEnabled = @@ -181,9 +188,8 @@ class FeatureFlagNotificationViewModel @Inject constructor( ) } } - } - private fun setE2EIRequiredState(userId: UserId) = viewModelScope.launch { + private suspend fun setE2EIRequiredState(userId: UserId) { coreLogic.getSessionScope(userId).observeE2EIRequired().collect { result -> val state = when (result) { E2EIRequiredResult.NoGracePeriod.Create -> FeatureFlagState.E2EIRequired.NoGracePeriod.Create @@ -239,11 +245,8 @@ class FeatureFlagNotificationViewModel @Inject constructor( fun markTeamAppLockStatusAsNot() { viewModelScope.launch { - val currentSession = currentSessionUseCase() - if (currentSessionUseCase() is CurrentSessionResult.Success) { - coreLogic.getSessionScope( - (currentSession as CurrentSessionResult.Success).accountInfo.userId - ).markTeamAppLockStatusAsNotified() + currentUserId?.let { + coreLogic.getSessionScope(it).markTeamAppLockStatusAsNotified() } } } diff --git a/app/src/main/kotlin/com/wire/android/ui/sharing/ImportMediaScreen.kt b/app/src/main/kotlin/com/wire/android/ui/sharing/ImportMediaScreen.kt index a609edb498..e7980539ba 100644 --- a/app/src/main/kotlin/com/wire/android/ui/sharing/ImportMediaScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/sharing/ImportMediaScreen.kt @@ -81,8 +81,6 @@ fun ImportMediaScreen( navigator: Navigator, featureFlagNotificationViewModel: FeatureFlagNotificationViewModel = hiltViewModel() ) { - featureFlagNotificationViewModel.loadInitialSync() - when (val fileSharingRestrictedState = featureFlagNotificationViewModel.featureFlagState.fileSharingRestrictedState) { FeatureFlagState.SharingRestrictedState.NO_USER -> { diff --git a/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModel.kt index 8eba4d42aa..f8432fef96 100644 --- a/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModel.kt @@ -209,7 +209,7 @@ class SelfUserProfileViewModel @Inject constructor( }.join() val logoutReason = if (wipeData) LogoutReason.SELF_HARD_LOGOUT else LogoutReason.SELF_SOFT_LOGOUT - logout(logoutReason) + logout(logoutReason, waitUntilCompletes = true) if (wipeData) { // TODO this should be moved to some service that will clear all the data in the app dataStore.clear() diff --git a/app/src/test/kotlin/com/wire/android/GlobalObserversManagerTest.kt b/app/src/test/kotlin/com/wire/android/GlobalObserversManagerTest.kt index 0a0d284ec9..20571a4d81 100644 --- a/app/src/test/kotlin/com/wire/android/GlobalObserversManagerTest.kt +++ b/app/src/test/kotlin/com/wire/android/GlobalObserversManagerTest.kt @@ -3,6 +3,7 @@ package com.wire.android import com.wire.android.config.CoroutineTestExtension import com.wire.android.config.TestDispatcherProvider import com.wire.android.config.mockUri +import com.wire.android.datastore.UserDataStoreProvider import com.wire.android.framework.TestUser import com.wire.android.notification.NotificationChannelsManager import com.wire.android.notification.WireNotificationManager @@ -80,12 +81,16 @@ class GlobalObserversManagerTest { @MockK lateinit var notificationManager: WireNotificationManager + @MockK + lateinit var userDataStoreProvider: UserDataStoreProvider + private val manager by lazy { GlobalObserversManager( dispatcherProvider = TestDispatcherProvider(), coreLogic = coreLogic, notificationChannelsManager = notificationChannelsManager, - notificationManager = notificationManager + notificationManager = notificationManager, + userDataStoreProvider = userDataStoreProvider, ) } diff --git a/app/src/test/kotlin/com/wire/android/ui/WireActivityViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/WireActivityViewModelTest.kt index 3cb4ac1ff9..0249183c8e 100644 --- a/app/src/test/kotlin/com/wire/android/ui/WireActivityViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/WireActivityViewModelTest.kt @@ -57,6 +57,8 @@ import com.wire.kalium.logic.feature.server.GetServerConfigResult import com.wire.kalium.logic.feature.server.GetServerConfigUseCase import com.wire.kalium.logic.feature.session.CurrentSessionFlowUseCase import com.wire.kalium.logic.feature.session.CurrentSessionResult +import com.wire.kalium.logic.feature.session.DoesValidSessionExistResult +import com.wire.kalium.logic.feature.session.DoesValidSessionExistUseCase import com.wire.kalium.logic.feature.session.GetSessionsUseCase import com.wire.kalium.logic.feature.user.screenshotCensoring.ObserveScreenshotCensoringConfigResult import com.wire.kalium.logic.feature.user.screenshotCensoring.ObserveScreenshotCensoringConfigUseCase @@ -517,8 +519,9 @@ class WireActivityViewModelTest { } @Test - fun `when dismissNewClientsDialog is called, then cleared NewClients for user`() = runTest { + fun `given session exists, when dismissNewClientsDialog is called, then cleared NewClients for user`() = runTest { val (arrangement, viewModel) = Arrangement() + .withSomeCurrentSession() .arrange() viewModel.dismissNewClientsDialog(USER_ID) @@ -526,6 +529,17 @@ class WireActivityViewModelTest { coVerify(exactly = 1) { arrangement.clearNewClientsForUser(USER_ID) } } + @Test + fun `given session does not exist, when dismissNewClientsDialog is called, then do nothing`() = runTest { + val (arrangement, viewModel) = Arrangement() + .withNoCurrentSession() + .arrange() + + viewModel.dismissNewClientsDialog(USER_ID) + + coVerify(exactly = 0) { arrangement.clearNewClientsForUser(USER_ID) } + } + @Test fun `given session and screenshot censoring disabled, when observing it, then set state to false`() = runTest { val (_, viewModel) = Arrangement() @@ -601,6 +615,9 @@ class WireActivityViewModelTest { @MockK lateinit var currentSessionFlow: CurrentSessionFlowUseCase + @MockK + lateinit var doesValidSessionExist: DoesValidSessionExistUseCase + @MockK lateinit var getServerConfigUseCase: GetServerConfigUseCase @@ -662,6 +679,7 @@ class WireActivityViewModelTest { coreLogic = coreLogic, dispatchers = TestDispatcherProvider(), currentSessionFlow = currentSessionFlow, + doesValidSessionExist = doesValidSessionExist, getServerConfigUseCase = getServerConfigUseCase, deepLinkProcessor = deepLinkProcessor, authServerConfigProvider = authServerConfigProvider, @@ -682,11 +700,13 @@ class WireActivityViewModelTest { fun withSomeCurrentSession(): Arrangement = apply { coEvery { currentSessionFlow() } returns flowOf(CurrentSessionResult.Success(TEST_ACCOUNT_INFO)) coEvery { coreLogic.getGlobalScope().session.currentSession() } returns CurrentSessionResult.Success(TEST_ACCOUNT_INFO) + coEvery { doesValidSessionExist(any()) } returns DoesValidSessionExistResult.Success(true) } fun withNoCurrentSession(): Arrangement { coEvery { currentSessionFlow() } returns flowOf(CurrentSessionResult.Failure.SessionNotFound) coEvery { coreLogic.getGlobalScope().session.currentSession() } returns CurrentSessionResult.Failure.SessionNotFound + coEvery { doesValidSessionExist(any()) } returns DoesValidSessionExistResult.Success(false) return this } diff --git a/app/src/test/kotlin/com/wire/android/ui/home/sync/FeatureFlagNotificationViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/home/sync/FeatureFlagNotificationViewModelTest.kt index 9251116f8b..1257672329 100644 --- a/app/src/test/kotlin/com/wire/android/ui/home/sync/FeatureFlagNotificationViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/home/sync/FeatureFlagNotificationViewModelTest.kt @@ -14,8 +14,8 @@ import com.wire.kalium.logic.data.auth.AccountInfo import com.wire.kalium.logic.data.sync.SyncState import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.logic.feature.applock.AppLockTeamFeatureConfigObserver +import com.wire.kalium.logic.feature.session.CurrentSessionFlowUseCase import com.wire.kalium.logic.feature.session.CurrentSessionResult -import com.wire.kalium.logic.feature.session.CurrentSessionUseCase import com.wire.kalium.logic.feature.user.E2EIRequiredResult import com.wire.kalium.logic.feature.user.MarkEnablingE2EIAsNotifiedUseCase import com.wire.kalium.logic.feature.user.MarkSelfDeletionStatusAsNotifiedUseCase @@ -47,7 +47,6 @@ class FeatureFlagNotificationViewModelTest { val (_, viewModel) = Arrangement() .withCurrentSessions(CurrentSessionResult.Failure.SessionNotFound) .arrange() - viewModel.initialSync() advanceUntilIdle() assertEquals( @@ -62,7 +61,6 @@ class FeatureFlagNotificationViewModelTest { .withCurrentSessions(CurrentSessionResult.Success(AccountInfo.Valid(TestUser.USER_ID))) .withFileSharingStatus(flowOf(FileSharingStatus(FileSharingStatus.Value.Disabled, false))) .arrange() - viewModel.initialSync() advanceUntilIdle() assertEquals( @@ -77,7 +75,6 @@ class FeatureFlagNotificationViewModelTest { .withCurrentSessions(CurrentSessionResult.Success(AccountInfo.Valid(UserId("value", "domain")))) .withGuestRoomLinkFeatureFlag(flowOf(GuestRoomLinkStatus(true, false))) .arrange() - viewModel.initialSync() advanceUntilIdle() viewModel.dismissGuestRoomLinkDialog() advanceUntilIdle() @@ -95,7 +92,6 @@ class FeatureFlagNotificationViewModelTest { .withCurrentSessions(CurrentSessionResult.Success(AccountInfo.Valid(TestUser.USER_ID))) .withFileSharingStatus(flowOf(FileSharingStatus(FileSharingStatus.Value.EnabledAll, false))) .arrange() - viewModel.initialSync() advanceUntilIdle() assertEquals( @@ -109,7 +105,6 @@ class FeatureFlagNotificationViewModelTest { val (arrangement, viewModel) = Arrangement() .withCurrentSessions(CurrentSessionResult.Success(AccountInfo.Valid(UserId("value", "domain")))) .arrange() - viewModel.initialSync() advanceUntilIdle() viewModel.dismissSelfDeletingMessagesDialog() advanceUntilIdle() @@ -125,8 +120,6 @@ class FeatureFlagNotificationViewModelTest { .withIsAppLockSetup(false) .withTeamAppLockEnforce(AppLockTeamConfig(true, Duration.ZERO, false)) .arrange() - - viewModel.initialSync() advanceUntilIdle() assertTrue(viewModel.featureFlagState.shouldShowTeamAppLockDialog) @@ -137,7 +130,6 @@ class FeatureFlagNotificationViewModelTest { val (arrangement, viewModel) = Arrangement() .withE2EIRequiredSettings(E2EIRequiredResult.NoGracePeriod.Create) .arrange() - viewModel.initialSync() advanceUntilIdle() assertEquals(FeatureFlagState.E2EIRequired.NoGracePeriod.Create, viewModel.featureFlagState.e2EIRequired) @@ -149,7 +141,6 @@ class FeatureFlagNotificationViewModelTest { val (arrangement, viewModel) = Arrangement() .withE2EIRequiredSettings(E2EIRequiredResult.WithGracePeriod.Create(gracePeriod)) .arrange() - viewModel.initialSync() advanceUntilIdle() viewModel.snoozeE2EIdRequiredDialog(FeatureFlagState.E2EIRequired.WithGracePeriod.Create(gracePeriod)) @@ -179,7 +170,6 @@ class FeatureFlagNotificationViewModelTest { val (arrangement, viewModel) = Arrangement() .withE2EIRequiredSettings(E2EIRequiredResult.NoGracePeriod.Renew) .arrange() - viewModel.initialSync() advanceUntilIdle() assertEquals(FeatureFlagState.E2EIRequired.NoGracePeriod.Renew, viewModel.featureFlagState.e2EIRequired) @@ -191,7 +181,6 @@ class FeatureFlagNotificationViewModelTest { val (arrangement, viewModel) = Arrangement() .withE2EIRequiredSettings(E2EIRequiredResult.WithGracePeriod.Renew(gracePeriod)) .arrange() - viewModel.initialSync() advanceUntilIdle() viewModel.snoozeE2EIdRequiredDialog(FeatureFlagState.E2EIRequired.WithGracePeriod.Renew(gracePeriod)) @@ -221,8 +210,6 @@ class FeatureFlagNotificationViewModelTest { val (_, viewModel) = Arrangement() .withEndCallDialog() .arrange() - - viewModel.initialSync() advanceUntilIdle() assertEquals(true, viewModel.featureFlagState.showCallEndedBecauseOfConversationDegraded) @@ -235,7 +222,6 @@ class FeatureFlagNotificationViewModelTest { .withAppLockSource(AppLockSource.TeamEnforced) .withDisableAppLockUseCase() .arrange() - viewModel.initialSync() advanceUntilIdle() viewModel.confirmAppLockNotEnforced() @@ -250,7 +236,6 @@ class FeatureFlagNotificationViewModelTest { .withCurrentSessions(CurrentSessionResult.Success(AccountInfo.Valid(TestUser.USER_ID))) .withAppLockSource(AppLockSource.Manual) .arrange() - viewModel.initialSync() advanceUntilIdle() viewModel.confirmAppLockNotEnforced() @@ -260,15 +245,9 @@ class FeatureFlagNotificationViewModelTest { } private inner class Arrangement { - init { - MockKAnnotations.init(this, relaxUnitFun = true) - coEvery { currentSession() } returns CurrentSessionResult.Success(AccountInfo.Valid(TestUser.USER_ID)) - coEvery { coreLogic.getSessionScope(any()).observeSyncState() } returns flowOf(SyncState.Live) - coEvery { coreLogic.getSessionScope(any()).observeTeamSettingsSelfDeletionStatus() } returns flowOf() - } @MockK - lateinit var currentSession: CurrentSessionUseCase + lateinit var currentSession: CurrentSessionFlowUseCase @MockK lateinit var coreLogic: CoreLogic @@ -291,14 +270,19 @@ class FeatureFlagNotificationViewModelTest { @MockK lateinit var globalDataStore: GlobalDataStore - val viewModel: FeatureFlagNotificationViewModel = FeatureFlagNotificationViewModel( - coreLogic = coreLogic, - currentSessionUseCase = currentSession, - globalDataStore = globalDataStore, - disableAppLockUseCase = disableAppLockUseCase - ) - + val viewModel: FeatureFlagNotificationViewModel by lazy { + FeatureFlagNotificationViewModel( + coreLogic = coreLogic, + currentSessionFlow = currentSession, + globalDataStore = globalDataStore, + disableAppLockUseCase = disableAppLockUseCase + ) + } init { + MockKAnnotations.init(this, relaxUnitFun = true) + coEvery { currentSession() } returns flowOf(CurrentSessionResult.Success(AccountInfo.Valid(TestUser.USER_ID))) + coEvery { coreLogic.getSessionScope(any()).observeSyncState() } returns flowOf(SyncState.Live) + coEvery { coreLogic.getSessionScope(any()).observeTeamSettingsSelfDeletionStatus() } returns flowOf() every { coreLogic.getSessionScope(any()).markGuestLinkFeatureFlagAsNotChanged } returns markGuestLinkFeatureFlagAsNotChanged every { coreLogic.getSessionScope(any()).markSelfDeletingMessagesAsNotified } returns markSelfDeletingStatusAsNotified every { coreLogic.getSessionScope(any()).markE2EIRequiredAsNotified } returns markE2EIRequiredAsNotified @@ -311,7 +295,7 @@ class FeatureFlagNotificationViewModelTest { } fun withCurrentSessions(result: CurrentSessionResult) = apply { - coEvery { currentSession() } returns result + coEvery { currentSession() } returns flowOf(result) } fun withIsAppLockSetup(result: Boolean) = apply { diff --git a/kalium b/kalium index 09ad1bdf1e..55c72d2523 160000 --- a/kalium +++ b/kalium @@ -1 +1 @@ -Subproject commit 09ad1bdf1e3c6a550d2684069762bc4e4cd29f7d +Subproject commit 55c72d25232ba3e7d3306710460ab48141011f7f