Loading packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +55 −0 Original line number Diff line number Diff line Loading @@ -44,6 +44,7 @@ import com.android.systemui.biometrics.data.repository.fingerprintPropertyReposi import com.android.systemui.biometrics.shared.model.FingerprintSensorType import com.android.systemui.biometrics.shared.model.SensorStrength import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.bouncerInteractor import com.android.systemui.bouncer.shared.logging.BouncerUiEvent import com.android.systemui.classifier.FalsingCollector Loading @@ -54,6 +55,7 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository import com.android.systemui.deviceentry.domain.interactor.deviceEntryHapticsInteractor import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus import com.android.systemui.flags.EnableSceneContainer Loading Loading @@ -114,6 +116,7 @@ import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runCurrent Loading Loading @@ -2356,6 +2359,58 @@ class SceneContainerStartableTest : SysuiTestCase() { assertThat(isLockscreenEnabled).isTrue() } @Test fun replacesLockscreenSceneOnBackStack_whenUnlockdViaAlternateBouncer_fromShade() = testScope.runTest { val transitionState = prepareState( isDeviceUnlocked = false, initialSceneKey = Scenes.Lockscreen, authenticationMethod = AuthenticationMethodModel.Pin, ) underTest.start() val isUnlocked by collectLastValue( kosmos.deviceUnlockedInteractor.deviceUnlockStatus.map { it.isUnlocked } ) val currentScene by collectLastValue(sceneInteractor.currentScene) val backStack by collectLastValue(sceneBackInteractor.backStack) val isAlternateBouncerVisible by collectLastValue(kosmos.alternateBouncerInteractor.isVisible) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(Scenes.Lockscreen) assertThat(isAlternateBouncerVisible).isFalse() // Change to shade. sceneInteractor.changeScene(Scenes.Shade, "") transitionState.value = ObservableTransitionState.Idle(Scenes.Shade) runCurrent() assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(Scenes.Shade) assertThat(backStack?.asIterable()?.first()).isEqualTo(Scenes.Lockscreen) assertThat(isAlternateBouncerVisible).isFalse() // Show the alternate bouncer. kosmos.alternateBouncerInteractor.forceShow() kosmos.sysuiStatusBarStateController.leaveOpen = true // leave shade open runCurrent() assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(Scenes.Shade) assertThat(backStack?.asIterable()?.first()).isEqualTo(Scenes.Lockscreen) assertThat(isAlternateBouncerVisible).isTrue() // Trigger a fingerprint unlock. kosmos.deviceEntryFingerprintAuthRepository.setAuthenticationStatus( SuccessFingerprintAuthenticationStatus(0, true) ) runCurrent() assertThat(isUnlocked).isTrue() assertThat(currentScene).isEqualTo(Scenes.Shade) assertThat(backStack?.asIterable()?.first()).isEqualTo(Scenes.Gone) assertThat(isAlternateBouncerVisible).isFalse() } private fun TestScope.emulateSceneTransition( transitionStateFlow: MutableStateFlow<ObservableTransitionState>, toScene: SceneKey, Loading packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +16 −10 Original line number Diff line number Diff line Loading @@ -373,6 +373,7 @@ constructor( "device was unlocked with alternate bouncer showing" + " and shade didn't need to be left open" } else { replaceLockscreenSceneOnBackStack() null } } Loading @@ -391,16 +392,7 @@ constructor( val prevScene = previousScene.value val targetScene = prevScene ?: Scenes.Gone if (targetScene != Scenes.Gone) { sceneBackInteractor.updateBackStack { stack -> val list = stack.asIterable().toMutableList() check(list.last() == Scenes.Lockscreen) { "The bottommost/last SceneKey of the back stack isn't" + " the Lockscreen scene like expected. The back" + " stack is $stack." } list[list.size - 1] = Scenes.Gone sceneStackOf(*list.toTypedArray()) } replaceLockscreenSceneOnBackStack() } targetScene to "device was unlocked with primary bouncer showing," + Loading Loading @@ -435,6 +427,20 @@ constructor( } } /** If the [Scenes.Lockscreen] is on the backstack, replaces it with [Scenes.Gone]. */ private fun replaceLockscreenSceneOnBackStack() { sceneBackInteractor.updateBackStack { stack -> val list = stack.asIterable().toMutableList() check(list.last() == Scenes.Lockscreen) { "The bottommost/last SceneKey of the back stack isn't" + " the Lockscreen scene like expected. The back" + " stack is $stack." } list[list.size - 1] = Scenes.Gone sceneStackOf(*list.toTypedArray()) } } private fun handlePowerState() { applicationScope.launch { powerInteractor.detailedWakefulness.collect { wakefulness -> Loading packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +7 −0 Original line number Diff line number Diff line Loading @@ -520,6 +520,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mListenForCanShowAlternateBouncer.cancel(null); } mListenForCanShowAlternateBouncer = null; // Collector that keeps the AlternateBouncerInteractor#canShowAlternateBouncer flow hot. mListenForCanShowAlternateBouncer = mJavaAdapter.alwaysCollectFlow( mAlternateBouncerInteractor.getCanShowAlternateBouncer(), Loading Loading @@ -568,6 +569,12 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } private void consumeCanShowAlternateBouncer(boolean canShow) { if (SceneContainerFlag.isEnabled()) { // When the scene framework is enabled, the alternative bouncer is hidden from the scene // framework logic so there's no need for this logic here. return; } // Hack: this is required to fix issues where // KeyguardBouncerRepository#alternateBouncerVisible state is incorrectly set and then never // reset. This is caused by usages of show()/forceShow() that only read this flow to set the Loading Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +55 −0 Original line number Diff line number Diff line Loading @@ -44,6 +44,7 @@ import com.android.systemui.biometrics.data.repository.fingerprintPropertyReposi import com.android.systemui.biometrics.shared.model.FingerprintSensorType import com.android.systemui.biometrics.shared.model.SensorStrength import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.bouncerInteractor import com.android.systemui.bouncer.shared.logging.BouncerUiEvent import com.android.systemui.classifier.FalsingCollector Loading @@ -54,6 +55,7 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository import com.android.systemui.deviceentry.domain.interactor.deviceEntryHapticsInteractor import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus import com.android.systemui.flags.EnableSceneContainer Loading Loading @@ -114,6 +116,7 @@ import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runCurrent Loading Loading @@ -2356,6 +2359,58 @@ class SceneContainerStartableTest : SysuiTestCase() { assertThat(isLockscreenEnabled).isTrue() } @Test fun replacesLockscreenSceneOnBackStack_whenUnlockdViaAlternateBouncer_fromShade() = testScope.runTest { val transitionState = prepareState( isDeviceUnlocked = false, initialSceneKey = Scenes.Lockscreen, authenticationMethod = AuthenticationMethodModel.Pin, ) underTest.start() val isUnlocked by collectLastValue( kosmos.deviceUnlockedInteractor.deviceUnlockStatus.map { it.isUnlocked } ) val currentScene by collectLastValue(sceneInteractor.currentScene) val backStack by collectLastValue(sceneBackInteractor.backStack) val isAlternateBouncerVisible by collectLastValue(kosmos.alternateBouncerInteractor.isVisible) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(Scenes.Lockscreen) assertThat(isAlternateBouncerVisible).isFalse() // Change to shade. sceneInteractor.changeScene(Scenes.Shade, "") transitionState.value = ObservableTransitionState.Idle(Scenes.Shade) runCurrent() assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(Scenes.Shade) assertThat(backStack?.asIterable()?.first()).isEqualTo(Scenes.Lockscreen) assertThat(isAlternateBouncerVisible).isFalse() // Show the alternate bouncer. kosmos.alternateBouncerInteractor.forceShow() kosmos.sysuiStatusBarStateController.leaveOpen = true // leave shade open runCurrent() assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(Scenes.Shade) assertThat(backStack?.asIterable()?.first()).isEqualTo(Scenes.Lockscreen) assertThat(isAlternateBouncerVisible).isTrue() // Trigger a fingerprint unlock. kosmos.deviceEntryFingerprintAuthRepository.setAuthenticationStatus( SuccessFingerprintAuthenticationStatus(0, true) ) runCurrent() assertThat(isUnlocked).isTrue() assertThat(currentScene).isEqualTo(Scenes.Shade) assertThat(backStack?.asIterable()?.first()).isEqualTo(Scenes.Gone) assertThat(isAlternateBouncerVisible).isFalse() } private fun TestScope.emulateSceneTransition( transitionStateFlow: MutableStateFlow<ObservableTransitionState>, toScene: SceneKey, Loading
packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +16 −10 Original line number Diff line number Diff line Loading @@ -373,6 +373,7 @@ constructor( "device was unlocked with alternate bouncer showing" + " and shade didn't need to be left open" } else { replaceLockscreenSceneOnBackStack() null } } Loading @@ -391,16 +392,7 @@ constructor( val prevScene = previousScene.value val targetScene = prevScene ?: Scenes.Gone if (targetScene != Scenes.Gone) { sceneBackInteractor.updateBackStack { stack -> val list = stack.asIterable().toMutableList() check(list.last() == Scenes.Lockscreen) { "The bottommost/last SceneKey of the back stack isn't" + " the Lockscreen scene like expected. The back" + " stack is $stack." } list[list.size - 1] = Scenes.Gone sceneStackOf(*list.toTypedArray()) } replaceLockscreenSceneOnBackStack() } targetScene to "device was unlocked with primary bouncer showing," + Loading Loading @@ -435,6 +427,20 @@ constructor( } } /** If the [Scenes.Lockscreen] is on the backstack, replaces it with [Scenes.Gone]. */ private fun replaceLockscreenSceneOnBackStack() { sceneBackInteractor.updateBackStack { stack -> val list = stack.asIterable().toMutableList() check(list.last() == Scenes.Lockscreen) { "The bottommost/last SceneKey of the back stack isn't" + " the Lockscreen scene like expected. The back" + " stack is $stack." } list[list.size - 1] = Scenes.Gone sceneStackOf(*list.toTypedArray()) } } private fun handlePowerState() { applicationScope.launch { powerInteractor.detailedWakefulness.collect { wakefulness -> Loading
packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +7 −0 Original line number Diff line number Diff line Loading @@ -520,6 +520,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mListenForCanShowAlternateBouncer.cancel(null); } mListenForCanShowAlternateBouncer = null; // Collector that keeps the AlternateBouncerInteractor#canShowAlternateBouncer flow hot. mListenForCanShowAlternateBouncer = mJavaAdapter.alwaysCollectFlow( mAlternateBouncerInteractor.getCanShowAlternateBouncer(), Loading Loading @@ -568,6 +569,12 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } private void consumeCanShowAlternateBouncer(boolean canShow) { if (SceneContainerFlag.isEnabled()) { // When the scene framework is enabled, the alternative bouncer is hidden from the scene // framework logic so there's no need for this logic here. return; } // Hack: this is required to fix issues where // KeyguardBouncerRepository#alternateBouncerVisible state is incorrectly set and then never // reset. This is caused by usages of show()/forceShow() that only read this flow to set the Loading