From 9a5b360c112f19d37c0f9208d302237f886f059b Mon Sep 17 00:00:00 2001 From: burakov Date: Tue, 11 Jul 2023 21:36:03 +0000 Subject: [PATCH] [flexiglass] Resets the scene when the device goes to sleep. When the device goes to sleep (usually following a power button click or screen timeout), we will switch to either Gone or Lockscreen as the current scene, depending on whether the device was unlocked or not, respectively. We use the ASLEEP wakefulness state as our signal instead of listening to screen off events because the latter does not necessarily need to trigger a scene reset, e.g. in the case of a screen turning off when unfolding a foldable device. Fix: 290403062 Test: new unit tests added Test: manually verified in system UI that screen timeout moves back to the Lockscreen scene when the device is locked, and to the Gone scene when the device is unlocked. Change-Id: Iee5474d45c281bbd5a76c06ba9c64de31a1d8255 --- .../repository/AuthenticationRepository.kt | 7 +- .../data/repository/KeyguardRepository.kt | 10 +- .../SystemUiDefaultSceneContainerStartable.kt | 30 ++++- ...temUiDefaultSceneContainerStartableTest.kt | 106 ++++++++++++++++++ .../data/repository/FakeKeyguardRepository.kt | 2 +- .../android/systemui/scene/SceneTestUtils.kt | 40 ++++++- 6 files changed, 178 insertions(+), 17 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt index 43fb3630bd197..444491f53629a 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt @@ -144,12 +144,7 @@ constructor( private val lockPatternUtils: LockPatternUtils, ) : AuthenticationRepository { - override val isUnlocked: StateFlow = - keyguardRepository.isKeyguardUnlocked.stateIn( - scope = applicationScope, - started = SharingStarted.WhileSubscribed(), - initialValue = false, - ) + override val isUnlocked: StateFlow = keyguardRepository.isKeyguardUnlocked override suspend fun isLockscreenEnabled(): Boolean { return withContext(backgroundDispatcher) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt index e7704d6251689..d119920e1a422 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt @@ -87,7 +87,7 @@ interface KeyguardRepository { val isKeyguardShowing: Flow /** Is the keyguard in a unlocked state? */ - val isKeyguardUnlocked: Flow + val isKeyguardUnlocked: StateFlow /** Is an activity showing over the keyguard? */ val isKeyguardOccluded: Flow @@ -299,7 +299,7 @@ constructor( } .distinctUntilChanged() - override val isKeyguardUnlocked: Flow = + override val isKeyguardUnlocked: StateFlow = conflatedCallbackFlow { val callback = object : KeyguardStateController.Callback { @@ -330,7 +330,11 @@ constructor( awaitClose { keyguardStateController.removeCallback(callback) } } - .distinctUntilChanged() + .stateIn( + scope = scope, + started = SharingStarted.WhileSubscribed(), + initialValue = keyguardStateController.isUnlocked, + ) override val isKeyguardGoingAway: Flow = conflatedCallbackFlow { val callback = diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartable.kt index 59f82f0347234..285ff74f8b2e2 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartable.kt @@ -22,6 +22,8 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.shared.model.WakefulnessState import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.model.SceneContainerNames import com.android.systemui.scene.shared.model.SceneKey @@ -45,6 +47,7 @@ constructor( @Application private val applicationScope: CoroutineScope, private val sceneInteractor: SceneInteractor, private val authenticationInteractor: AuthenticationInteractor, + private val keyguardInteractor: KeyguardInteractor, private val featureFlags: FeatureFlags, ) : CoreStartable { @@ -78,7 +81,7 @@ constructor( when { isUnlocked -> when (currentSceneKey) { - // When the device becomes unlocked in Bouncer, go to the Gone. + // When the device becomes unlocked in Bouncer, go to Gone. is SceneKey.Bouncer -> SceneKey.Gone // When the device becomes unlocked in Lockscreen, go to Gone if // bypass is enabled. @@ -101,15 +104,30 @@ constructor( } } .filterNotNull() - .collect { targetSceneKey -> - sceneInteractor.setCurrentScene( - containerName = CONTAINER_NAME, - scene = SceneModel(targetSceneKey), - ) + .collect { targetSceneKey -> switchToScene(targetSceneKey) } + } + + applicationScope.launch { + keyguardInteractor.wakefulnessModel + .map { it.state == WakefulnessState.ASLEEP } + .distinctUntilChanged() + .collect { isAsleep -> + if (isAsleep) { + // When the device goes to sleep, reset the current scene. + val isUnlocked = authenticationInteractor.isUnlocked.value + switchToScene(if (isUnlocked) SceneKey.Gone else SceneKey.Lockscreen) + } } } } + private fun switchToScene(targetSceneKey: SceneKey) { + sceneInteractor.setCurrentScene( + containerName = CONTAINER_NAME, + scene = SceneModel(targetSceneKey), + ) + } + companion object { private const val CONTAINER_NAME = SceneContainerNames.SYSTEM_UI_DEFAULT } diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartableTest.kt index df5e7bcd16be3..3e9ddcb06389b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartableTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartableTest.kt @@ -20,6 +20,9 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.shared.model.WakeSleepReason +import com.android.systemui.keyguard.shared.model.WakefulnessModel +import com.android.systemui.keyguard.shared.model.WakefulnessState import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.shared.model.SceneContainerNames import com.android.systemui.scene.shared.model.SceneKey @@ -47,12 +50,18 @@ class SystemUiDefaultSceneContainerStartableTest : SysuiTestCase() { utils.authenticationInteractor( repository = authenticationRepository, ) + private val keyguardRepository = utils.keyguardRepository() + private val keyguardInteractor = + utils.keyguardInteractor( + repository = keyguardRepository, + ) private val underTest = SystemUiDefaultSceneContainerStartable( applicationScope = testScope.backgroundScope, sceneInteractor = sceneInteractor, authenticationInteractor = authenticationInteractor, + keyguardInteractor = keyguardInteractor, featureFlags = featureFlags, ) @@ -280,6 +289,94 @@ class SystemUiDefaultSceneContainerStartableTest : SysuiTestCase() { assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) } + @Test + fun switchToGoneWhenDeviceSleepsUnlocked_featureEnabled() = + testScope.runTest { + val currentSceneKey by + collectLastValue( + sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map { + it.key + } + ) + prepareState( + isFeatureEnabled = true, + isDeviceUnlocked = true, + initialSceneKey = SceneKey.Shade, + ) + assertThat(currentSceneKey).isEqualTo(SceneKey.Shade) + underTest.start() + + keyguardRepository.setWakefulnessModel(ASLEEP) + + assertThat(currentSceneKey).isEqualTo(SceneKey.Gone) + } + + @Test + fun switchToGoneWhenDeviceSleepsUnlocked_featureDisabled() = + testScope.runTest { + val currentSceneKey by + collectLastValue( + sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map { + it.key + } + ) + prepareState( + isFeatureEnabled = false, + isDeviceUnlocked = true, + initialSceneKey = SceneKey.Shade, + ) + assertThat(currentSceneKey).isEqualTo(SceneKey.Shade) + underTest.start() + + keyguardRepository.setWakefulnessModel(ASLEEP) + + assertThat(currentSceneKey).isEqualTo(SceneKey.Shade) + } + + @Test + fun switchToLockscreenWhenDeviceSleepsLocked_featureEnabled() = + testScope.runTest { + val currentSceneKey by + collectLastValue( + sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map { + it.key + } + ) + prepareState( + isFeatureEnabled = true, + isDeviceUnlocked = false, + initialSceneKey = SceneKey.Shade, + ) + assertThat(currentSceneKey).isEqualTo(SceneKey.Shade) + underTest.start() + + keyguardRepository.setWakefulnessModel(ASLEEP) + + assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) + } + + @Test + fun switchToLockscreenWhenDeviceSleepsLocked_featureDisabled() = + testScope.runTest { + val currentSceneKey by + collectLastValue( + sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map { + it.key + } + ) + prepareState( + isFeatureEnabled = false, + isDeviceUnlocked = false, + initialSceneKey = SceneKey.Shade, + ) + assertThat(currentSceneKey).isEqualTo(SceneKey.Shade) + underTest.start() + + keyguardRepository.setWakefulnessModel(ASLEEP) + + assertThat(currentSceneKey).isEqualTo(SceneKey.Shade) + } + private fun prepareState( isFeatureEnabled: Boolean = true, isDeviceUnlocked: Boolean = false, @@ -293,4 +390,13 @@ class SystemUiDefaultSceneContainerStartableTest : SysuiTestCase() { sceneInteractor.setCurrentScene(SceneContainerNames.SYSTEM_UI_DEFAULT, SceneModel(it)) } } + + companion object { + private val ASLEEP = + WakefulnessModel( + state = WakefulnessState.ASLEEP, + lastWakeReason = WakeSleepReason.POWER_BUTTON, + lastSleepReason = WakeSleepReason.POWER_BUTTON + ) + } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt index 60a4951735c51..63097401bc5a2 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt @@ -48,7 +48,7 @@ class FakeKeyguardRepository : KeyguardRepository { override val isKeyguardShowing: Flow = _isKeyguardShowing private val _isKeyguardUnlocked = MutableStateFlow(false) - override val isKeyguardUnlocked: Flow = _isKeyguardUnlocked + override val isKeyguardUnlocked: StateFlow = _isKeyguardUnlocked.asStateFlow() private val _isKeyguardOccluded = MutableStateFlow(false) override val isKeyguardOccluded: Flow = _isKeyguardOccluded diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt index d797962bae8b8..47e1daf4008c5 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt @@ -22,11 +22,20 @@ import com.android.systemui.authentication.data.repository.AuthenticationReposit import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor import com.android.systemui.bouncer.data.repository.BouncerRepository +import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.data.repository.FakeCommandQueue +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.KeyguardRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor +import com.android.systemui.keyguard.shared.model.WakeSleepReason +import com.android.systemui.keyguard.shared.model.WakefulnessModel +import com.android.systemui.keyguard.shared.model.WakefulnessState import com.android.systemui.scene.data.repository.SceneContainerRepository import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.model.SceneContainerConfig @@ -53,7 +62,11 @@ class SceneTestUtils( ) { val testDispatcher = StandardTestDispatcher() val testScope = TestScope(testDispatcher) - val featureFlags = FakeFeatureFlags().apply { set(Flags.SCENE_CONTAINER, true) } + val featureFlags = + FakeFeatureFlags().apply { + set(Flags.SCENE_CONTAINER, true) + set(Flags.FACE_AUTH_REFACTOR, false) + } private val userRepository: UserRepository by lazy { FakeUserRepository().apply { val users = listOf(UserInfo(/* id= */ 0, "name", /* flags= */ 0)) @@ -67,6 +80,17 @@ class SceneTestUtils( currentTime = { testScope.currentTime }, ) } + val keyguardRepository: FakeKeyguardRepository by lazy { + FakeKeyguardRepository().apply { + setWakefulnessModel( + WakefulnessModel( + WakefulnessState.AWAKE, + WakeSleepReason.OTHER, + WakeSleepReason.OTHER, + ) + ) + } + } private val context = test.context fun fakeSceneContainerRepository( @@ -122,6 +146,20 @@ class SceneTestUtils( ) } + fun keyguardRepository(): FakeKeyguardRepository { + return keyguardRepository + } + + fun keyguardInteractor(repository: KeyguardRepository): KeyguardInteractor { + return KeyguardInteractor( + repository = repository, + commandQueue = FakeCommandQueue(), + featureFlags = featureFlags, + bouncerRepository = FakeKeyguardBouncerRepository(), + configurationRepository = FakeConfigurationRepository() + ) + } + fun bouncerInteractor( authenticationInteractor: AuthenticationInteractor, sceneInteractor: SceneInteractor,