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

Commit f5d84412 authored by Alejandro Nijamkin's avatar Alejandro Nijamkin Committed by Cherrypicker Worker
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
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:f520a2fc4b44e253ec7ff0e54bfc65b8d78e34b9)
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:10c3edc04239b6f5c275a6d0329e5235e7c0fc11)
Merged-In: Ied9fb452755a2791ee8bb28f0faa29f9d4ddfd0d
Change-Id: Ied9fb452755a2791ee8bb28f0faa29f9d4ddfd0d
parent c3a125eb
Loading
Loading
Loading
Loading
+15 −14
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;
@@ -82,8 +83,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;
@@ -394,7 +393,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;

@@ -425,8 +424,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);
@@ -455,7 +454,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
        mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
        mBouncerMessageInteractor = bouncerMessageInteractor;
        mUserInteractor = userInteractor;
        mSceneInteractor = sceneInteractor;
        mAuthenticationInteractor = authenticationInteractor;
        mJavaAdapter = javaAdapter;
        mKeyguardTransitionInteractor = keyguardTransitionInteractor;
    }
@@ -482,19 +481,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
+26 −11
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
@@ -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)
+27 −71
Original line number Diff line number Diff line
@@ -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
Loading