diff options
6 files changed, 96 insertions, 116 deletions
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 9e83cc12d198..4553a56f599c 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -68,6 +68,7 @@ import com.android.keyguard.dagger.KeyguardBouncerScope; import com.android.settingslib.utils.ThreadUtils; import com.android.systemui.Gefingerpoken; import com.android.systemui.R; +import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor; import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate; import com.android.systemui.biometrics.SideFpsController; import com.android.systemui.biometrics.SideFpsUiRequestSource; @@ -81,8 +82,6 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; -import com.android.systemui.scene.domain.interactor.SceneInteractor; -import com.android.systemui.scene.shared.model.SceneKey; import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -400,7 +399,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard } }; private final UserInteractor mUserInteractor; - private final Provider<SceneInteractor> mSceneInteractor; + private final Provider<AuthenticationInteractor> mAuthenticationInteractor; private final Provider<JavaAdapter> mJavaAdapter; @Nullable private Job mSceneTransitionCollectionJob; @@ -431,8 +430,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard Provider<JavaAdapter> javaAdapter, UserInteractor userInteractor, FaceAuthAccessibilityDelegate faceAuthAccessibilityDelegate, - Provider<SceneInteractor> sceneInteractor, - KeyguardTransitionInteractor keyguardTransitionInteractor + KeyguardTransitionInteractor keyguardTransitionInteractor, + Provider<AuthenticationInteractor> authenticationInteractor ) { super(view); view.setAccessibilityDelegate(faceAuthAccessibilityDelegate); @@ -461,7 +460,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor; mBouncerMessageInteractor = bouncerMessageInteractor; mUserInteractor = userInteractor; - mSceneInteractor = sceneInteractor; + mAuthenticationInteractor = authenticationInteractor; mJavaAdapter = javaAdapter; mKeyguardTransitionInteractor = keyguardTransitionInteractor; } @@ -488,19 +487,21 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard showPrimarySecurityScreen(false); if (mFeatureFlags.isEnabled(Flags.SCENE_CONTAINER)) { - // When the scene framework transitions from bouncer to gone, we dismiss the keyguard. + // When the scene framework says that the lockscreen has been dismissed, dismiss the + // keyguard here, revealing the underlying app or launcher: mSceneTransitionCollectionJob = mJavaAdapter.get().alwaysCollectFlow( - mSceneInteractor.get().finishedSceneTransitions( - /* from= */ SceneKey.Bouncer.INSTANCE, - /* to= */ SceneKey.Gone.INSTANCE), - unused -> { - final int selectedUserId = mUserInteractor.getSelectedUserId(); - showNextSecurityScreenOrFinish( + mAuthenticationInteractor.get().isLockscreenDismissed(), + isLockscreenDismissed -> { + if (isLockscreenDismissed) { + final int selectedUserId = mUserInteractor.getSelectedUserId(); + showNextSecurityScreenOrFinish( /* authenticated= */ true, selectedUserId, /* bypassSecondaryLockScreen= */ true, mSecurityModel.getSecurityMode(selectedUserId)); - }); + } + } + ); } } diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt index e121790f07b0..5dec485a339a 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt @@ -114,14 +114,18 @@ constructor( * - `true` doesn't mean the lockscreen is invisible (since this state changes before the * transition occurs). */ - private val isLockscreenDismissed = + val isLockscreenDismissed: StateFlow<Boolean> = sceneInteractor.desiredScene .map { it.key } .filter { currentScene -> currentScene == SceneKey.Gone || currentScene == SceneKey.Lockscreen } .map { it == SceneKey.Gone } - .distinctUntilChanged() + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = false, + ) /** * Whether it's currently possible to swipe up to dismiss the lockscreen without requiring diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt index cf7abdd34b70..76d9b039e112 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt @@ -17,21 +17,22 @@ package com.android.systemui.scene.domain.interactor import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.scene.data.repository.SceneContainerRepository import com.android.systemui.scene.shared.logger.SceneLogger import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.RemoteUserInput import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel -import com.android.systemui.util.kotlin.pairwise import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.flow.stateIn /** * Generic business logic and app state accessors for the scene framework. @@ -44,6 +45,7 @@ import kotlinx.coroutines.flow.mapNotNull class SceneInteractor @Inject constructor( + @Application applicationScope: CoroutineScope, private val repository: SceneContainerRepository, private val logger: SceneLogger, ) { @@ -88,6 +90,22 @@ constructor( */ val transitionState: StateFlow<ObservableTransitionState> = repository.transitionState + /** + * The key of the scene that the UI is currently transitioning to or `null` if there is no + * active transition at the moment. + * + * This is a convenience wrapper around [transitionState], meant for flow-challenged consumers + * like Java code. + */ + val transitioningTo: StateFlow<SceneKey?> = + transitionState + .map { state -> (state as? ObservableTransitionState.Transition)?.toScene } + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = null, + ) + /** Whether the scene container is visible. */ val isVisible: StateFlow<Boolean> = repository.isVisible @@ -142,21 +160,6 @@ constructor( repository.setTransitionState(transitionState) } - /** - * Returns a stream of events that emits one [Unit] every time the framework transitions from - * [from] to [to]. - */ - fun finishedSceneTransitions(from: SceneKey, to: SceneKey): Flow<Unit> { - return transitionState - .mapNotNull { it as? ObservableTransitionState.Idle } - .map { idleState -> idleState.scene } - .distinctUntilChanged() - .pairwise() - .mapNotNull { (previousSceneKey, currentSceneKey) -> - Unit.takeIf { previousSceneKey == from && currentSceneKey == to } - } - } - /** Handles a remote user input. */ fun onRemoteUserInput(input: RemoteUserInput) { _remoteUserInput.value = input diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt index 1a98c1242e4b..e7483ad71b9d 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt @@ -38,6 +38,7 @@ import com.android.keyguard.KeyguardSecurityContainer.UserSwitcherViewMode.UserS import com.android.keyguard.KeyguardSecurityModel.SecurityMode import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate import com.android.systemui.biometrics.SideFpsController import com.android.systemui.biometrics.SideFpsUiRequestSource @@ -147,6 +148,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { private lateinit var sceneTestUtils: SceneTestUtils private lateinit var sceneInteractor: SceneInteractor private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor + private lateinit var authenticationInteractor: AuthenticationInteractor private lateinit var sceneTransitionStateFlow: MutableStateFlow<ObservableTransitionState> private lateinit var underTest: KeyguardSecurityContainerController @@ -214,6 +216,11 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { sceneTransitionStateFlow = MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Lockscreen)) sceneInteractor.setTransitionState(sceneTransitionStateFlow) + authenticationInteractor = + sceneTestUtils.authenticationInteractor( + repository = sceneTestUtils.authenticationRepository(), + sceneInteractor = sceneInteractor + ) underTest = KeyguardSecurityContainerController( @@ -243,9 +250,10 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { { JavaAdapter(sceneTestUtils.testScope.backgroundScope) }, userInteractor, faceAuthAccessibilityDelegate, - { sceneInteractor }, keyguardTransitionInteractor - ) + ) { + authenticationInteractor + } } @Test @@ -760,7 +768,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { } @Test - fun dismissesKeyguard_whenSceneChangesFromBouncerToGone() = + fun dismissesKeyguard_whenSceneChangesToGone() = sceneTestUtils.testScope.runTest { featureFlags.set(Flags.SCENE_CONTAINER, true) @@ -822,23 +830,30 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { runCurrent() verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt()) - // While not listening, moving back to the bouncer does not dismiss the keyguard. - sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer, null), "reason") + // While not listening, moving to the lockscreen does not dismiss the keyguard. + sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen, null), "reason") sceneTransitionStateFlow.value = - ObservableTransitionState.Transition(SceneKey.Gone, SceneKey.Bouncer, flowOf(.5f)) + ObservableTransitionState.Transition( + SceneKey.Gone, + SceneKey.Lockscreen, + flowOf(.5f) + ) runCurrent() - sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer, null), "reason") - sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Bouncer) + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen, null), "reason") + sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Lockscreen) runCurrent() verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt()) // Reattaching the view starts listening again so moving from the bouncer scene to the - // gone - // scene now does dismiss the keyguard again. + // gone scene now does dismiss the keyguard again, this time from lockscreen. underTest.onViewAttached() sceneInteractor.changeScene(SceneModel(SceneKey.Gone, null), "reason") sceneTransitionStateFlow.value = - ObservableTransitionState.Transition(SceneKey.Bouncer, SceneKey.Gone, flowOf(.5f)) + ObservableTransitionState.Transition( + SceneKey.Lockscreen, + SceneKey.Gone, + flowOf(.5f) + ) runCurrent() sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone, null), "reason") sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone) diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt index 0a93a7ca465f..16cc924b5754 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt @@ -28,9 +28,6 @@ import com.android.systemui.scene.shared.model.SceneModel import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.launch -import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith @@ -105,91 +102,50 @@ class SceneInteractorTest : SysuiTestCase() { } @Test - fun isVisible() = - testScope.runTest { - val isVisible by collectLastValue(underTest.isVisible) - assertThat(isVisible).isTrue() - - underTest.setVisible(false, "reason") - assertThat(isVisible).isFalse() - - underTest.setVisible(true, "reason") - assertThat(isVisible).isTrue() - } - - @Test - fun finishedSceneTransitions() = + fun transitioningTo() = testScope.runTest { val transitionState = MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Idle(SceneKey.Lockscreen) + ObservableTransitionState.Idle(underTest.desiredScene.value.key) ) underTest.setTransitionState(transitionState) - var transitionCount = 0 - val job = launch { - underTest - .finishedSceneTransitions( - from = SceneKey.Shade, - to = SceneKey.QuickSettings, - ) - .collect { transitionCount++ } - } - assertThat(transitionCount).isEqualTo(0) + val transitionTo by collectLastValue(underTest.transitioningTo) + assertThat(transitionTo).isNull() underTest.changeScene(SceneModel(SceneKey.Shade), "reason") + assertThat(transitionTo).isNull() + + val progress = MutableStateFlow(0f) transitionState.value = ObservableTransitionState.Transition( - fromScene = SceneKey.Lockscreen, + fromScene = underTest.desiredScene.value.key, toScene = SceneKey.Shade, - progress = flowOf(0.5f), + progress = progress, ) - runCurrent() - underTest.onSceneChanged(SceneModel(SceneKey.Shade), "reason") - transitionState.value = ObservableTransitionState.Idle(SceneKey.Shade) - runCurrent() - assertThat(transitionCount).isEqualTo(0) + assertThat(transitionTo).isEqualTo(SceneKey.Shade) - underTest.changeScene(SceneModel(SceneKey.QuickSettings), "reason") - transitionState.value = - ObservableTransitionState.Transition( - fromScene = SceneKey.Shade, - toScene = SceneKey.QuickSettings, - progress = flowOf(0.5f), - ) - runCurrent() - underTest.onSceneChanged(SceneModel(SceneKey.QuickSettings), "reason") - transitionState.value = ObservableTransitionState.Idle(SceneKey.QuickSettings) - runCurrent() - assertThat(transitionCount).isEqualTo(1) + progress.value = 0.5f + assertThat(transitionTo).isEqualTo(SceneKey.Shade) + + progress.value = 1f + assertThat(transitionTo).isEqualTo(SceneKey.Shade) - underTest.changeScene(SceneModel(SceneKey.Shade), "reason") - transitionState.value = - ObservableTransitionState.Transition( - fromScene = SceneKey.QuickSettings, - toScene = SceneKey.Shade, - progress = flowOf(0.5f), - ) - runCurrent() - underTest.onSceneChanged(SceneModel(SceneKey.Shade), "reason") transitionState.value = ObservableTransitionState.Idle(SceneKey.Shade) - runCurrent() - assertThat(transitionCount).isEqualTo(1) + assertThat(transitionTo).isNull() + } - underTest.changeScene(SceneModel(SceneKey.QuickSettings), "reason") - transitionState.value = - ObservableTransitionState.Transition( - fromScene = SceneKey.Shade, - toScene = SceneKey.QuickSettings, - progress = flowOf(0.5f), - ) - runCurrent() - underTest.onSceneChanged(SceneModel(SceneKey.QuickSettings), "reason") - transitionState.value = ObservableTransitionState.Idle(SceneKey.QuickSettings) - runCurrent() - assertThat(transitionCount).isEqualTo(2) + @Test + fun isVisible() = + testScope.runTest { + val isVisible by collectLastValue(underTest.isVisible) + assertThat(isVisible).isTrue() - job.cancel() + underTest.setVisible(false, "reason") + assertThat(isVisible).isFalse() + + underTest.setVisible(true, "reason") + assertThat(isVisible).isTrue() } @Test diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt index 0829f31e3890..df7f44770658 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt @@ -123,6 +123,7 @@ class SceneTestUtils( repository: SceneContainerRepository = fakeSceneContainerRepository() ): SceneInteractor { return SceneInteractor( + applicationScope = applicationScope(), repository = repository, logger = mock(), ) |