From 29384596d4ab1a4e6162291b2856ee03bf3d5c84 Mon Sep 17 00:00:00 2001 From: David Perez Date: Wed, 6 Nov 2024 11:40:54 -0600 Subject: [PATCH] PM-14410: App restart timeout action (#4237) --- .../di/ActivityAccessibilityModule.kt | 6 +- .../AccessibilityActivityManagerImpl.kt | 6 +- .../autofill/di/ActivityAutofillModule.kt | 6 +- .../manager/AutofillActivityManagerImpl.kt | 6 +- .../platform/manager/AppForegroundManager.kt | 15 --- .../manager/AppForegroundManagerImpl.kt | 36 ----- .../data/platform/manager/AppStateManager.kt | 24 ++++ .../platform/manager/AppStateManagerImpl.kt | 72 ++++++++++ .../manager/di/PlatformManagerModule.kt | 13 +- .../manager/model/AppCreationState.kt | 16 +++ .../restriction/RestrictionManagerImpl.kt | 6 +- .../vault/manager/VaultLockManagerImpl.kt | 29 ++++- .../vault/manager/di/VaultManagerModule.kt | 6 +- .../AccessibilityActivityManagerTest.kt | 6 +- .../manager/AutofillActivityManagerTest.kt | 6 +- .../manager/AppForegroundManagerTest.kt | 44 ------- .../platform/manager/AppStateManagerTest.kt | 82 ++++++++++++ .../restriction/RestrictionManagerTest.kt | 30 ++--- ...roundManager.kt => FakeAppStateManager.kt} | 20 ++- .../vault/manager/VaultLockManagerTest.kt | 123 ++++++++++++++---- docs/ARCHITECTURE.md | 2 +- 21 files changed, 380 insertions(+), 174 deletions(-) delete mode 100644 app/src/main/java/com/x8bit/bitwarden/data/platform/manager/AppForegroundManager.kt delete mode 100644 app/src/main/java/com/x8bit/bitwarden/data/platform/manager/AppForegroundManagerImpl.kt create mode 100644 app/src/main/java/com/x8bit/bitwarden/data/platform/manager/AppStateManager.kt create mode 100644 app/src/main/java/com/x8bit/bitwarden/data/platform/manager/AppStateManagerImpl.kt create mode 100644 app/src/main/java/com/x8bit/bitwarden/data/platform/manager/model/AppCreationState.kt delete mode 100644 app/src/test/java/com/x8bit/bitwarden/data/platform/manager/AppForegroundManagerTest.kt create mode 100644 app/src/test/java/com/x8bit/bitwarden/data/platform/manager/AppStateManagerTest.kt rename app/src/test/java/com/x8bit/bitwarden/data/platform/manager/util/{FakeAppForegroundManager.kt => FakeAppStateManager.kt} (52%) diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/accessibility/di/ActivityAccessibilityModule.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/accessibility/di/ActivityAccessibilityModule.kt index 33ddff1f5..636a6d15d 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/autofill/accessibility/di/ActivityAccessibilityModule.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/accessibility/di/ActivityAccessibilityModule.kt @@ -5,7 +5,7 @@ import androidx.lifecycle.LifecycleCoroutineScope import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityActivityManager import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityActivityManagerImpl import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityEnabledManager -import com.x8bit.bitwarden.data.platform.manager.AppForegroundManager +import com.x8bit.bitwarden.data.platform.manager.AppStateManager import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -24,13 +24,13 @@ object ActivityAccessibilityModule { fun providesAccessibilityActivityManager( @ApplicationContext context: Context, accessibilityEnabledManager: AccessibilityEnabledManager, - appForegroundManager: AppForegroundManager, + appStateManager: AppStateManager, lifecycleScope: LifecycleCoroutineScope, ): AccessibilityActivityManager = AccessibilityActivityManagerImpl( context = context, accessibilityEnabledManager = accessibilityEnabledManager, - appForegroundManager = appForegroundManager, + appStateManager = appStateManager, lifecycleScope = lifecycleScope, ) } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/accessibility/manager/AccessibilityActivityManagerImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/accessibility/manager/AccessibilityActivityManagerImpl.kt index 62670454b..0777298b6 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/autofill/accessibility/manager/AccessibilityActivityManagerImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/accessibility/manager/AccessibilityActivityManagerImpl.kt @@ -3,7 +3,7 @@ package com.x8bit.bitwarden.data.autofill.accessibility.manager import android.content.Context import androidx.lifecycle.LifecycleCoroutineScope import com.x8bit.bitwarden.data.autofill.accessibility.util.isAccessibilityServiceEnabled -import com.x8bit.bitwarden.data.platform.manager.AppForegroundManager +import com.x8bit.bitwarden.data.platform.manager.AppStateManager import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -13,11 +13,11 @@ import kotlinx.coroutines.flow.onEach class AccessibilityActivityManagerImpl( private val context: Context, private val accessibilityEnabledManager: AccessibilityEnabledManager, - appForegroundManager: AppForegroundManager, + appStateManager: AppStateManager, lifecycleScope: LifecycleCoroutineScope, ) : AccessibilityActivityManager { init { - appForegroundManager + appStateManager .appForegroundStateFlow .onEach { accessibilityEnabledManager.isAccessibilityEnabled = diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/di/ActivityAutofillModule.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/di/ActivityAutofillModule.kt index e5d4cba3b..2a63df444 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/autofill/di/ActivityAutofillModule.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/di/ActivityAutofillModule.kt @@ -8,7 +8,7 @@ import androidx.lifecycle.lifecycleScope import com.x8bit.bitwarden.data.autofill.manager.AutofillActivityManager import com.x8bit.bitwarden.data.autofill.manager.AutofillActivityManagerImpl import com.x8bit.bitwarden.data.autofill.manager.AutofillEnabledManager -import com.x8bit.bitwarden.data.platform.manager.AppForegroundManager +import com.x8bit.bitwarden.data.platform.manager.AppStateManager import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -27,13 +27,13 @@ object ActivityAutofillModule { @Provides fun provideAutofillActivityManager( @ActivityScopedManager autofillManager: AutofillManager, - appForegroundManager: AppForegroundManager, + appStateManager: AppStateManager, autofillEnabledManager: AutofillEnabledManager, lifecycleScope: LifecycleCoroutineScope, ): AutofillActivityManager = AutofillActivityManagerImpl( autofillManager = autofillManager, - appForegroundManager = appForegroundManager, + appStateManager = appStateManager, autofillEnabledManager = autofillEnabledManager, lifecycleScope = lifecycleScope, ) diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/manager/AutofillActivityManagerImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/manager/AutofillActivityManagerImpl.kt index e01a7713d..154be4db9 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/autofill/manager/AutofillActivityManagerImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/manager/AutofillActivityManagerImpl.kt @@ -2,7 +2,7 @@ package com.x8bit.bitwarden.data.autofill.manager import android.view.autofill.AutofillManager import androidx.lifecycle.LifecycleCoroutineScope -import com.x8bit.bitwarden.data.platform.manager.AppForegroundManager +import com.x8bit.bitwarden.data.platform.manager.AppStateManager import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -12,7 +12,7 @@ import kotlinx.coroutines.flow.onEach class AutofillActivityManagerImpl( private val autofillManager: AutofillManager, private val autofillEnabledManager: AutofillEnabledManager, - appForegroundManager: AppForegroundManager, + appStateManager: AppStateManager, lifecycleScope: LifecycleCoroutineScope, ) : AutofillActivityManager { private val isAutofillEnabledAndSupported: Boolean @@ -21,7 +21,7 @@ class AutofillActivityManagerImpl( autofillManager.isAutofillSupported init { - appForegroundManager + appStateManager .appForegroundStateFlow .onEach { autofillEnabledManager.isAutofillEnabled = isAutofillEnabledAndSupported } .launchIn(lifecycleScope) diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/AppForegroundManager.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/AppForegroundManager.kt deleted file mode 100644 index 268842c5f..000000000 --- a/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/AppForegroundManager.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.x8bit.bitwarden.data.platform.manager - -import com.x8bit.bitwarden.data.platform.manager.model.AppForegroundState -import kotlinx.coroutines.flow.StateFlow - -/** - * A manager for tracking app foreground state changes. - */ -interface AppForegroundManager { - - /** - * Emits whenever there are changes to the app foreground state. - */ - val appForegroundStateFlow: StateFlow -} diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/AppForegroundManagerImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/AppForegroundManagerImpl.kt deleted file mode 100644 index 3f0a51075..000000000 --- a/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/AppForegroundManagerImpl.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.x8bit.bitwarden.data.platform.manager - -import androidx.lifecycle.DefaultLifecycleObserver -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.ProcessLifecycleOwner -import com.x8bit.bitwarden.data.platform.manager.model.AppForegroundState -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow - -/** - * Primary implementation of [AppForegroundManager]. - */ -class AppForegroundManagerImpl( - processLifecycleOwner: LifecycleOwner = ProcessLifecycleOwner.get(), -) : AppForegroundManager { - private val mutableAppForegroundStateFlow = - MutableStateFlow(AppForegroundState.BACKGROUNDED) - - override val appForegroundStateFlow: StateFlow - get() = mutableAppForegroundStateFlow.asStateFlow() - - init { - processLifecycleOwner.lifecycle.addObserver( - object : DefaultLifecycleObserver { - override fun onStart(owner: LifecycleOwner) { - mutableAppForegroundStateFlow.value = AppForegroundState.FOREGROUNDED - } - - override fun onStop(owner: LifecycleOwner) { - mutableAppForegroundStateFlow.value = AppForegroundState.BACKGROUNDED - } - }, - ) - } -} diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/AppStateManager.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/AppStateManager.kt new file mode 100644 index 000000000..7bf69d039 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/AppStateManager.kt @@ -0,0 +1,24 @@ +package com.x8bit.bitwarden.data.platform.manager + +import com.x8bit.bitwarden.data.autofill.accessibility.BitwardenAccessibilityService +import com.x8bit.bitwarden.data.platform.manager.model.AppCreationState +import com.x8bit.bitwarden.data.platform.manager.model.AppForegroundState +import kotlinx.coroutines.flow.StateFlow + +/** + * A manager for tracking app foreground state changes. + */ +interface AppStateManager { + /** + * Emits whenever there are changes to the app creation state. + * + * This is required because the [BitwardenAccessibilityService] will keep the app process alive + * when the app would otherwise be destroyed. + */ + val appCreatedStateFlow: StateFlow + + /** + * Emits whenever there are changes to the app foreground state. + */ + val appForegroundStateFlow: StateFlow +} diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/AppStateManagerImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/AppStateManagerImpl.kt new file mode 100644 index 000000000..4baa775c0 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/AppStateManagerImpl.kt @@ -0,0 +1,72 @@ +package com.x8bit.bitwarden.data.platform.manager + +import android.app.Activity +import android.app.Application +import android.os.Bundle +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.ProcessLifecycleOwner +import com.x8bit.bitwarden.data.platform.manager.model.AppCreationState +import com.x8bit.bitwarden.data.platform.manager.model.AppForegroundState +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +/** + * Primary implementation of [AppStateManager]. + */ +class AppStateManagerImpl( + application: Application, + processLifecycleOwner: LifecycleOwner = ProcessLifecycleOwner.get(), +) : AppStateManager { + private val mutableAppCreationStateFlow = MutableStateFlow(AppCreationState.DESTROYED) + private val mutableAppForegroundStateFlow = MutableStateFlow(AppForegroundState.BACKGROUNDED) + + override val appCreatedStateFlow: StateFlow + get() = mutableAppCreationStateFlow.asStateFlow() + + override val appForegroundStateFlow: StateFlow + get() = mutableAppForegroundStateFlow.asStateFlow() + + init { + application.registerActivityLifecycleCallbacks(AppCreationCallback()) + processLifecycleOwner.lifecycle.addObserver(AppForegroundObserver()) + } + + private inner class AppForegroundObserver : DefaultLifecycleObserver { + override fun onStart(owner: LifecycleOwner) { + mutableAppForegroundStateFlow.value = AppForegroundState.FOREGROUNDED + } + + override fun onStop(owner: LifecycleOwner) { + mutableAppForegroundStateFlow.value = AppForegroundState.BACKGROUNDED + } + } + + private inner class AppCreationCallback : Application.ActivityLifecycleCallbacks { + private var activityCount: Int = 0 + + override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { + activityCount++ + // Always be in a created state if we have an activity + mutableAppCreationStateFlow.value = AppCreationState.CREATED + } + + override fun onActivityDestroyed(activity: Activity) { + activityCount-- + if (activityCount == 0 && !activity.isChangingConfigurations) { + mutableAppCreationStateFlow.value = AppCreationState.DESTROYED + } + } + + override fun onActivityStarted(activity: Activity) = Unit + + override fun onActivityResumed(activity: Activity) = Unit + + override fun onActivityPaused(activity: Activity) = Unit + + override fun onActivityStopped(activity: Activity) = Unit + + override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) = Unit + } +} diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/di/PlatformManagerModule.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/di/PlatformManagerModule.kt index ce53e923d..b962ccb8f 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/di/PlatformManagerModule.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/di/PlatformManagerModule.kt @@ -14,8 +14,8 @@ import com.x8bit.bitwarden.data.platform.datasource.network.authenticator.Refres import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.BaseUrlInterceptors import com.x8bit.bitwarden.data.platform.datasource.network.service.EventService import com.x8bit.bitwarden.data.platform.datasource.network.service.PushService -import com.x8bit.bitwarden.data.platform.manager.AppForegroundManager -import com.x8bit.bitwarden.data.platform.manager.AppForegroundManagerImpl +import com.x8bit.bitwarden.data.platform.manager.AppStateManager +import com.x8bit.bitwarden.data.platform.manager.AppStateManagerImpl import com.x8bit.bitwarden.data.platform.manager.AssetManager import com.x8bit.bitwarden.data.platform.manager.AssetManagerImpl import com.x8bit.bitwarden.data.platform.manager.BiometricsEncryptionManager @@ -80,8 +80,9 @@ object PlatformManagerModule { @Provides @Singleton - fun provideAppForegroundManager(): AppForegroundManager = - AppForegroundManagerImpl() + fun provideAppStateManager( + application: Application, + ): AppStateManager = AppStateManagerImpl(application = application) @Provides @Singleton @@ -267,11 +268,11 @@ object PlatformManagerModule { @Singleton fun provideRestrictionManager( @ApplicationContext context: Context, - appForegroundManager: AppForegroundManager, + appStateManager: AppStateManager, dispatcherManager: DispatcherManager, environmentRepository: EnvironmentRepository, ): RestrictionManager = RestrictionManagerImpl( - appForegroundManager = appForegroundManager, + appStateManager = appStateManager, dispatcherManager = dispatcherManager, context = context, environmentRepository = environmentRepository, diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/model/AppCreationState.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/model/AppCreationState.kt new file mode 100644 index 000000000..e5bb388c9 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/model/AppCreationState.kt @@ -0,0 +1,16 @@ +package com.x8bit.bitwarden.data.platform.manager.model + +/** + * Represents the creation state of the app. + */ +enum class AppCreationState { + /** + * Denotes that the app is currently created. + */ + CREATED, + + /** + * Denotes that the app is currently destroyed. + */ + DESTROYED, +} diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/restriction/RestrictionManagerImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/restriction/RestrictionManagerImpl.kt index 4f75d8d75..cac5655c4 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/restriction/RestrictionManagerImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/restriction/RestrictionManagerImpl.kt @@ -6,7 +6,7 @@ import android.content.Intent import android.content.IntentFilter import android.content.RestrictionsManager import android.os.Bundle -import com.x8bit.bitwarden.data.platform.manager.AppForegroundManager +import com.x8bit.bitwarden.data.platform.manager.AppStateManager import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager import com.x8bit.bitwarden.data.platform.manager.model.AppForegroundState import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository @@ -19,7 +19,7 @@ import kotlinx.coroutines.flow.onEach * The default implementation of the [RestrictionManager]. */ class RestrictionManagerImpl( - appForegroundManager: AppForegroundManager, + appStateManager: AppStateManager, dispatcherManager: DispatcherManager, private val context: Context, private val environmentRepository: EnvironmentRepository, @@ -31,7 +31,7 @@ class RestrictionManagerImpl( private var isReceiverRegistered = false init { - appForegroundManager + appStateManager .appForegroundStateFlow .onEach { when (it) { diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/manager/VaultLockManagerImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/manager/VaultLockManagerImpl.kt index 41189cb87..1320c2ca3 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/manager/VaultLockManagerImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/manager/VaultLockManagerImpl.kt @@ -12,8 +12,9 @@ import com.x8bit.bitwarden.data.auth.manager.UserLogoutManager import com.x8bit.bitwarden.data.auth.repository.util.toSdkParams import com.x8bit.bitwarden.data.auth.repository.util.userAccountTokens import com.x8bit.bitwarden.data.auth.repository.util.userSwitchingChangesFlow -import com.x8bit.bitwarden.data.platform.manager.AppForegroundManager +import com.x8bit.bitwarden.data.platform.manager.AppStateManager import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager +import com.x8bit.bitwarden.data.platform.manager.model.AppCreationState import com.x8bit.bitwarden.data.platform.manager.model.AppForegroundState import com.x8bit.bitwarden.data.platform.repository.SettingsRepository import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeout @@ -65,7 +66,7 @@ class VaultLockManagerImpl( private val authSdkSource: AuthSdkSource, private val vaultSdkSource: VaultSdkSource, private val settingsRepository: SettingsRepository, - private val appForegroundManager: AppForegroundManager, + private val appStateManager: AppStateManager, private val userLogoutManager: UserLogoutManager, private val trustedDeviceManager: TrustedDeviceManager, dispatcherManager: DispatcherManager, @@ -90,6 +91,7 @@ class VaultLockManagerImpl( get() = mutableVaultStateEventSharedFlow.asSharedFlow() init { + observeAppCreationChanges() observeAppForegroundChanges() observeUserSwitchingChanges() observeVaultTimeoutChanges() @@ -302,10 +304,31 @@ class VaultLockManagerImpl( } } + private fun observeAppCreationChanges() { + appStateManager + .appCreatedStateFlow + .onEach { appCreationState -> + when (appCreationState) { + AppCreationState.CREATED -> Unit + AppCreationState.DESTROYED -> handleOnDestroyed() + } + } + .launchIn(unconfinedScope) + } + + private fun handleOnDestroyed() { + activeUserId?.let { userId -> + checkForVaultTimeout( + userId = userId, + checkTimeoutReason = CheckTimeoutReason.APP_RESTARTED, + ) + } + } + private fun observeAppForegroundChanges() { var isFirstForeground = true - appForegroundManager + appStateManager .appForegroundStateFlow .onEach { appForegroundState -> when (appForegroundState) { diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/manager/di/VaultManagerModule.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/manager/di/VaultManagerModule.kt index 519ea6fc3..18ca9ebf2 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/manager/di/VaultManagerModule.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/manager/di/VaultManagerModule.kt @@ -5,7 +5,7 @@ import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource import com.x8bit.bitwarden.data.auth.datasource.sdk.AuthSdkSource import com.x8bit.bitwarden.data.auth.manager.TrustedDeviceManager import com.x8bit.bitwarden.data.auth.manager.UserLogoutManager -import com.x8bit.bitwarden.data.platform.manager.AppForegroundManager +import com.x8bit.bitwarden.data.platform.manager.AppStateManager import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager import com.x8bit.bitwarden.data.platform.repository.SettingsRepository import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource @@ -72,7 +72,7 @@ object VaultManagerModule { authSdkSource: AuthSdkSource, vaultSdkSource: VaultSdkSource, settingsRepository: SettingsRepository, - appForegroundManager: AppForegroundManager, + appStateManager: AppStateManager, userLogoutManager: UserLogoutManager, dispatcherManager: DispatcherManager, trustedDeviceManager: TrustedDeviceManager, @@ -82,7 +82,7 @@ object VaultManagerModule { authSdkSource = authSdkSource, vaultSdkSource = vaultSdkSource, settingsRepository = settingsRepository, - appForegroundManager = appForegroundManager, + appStateManager = appStateManager, userLogoutManager = userLogoutManager, dispatcherManager = dispatcherManager, trustedDeviceManager = trustedDeviceManager, diff --git a/app/src/test/java/com/x8bit/bitwarden/data/autofill/accessibility/manager/AccessibilityActivityManagerTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/autofill/accessibility/manager/AccessibilityActivityManagerTest.kt index e328fc834..d8c5e63bd 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/autofill/accessibility/manager/AccessibilityActivityManagerTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/autofill/accessibility/manager/AccessibilityActivityManagerTest.kt @@ -4,7 +4,7 @@ import android.content.Context import androidx.lifecycle.LifecycleCoroutineScope import app.cash.turbine.test import com.x8bit.bitwarden.data.autofill.accessibility.util.isAccessibilityServiceEnabled -import com.x8bit.bitwarden.data.platform.manager.AppForegroundManager +import com.x8bit.bitwarden.data.platform.manager.AppStateManager import com.x8bit.bitwarden.data.platform.manager.model.AppForegroundState import io.mockk.every import io.mockk.mockk @@ -26,7 +26,7 @@ class AccessibilityActivityManagerTest { private val accessibilityEnabledManager: AccessibilityEnabledManager = AccessibilityEnabledManagerImpl() private val mutableAppForegroundStateFlow = MutableStateFlow(AppForegroundState.BACKGROUNDED) - private val appForegroundManager: AppForegroundManager = mockk { + private val appStateManager: AppStateManager = mockk { every { appForegroundStateFlow } returns mutableAppForegroundStateFlow } private val lifecycleScope = mockk { @@ -43,7 +43,7 @@ class AccessibilityActivityManagerTest { autofillActivityManager = AccessibilityActivityManagerImpl( context = context, accessibilityEnabledManager = accessibilityEnabledManager, - appForegroundManager = appForegroundManager, + appStateManager = appStateManager, lifecycleScope = lifecycleScope, ) } diff --git a/app/src/test/java/com/x8bit/bitwarden/data/autofill/manager/AutofillActivityManagerTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/autofill/manager/AutofillActivityManagerTest.kt index 525f4d4ab..01ee3a0ce 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/autofill/manager/AutofillActivityManagerTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/autofill/manager/AutofillActivityManagerTest.kt @@ -3,7 +3,7 @@ package com.x8bit.bitwarden.data.autofill.manager import android.view.autofill.AutofillManager import androidx.lifecycle.LifecycleCoroutineScope import app.cash.turbine.test -import com.x8bit.bitwarden.data.platform.manager.AppForegroundManager +import com.x8bit.bitwarden.data.platform.manager.AppStateManager import com.x8bit.bitwarden.data.platform.manager.model.AppForegroundState import io.mockk.every import io.mockk.just @@ -28,7 +28,7 @@ class AutofillActivityManagerTest { private val autofillEnabledManager: AutofillEnabledManager = AutofillEnabledManagerImpl() private val mutableAppForegroundStateFlow = MutableStateFlow(AppForegroundState.BACKGROUNDED) - private val appForegroundManager: AppForegroundManager = mockk { + private val appStateManager: AppStateManager = mockk { every { appForegroundStateFlow } returns mutableAppForegroundStateFlow } private val lifecycleScope = mockk { @@ -39,7 +39,7 @@ class AutofillActivityManagerTest { @Suppress("unused") private val autofillActivityManager: AutofillActivityManager = AutofillActivityManagerImpl( autofillManager = autofillManager, - appForegroundManager = appForegroundManager, + appStateManager = appStateManager, autofillEnabledManager = autofillEnabledManager, lifecycleScope = lifecycleScope, ) diff --git a/app/src/test/java/com/x8bit/bitwarden/data/platform/manager/AppForegroundManagerTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/platform/manager/AppForegroundManagerTest.kt deleted file mode 100644 index fbf8c1d0d..000000000 --- a/app/src/test/java/com/x8bit/bitwarden/data/platform/manager/AppForegroundManagerTest.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.x8bit.bitwarden.data.platform.manager - -import app.cash.turbine.test -import com.x8bit.bitwarden.data.platform.manager.model.AppForegroundState -import com.x8bit.bitwarden.data.util.FakeLifecycleOwner -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test - -class AppForegroundManagerTest { - - private val fakeLifecycleOwner = FakeLifecycleOwner() - - private val appForegroundManager = AppForegroundManagerImpl( - processLifecycleOwner = fakeLifecycleOwner, - ) - - @Suppress("MaxLineLength") - @Test - fun `appForegroundStateFlow should emit whenever the underlying ProcessLifecycleOwner receives start and stop events`() = - runTest { - appForegroundManager.appForegroundStateFlow.test { - // Initial state is BACKGROUNDED - assertEquals( - AppForegroundState.BACKGROUNDED, - awaitItem(), - ) - - fakeLifecycleOwner.lifecycle.dispatchOnStart() - - assertEquals( - AppForegroundState.FOREGROUNDED, - awaitItem(), - ) - - fakeLifecycleOwner.lifecycle.dispatchOnStop() - - assertEquals( - AppForegroundState.BACKGROUNDED, - awaitItem(), - ) - } - } -} diff --git a/app/src/test/java/com/x8bit/bitwarden/data/platform/manager/AppStateManagerTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/platform/manager/AppStateManagerTest.kt new file mode 100644 index 000000000..0021001d2 --- /dev/null +++ b/app/src/test/java/com/x8bit/bitwarden/data/platform/manager/AppStateManagerTest.kt @@ -0,0 +1,82 @@ +package com.x8bit.bitwarden.data.platform.manager + +import android.app.Activity +import android.app.Application +import app.cash.turbine.test +import com.x8bit.bitwarden.data.platform.manager.model.AppCreationState +import com.x8bit.bitwarden.data.platform.manager.model.AppForegroundState +import com.x8bit.bitwarden.data.util.FakeLifecycleOwner +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import io.mockk.runs +import io.mockk.slot +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class AppStateManagerTest { + + private val activityLifecycleCallbacks = slot() + private val application = mockk { + every { registerActivityLifecycleCallbacks(capture(activityLifecycleCallbacks)) } just runs + } + private val fakeLifecycleOwner = FakeLifecycleOwner() + + private val appStateManager = AppStateManagerImpl( + application = application, + processLifecycleOwner = fakeLifecycleOwner, + ) + + @Suppress("MaxLineLength") + @Test + fun `appForegroundStateFlow should emit whenever the underlying ProcessLifecycleOwner receives start and stop events`() = + runTest { + appStateManager.appForegroundStateFlow.test { + // Initial state is BACKGROUNDED + assertEquals( + AppForegroundState.BACKGROUNDED, + awaitItem(), + ) + + fakeLifecycleOwner.lifecycle.dispatchOnStart() + + assertEquals( + AppForegroundState.FOREGROUNDED, + awaitItem(), + ) + + fakeLifecycleOwner.lifecycle.dispatchOnStop() + + assertEquals( + AppForegroundState.BACKGROUNDED, + awaitItem(), + ) + } + } + + @Suppress("MaxLineLength") + @Test + fun `appCreatedStateFlow should emit whenever the underlying activities are all destroyed or a creation event occurs`() = + runTest { + val activity = mockk { + every { isChangingConfigurations } returns false + } + appStateManager.appCreatedStateFlow.test { + // Initial state is DESTROYED + assertEquals(AppCreationState.DESTROYED, awaitItem()) + + activityLifecycleCallbacks.captured.onActivityCreated(activity, null) + assertEquals(AppCreationState.CREATED, awaitItem()) + + activityLifecycleCallbacks.captured.onActivityCreated(activity, null) + expectNoEvents() + + activityLifecycleCallbacks.captured.onActivityDestroyed(activity) + expectNoEvents() + + activityLifecycleCallbacks.captured.onActivityDestroyed(activity) + assertEquals(AppCreationState.DESTROYED, awaitItem()) + } + } +} diff --git a/app/src/test/java/com/x8bit/bitwarden/data/platform/manager/restriction/RestrictionManagerTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/platform/manager/restriction/RestrictionManagerTest.kt index e253f253d..b764f1ae0 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/platform/manager/restriction/RestrictionManagerTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/platform/manager/restriction/RestrictionManagerTest.kt @@ -7,7 +7,7 @@ import android.os.Bundle import com.x8bit.bitwarden.data.auth.datasource.disk.model.EnvironmentUrlDataJson import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager import com.x8bit.bitwarden.data.platform.manager.model.AppForegroundState -import com.x8bit.bitwarden.data.platform.manager.util.FakeAppForegroundManager +import com.x8bit.bitwarden.data.platform.manager.util.FakeAppStateManager import com.x8bit.bitwarden.data.platform.repository.model.Environment import com.x8bit.bitwarden.data.platform.repository.util.FakeEnvironmentRepository import io.mockk.clearMocks @@ -27,7 +27,7 @@ class RestrictionManagerTest { every { registerReceiver(any(), any()) } returns null every { unregisterReceiver(any()) } just runs } - private val fakeAppForegroundManager = FakeAppForegroundManager() + private val fakeAppStateManager = FakeAppStateManager() private val fakeDispatcherManager = FakeDispatcherManager().apply { setMain(unconfined) } @@ -35,7 +35,7 @@ class RestrictionManagerTest { private val restrictionsManager = mockk() private val restrictionManager: RestrictionManager = RestrictionManagerImpl( - appForegroundManager = fakeAppForegroundManager, + appStateManager = fakeAppStateManager, dispatcherManager = fakeDispatcherManager, context = context, environmentRepository = fakeEnvironmentRepository, @@ -51,7 +51,7 @@ class RestrictionManagerTest { fun `on app foreground with a null bundle should register receiver and do nothing else`() { every { restrictionsManager.applicationRestrictions } returns null - fakeAppForegroundManager.appForegroundState = AppForegroundState.FOREGROUNDED + fakeAppStateManager.appForegroundState = AppForegroundState.FOREGROUNDED verify(exactly = 1) { context.registerReceiver(any(), any()) @@ -63,7 +63,7 @@ class RestrictionManagerTest { fun `on app foreground with an empty bundle should register receiver and do nothing else`() { every { restrictionsManager.applicationRestrictions } returns mockBundle() - fakeAppForegroundManager.appForegroundState = AppForegroundState.FOREGROUNDED + fakeAppStateManager.appForegroundState = AppForegroundState.FOREGROUNDED verify(exactly = 1) { context.registerReceiver(any(), any()) @@ -78,7 +78,7 @@ class RestrictionManagerTest { restrictionsManager.applicationRestrictions } returns mockBundle("key" to "unknown") - fakeAppForegroundManager.appForegroundState = AppForegroundState.FOREGROUNDED + fakeAppStateManager.appForegroundState = AppForegroundState.FOREGROUNDED verify(exactly = 1) { context.registerReceiver(any(), any()) @@ -93,7 +93,7 @@ class RestrictionManagerTest { restrictionsManager.applicationRestrictions } returns mockBundle("baseEnvironmentUrl" to "https://vault.bitwarden.com") - fakeAppForegroundManager.appForegroundState = AppForegroundState.FOREGROUNDED + fakeAppStateManager.appForegroundState = AppForegroundState.FOREGROUNDED verify(exactly = 1) { context.registerReceiver(any(), any()) @@ -109,7 +109,7 @@ class RestrictionManagerTest { restrictionsManager.applicationRestrictions } returns mockBundle("baseEnvironmentUrl" to baseUrl) - fakeAppForegroundManager.appForegroundState = AppForegroundState.FOREGROUNDED + fakeAppStateManager.appForegroundState = AppForegroundState.FOREGROUNDED verify(exactly = 1) { context.registerReceiver(any(), any()) @@ -130,7 +130,7 @@ class RestrictionManagerTest { restrictionsManager.applicationRestrictions } returns mockBundle("baseEnvironmentUrl" to "https://vault.bitwarden.eu") - fakeAppForegroundManager.appForegroundState = AppForegroundState.FOREGROUNDED + fakeAppStateManager.appForegroundState = AppForegroundState.FOREGROUNDED verify(exactly = 1) { context.registerReceiver(any(), any()) @@ -147,7 +147,7 @@ class RestrictionManagerTest { restrictionsManager.applicationRestrictions } returns mockBundle("baseEnvironmentUrl" to baseUrl) - fakeAppForegroundManager.appForegroundState = AppForegroundState.FOREGROUNDED + fakeAppStateManager.appForegroundState = AppForegroundState.FOREGROUNDED verify(exactly = 1) { context.registerReceiver(any(), any()) @@ -172,7 +172,7 @@ class RestrictionManagerTest { restrictionsManager.applicationRestrictions } returns mockBundle("baseEnvironmentUrl" to baseUrl) - fakeAppForegroundManager.appForegroundState = AppForegroundState.FOREGROUNDED + fakeAppStateManager.appForegroundState = AppForegroundState.FOREGROUNDED verify(exactly = 1) { context.registerReceiver(any(), any()) @@ -192,7 +192,7 @@ class RestrictionManagerTest { restrictionsManager.applicationRestrictions } returns mockBundle("baseEnvironmentUrl" to baseUrl) - fakeAppForegroundManager.appForegroundState = AppForegroundState.FOREGROUNDED + fakeAppStateManager.appForegroundState = AppForegroundState.FOREGROUNDED verify(exactly = 1) { context.registerReceiver(any(), any()) @@ -207,7 +207,7 @@ class RestrictionManagerTest { @Test fun `on app background when not foregrounded should do nothing`() { - fakeAppForegroundManager.appForegroundState = AppForegroundState.BACKGROUNDED + fakeAppStateManager.appForegroundState = AppForegroundState.BACKGROUNDED verify(exactly = 0) { context.unregisterReceiver(any()) @@ -218,10 +218,10 @@ class RestrictionManagerTest { @Test fun `on app background after foreground should unregister receiver`() { every { restrictionsManager.applicationRestrictions } returns null - fakeAppForegroundManager.appForegroundState = AppForegroundState.FOREGROUNDED + fakeAppStateManager.appForegroundState = AppForegroundState.FOREGROUNDED clearMocks(context, restrictionsManager, answers = false) - fakeAppForegroundManager.appForegroundState = AppForegroundState.BACKGROUNDED + fakeAppStateManager.appForegroundState = AppForegroundState.BACKGROUNDED verify(exactly = 1) { context.unregisterReceiver(any()) diff --git a/app/src/test/java/com/x8bit/bitwarden/data/platform/manager/util/FakeAppForegroundManager.kt b/app/src/test/java/com/x8bit/bitwarden/data/platform/manager/util/FakeAppStateManager.kt similarity index 52% rename from app/src/test/java/com/x8bit/bitwarden/data/platform/manager/util/FakeAppForegroundManager.kt rename to app/src/test/java/com/x8bit/bitwarden/data/platform/manager/util/FakeAppStateManager.kt index 2bd8e637a..74a7ee939 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/platform/manager/util/FakeAppForegroundManager.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/platform/manager/util/FakeAppStateManager.kt @@ -1,20 +1,34 @@ package com.x8bit.bitwarden.data.platform.manager.util -import com.x8bit.bitwarden.data.platform.manager.AppForegroundManager +import com.x8bit.bitwarden.data.platform.manager.AppStateManager +import com.x8bit.bitwarden.data.platform.manager.model.AppCreationState import com.x8bit.bitwarden.data.platform.manager.model.AppForegroundState import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow /** - * A faked implementation of [AppForegroundManager] + * A faked implementation of [AppStateManager] */ -class FakeAppForegroundManager : AppForegroundManager { +class FakeAppStateManager : AppStateManager { + private val mutableAppCreationStateFlow = MutableStateFlow(AppCreationState.DESTROYED) private val mutableAppForegroundStateFlow = MutableStateFlow(AppForegroundState.BACKGROUNDED) + override val appCreatedStateFlow: StateFlow + get() = mutableAppCreationStateFlow.asStateFlow() + override val appForegroundStateFlow: StateFlow get() = mutableAppForegroundStateFlow.asStateFlow() + /** + * The current [AppCreationState] tracked by the [appCreatedStateFlow]. + */ + var appCreationState: AppCreationState + get() = mutableAppCreationStateFlow.value + set(value) { + mutableAppCreationStateFlow.value = value + } + /** * The current [AppForegroundState] tracked by the [appForegroundStateFlow]. */ diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/manager/VaultLockManagerTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/manager/VaultLockManagerTest.kt index afc420842..92784cfc5 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/vault/manager/VaultLockManagerTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/manager/VaultLockManagerTest.kt @@ -15,8 +15,9 @@ import com.x8bit.bitwarden.data.auth.manager.UserLogoutManager import com.x8bit.bitwarden.data.auth.manager.model.LogoutEvent import com.x8bit.bitwarden.data.auth.repository.util.toSdkParams import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager +import com.x8bit.bitwarden.data.platform.manager.model.AppCreationState import com.x8bit.bitwarden.data.platform.manager.model.AppForegroundState -import com.x8bit.bitwarden.data.platform.manager.util.FakeAppForegroundManager +import com.x8bit.bitwarden.data.platform.manager.util.FakeAppStateManager import com.x8bit.bitwarden.data.platform.repository.SettingsRepository import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeout import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeoutAction @@ -53,7 +54,7 @@ import org.junit.jupiter.api.Test @Suppress("LargeClass") class VaultLockManagerTest { private val fakeAuthDiskSource = FakeAuthDiskSource() - private val fakeAppForegroundManager = FakeAppForegroundManager() + private val fakeAppStateManager = FakeAppStateManager() private val authSdkSource: AuthSdkSource = mockk { coEvery { hashPassword( @@ -90,7 +91,7 @@ class VaultLockManagerTest { authSdkSource = authSdkSource, vaultSdkSource = vaultSdkSource, settingsRepository = settingsRepository, - appForegroundManager = fakeAppForegroundManager, + appStateManager = fakeAppStateManager, userLogoutManager = userLogoutManager, trustedDeviceManager = trustedDeviceManager, dispatcherManager = fakeDispatcherManager, @@ -147,18 +148,86 @@ class VaultLockManagerTest { } @Test - fun `app coming into background subsequent times should perform timeout action if necessary`() { + fun `app being destroyed should perform timeout action if necessary`() { setAccountTokens() fakeAuthDiskSource.userState = MOCK_USER_STATE - // Start in a foregrounded state - fakeAppForegroundManager.appForegroundState = AppForegroundState.FOREGROUNDED - // Will be used within each loop to reset the test to a suitable initial state. fun resetTest(vaultTimeout: VaultTimeout) { clearVerifications(userLogoutManager) mutableVaultTimeoutStateFlow.value = vaultTimeout - fakeAppForegroundManager.appForegroundState = AppForegroundState.FOREGROUNDED + fakeAppStateManager.appCreationState = AppCreationState.CREATED + verifyUnlockedVaultBlocking(userId = USER_ID) + assertTrue(vaultLockManager.isVaultUnlocked(USER_ID)) + } + + // Test Lock action + mutableVaultTimeoutActionStateFlow.value = VaultTimeoutAction.LOCK + MOCK_TIMEOUTS.forEach { vaultTimeout -> + resetTest(vaultTimeout = vaultTimeout) + fakeAppStateManager.appCreationState = AppCreationState.DESTROYED + + when (vaultTimeout) { + VaultTimeout.FifteenMinutes, + VaultTimeout.ThirtyMinutes, + VaultTimeout.OneHour, + VaultTimeout.FourHours, + is VaultTimeout.Custom, + VaultTimeout.Immediately, + VaultTimeout.OneMinute, + VaultTimeout.FiveMinutes, + VaultTimeout.OnAppRestart, + -> { + assertFalse(vaultLockManager.isVaultUnlocked(USER_ID)) + } + + VaultTimeout.Never -> { + assertTrue(vaultLockManager.isVaultUnlocked(USER_ID)) + } + } + verify(exactly = 0) { userLogoutManager.softLogout(any()) } + } + + // Test Logout action + mutableVaultTimeoutActionStateFlow.value = VaultTimeoutAction.LOGOUT + MOCK_TIMEOUTS.forEach { vaultTimeout -> + resetTest(vaultTimeout = vaultTimeout) + fakeAppStateManager.appCreationState = AppCreationState.DESTROYED + + when (vaultTimeout) { + VaultTimeout.OnAppRestart, + VaultTimeout.FifteenMinutes, + VaultTimeout.ThirtyMinutes, + VaultTimeout.OneHour, + VaultTimeout.FourHours, + is VaultTimeout.Custom, + VaultTimeout.Immediately, + VaultTimeout.OneMinute, + VaultTimeout.FiveMinutes, + -> { + verify(exactly = 1) { userLogoutManager.softLogout(any()) } + } + + VaultTimeout.Never -> { + verify(exactly = 0) { userLogoutManager.softLogout(any()) } + } + } + } + } + + @Test + fun `app coming into background subsequent times should perform timeout action if necessary`() { + setAccountTokens() + fakeAuthDiskSource.userState = MOCK_USER_STATE + + // Start in a foregrounded state + fakeAppStateManager.appForegroundState = AppForegroundState.FOREGROUNDED + + // Will be used within each loop to reset the test to a suitable initial state. + fun resetTest(vaultTimeout: VaultTimeout) { + clearVerifications(userLogoutManager) + mutableVaultTimeoutStateFlow.value = vaultTimeout + fakeAppStateManager.appForegroundState = AppForegroundState.FOREGROUNDED verifyUnlockedVaultBlocking(userId = USER_ID) assertTrue(vaultLockManager.isVaultUnlocked(USER_ID)) } @@ -168,7 +237,7 @@ class VaultLockManagerTest { MOCK_TIMEOUTS.forEach { vaultTimeout -> resetTest(vaultTimeout = vaultTimeout) - fakeAppForegroundManager.appForegroundState = AppForegroundState.BACKGROUNDED + fakeAppStateManager.appForegroundState = AppForegroundState.BACKGROUNDED // Advance by 6 minutes. Only actions with a timeout less than this will be triggered. testDispatcher.scheduler.advanceTimeBy(delayTimeMillis = 6 * 60 * 1000L) @@ -201,7 +270,7 @@ class VaultLockManagerTest { mutableVaultTimeoutActionStateFlow.value = VaultTimeoutAction.LOGOUT MOCK_TIMEOUTS.forEach { vaultTimeout -> resetTest(vaultTimeout = vaultTimeout) - fakeAppForegroundManager.appForegroundState = AppForegroundState.BACKGROUNDED + fakeAppStateManager.appForegroundState = AppForegroundState.BACKGROUNDED // Advance by 6 minutes. Only actions with a timeout less than this will be triggered. testDispatcher.scheduler.advanceTimeBy(delayTimeMillis = 6 * 60 * 1000L) @@ -236,11 +305,11 @@ class VaultLockManagerTest { mutableVaultTimeoutActionStateFlow.value = VaultTimeoutAction.LOCK mutableVaultTimeoutStateFlow.value = VaultTimeout.Never - fakeAppForegroundManager.appForegroundState = AppForegroundState.BACKGROUNDED + fakeAppStateManager.appForegroundState = AppForegroundState.BACKGROUNDED verifyUnlockedVaultBlocking(userId = USER_ID) assertTrue(vaultLockManager.isVaultUnlocked(USER_ID)) - fakeAppForegroundManager.appForegroundState = AppForegroundState.FOREGROUNDED + fakeAppStateManager.appForegroundState = AppForegroundState.FOREGROUNDED assertTrue(vaultLockManager.isVaultUnlocked(USER_ID)) } @@ -253,11 +322,11 @@ class VaultLockManagerTest { mutableVaultTimeoutActionStateFlow.value = VaultTimeoutAction.LOCK mutableVaultTimeoutStateFlow.value = VaultTimeout.OnAppRestart - fakeAppForegroundManager.appForegroundState = AppForegroundState.BACKGROUNDED + fakeAppStateManager.appForegroundState = AppForegroundState.BACKGROUNDED verifyUnlockedVaultBlocking(userId = USER_ID) assertTrue(vaultLockManager.isVaultUnlocked(USER_ID)) - fakeAppForegroundManager.appForegroundState = AppForegroundState.FOREGROUNDED + fakeAppStateManager.appForegroundState = AppForegroundState.FOREGROUNDED assertFalse(vaultLockManager.isVaultUnlocked(USER_ID)) } @@ -269,11 +338,11 @@ class VaultLockManagerTest { mutableVaultTimeoutActionStateFlow.value = VaultTimeoutAction.LOCK mutableVaultTimeoutStateFlow.value = VaultTimeout.ThirtyMinutes - fakeAppForegroundManager.appForegroundState = AppForegroundState.BACKGROUNDED + fakeAppStateManager.appForegroundState = AppForegroundState.BACKGROUNDED verifyUnlockedVaultBlocking(userId = USER_ID) assertTrue(vaultLockManager.isVaultUnlocked(USER_ID)) - fakeAppForegroundManager.appForegroundState = AppForegroundState.FOREGROUNDED + fakeAppStateManager.appForegroundState = AppForegroundState.FOREGROUNDED assertFalse(vaultLockManager.isVaultUnlocked(USER_ID)) } @@ -286,7 +355,7 @@ class VaultLockManagerTest { mutableVaultTimeoutActionStateFlow.value = VaultTimeoutAction.LOCK mutableVaultTimeoutStateFlow.value = VaultTimeout.ThirtyMinutes - fakeAppForegroundManager.appForegroundState = AppForegroundState.FOREGROUNDED + fakeAppStateManager.appForegroundState = AppForegroundState.FOREGROUNDED assertFalse(vaultLockManager.isVaultUnlocked(USER_ID)) } @@ -298,11 +367,11 @@ class VaultLockManagerTest { mutableVaultTimeoutActionStateFlow.value = VaultTimeoutAction.LOGOUT mutableVaultTimeoutStateFlow.value = VaultTimeout.ThirtyMinutes - fakeAppForegroundManager.appForegroundState = AppForegroundState.BACKGROUNDED + fakeAppStateManager.appForegroundState = AppForegroundState.BACKGROUNDED verifyUnlockedVaultBlocking(userId = USER_ID) assertTrue(vaultLockManager.isVaultUnlocked(USER_ID)) - fakeAppForegroundManager.appForegroundState = AppForegroundState.FOREGROUNDED + fakeAppStateManager.appForegroundState = AppForegroundState.FOREGROUNDED verify(exactly = 1) { settingsRepository.getVaultTimeoutActionStateFlow(USER_ID) } } @@ -314,11 +383,11 @@ class VaultLockManagerTest { mutableVaultTimeoutActionStateFlow.value = VaultTimeoutAction.LOGOUT mutableVaultTimeoutStateFlow.value = VaultTimeout.ThirtyMinutes - fakeAppForegroundManager.appForegroundState = AppForegroundState.BACKGROUNDED + fakeAppStateManager.appForegroundState = AppForegroundState.BACKGROUNDED verifyUnlockedVaultBlocking(userId = USER_ID) assertTrue(vaultLockManager.isVaultUnlocked(USER_ID)) - fakeAppForegroundManager.appForegroundState = AppForegroundState.FOREGROUNDED + fakeAppStateManager.appForegroundState = AppForegroundState.FOREGROUNDED verify(exactly = 0) { settingsRepository.getVaultTimeoutActionStateFlow(USER_ID) } } @@ -329,14 +398,14 @@ class VaultLockManagerTest { fakeAuthDiskSource.userState = MOCK_USER_STATE // Start in a foregrounded state - fakeAppForegroundManager.appForegroundState = AppForegroundState.BACKGROUNDED + fakeAppStateManager.appForegroundState = AppForegroundState.BACKGROUNDED // We want to skip the first time since that is different from subsequent foregrounds - fakeAppForegroundManager.appForegroundState = AppForegroundState.FOREGROUNDED + fakeAppStateManager.appForegroundState = AppForegroundState.FOREGROUNDED // Will be used within each loop to reset the test to a suitable initial state. fun resetTest(vaultTimeout: VaultTimeout) { mutableVaultTimeoutStateFlow.value = vaultTimeout - fakeAppForegroundManager.appForegroundState = AppForegroundState.BACKGROUNDED + fakeAppStateManager.appForegroundState = AppForegroundState.BACKGROUNDED clearVerifications(userLogoutManager) verifyUnlockedVaultBlocking(userId = USER_ID) assertTrue(vaultLockManager.isVaultUnlocked(USER_ID)) @@ -347,7 +416,7 @@ class VaultLockManagerTest { MOCK_TIMEOUTS.forEach { vaultTimeout -> resetTest(vaultTimeout = vaultTimeout) - fakeAppForegroundManager.appForegroundState = AppForegroundState.FOREGROUNDED + fakeAppStateManager.appForegroundState = AppForegroundState.FOREGROUNDED // Advance by 6 minutes. Only actions with a timeout less than this will be triggered. testDispatcher.scheduler.advanceTimeBy(delayTimeMillis = 6 * 60 * 1000L) @@ -361,7 +430,7 @@ class VaultLockManagerTest { MOCK_TIMEOUTS.forEach { vaultTimeout -> resetTest(vaultTimeout = vaultTimeout) - fakeAppForegroundManager.appForegroundState = AppForegroundState.FOREGROUNDED + fakeAppStateManager.appForegroundState = AppForegroundState.FOREGROUNDED // Advance by 6 minutes. Only actions with a timeout less than this will be triggered. testDispatcher.scheduler.advanceTimeBy(delayTimeMillis = 6 * 60 * 1000L) @@ -376,7 +445,7 @@ class VaultLockManagerTest { fun `switching users should perform lock actions or start a timer for each user if necessary`() { val userId2 = "mockId-2" setAccountTokens(listOf(USER_ID, userId2)) - fakeAppForegroundManager.appForegroundState = AppForegroundState.FOREGROUNDED + fakeAppStateManager.appForegroundState = AppForegroundState.FOREGROUNDED fakeAuthDiskSource.userState = UserStateJson( activeUserId = USER_ID, accounts = mapOf( diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 22321beb2..281f0bbd2 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -46,7 +46,7 @@ Note that these data sources are constructed in a manner that adheres to a very ### Managers -Manager classes represent something of a middle level of the data layer. While some manager classes like [VaultLockManager](../app/src/main/java/com/x8bit/bitwarden/data/vault/manager/VaultLockManager.kt) depend on the the lower-level data sources, others are wrappers around OS-level classes (ex: [AppForegroundManager](../app/src/main/java/com/x8bit/bitwarden/data/platform/manager/AppForegroundManager.kt)) while others have no dependencies at all (ex: [SpecialCircumstanceManager](../app/src/main/java/com/x8bit/bitwarden/data/platform/manager/SpecialCircumstanceManager.kt)). The commonality amongst the manager classes is that they tend to have a single discrete responsibility. These classes may also exist solely in the data layer for use inside a repository or manager class, like [AppForegroundManager](../app/src/main/java/com/x8bit/bitwarden/data/platform/manager/AppForegroundManager.kt), or may be exposed directly to the UI layer, like [SpecialCircumstanceManager](../app/src/main/java/com/x8bit/bitwarden/data/platform/manager/SpecialCircumstanceManager.kt). +Manager classes represent something of a middle level of the data layer. While some manager classes like [VaultLockManager](../app/src/main/java/com/x8bit/bitwarden/data/vault/manager/VaultLockManager.kt) depend on the the lower-level data sources, others are wrappers around OS-level classes (ex: [AppStateManager](../app/src/main/java/com/x8bit/bitwarden/data/platform/manager/AppStateManager.kt)) while others have no dependencies at all (ex: [SpecialCircumstanceManager](../app/src/main/java/com/x8bit/bitwarden/data/platform/manager/SpecialCircumstanceManager.kt)). The commonality amongst the manager classes is that they tend to have a single discrete responsibility. These classes may also exist solely in the data layer for use inside a repository or manager class, like [AppStateManager](../app/src/main/java/com/x8bit/bitwarden/data/platform/manager/AppStateManager.kt), or may be exposed directly to the UI layer, like [SpecialCircumstanceManager](../app/src/main/java/com/x8bit/bitwarden/data/platform/manager/SpecialCircumstanceManager.kt). ### Repositories