Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 07a2e318 authored by Alejandro Nijamkin's avatar Alejandro Nijamkin Committed by Ale Nijamkin
Browse files

[flexiglass] Dismiss keyguard when any scene goes to Gone.

We have a bug where moving to the Gone scene from scenes that are not
Bouncer (for example from lockscreen in Swipe) cause a black screen to
appear.

This happens because the code in KeyguardSecurityContainerController
which is responsible for dismissing the keyguard only did so for the
bouncer -> gone scene change.

The CL fixes the bug by allowing any change to the gone scene to trigger
the dismissal of the keyguard.

Fix: 295038434
Fix: 295223686
Test: unit tests still pass
Test: verified that I can see the launcher when swiping away the
lockscreen

Change-Id: Ied9fb452755a2791ee8bb28f0faa29f9d4ddfd0d
Merged-In: Ied9fb452755a2791ee8bb28f0faa29f9d4ddfd0d
parent a6894d2d
Loading
Loading
Loading
Loading
+14 −13
Original line number Diff line number Diff line
@@ -69,6 +69,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.KeyguardFaceAuthInteracto
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;
@@ -388,7 +387,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;

@@ -419,7 +418,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
            Provider<JavaAdapter> javaAdapter,
            UserInteractor userInteractor,
            FaceAuthAccessibilityDelegate faceAuthAccessibilityDelegate,
            Provider<SceneInteractor> sceneInteractor
            Provider<AuthenticationInteractor> authenticationInteractor
    ) {
        super(view);
        view.setAccessibilityDelegate(faceAuthAccessibilityDelegate);
@@ -448,7 +447,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
        mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
        mBouncerMessageInteractor = bouncerMessageInteractor;
        mUserInteractor = userInteractor;
        mSceneInteractor = sceneInteractor;
        mAuthenticationInteractor = authenticationInteractor;
        mJavaAdapter = javaAdapter;
    }

@@ -474,19 +473,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 -> {
                mAuthenticationInteractor.get().isLockscreenDismissed(),
                isLockscreenDismissed -> {
                    if (isLockscreenDismissed) {
                        final int selectedUserId = mUserInteractor.getSelectedUserId();
                        showNextSecurityScreenOrFinish(
                            /* authenticated= */ true,
                            selectedUserId,
                            /* bypassSecondaryLockScreen= */ true,
                            mSecurityModel.getSecurityMode(selectedUserId));
                });
                    }
                }
            );
        }
    }

+6 −2
Original line number Diff line number Diff line
@@ -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
+21 −18
Original line number Diff line number Diff line
@@ -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
+51 −7
Original line number Diff line number Diff line
@@ -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
@@ -144,6 +145,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
    private lateinit var testableResources: TestableResources
    private lateinit var sceneTestUtils: SceneTestUtils
    private lateinit var sceneInteractor: SceneInteractor
    private lateinit var authenticationInteractor: AuthenticationInteractor
    private lateinit var sceneTransitionStateFlow: MutableStateFlow<ObservableTransitionState>

    private lateinit var underTest: KeyguardSecurityContainerController
@@ -207,6 +209,11 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
        sceneTransitionStateFlow =
            MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Lockscreen))
        sceneInteractor.setTransitionState(sceneTransitionStateFlow)
        authenticationInteractor =
            sceneTestUtils.authenticationInteractor(
                repository = sceneTestUtils.authenticationRepository(),
                sceneInteractor = sceneInteractor
            )

        underTest =
            KeyguardSecurityContainerController(
@@ -237,7 +244,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
                userInteractor,
                faceAuthAccessibilityDelegate,
            ) {
                sceneInteractor
                authenticationInteractor
            }
    }

