From a6894d2dcbf6960f7c62af385c7a704bf1ef035e Mon Sep 17 00:00:00 2001 From: Alejandro Nijamkin Date: Mon, 7 Aug 2023 16:14:30 -0700 Subject: [PATCH] [flexiglass] Minor tweaks to support Trust Managers. The "trust manager" is a system that allows the user to select friendly networks, devices, or locations, where their device can become more relaxed and only automatically lock after 4 hours or when explicitly locked using the power menu's "lockdown" option. Since the trust system occurs in the upstream, Flexiglass didn't need to make any changes to have it work with it. The device simply doesn't enter locked state or enters lock state through the lockdown power menu option and Flexiglass obeys. The problem is with some transitions that Flexiglass didn't support: 1. Swiping up lockscreen while lockscreen is unlocked should dismiss the lockscreen (even if the auth method is not swipe). This supports dismissing the lockscreen from AOD if, ever needed (likely, this is no needed as the device must be woken up before user input can be processed) 2. (more important than #1), automatically dismiss the lockscreen and go to the Gone scene if the device wakes up while unlocked, regardless of which auth method is currently selected. This prevents the Flexiglass lockscreen scene UI from showing over the unlocked Launcher UI both while the device is unlocked due to the trust manager or due to the lock timeout not having elapsed yet. Fix: 281730986 Fix: 294283030 Test: updated unit tests Test: added new integration test cases Test: manually verified that trust managers and lockdown mode both work (see b/281730986#comment2 for details) Test: manually verified the the screen off + screen on (within less time than the lock timeout) scenario doesn't end up with a lockscreen over the launcher. Change-Id: Ia9d5b620d78c2ad7e47ceb218e7765bdf5eed52d --- .../ui/viewmodel/LockscreenSceneViewModel.kt | 18 +- .../startable/SceneContainerStartable.kt | 24 ++- .../scene/SceneFrameworkIntegrationTest.kt | 46 +++-- .../startable/SceneContainerStartableTest.kt | 159 ++++-------------- .../android/systemui/scene/SceneTestUtils.kt | 15 ++ 5 files changed, 108 insertions(+), 154 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt index 11e85d0d85e16..6d3b7f18e9745 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt @@ -26,6 +26,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.scene.shared.model.SceneKey import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map @@ -51,15 +52,14 @@ constructor( ) /** The key of the scene we should switch to when swiping up. */ - val upDestinationSceneKey = - authenticationInteractor.canSwipeToDismiss - .map { canSwipeToDismiss -> upDestinationSceneKey(canSwipeToDismiss) } - .stateIn( - scope = applicationScope, - started = SharingStarted.WhileSubscribed(), - initialValue = - upDestinationSceneKey(authenticationInteractor.canSwipeToDismiss.value), - ) + val upDestinationSceneKey: Flow = + authenticationInteractor.isUnlocked.map { isUnlocked -> + if (isUnlocked) { + SceneKey.Gone + } else { + SceneKey.Bouncer + } + } /** Notifies that the lock button on the lock screen was clicked. */ fun onLockButtonClicked() { diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt index afefccb272146..17470998cf74a 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt @@ -178,12 +178,24 @@ constructor( } WakefulnessState.STARTING_TO_WAKE -> { val authMethod = authenticationInteractor.getAuthenticationMethod() - if (authMethod == AuthenticationMethodModel.None) { - switchToScene( - targetSceneKey = SceneKey.Gone, - loggingReason = - "device is starting to wake up while auth method is None", - ) + val isUnlocked = authenticationInteractor.isUnlocked.value + when { + authMethod == AuthenticationMethodModel.None -> { + switchToScene( + targetSceneKey = SceneKey.Gone, + loggingReason = + "device is starting to wake up while auth method is" + + " none", + ) + } + authMethod.isSecure && isUnlocked -> { + switchToScene( + targetSceneKey = SceneKey.Gone, + loggingReason = + "device is starting to wake up while unlocked with a" + + " secure auth method", + ) + } } } else -> Unit diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index a537121758ea7..8caf6dc3e28aa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -20,7 +20,6 @@ package com.android.systemui.scene import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.authentication.data.model.AuthenticationMethodModel as DataLayerAuthenticationMethodModel import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel import com.android.systemui.authentication.domain.model.AuthenticationMethodModel @@ -31,6 +30,7 @@ import com.android.systemui.flags.Flags import com.android.systemui.keyguard.shared.model.WakefulnessState import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel import com.android.systemui.model.SysUiState +import com.android.systemui.scene.SceneTestUtils.Companion.toDataLayer import com.android.systemui.scene.domain.startable.SceneContainerStartable import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey @@ -211,6 +211,17 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { assertCurrentScene(SceneKey.Gone) } + @Test + fun withAuthMethodSwipe_deviceWakeUp_doesNotSkipLockscreen() = + testScope.runTest { + setAuthMethod(AuthenticationMethodModel.Swipe) + putDeviceToSleep(instantlyLockDevice = false) + assertCurrentScene(SceneKey.Lockscreen) + + wakeUpDevice() + assertCurrentScene(SceneKey.Lockscreen) + } + @Test fun deviceGoesToSleep_switchesToLockscreen() = testScope.runTest { @@ -235,6 +246,26 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { assertCurrentScene(SceneKey.Gone) } + @Test + fun deviceWakesUpWhileUnlocked_dismissesLockscreen() = + testScope.runTest { + unlockDevice() + assertCurrentScene(SceneKey.Gone) + putDeviceToSleep(instantlyLockDevice = false) + assertCurrentScene(SceneKey.Lockscreen) + wakeUpDevice() + assertCurrentScene(SceneKey.Gone) + } + + @Test + fun swipeUpOnLockscreenWhileUnlocked_dismissesLockscreen() = + testScope.runTest { + unlockDevice() + val upDestinationSceneKey by + collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey) + assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Gone) + } + @Test fun deviceGoesToSleep_withLockTimeout_staysOnLockscreen() = testScope.runTest { @@ -462,17 +493,4 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { lockDevice() } } - - private fun DomainLayerAuthenticationMethodModel.toDataLayer(): - DataLayerAuthenticationMethodModel { - return when (this) { - DomainLayerAuthenticationMethodModel.None -> DataLayerAuthenticationMethodModel.None - DomainLayerAuthenticationMethodModel.Swipe -> DataLayerAuthenticationMethodModel.None - DomainLayerAuthenticationMethodModel.Pin -> DataLayerAuthenticationMethodModel.Pin - DomainLayerAuthenticationMethodModel.Password -> - DataLayerAuthenticationMethodModel.Password - DomainLayerAuthenticationMethodModel.Pattern -> - DataLayerAuthenticationMethodModel.Pattern - } - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index 45db7a0b17f15..951cadd7664e5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt @@ -21,7 +21,7 @@ package com.android.systemui.scene.domain.startable import android.view.Display import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.authentication.data.model.AuthenticationMethodModel +import com.android.systemui.authentication.domain.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.Flags import com.android.systemui.keyguard.shared.model.WakeSleepReason @@ -29,6 +29,7 @@ import com.android.systemui.keyguard.shared.model.WakefulnessModel import com.android.systemui.keyguard.shared.model.WakefulnessState import com.android.systemui.model.SysUiState import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.scene.SceneTestUtils.Companion.toDataLayer import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel @@ -80,14 +81,13 @@ class SceneContainerStartableTest : SysuiTestCase() { ) @Test - fun hydrateVisibility_featureEnabled() = + fun hydrateVisibility() = testScope.runTest { val currentDesiredSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) val isVisible by collectLastValue(sceneInteractor.isVisible) val transitionStateFlow = prepareState( - isFeatureEnabled = true, isDeviceUnlocked = true, initialSceneKey = SceneKey.Gone, ) @@ -123,44 +123,10 @@ class SceneContainerStartableTest : SysuiTestCase() { } @Test - fun hydrateVisibility_featureDisabled() = - testScope.runTest { - val currentDesiredSceneKey by - collectLastValue(sceneInteractor.desiredScene.map { it.key }) - val isVisible by collectLastValue(sceneInteractor.isVisible) - val transitionStateFlow = - prepareState( - isFeatureEnabled = false, - isDeviceUnlocked = true, - initialSceneKey = SceneKey.Gone, - ) - assertThat(currentDesiredSceneKey).isEqualTo(SceneKey.Gone) - assertThat(isVisible).isTrue() - - underTest.start() - - assertThat(isVisible).isTrue() - - sceneInteractor.changeScene(SceneModel(SceneKey.Shade), "reason") - transitionStateFlow.value = - ObservableTransitionState.Transition( - fromScene = SceneKey.Gone, - toScene = SceneKey.Shade, - progress = flowOf(0.5f), - ) - assertThat(isVisible).isTrue() - - sceneInteractor.onSceneChanged(SceneModel(SceneKey.Shade), "reason") - transitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Shade) - assertThat(isVisible).isTrue() - } - - @Test - fun switchToLockscreenWhenDeviceLocks_featureEnabled() = + fun switchToLockscreenWhenDeviceLocks() = testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( - isFeatureEnabled = true, isDeviceUnlocked = true, initialSceneKey = SceneKey.Gone, ) @@ -173,28 +139,10 @@ class SceneContainerStartableTest : SysuiTestCase() { } @Test - fun switchToLockscreenWhenDeviceLocks_featureDisabled() = + fun switchFromBouncerToGoneWhenDeviceUnlocked() = testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( - isFeatureEnabled = false, - isDeviceUnlocked = false, - initialSceneKey = SceneKey.Gone, - ) - assertThat(currentSceneKey).isEqualTo(SceneKey.Gone) - underTest.start() - - authenticationRepository.setUnlocked(false) - - assertThat(currentSceneKey).isEqualTo(SceneKey.Gone) - } - - @Test - fun switchFromBouncerToGoneWhenDeviceUnlocked_featureEnabled() = - testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) - prepareState( - isFeatureEnabled = true, isDeviceUnlocked = false, initialSceneKey = SceneKey.Bouncer, ) @@ -207,28 +155,10 @@ class SceneContainerStartableTest : SysuiTestCase() { } @Test - fun switchFromBouncerToGoneWhenDeviceUnlocked_featureDisabled() = + fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn() = testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( - isFeatureEnabled = false, - isDeviceUnlocked = false, - initialSceneKey = SceneKey.Bouncer, - ) - assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer) - underTest.start() - - authenticationRepository.setUnlocked(true) - - assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer) - } - - @Test - fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOn_bypassOn() = - testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) - prepareState( - isFeatureEnabled = true, isBypassEnabled = true, initialSceneKey = SceneKey.Lockscreen, ) @@ -241,11 +171,10 @@ class SceneContainerStartableTest : SysuiTestCase() { } @Test - fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOn_bypassOff() = + fun stayOnLockscreenWhenDeviceUnlocksWithBypassOff() = testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( - isFeatureEnabled = true, isBypassEnabled = false, initialSceneKey = SceneKey.Lockscreen, ) @@ -258,28 +187,10 @@ class SceneContainerStartableTest : SysuiTestCase() { } @Test - fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOff_bypassOn() = + fun switchToLockscreenWhenDeviceSleepsLocked() = testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( - isFeatureEnabled = false, - isBypassEnabled = true, - initialSceneKey = SceneKey.Lockscreen, - ) - assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) - underTest.start() - - authenticationRepository.setUnlocked(true) - - assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) - } - - @Test - fun switchToLockscreenWhenDeviceSleepsLocked_featureEnabled() = - testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) - prepareState( - isFeatureEnabled = true, isDeviceUnlocked = false, initialSceneKey = SceneKey.Shade, ) @@ -291,23 +202,6 @@ class SceneContainerStartableTest : SysuiTestCase() { assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) } - @Test - fun switchToLockscreenWhenDeviceSleepsLocked_featureDisabled() = - testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) - prepareState( - isFeatureEnabled = false, - isDeviceUnlocked = false, - initialSceneKey = SceneKey.Shade, - ) - assertThat(currentSceneKey).isEqualTo(SceneKey.Shade) - underTest.start() - - keyguardRepository.setWakefulnessModel(STARTING_TO_SLEEP) - - assertThat(currentSceneKey).isEqualTo(SceneKey.Shade) - } - @Test fun hydrateSystemUiState() = testScope.runTest { @@ -339,11 +233,10 @@ class SceneContainerStartableTest : SysuiTestCase() { } @Test - fun switchToGoneWhenDeviceStartsToWakeUp_authMethodNone_featureEnabled() = + fun switchToGoneWhenDeviceStartsToWakeUp_authMethodNone() = testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( - isFeatureEnabled = true, initialSceneKey = SceneKey.Lockscreen, authenticationMethod = AuthenticationMethodModel.None, ) @@ -356,11 +249,26 @@ class SceneContainerStartableTest : SysuiTestCase() { } @Test - fun switchToGoneWhenDeviceStartsToWakeUp_authMethodNotNone_featureEnabled() = + fun stayOnLockscreenWhenDeviceStartsToWakeUp_authMethodSwipe() = + testScope.runTest { + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) + prepareState( + initialSceneKey = SceneKey.Lockscreen, + authenticationMethod = AuthenticationMethodModel.Swipe, + ) + assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) + underTest.start() + + keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE) + + assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) + } + + @Test + fun doesNotSwitchToGoneWhenDeviceStartsToWakeUp_authMethodSecure() = testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( - isFeatureEnabled = true, initialSceneKey = SceneKey.Lockscreen, authenticationMethod = AuthenticationMethodModel.Pin, ) @@ -373,30 +281,31 @@ class SceneContainerStartableTest : SysuiTestCase() { } @Test - fun switchToGoneWhenDeviceStartsToWakeUp_authMethodNone_featureDisabled() = + fun switchToGoneWhenDeviceStartsToWakeUp_authMethodSecure_deviceUnlocked() = testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( - isFeatureEnabled = false, initialSceneKey = SceneKey.Lockscreen, - authenticationMethod = AuthenticationMethodModel.None, + authenticationMethod = AuthenticationMethodModel.Pin, + isDeviceUnlocked = false, ) assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) underTest.start() + authenticationRepository.setUnlocked(true) + runCurrent() keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE) - assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) + assertThat(currentSceneKey).isEqualTo(SceneKey.Gone) } private fun prepareState( - isFeatureEnabled: Boolean = true, isDeviceUnlocked: Boolean = false, isBypassEnabled: Boolean = false, initialSceneKey: SceneKey? = null, authenticationMethod: AuthenticationMethodModel? = null, ): MutableStateFlow { - featureFlags.set(Flags.SCENE_CONTAINER, isFeatureEnabled) + featureFlags.set(Flags.SCENE_CONTAINER, true) authenticationRepository.setUnlocked(isDeviceUnlocked) keyguardRepository.setBypassEnabled(isBypassEnabled) val transitionStateFlow = @@ -410,7 +319,7 @@ class SceneContainerStartableTest : SysuiTestCase() { sceneInteractor.onSceneChanged(SceneModel(it), "reason") } authenticationMethod?.let { - authenticationRepository.setAuthenticationMethod(authenticationMethod) + authenticationRepository.setAuthenticationMethod(authenticationMethod.toDataLayer()) authenticationRepository.setLockscreenEnabled( authenticationMethod != AuthenticationMethodModel.None ) 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 0829f31e3890d..893bbf38ff265 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 @@ -18,9 +18,11 @@ package com.android.systemui.scene import android.content.pm.UserInfo import com.android.systemui.SysuiTestCase +import com.android.systemui.authentication.data.model.AuthenticationMethodModel as DataLayerAuthenticationMethodModel import com.android.systemui.authentication.data.repository.AuthenticationRepository import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor +import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel import com.android.systemui.bouncer.data.repository.BouncerRepository import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository import com.android.systemui.bouncer.domain.interactor.BouncerInteractor @@ -201,5 +203,18 @@ class SceneTestUtils( RemoteUserInput(10f, 40f, RemoteUserInputAction.MOVE), RemoteUserInput(10f, 40f, RemoteUserInputAction.UP), ) + + fun DomainLayerAuthenticationMethodModel.toDataLayer(): DataLayerAuthenticationMethodModel { + return when (this) { + DomainLayerAuthenticationMethodModel.None -> DataLayerAuthenticationMethodModel.None + DomainLayerAuthenticationMethodModel.Swipe -> + DataLayerAuthenticationMethodModel.None + DomainLayerAuthenticationMethodModel.Pin -> DataLayerAuthenticationMethodModel.Pin + DomainLayerAuthenticationMethodModel.Password -> + DataLayerAuthenticationMethodModel.Password + DomainLayerAuthenticationMethodModel.Pattern -> + DataLayerAuthenticationMethodModel.Pattern + } + } } }