Loading packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt +40 −1 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.systemui.deviceentry.domain.interactor import android.testing.TestableLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository Loading @@ -42,11 +43,16 @@ import com.android.systemui.keyguard.data.repository.fakeTrustRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus import com.android.systemui.kosmos.testScope import com.android.systemui.scene.domain.interactor.sceneBackInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.domain.startable.sceneContainerStartable import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.sysuiStatusBarStateController import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before Loading @@ -66,10 +72,14 @@ class DeviceEntryInteractorTest : SysuiTestCase() { private val trustRepository by lazy { kosmos.fakeTrustRepository } private val sceneInteractor by lazy { kosmos.sceneInteractor } private val authenticationInteractor by lazy { kosmos.authenticationInteractor } private val sceneBackInteractor by lazy { kosmos.sceneBackInteractor } private val sceneContainerStartable by lazy { kosmos.sceneContainerStartable } private val sysuiStatusBarStateController by lazy { kosmos.sysuiStatusBarStateController } private lateinit var underTest: DeviceEntryInteractor @Before fun setUp() { sceneContainerStartable.start() underTest = kosmos.deviceEntryInteractor } Loading Loading @@ -423,8 +433,37 @@ class DeviceEntryInteractorTest : SysuiTestCase() { assertThat(isUnlocked).isTrue() } private fun switchToScene(sceneKey: SceneKey) { @Test fun isDeviceEntered_unlockedWhileOnShade_emitsTrue() = testScope.runTest { val isDeviceEntered by collectLastValue(underTest.isDeviceEntered) assertThat(isDeviceEntered).isFalse() val currentScene by collectLastValue(sceneInteractor.currentScene) assertThat(currentScene).isEqualTo(Scenes.Lockscreen) // Navigate to shade and bouncer: switchToScene(Scenes.Shade) assertThat(currentScene).isEqualTo(Scenes.Shade) // Simulating a "leave it open when the keyguard is hidden" which means the bouncer will // be // shown and successful authentication should take the user back to where they are, the // shade scene. sysuiStatusBarStateController.setLeaveOpenOnKeyguardHide(true) switchToScene(Scenes.Bouncer) assertThat(currentScene).isEqualTo(Scenes.Bouncer) assertThat(isDeviceEntered).isFalse() // Authenticate with PIN to unlock and dismiss the lockscreen: authenticationInteractor.authenticate(FakeAuthenticationRepository.DEFAULT_PIN) runCurrent() assertThat(isDeviceEntered).isTrue() } private fun TestScope.switchToScene(sceneKey: SceneKey) { sceneInteractor.changeScene(sceneKey, "reason") sceneInteractor.setTransitionState(flowOf(ObservableTransitionState.Idle(sceneKey))) runCurrent() } private suspend fun givenCanShowAlternateBouncer() { Loading packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +19 −30 Original line number Diff line number Diff line Loading @@ -161,9 +161,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { val upDestinationSceneKey = (actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer) kosmos.emulateUserDrivenTransition( to = upDestinationSceneKey, ) kosmos.emulateUserDrivenTransition(to = upDestinationSceneKey) kosmos.fakeSceneDataSource.pause() kosmos.enterPin() Loading Loading @@ -226,16 +224,14 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { (actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene assertThat(upDestinationSceneKey).isEqualTo(SceneFamilies.Home) assertThat(homeScene).isEqualTo(Scenes.Gone) kosmos.emulateUserDrivenTransition( to = homeScene, ) kosmos.emulateUserDrivenTransition(to = homeScene) } @Test fun withAuthMethodNone_deviceWakeUp_skipsLockscreen() = testScope.runTest { kosmos.setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = false) kosmos.putDeviceToSleep(instantlyLockDevice = false) kosmos.putDeviceToSleep() kosmos.assertCurrentScene(Scenes.Lockscreen) kosmos.wakeUpDevice() Loading @@ -246,7 +242,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { fun withAuthMethodSwipe_deviceWakeUp_doesNotSkipLockscreen() = testScope.runTest { kosmos.setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true) kosmos.putDeviceToSleep(instantlyLockDevice = false) kosmos.putDeviceToSleep() kosmos.assertCurrentScene(Scenes.Lockscreen) kosmos.wakeUpDevice() Loading Loading @@ -302,7 +298,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { testScope.runTest { kosmos.unlockDevice() kosmos.assertCurrentScene(Scenes.Gone) kosmos.putDeviceToSleep(instantlyLockDevice = false) kosmos.putDeviceToSleep() kosmos.assertCurrentScene(Scenes.Lockscreen) // Pretend like the timeout elapsed and now lock the device. Loading @@ -318,9 +314,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { val upDestinationSceneKey = (actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer) kosmos.emulateUserDrivenTransition( to = upDestinationSceneKey, ) kosmos.emulateUserDrivenTransition(to = upDestinationSceneKey) kosmos.fakeSceneDataSource.pause() kosmos.dismissIme() Loading Loading @@ -388,7 +382,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { kosmos.emulatePendingTransitionProgress(expectedVisible = true) kosmos.enterSimPin( authMethodAfterSimUnlock = AuthenticationMethodModel.None, enableLockscreen = false enableLockscreen = false, ) kosmos.assertCurrentScene(Scenes.Gone) Loading Loading @@ -434,7 +428,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { /** Updates the current authentication method and related states in the data layer. */ private fun Kosmos.setAuthMethod( authMethod: AuthenticationMethodModel, enableLockscreen: Boolean = true enableLockscreen: Boolean = true, ) { if (authMethod.isSecure) { assert(enableLockscreen) { Loading Loading @@ -538,24 +532,27 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { kosmos.fakeSceneDataSource.pause() sceneInteractor.changeScene(to, "reason") emulatePendingTransitionProgress( expectedVisible = to != Scenes.Gone, ) emulatePendingTransitionProgress(expectedVisible = to != Scenes.Gone) } /** * Locks the device immediately (without delay). * Locks the device. * * Asserts the device to be lockable (e.g. that the current authentication is secure). * * Not to be confused with [putDeviceToSleep], which may also instantly lock the device. * Internally emulates a power button press that puts the device to sleep, followed by another * power button press that wakes up the device but is then expected to be in the locked state. */ private suspend fun Kosmos.lockDevice() { val authMethod = authenticationInteractor.getAuthenticationMethod() assertWithMessage("The authentication method of $authMethod is not secure, cannot lock!") .that(authMethod.isSecure) .isTrue() sceneInteractor.changeScene(Scenes.Lockscreen, "") powerInteractor.setAsleepForTest() testScope.runCurrent() powerInteractor.setAwakeForTest() testScope.runCurrent() } Loading @@ -569,9 +566,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { fakeSceneDataSource.pause() enterPin() emulatePendingTransitionProgress( expectedVisible = false, ) emulatePendingTransitionProgress(expectedVisible = false) } /** Loading Loading @@ -645,9 +640,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { } /** Changes device wakefulness state from awake to asleep, going through intermediary states. */ private suspend fun Kosmos.putDeviceToSleep( instantlyLockDevice: Boolean = true, ) { private suspend fun Kosmos.putDeviceToSleep() { val wakefulnessModel = powerInteractor.detailedWakefulness.value assertWithMessage("Cannot put device to sleep as it's already asleep!") .that(wakefulnessModel.isAwake()) Loading @@ -655,10 +648,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { powerInteractor.setAsleepForTest() testScope.runCurrent() if (instantlyLockDevice) { lockDevice() } } /** Emulates the dismissal of the IME (soft keyboard). */ Loading packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorTest.kt +22 −0 Original line number Diff line number Diff line Loading @@ -28,6 +28,8 @@ import com.android.systemui.authentication.domain.interactor.authenticationInter import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.kosmos.testScope import com.android.systemui.scene.data.model.asIterable import com.android.systemui.scene.data.model.sceneStackOf import com.android.systemui.scene.domain.startable.sceneContainerStartable import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos Loading Loading @@ -173,12 +175,32 @@ class SceneBackInteractorTest : SysuiTestCase() { ) } @Test @EnableSceneContainer fun updateBackStack() = testScope.runTest { underTest.onSceneChange(from = Scenes.Lockscreen, to = Scenes.Shade) underTest.onSceneChange(from = Scenes.Shade, to = Scenes.QuickSettings) underTest.onSceneChange(from = Scenes.QuickSettings, to = Scenes.Bouncer) assertThat(underTest.backStack.value.asIterable().toList()) .isEqualTo(listOf(Scenes.QuickSettings, Scenes.Shade, Scenes.Lockscreen)) underTest.updateBackStack { stack -> // Reverse the stack, just to see if it can be done: sceneStackOf(*stack.asIterable().reversed().toTypedArray()) } assertThat(underTest.backStack.value.asIterable().toList()) .isEqualTo(listOf(Scenes.Lockscreen, Scenes.Shade, Scenes.QuickSettings)) } private suspend fun TestScope.assertRoute(vararg route: RouteNode) { val currentScene by collectLastValue(sceneInteractor.currentScene) val backScene by collectLastValue(underTest.backScene) route.forEachIndexed { index, node -> sceneInteractor.changeScene(node.changeSceneTo, "") runCurrent() assertWithMessage("node at index $index currentScene mismatch") .that(currentScene) .isEqualTo(node.changeSceneTo) Loading packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +77 −107 File changed.Preview size limit exceeded, changes collapsed. Show changes packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt +41 −17 Original line number Diff line number Diff line Loading @@ -24,8 +24,11 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.scene.data.model.asIterable import com.android.systemui.scene.domain.interactor.SceneBackInteractor import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.util.kotlin.pairwise import com.android.systemui.utils.coroutines.flow.mapLatestConflated import javax.inject.Inject import kotlinx.coroutines.CoroutineScope Loading @@ -34,6 +37,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map Loading @@ -59,6 +63,7 @@ constructor( private val deviceUnlockedInteractor: DeviceUnlockedInteractor, private val alternateBouncerInteractor: AlternateBouncerInteractor, private val dismissCallbackRegistry: DismissCallbackRegistry, sceneBackInteractor: SceneBackInteractor, ) { /** * Whether the device is unlocked. Loading Loading @@ -86,19 +91,40 @@ constructor( * Note: This does not imply that the lockscreen is visible or not. */ val isDeviceEntered: StateFlow<Boolean> = combine( // This flow emits true when the currentScene switches to Gone for the first time // after having been on Lockscreen. sceneInteractor.currentScene .filter { currentScene -> currentScene == Scenes.Gone || currentScene == Scenes.Lockscreen } .mapLatestConflated { scene -> if (scene == Scenes.Gone) { // Make sure device unlock status is definitely unlocked before we consider the // device "entered". // Make sure device unlock status is definitely unlocked before we // consider the device "entered". deviceUnlockedInteractor.deviceUnlockStatus.first { it.isUnlocked } true } else { false } }, // This flow emits true only if the bottom of the navigation back stack has been // switched from Lockscreen to Gone. In other words, only if the device was unlocked // while visiting at least one scene "above" the Lockscreen scene. sceneBackInteractor.backStack // The bottom of the back stack, which is Lockscreen, Gone, or null if empty. .map { it.asIterable().lastOrNull() } // Filter out cases where the stack changes but the bottom remains unchanged. .distinctUntilChanged() // Detect changes of the bottom of the stack, start with null, so the first // update emits a value and the logic doesn't need to wait for a second value // before emitting something. .pairwise(initialValue = null) // Replacing a bottom of the stack that was Lockscreen with Gone constitutes a // "device entered" event. .map { (from, to) -> from == Scenes.Lockscreen && to == Scenes.Gone }, ) { enteredDirectly, enteredOnBackStack -> enteredOnBackStack || enteredDirectly } .stateIn( scope = applicationScope, Loading Loading @@ -129,7 +155,7 @@ constructor( }, isLockscreenEnabled, deviceUnlockedInteractor.deviceUnlockStatus, isDeviceEntered isDeviceEntered, ) { isNoneAuthMethod, isLockscreenEnabled, deviceUnlockStatus, isDeviceEntered -> val isSwipeAuthMethod = isNoneAuthMethod && isLockscreenEnabled (isSwipeAuthMethod || Loading @@ -155,9 +181,7 @@ constructor( * canceled */ @JvmOverloads fun attemptDeviceEntry( callback: IKeyguardDismissCallback? = null, ) { fun attemptDeviceEntry(callback: IKeyguardDismissCallback? = null) { callback?.let { dismissCallbackRegistry.addCallback(it) } // TODO (b/307768356), Loading Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt +40 −1 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.systemui.deviceentry.domain.interactor import android.testing.TestableLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository Loading @@ -42,11 +43,16 @@ import com.android.systemui.keyguard.data.repository.fakeTrustRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus import com.android.systemui.kosmos.testScope import com.android.systemui.scene.domain.interactor.sceneBackInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.domain.startable.sceneContainerStartable import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.sysuiStatusBarStateController import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before Loading @@ -66,10 +72,14 @@ class DeviceEntryInteractorTest : SysuiTestCase() { private val trustRepository by lazy { kosmos.fakeTrustRepository } private val sceneInteractor by lazy { kosmos.sceneInteractor } private val authenticationInteractor by lazy { kosmos.authenticationInteractor } private val sceneBackInteractor by lazy { kosmos.sceneBackInteractor } private val sceneContainerStartable by lazy { kosmos.sceneContainerStartable } private val sysuiStatusBarStateController by lazy { kosmos.sysuiStatusBarStateController } private lateinit var underTest: DeviceEntryInteractor @Before fun setUp() { sceneContainerStartable.start() underTest = kosmos.deviceEntryInteractor } Loading Loading @@ -423,8 +433,37 @@ class DeviceEntryInteractorTest : SysuiTestCase() { assertThat(isUnlocked).isTrue() } private fun switchToScene(sceneKey: SceneKey) { @Test fun isDeviceEntered_unlockedWhileOnShade_emitsTrue() = testScope.runTest { val isDeviceEntered by collectLastValue(underTest.isDeviceEntered) assertThat(isDeviceEntered).isFalse() val currentScene by collectLastValue(sceneInteractor.currentScene) assertThat(currentScene).isEqualTo(Scenes.Lockscreen) // Navigate to shade and bouncer: switchToScene(Scenes.Shade) assertThat(currentScene).isEqualTo(Scenes.Shade) // Simulating a "leave it open when the keyguard is hidden" which means the bouncer will // be // shown and successful authentication should take the user back to where they are, the // shade scene. sysuiStatusBarStateController.setLeaveOpenOnKeyguardHide(true) switchToScene(Scenes.Bouncer) assertThat(currentScene).isEqualTo(Scenes.Bouncer) assertThat(isDeviceEntered).isFalse() // Authenticate with PIN to unlock and dismiss the lockscreen: authenticationInteractor.authenticate(FakeAuthenticationRepository.DEFAULT_PIN) runCurrent() assertThat(isDeviceEntered).isTrue() } private fun TestScope.switchToScene(sceneKey: SceneKey) { sceneInteractor.changeScene(sceneKey, "reason") sceneInteractor.setTransitionState(flowOf(ObservableTransitionState.Idle(sceneKey))) runCurrent() } private suspend fun givenCanShowAlternateBouncer() { Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +19 −30 Original line number Diff line number Diff line Loading @@ -161,9 +161,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { val upDestinationSceneKey = (actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer) kosmos.emulateUserDrivenTransition( to = upDestinationSceneKey, ) kosmos.emulateUserDrivenTransition(to = upDestinationSceneKey) kosmos.fakeSceneDataSource.pause() kosmos.enterPin() Loading Loading @@ -226,16 +224,14 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { (actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene assertThat(upDestinationSceneKey).isEqualTo(SceneFamilies.Home) assertThat(homeScene).isEqualTo(Scenes.Gone) kosmos.emulateUserDrivenTransition( to = homeScene, ) kosmos.emulateUserDrivenTransition(to = homeScene) } @Test fun withAuthMethodNone_deviceWakeUp_skipsLockscreen() = testScope.runTest { kosmos.setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = false) kosmos.putDeviceToSleep(instantlyLockDevice = false) kosmos.putDeviceToSleep() kosmos.assertCurrentScene(Scenes.Lockscreen) kosmos.wakeUpDevice() Loading @@ -246,7 +242,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { fun withAuthMethodSwipe_deviceWakeUp_doesNotSkipLockscreen() = testScope.runTest { kosmos.setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true) kosmos.putDeviceToSleep(instantlyLockDevice = false) kosmos.putDeviceToSleep() kosmos.assertCurrentScene(Scenes.Lockscreen) kosmos.wakeUpDevice() Loading Loading @@ -302,7 +298,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { testScope.runTest { kosmos.unlockDevice() kosmos.assertCurrentScene(Scenes.Gone) kosmos.putDeviceToSleep(instantlyLockDevice = false) kosmos.putDeviceToSleep() kosmos.assertCurrentScene(Scenes.Lockscreen) // Pretend like the timeout elapsed and now lock the device. Loading @@ -318,9 +314,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { val upDestinationSceneKey = (actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer) kosmos.emulateUserDrivenTransition( to = upDestinationSceneKey, ) kosmos.emulateUserDrivenTransition(to = upDestinationSceneKey) kosmos.fakeSceneDataSource.pause() kosmos.dismissIme() Loading Loading @@ -388,7 +382,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { kosmos.emulatePendingTransitionProgress(expectedVisible = true) kosmos.enterSimPin( authMethodAfterSimUnlock = AuthenticationMethodModel.None, enableLockscreen = false enableLockscreen = false, ) kosmos.assertCurrentScene(Scenes.Gone) Loading Loading @@ -434,7 +428,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { /** Updates the current authentication method and related states in the data layer. */ private fun Kosmos.setAuthMethod( authMethod: AuthenticationMethodModel, enableLockscreen: Boolean = true enableLockscreen: Boolean = true, ) { if (authMethod.isSecure) { assert(enableLockscreen) { Loading Loading @@ -538,24 +532,27 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { kosmos.fakeSceneDataSource.pause() sceneInteractor.changeScene(to, "reason") emulatePendingTransitionProgress( expectedVisible = to != Scenes.Gone, ) emulatePendingTransitionProgress(expectedVisible = to != Scenes.Gone) } /** * Locks the device immediately (without delay). * Locks the device. * * Asserts the device to be lockable (e.g. that the current authentication is secure). * * Not to be confused with [putDeviceToSleep], which may also instantly lock the device. * Internally emulates a power button press that puts the device to sleep, followed by another * power button press that wakes up the device but is then expected to be in the locked state. */ private suspend fun Kosmos.lockDevice() { val authMethod = authenticationInteractor.getAuthenticationMethod() assertWithMessage("The authentication method of $authMethod is not secure, cannot lock!") .that(authMethod.isSecure) .isTrue() sceneInteractor.changeScene(Scenes.Lockscreen, "") powerInteractor.setAsleepForTest() testScope.runCurrent() powerInteractor.setAwakeForTest() testScope.runCurrent() } Loading @@ -569,9 +566,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { fakeSceneDataSource.pause() enterPin() emulatePendingTransitionProgress( expectedVisible = false, ) emulatePendingTransitionProgress(expectedVisible = false) } /** Loading Loading @@ -645,9 +640,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { } /** Changes device wakefulness state from awake to asleep, going through intermediary states. */ private suspend fun Kosmos.putDeviceToSleep( instantlyLockDevice: Boolean = true, ) { private suspend fun Kosmos.putDeviceToSleep() { val wakefulnessModel = powerInteractor.detailedWakefulness.value assertWithMessage("Cannot put device to sleep as it's already asleep!") .that(wakefulnessModel.isAwake()) Loading @@ -655,10 +648,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { powerInteractor.setAsleepForTest() testScope.runCurrent() if (instantlyLockDevice) { lockDevice() } } /** Emulates the dismissal of the IME (soft keyboard). */ Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorTest.kt +22 −0 Original line number Diff line number Diff line Loading @@ -28,6 +28,8 @@ import com.android.systemui.authentication.domain.interactor.authenticationInter import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.kosmos.testScope import com.android.systemui.scene.data.model.asIterable import com.android.systemui.scene.data.model.sceneStackOf import com.android.systemui.scene.domain.startable.sceneContainerStartable import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos Loading Loading @@ -173,12 +175,32 @@ class SceneBackInteractorTest : SysuiTestCase() { ) } @Test @EnableSceneContainer fun updateBackStack() = testScope.runTest { underTest.onSceneChange(from = Scenes.Lockscreen, to = Scenes.Shade) underTest.onSceneChange(from = Scenes.Shade, to = Scenes.QuickSettings) underTest.onSceneChange(from = Scenes.QuickSettings, to = Scenes.Bouncer) assertThat(underTest.backStack.value.asIterable().toList()) .isEqualTo(listOf(Scenes.QuickSettings, Scenes.Shade, Scenes.Lockscreen)) underTest.updateBackStack { stack -> // Reverse the stack, just to see if it can be done: sceneStackOf(*stack.asIterable().reversed().toTypedArray()) } assertThat(underTest.backStack.value.asIterable().toList()) .isEqualTo(listOf(Scenes.Lockscreen, Scenes.Shade, Scenes.QuickSettings)) } private suspend fun TestScope.assertRoute(vararg route: RouteNode) { val currentScene by collectLastValue(sceneInteractor.currentScene) val backScene by collectLastValue(underTest.backScene) route.forEachIndexed { index, node -> sceneInteractor.changeScene(node.changeSceneTo, "") runCurrent() assertWithMessage("node at index $index currentScene mismatch") .that(currentScene) .isEqualTo(node.changeSceneTo) Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +77 −107 File changed.Preview size limit exceeded, changes collapsed. Show changes
packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt +41 −17 Original line number Diff line number Diff line Loading @@ -24,8 +24,11 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.scene.data.model.asIterable import com.android.systemui.scene.domain.interactor.SceneBackInteractor import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.util.kotlin.pairwise import com.android.systemui.utils.coroutines.flow.mapLatestConflated import javax.inject.Inject import kotlinx.coroutines.CoroutineScope Loading @@ -34,6 +37,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map Loading @@ -59,6 +63,7 @@ constructor( private val deviceUnlockedInteractor: DeviceUnlockedInteractor, private val alternateBouncerInteractor: AlternateBouncerInteractor, private val dismissCallbackRegistry: DismissCallbackRegistry, sceneBackInteractor: SceneBackInteractor, ) { /** * Whether the device is unlocked. Loading Loading @@ -86,19 +91,40 @@ constructor( * Note: This does not imply that the lockscreen is visible or not. */ val isDeviceEntered: StateFlow<Boolean> = combine( // This flow emits true when the currentScene switches to Gone for the first time // after having been on Lockscreen. sceneInteractor.currentScene .filter { currentScene -> currentScene == Scenes.Gone || currentScene == Scenes.Lockscreen } .mapLatestConflated { scene -> if (scene == Scenes.Gone) { // Make sure device unlock status is definitely unlocked before we consider the // device "entered". // Make sure device unlock status is definitely unlocked before we // consider the device "entered". deviceUnlockedInteractor.deviceUnlockStatus.first { it.isUnlocked } true } else { false } }, // This flow emits true only if the bottom of the navigation back stack has been // switched from Lockscreen to Gone. In other words, only if the device was unlocked // while visiting at least one scene "above" the Lockscreen scene. sceneBackInteractor.backStack // The bottom of the back stack, which is Lockscreen, Gone, or null if empty. .map { it.asIterable().lastOrNull() } // Filter out cases where the stack changes but the bottom remains unchanged. .distinctUntilChanged() // Detect changes of the bottom of the stack, start with null, so the first // update emits a value and the logic doesn't need to wait for a second value // before emitting something. .pairwise(initialValue = null) // Replacing a bottom of the stack that was Lockscreen with Gone constitutes a // "device entered" event. .map { (from, to) -> from == Scenes.Lockscreen && to == Scenes.Gone }, ) { enteredDirectly, enteredOnBackStack -> enteredOnBackStack || enteredDirectly } .stateIn( scope = applicationScope, Loading Loading @@ -129,7 +155,7 @@ constructor( }, isLockscreenEnabled, deviceUnlockedInteractor.deviceUnlockStatus, isDeviceEntered isDeviceEntered, ) { isNoneAuthMethod, isLockscreenEnabled, deviceUnlockStatus, isDeviceEntered -> val isSwipeAuthMethod = isNoneAuthMethod && isLockscreenEnabled (isSwipeAuthMethod || Loading @@ -155,9 +181,7 @@ constructor( * canceled */ @JvmOverloads fun attemptDeviceEntry( callback: IKeyguardDismissCallback? = null, ) { fun attemptDeviceEntry(callback: IKeyguardDismissCallback? = null) { callback?.let { dismissCallbackRegistry.addCallback(it) } // TODO (b/307768356), Loading