diff options
6 files changed, 170 insertions, 44 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index d3b51d1d17f7..97f06a00f42a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt @@ -1192,41 +1192,6 @@ class SceneContainerStartableTest : SysuiTestCase() { } @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 { underTest.start() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModelTest.kt index 8b97739af1db..f5022b9cff8b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModelTest.kt @@ -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 @@ -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() + } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt index 0a7526a41d65..35cb8c3b6280 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt @@ -571,15 +571,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) } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java index 7e0454c1fa2a..3f3ad13f9b12 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java @@ -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; @@ -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 diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModel.kt index 9c4bf1faf5cd..9655d928a9b9 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModel.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModel.kt @@ -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. */ @@ -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 @@ -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() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeWindowModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeWindowModelKosmos.kt index 4b42e07f1f54..974259529ba6 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeWindowModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeWindowModelKosmos.kt @@ -16,12 +16,18 @@ package com.android.systemui.shade.ui.viewmodel +import com.android.systemui.authentication.domain.interactor.authenticationInteractor +import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.kosmos.Kosmos +import com.android.systemui.scene.domain.interactor.sceneInteractor val Kosmos.notificationShadeWindowModel: NotificationShadeWindowModel by Kosmos.Fixture { NotificationShadeWindowModel( keyguardTransitionInteractor, + sceneInteractor = { sceneInteractor }, + authenticationInteractor = { authenticationInteractor }, + primaryBouncerInteractor = primaryBouncerInteractor, ) } |