@@ -753,7 +760,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
    }

    @Test
    fun dismissesKeyguard_whenSceneChangesFromBouncerToGone() =
    fun dismissesKeyguard_whenSceneChangesToGone() =
        sceneTestUtils.testScope.runTest {
            featureFlags.set(Flags.SCENE_CONTAINER, true)

@@ -790,12 +797,32 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
            runCurrent()
            verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt())

            // While listening, moving back to the lockscreen scene does not dismiss the keyguard
            // again.
            clearInvocations(viewMediatorCallback)
            sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen, null), "reason")
            sceneTransitionStateFlow.value =
                ObservableTransitionState.Transition(
                    SceneKey.Gone,
                    SceneKey.Lockscreen,
                    flowOf(.5f)
                )
            runCurrent()
            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen, null), "reason")
            sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Lockscreen)
            runCurrent()
            verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())

            // While listening, moving back to the bouncer scene does not dismiss the keyguard
            // again.
            clearInvocations(viewMediatorCallback)
            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer, null), "reason")
            sceneTransitionStateFlow.value =
                ObservableTransitionState.Transition(SceneKey.Gone, SceneKey.Bouncer, flowOf(.5f))
                ObservableTransitionState.Transition(
                    SceneKey.Lockscreen,
                    SceneKey.Bouncer,
                    flowOf(.5f)
                )
            runCurrent()
            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer, null), "reason")
            sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Bouncer)
@@ -815,7 +842,21 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
            runCurrent()
            verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())

            // While not listening, moving back to the bouncer does not dismiss the keyguard.
            // 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.Lockscreen,
                    flowOf(.5f)
                )
            runCurrent()
            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen, null), "reason")
            sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Lockscreen)
            runCurrent()
            verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())

            // While not listening, moving to the bouncer does not dismiss the keyguard.
            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer, null), "reason")
            sceneTransitionStateFlow.value =
                ObservableTransitionState.Transition(SceneKey.Gone, SceneKey.Bouncer, flowOf(.5f))
@@ -826,12 +867,15 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
            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)
+64 −2
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -605,7 +606,68 @@ class AuthenticationInteractorTest : SysuiTestCase() {
            assertThat(hintedPinLength).isNull()
        }

    private fun switchToScene(sceneKey: SceneKey) {
        sceneInteractor.changeScene(SceneModel(sceneKey), "reason")
    @Test
    fun isLockscreenDismissed() =
        testScope.runTest {
            val isLockscreenDismissed by collectLastValue(underTest.isLockscreenDismissed)
            // Start on lockscreen.
            switchToScene(SceneKey.Lockscreen)
            assertThat(isLockscreenDismissed).isFalse()

            // The user swipes down to reveal shade.
            switchToScene(SceneKey.Shade)
            assertThat(isLockscreenDismissed).isFalse()

            // The user swipes down to reveal quick settings.
            switchToScene(SceneKey.QuickSettings)
            assertThat(isLockscreenDismissed).isFalse()

            // The user swipes up to go back to shade.
            switchToScene(SceneKey.Shade)
            assertThat(isLockscreenDismissed).isFalse()

            // The user swipes up to reveal bouncer.
            switchToScene(SceneKey.Bouncer)
            assertThat(isLockscreenDismissed).isFalse()

            // The user hits back to return to lockscreen.
            switchToScene(SceneKey.Lockscreen)
            assertThat(isLockscreenDismissed).isFalse()

            // The user swipes up to reveal bouncer.
            switchToScene(SceneKey.Bouncer)
            assertThat(isLockscreenDismissed).isFalse()

            // The user enters correct credentials and goes to gone.
            switchToScene(SceneKey.Gone)
            assertThat(isLockscreenDismissed).isTrue()

            // The user swipes down to reveal shade.
            switchToScene(SceneKey.Shade)
            assertThat(isLockscreenDismissed).isTrue()

            // The user swipes down to reveal quick settings.
            switchToScene(SceneKey.QuickSettings)
            assertThat(isLockscreenDismissed).isTrue()

            // The user swipes up to go back to shade.
            switchToScene(SceneKey.Shade)
            assertThat(isLockscreenDismissed).isTrue()

            // The user swipes up to go back to gone.
            switchToScene(SceneKey.Gone)
            assertThat(isLockscreenDismissed).isTrue()

            // The device goes to sleep, returning to the lockscreen.
            switchToScene(SceneKey.Lockscreen)
            assertThat(isLockscreenDismissed).isFalse()
        }

    private fun TestScope.switchToScene(sceneKey: SceneKey) {
        val model = SceneModel(sceneKey)
        val loggingReason = "reason"
        sceneInteractor.changeScene(model, loggingReason)
        sceneInteractor.onSceneChanged(model, loggingReason)
        runCurrent()
    }
}
Loading