[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
This commit is contained in:
burakov
2023-07-11 21:36:03 +00:00
committed by Danny Burakov
parent 929cb5c3a3
commit 9a5b360c11
6 changed files with 178 additions and 17 deletions

View File

@@ -144,12 +144,7 @@ constructor(
private val lockPatternUtils: LockPatternUtils,
) : AuthenticationRepository {
override val isUnlocked: StateFlow<Boolean> =
keyguardRepository.isKeyguardUnlocked.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
initialValue = false,
)
override val isUnlocked: StateFlow<Boolean> = keyguardRepository.isKeyguardUnlocked
override suspend fun isLockscreenEnabled(): Boolean {
return withContext(backgroundDispatcher) {

View File

@@ -87,7 +87,7 @@ interface KeyguardRepository {
val isKeyguardShowing: Flow<Boolean>
/** Is the keyguard in a unlocked state? */
val isKeyguardUnlocked: Flow<Boolean>
val isKeyguardUnlocked: StateFlow<Boolean>
/** Is an activity showing over the keyguard? */
val isKeyguardOccluded: Flow<Boolean>
@@ -299,7 +299,7 @@ constructor(
}
.distinctUntilChanged()
override val isKeyguardUnlocked: Flow<Boolean> =
override val isKeyguardUnlocked: StateFlow<Boolean> =
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<Boolean> = conflatedCallbackFlow {
val callback =

View File

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

View File

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

View File

@@ -48,7 +48,7 @@ class FakeKeyguardRepository : KeyguardRepository {
override val isKeyguardShowing: Flow<Boolean> = _isKeyguardShowing
private val _isKeyguardUnlocked = MutableStateFlow(false)
override val isKeyguardUnlocked: Flow<Boolean> = _isKeyguardUnlocked
override val isKeyguardUnlocked: StateFlow<Boolean> = _isKeyguardUnlocked.asStateFlow()
private val _isKeyguardOccluded = MutableStateFlow(false)
override val isKeyguardOccluded: Flow<Boolean> = _isKeyguardOccluded

View File

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