[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
This commit is contained in:
Alejandro Nijamkin
2023-08-07 16:14:30 -07:00
parent 1bc1d42def
commit a6894d2dcb
5 changed files with 108 additions and 154 deletions

View File

@@ -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<SceneKey> =
authenticationInteractor.isUnlocked.map { isUnlocked ->
if (isUnlocked) {
SceneKey.Gone
} else {
SceneKey.Bouncer
}
}
/** Notifies that the lock button on the lock screen was clicked. */
fun onLockButtonClicked() {

View File

@@ -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

View File

@@ -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
}
}
}

View File

@@ -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<ObservableTransitionState> {
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
)

View File

@@ -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
}
}
}
}