Loading packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +0 −35 Original line number Diff line number Diff line Loading @@ -1191,41 +1191,6 @@ class SceneContainerStartableTest : SysuiTestCase() { verify(notificationShadeWindowController, times(1)).setKeyguardShowing(true) } @Test fun hydrateWindowController_setBouncerShowing() = testScope.runTest { underTest.start() val notificationShadeWindowController = kosmos.notificationShadeWindowController val transitionStateFlow = prepareState(initialSceneKey = Scenes.Lockscreen) val currentScene by collectLastValue(sceneInteractor.currentScene) assertThat(currentScene).isEqualTo(Scenes.Lockscreen) verify(notificationShadeWindowController, never()).setBouncerShowing(true) verify(notificationShadeWindowController, times(1)).setBouncerShowing(false) emulateSceneTransition(transitionStateFlow, Scenes.Bouncer) verify(notificationShadeWindowController, times(1)).setBouncerShowing(true) verify(notificationShadeWindowController, times(1)).setBouncerShowing(false) emulateSceneTransition(transitionStateFlow, Scenes.Lockscreen) verify(notificationShadeWindowController, times(1)).setBouncerShowing(true) verify(notificationShadeWindowController, times(2)).setBouncerShowing(false) kosmos.deviceEntryFingerprintAuthRepository.setAuthenticationStatus( SuccessFingerprintAuthenticationStatus(0, true) ) assertThat(currentScene).isEqualTo(Scenes.Gone) verify(notificationShadeWindowController, times(1)).setBouncerShowing(true) verify(notificationShadeWindowController, times(2)).setBouncerShowing(false) emulateSceneTransition(transitionStateFlow, Scenes.Lockscreen) verify(notificationShadeWindowController, times(1)).setBouncerShowing(true) verify(notificationShadeWindowController, times(2)).setBouncerShowing(false) emulateSceneTransition(transitionStateFlow, Scenes.Bouncer) verify(notificationShadeWindowController, times(2)).setBouncerShowing(true) verify(notificationShadeWindowController, times(2)).setBouncerShowing(false) } @Test fun hydrateWindowController_setKeyguardOccluded() = testScope.runTest { Loading packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModelTest.kt +96 −0 Original line number Diff line number Diff line Loading @@ -16,18 +16,28 @@ package com.android.systemui.shade.ui.viewmodel import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.testScope import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith Loading Loading @@ -150,4 +160,90 @@ class NotificationShadeWindowModelTest : SysuiTestCase() { ) assertThat(isKeyguardOccluded).isTrue() } @Test @EnableSceneContainer fun withSceneContainer_bouncerShowing_providesTheCorrectState() = testScope.runTest { val bouncerShowing by collectLastValue(underTest.isBouncerShowing) val transitionState = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Idle(Scenes.Lockscreen) ) kosmos.sceneInteractor.setTransitionState(transitionState) runCurrent() assertThat(bouncerShowing).isFalse() transitionState.value = ObservableTransitionState.Idle(Scenes.Bouncer) runCurrent() assertThat(bouncerShowing).isTrue() } @Test @EnableFlags(com.android.systemui.Flags.FLAG_COMPOSE_BOUNCER) fun withComposeBouncer_bouncerShowing_providesTheCorrectState() = testScope.runTest { val bouncerShowing by collectLastValue(underTest.isBouncerShowing) kosmos.fakeKeyguardBouncerRepository.setPrimaryShow(isShowing = false) runCurrent() assertThat(bouncerShowing).isFalse() kosmos.fakeKeyguardBouncerRepository.setPrimaryShow(isShowing = true) runCurrent() assertThat(bouncerShowing).isTrue() } @Test @EnableSceneContainer fun withSceneContainer_doesBouncerRequireIme_providesTheCorrectState() = testScope.runTest { val bouncerRequiresIme by collectLastValue(underTest.doesBouncerRequireIme) kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Pin ) val transitionState = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Idle(Scenes.Bouncer) ) kosmos.sceneInteractor.setTransitionState(transitionState) runCurrent() assertThat(bouncerRequiresIme).isFalse() // go back to lockscreen transitionState.value = ObservableTransitionState.Idle(Scenes.Lockscreen) runCurrent() // change auth method kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) // go back to bouncer transitionState.value = ObservableTransitionState.Idle(Scenes.Bouncer) runCurrent() assertThat(bouncerRequiresIme).isTrue() } @Test @EnableFlags(com.android.systemui.Flags.FLAG_COMPOSE_BOUNCER) fun withComposeBouncer_doesBouncerRequireIme_providesTheCorrectState() = testScope.runTest { val bouncerRequiresIme by collectLastValue(underTest.doesBouncerRequireIme) kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Pin ) kosmos.fakeKeyguardBouncerRepository.setPrimaryShow(isShowing = true) runCurrent() assertThat(bouncerRequiresIme).isFalse() kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) kosmos.fakeKeyguardBouncerRepository.setPrimaryShow(isShowing = true) runCurrent() assertThat(bouncerRequiresIme).isFalse() } } packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +0 −9 Original line number Diff line number Diff line Loading @@ -570,15 +570,6 @@ constructor( } } applicationScope.launch { sceneInteractor.currentScene .map { it == Scenes.Bouncer } .distinctUntilChanged() .collect { isBouncerShowing -> windowController.setBouncerShowing(isBouncerShowing) } } applicationScope.launch { occlusionInteractor.invisibleDueToOcclusion.collect { invisibleDueToOcclusion -> windowController.setKeyguardOccluded(invisibleDueToOcclusion) Loading packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java +7 −0 Original line number Diff line number Diff line Loading @@ -49,6 +49,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.Dumpable; import com.android.systemui.Flags; import com.android.systemui.biometrics.AuthController; import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.communal.domain.interactor.CommunalInteractor; import com.android.systemui.dagger.SysUISingleton; Loading Loading @@ -342,6 +343,12 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW this::setKeyguardOccluded ); } if (ComposeBouncerFlags.INSTANCE.isComposeBouncerOrSceneContainerEnabled()) { collectFlow(mWindowRootView, mNotificationShadeWindowModel.isBouncerShowing(), this::setBouncerShowing); collectFlow(mWindowRootView, mNotificationShadeWindowModel.getDoesBouncerRequireIme(), this::setKeyguardNeedsInput); } } @Override Loading packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModel.kt +61 −0 Original line number Diff line number Diff line Loading @@ -16,16 +16,25 @@ package com.android.systemui.shade.ui.viewmodel import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.util.kotlin.BooleanFlowOperators.any import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map /** Models UI state for the shade window. */ Loading @@ -34,6 +43,9 @@ class NotificationShadeWindowModel @Inject constructor( keyguardTransitionInteractor: KeyguardTransitionInteractor, sceneInteractor: dagger.Lazy<SceneInteractor>, authenticationInteractor: dagger.Lazy<AuthenticationInteractor>, primaryBouncerInteractor: PrimaryBouncerInteractor, ) { /** * Considered to be occluded if in OCCLUDED, DREAMING, GLANCEABLE_HUB/Communal, or transitioning Loading Loading @@ -70,4 +82,53 @@ constructor( ), ) .any() /** * Whether bouncer is currently showing or not. * * Applicable only when either [SceneContainerFlag] or [ComposeBouncerFlags] are enabled, * otherwise it throws an error. */ val isBouncerShowing: Flow<Boolean> = when { SceneContainerFlag.isEnabled -> { sceneInteractor.get().transitionState.map { it.isIdle(Scenes.Bouncer) } } ComposeBouncerFlags.isOnlyComposeBouncerEnabled() -> primaryBouncerInteractor.isShowing else -> flow { error( "Consume this flow only when SceneContainerFlag " + "or ComposeBouncerFlags are enabled" ) } }.distinctUntilChanged() /** * Whether the bouncer currently require IME for device entry. * * This emits true when the authentication method is set to password and the bouncer is * currently showing. Throws an error when this is used without either [SceneContainerFlag] or * [ComposeBouncerFlags] */ val doesBouncerRequireIme: Flow<Boolean> = if (ComposeBouncerFlags.isComposeBouncerOrSceneContainerEnabled()) { // This is required to make the window, where the bouncer resides, // focusable. InputMethodManager allows IME to be shown only for views // in windows that do not have the FLAG_NOT_FOCUSABLE flag. isBouncerShowing .sample(authenticationInteractor.get().authenticationMethod, ::Pair) .map { (showing, authMethod) -> showing && authMethod == AuthenticationMethodModel.Password } } else { flow { error( "Consume this flow only when SceneContainerFlag " + "or ComposeBouncerFlags are enabled" ) } } .distinctUntilChanged() } Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +0 −35 Original line number Diff line number Diff line Loading @@ -1191,41 +1191,6 @@ class SceneContainerStartableTest : SysuiTestCase() { verify(notificationShadeWindowController, times(1)).setKeyguardShowing(true) } @Test fun hydrateWindowController_setBouncerShowing() = testScope.runTest { underTest.start() val notificationShadeWindowController = kosmos.notificationShadeWindowController val transitionStateFlow = prepareState(initialSceneKey = Scenes.Lockscreen) val currentScene by collectLastValue(sceneInteractor.currentScene) assertThat(currentScene).isEqualTo(Scenes.Lockscreen) verify(notificationShadeWindowController, never()).setBouncerShowing(true) verify(notificationShadeWindowController, times(1)).setBouncerShowing(false) emulateSceneTransition(transitionStateFlow, Scenes.Bouncer) verify(notificationShadeWindowController, times(1)).setBouncerShowing(true) verify(notificationShadeWindowController, times(1)).setBouncerShowing(false) emulateSceneTransition(transitionStateFlow, Scenes.Lockscreen) verify(notificationShadeWindowController, times(1)).setBouncerShowing(true) verify(notificationShadeWindowController, times(2)).setBouncerShowing(false) kosmos.deviceEntryFingerprintAuthRepository.setAuthenticationStatus( SuccessFingerprintAuthenticationStatus(0, true) ) assertThat(currentScene).isEqualTo(Scenes.Gone) verify(notificationShadeWindowController, times(1)).setBouncerShowing(true) verify(notificationShadeWindowController, times(2)).setBouncerShowing(false) emulateSceneTransition(transitionStateFlow, Scenes.Lockscreen) verify(notificationShadeWindowController, times(1)).setBouncerShowing(true) verify(notificationShadeWindowController, times(2)).setBouncerShowing(false) emulateSceneTransition(transitionStateFlow, Scenes.Bouncer) verify(notificationShadeWindowController, times(2)).setBouncerShowing(true) verify(notificationShadeWindowController, times(2)).setBouncerShowing(false) } @Test fun hydrateWindowController_setKeyguardOccluded() = testScope.runTest { Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModelTest.kt +96 −0 Original line number Diff line number Diff line Loading @@ -16,18 +16,28 @@ package com.android.systemui.shade.ui.viewmodel import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.testScope import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith Loading Loading @@ -150,4 +160,90 @@ class NotificationShadeWindowModelTest : SysuiTestCase() { ) assertThat(isKeyguardOccluded).isTrue() } @Test @EnableSceneContainer fun withSceneContainer_bouncerShowing_providesTheCorrectState() = testScope.runTest { val bouncerShowing by collectLastValue(underTest.isBouncerShowing) val transitionState = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Idle(Scenes.Lockscreen) ) kosmos.sceneInteractor.setTransitionState(transitionState) runCurrent() assertThat(bouncerShowing).isFalse() transitionState.value = ObservableTransitionState.Idle(Scenes.Bouncer) runCurrent() assertThat(bouncerShowing).isTrue() } @Test @EnableFlags(com.android.systemui.Flags.FLAG_COMPOSE_BOUNCER) fun withComposeBouncer_bouncerShowing_providesTheCorrectState() = testScope.runTest { val bouncerShowing by collectLastValue(underTest.isBouncerShowing) kosmos.fakeKeyguardBouncerRepository.setPrimaryShow(isShowing = false) runCurrent() assertThat(bouncerShowing).isFalse() kosmos.fakeKeyguardBouncerRepository.setPrimaryShow(isShowing = true) runCurrent() assertThat(bouncerShowing).isTrue() } @Test @EnableSceneContainer fun withSceneContainer_doesBouncerRequireIme_providesTheCorrectState() = testScope.runTest { val bouncerRequiresIme by collectLastValue(underTest.doesBouncerRequireIme) kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Pin ) val transitionState = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Idle(Scenes.Bouncer) ) kosmos.sceneInteractor.setTransitionState(transitionState) runCurrent() assertThat(bouncerRequiresIme).isFalse() // go back to lockscreen transitionState.value = ObservableTransitionState.Idle(Scenes.Lockscreen) runCurrent() // change auth method kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) // go back to bouncer transitionState.value = ObservableTransitionState.Idle(Scenes.Bouncer) runCurrent() assertThat(bouncerRequiresIme).isTrue() } @Test @EnableFlags(com.android.systemui.Flags.FLAG_COMPOSE_BOUNCER) fun withComposeBouncer_doesBouncerRequireIme_providesTheCorrectState() = testScope.runTest { val bouncerRequiresIme by collectLastValue(underTest.doesBouncerRequireIme) kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Pin ) kosmos.fakeKeyguardBouncerRepository.setPrimaryShow(isShowing = true) runCurrent() assertThat(bouncerRequiresIme).isFalse() kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) kosmos.fakeKeyguardBouncerRepository.setPrimaryShow(isShowing = true) runCurrent() assertThat(bouncerRequiresIme).isFalse() } }
packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +0 −9 Original line number Diff line number Diff line Loading @@ -570,15 +570,6 @@ constructor( } } applicationScope.launch { sceneInteractor.currentScene .map { it == Scenes.Bouncer } .distinctUntilChanged() .collect { isBouncerShowing -> windowController.setBouncerShowing(isBouncerShowing) } } applicationScope.launch { occlusionInteractor.invisibleDueToOcclusion.collect { invisibleDueToOcclusion -> windowController.setKeyguardOccluded(invisibleDueToOcclusion) Loading
packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java +7 −0 Original line number Diff line number Diff line Loading @@ -49,6 +49,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.Dumpable; import com.android.systemui.Flags; import com.android.systemui.biometrics.AuthController; import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.communal.domain.interactor.CommunalInteractor; import com.android.systemui.dagger.SysUISingleton; Loading Loading @@ -342,6 +343,12 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW this::setKeyguardOccluded ); } if (ComposeBouncerFlags.INSTANCE.isComposeBouncerOrSceneContainerEnabled()) { collectFlow(mWindowRootView, mNotificationShadeWindowModel.isBouncerShowing(), this::setBouncerShowing); collectFlow(mWindowRootView, mNotificationShadeWindowModel.getDoesBouncerRequireIme(), this::setKeyguardNeedsInput); } } @Override Loading
packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModel.kt +61 −0 Original line number Diff line number Diff line Loading @@ -16,16 +16,25 @@ package com.android.systemui.shade.ui.viewmodel import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.util.kotlin.BooleanFlowOperators.any import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map /** Models UI state for the shade window. */ Loading @@ -34,6 +43,9 @@ class NotificationShadeWindowModel @Inject constructor( keyguardTransitionInteractor: KeyguardTransitionInteractor, sceneInteractor: dagger.Lazy<SceneInteractor>, authenticationInteractor: dagger.Lazy<AuthenticationInteractor>, primaryBouncerInteractor: PrimaryBouncerInteractor, ) { /** * Considered to be occluded if in OCCLUDED, DREAMING, GLANCEABLE_HUB/Communal, or transitioning Loading Loading @@ -70,4 +82,53 @@ constructor( ), ) .any() /** * Whether bouncer is currently showing or not. * * Applicable only when either [SceneContainerFlag] or [ComposeBouncerFlags] are enabled, * otherwise it throws an error. */ val isBouncerShowing: Flow<Boolean> = when { SceneContainerFlag.isEnabled -> { sceneInteractor.get().transitionState.map { it.isIdle(Scenes.Bouncer) } } ComposeBouncerFlags.isOnlyComposeBouncerEnabled() -> primaryBouncerInteractor.isShowing else -> flow { error( "Consume this flow only when SceneContainerFlag " + "or ComposeBouncerFlags are enabled" ) } }.distinctUntilChanged() /** * Whether the bouncer currently require IME for device entry. * * This emits true when the authentication method is set to password and the bouncer is * currently showing. Throws an error when this is used without either [SceneContainerFlag] or * [ComposeBouncerFlags] */ val doesBouncerRequireIme: Flow<Boolean> = if (ComposeBouncerFlags.isComposeBouncerOrSceneContainerEnabled()) { // This is required to make the window, where the bouncer resides, // focusable. InputMethodManager allows IME to be shown only for views // in windows that do not have the FLAG_NOT_FOCUSABLE flag. isBouncerShowing .sample(authenticationInteractor.get().authenticationMethod, ::Pair) .map { (showing, authMethod) -> showing && authMethod == AuthenticationMethodModel.Password } } else { flow { error( "Consume this flow only when SceneContainerFlag " + "or ComposeBouncerFlags are enabled" ) } } .distinctUntilChanged() }