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

Commit 9e843a54 authored by Matt Pietal's avatar Matt Pietal
Browse files

Never ignore OCCLUDED transition

In an earlier attempt to cleanup some transition code, a check was
added to validate a transition request to make sure the "FROM" value
matched the existing state... a good idea. However, when exiting AOD
to launch camera a race condition existed that would result in
FromDreamingTransitionInteractor starting the transition
LOCKSCREEN->OCCLUDED. Previous code made a special case for this but
the new code ignored it, resulting in AOD over camera.

The new code updates conditions from both LOCKSCREEN and DREAMING to
make sure the device goes to OCCLUDED.

Fixes: 300406718
Test: atest KeyguardTransitionScenariosTest
Test: Tablet - Launch Home Controls over Dream, enter and exit
dreaming from LOCKSCREEN or GONE
Test: Phone - Test camera launches from AOD/DREAMING/LOCKSCREEN states

Change-Id: Ia3a90e650b34b5f480753b14398449cf51c42ce3
parent ef18eed3
Loading
Loading
Loading
Loading
+4 −21
Original line number Diff line number Diff line
@@ -29,9 +29,7 @@ import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch

@SysUISingleton
@@ -64,29 +62,14 @@ constructor(

    private fun listenForDreamingToOccluded() {
        scope.launch {
            keyguardInteractor.isDreaming
                // Add a slight delay, as dreaming and occluded events will arrive with a small gap
                // in time. This prevents a transition to OCCLUSION happening prematurely.
                .onEach { delay(50) }
                .sample(
                    combine(
                        keyguardInteractor.isKeyguardOccluded,
                        transitionInteractor.startedKeyguardTransitionStep,
                        ::Pair,
                    ),
                    ::toTriple
                )
                .collect { (isDreaming, isOccluded, lastStartedTransition) ->
            combine(keyguardInteractor.isKeyguardOccluded, keyguardInteractor.isDreaming, ::Pair)
                .sample(transitionInteractor.startedKeyguardTransitionStep, ::toTriple)
                .collect { (isOccluded, isDreaming, lastStartedTransition) ->
                    if (
                        isOccluded &&
                            !isDreaming &&
                            (lastStartedTransition.to == KeyguardState.DREAMING ||
                                lastStartedTransition.to == KeyguardState.LOCKSCREEN)
                            lastStartedTransition.to == KeyguardState.DREAMING
                    ) {
                        // At the moment, checking for LOCKSCREEN state above provides a corrective
                        // action. There's no great signal to determine when the dream is ending
                        // and a transition to OCCLUDED is beginning directly. For now, the solution
                        // is DREAMING->LOCKSCREEN->OCCLUDED
                        startTransitionTo(KeyguardState.OCCLUDED)
                    }
                }
+3 −10
Original line number Diff line number Diff line
@@ -318,16 +318,9 @@ constructor(
    private fun listenForLockscreenToOccluded() {
        scope.launch {
            keyguardInteractor.isKeyguardOccluded
                .sample(
                    combine(
                        transitionInteractor.startedKeyguardState,
                        keyguardInteractor.isDreaming,
                        ::Pair
                    ),
                    ::toTriple
                )
                .collect { (isOccluded, keyguardState, isDreaming) ->
                    if (isOccluded && !isDreaming && keyguardState == KeyguardState.LOCKSCREEN) {
                .sample(transitionInteractor.startedKeyguardState, ::Pair)
                .collect { (isOccluded, keyguardState) ->
                    if (isOccluded && keyguardState == KeyguardState.LOCKSCREEN) {
                        startTransitionTo(KeyguardState.OCCLUDED)
                    }
                }
+1 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.systemui.util.kotlin
class Utils {
    companion object {
        fun <A, B, C> toTriple(a: A, bc: Pair<B, C>) = Triple(a, bc.first, bc.second)
        fun <A, B, C> toTriple(ab: Pair<A, B>, c: C) = Triple(ab.first, ab.second, c)

        fun <A, B, C, D> toQuad(a: A, b: B, c: C, d: D) = Quad(a, b, c, d)
        fun <A, B, C, D> toQuad(a: A, bcd: Triple<B, C, D>) =
+83 −12
Original line number Diff line number Diff line
@@ -433,7 +433,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {

            // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED
            runTransitionAndSetWakefulness(
                    KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
                KeyguardState.GONE,
                KeyguardState.DREAMING_LOCKSCREEN_HOSTED
            )

            // WHEN the lockscreen hosted dream stops
            keyguardRepository.setIsActiveDreamLockscreenHosted(false)
@@ -457,7 +459,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
        testScope.runTest {
            // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED
            runTransitionAndSetWakefulness(
                    KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
                KeyguardState.GONE,
                KeyguardState.DREAMING_LOCKSCREEN_HOSTED
            )

            // WHEN biometrics succeeds with wake and unlock from dream mode
            keyguardRepository.setBiometricUnlockState(
@@ -487,7 +491,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {

            // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED
            runTransitionAndSetWakefulness(
                    KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
                KeyguardState.GONE,
                KeyguardState.DREAMING_LOCKSCREEN_HOSTED
            )

            // WHEN the primary bouncer is set to show
            bouncerRepository.setPrimaryShow(true)
@@ -515,7 +521,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {

            // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED
            runTransitionAndSetWakefulness(
                    KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
                KeyguardState.GONE,
                KeyguardState.DREAMING_LOCKSCREEN_HOSTED
            )

            // WHEN the device begins to sleep
            keyguardRepository.setIsActiveDreamLockscreenHosted(false)
@@ -547,7 +555,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {

            // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED
            runTransitionAndSetWakefulness(
                    KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
                KeyguardState.GONE,
                KeyguardState.DREAMING_LOCKSCREEN_HOSTED
            )

            // WHEN the keyguard is occluded and the lockscreen hosted dream stops
            keyguardRepository.setIsActiveDreamLockscreenHosted(false)
@@ -783,7 +793,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
        testScope.runTest {
            // GIVEN a prior transition has run to ALTERNATE_BOUNCER
            runTransitionAndSetWakefulness(
                    KeyguardState.LOCKSCREEN, KeyguardState.ALTERNATE_BOUNCER)
                KeyguardState.LOCKSCREEN,
                KeyguardState.ALTERNATE_BOUNCER
            )

            // WHEN the alternateBouncer stops showing and then the primary bouncer shows
            bouncerRepository.setPrimaryShow(true)
@@ -808,7 +820,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
            // GIVEN a prior transition has run to ALTERNATE_BOUNCER
            bouncerRepository.setAlternateVisible(true)
            runTransitionAndSetWakefulness(
                    KeyguardState.LOCKSCREEN, KeyguardState.ALTERNATE_BOUNCER)
                KeyguardState.LOCKSCREEN,
                KeyguardState.ALTERNATE_BOUNCER
            )

            // GIVEN the primary bouncer isn't showing, aod available and starting to sleep
            bouncerRepository.setPrimaryShow(false)
@@ -838,7 +852,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
            // GIVEN a prior transition has run to ALTERNATE_BOUNCER
            bouncerRepository.setAlternateVisible(true)
            runTransitionAndSetWakefulness(
                    KeyguardState.LOCKSCREEN, KeyguardState.ALTERNATE_BOUNCER)
                KeyguardState.LOCKSCREEN,
                KeyguardState.ALTERNATE_BOUNCER
            )

            // GIVEN the primary bouncer isn't showing, aod not available and starting to sleep
            // to sleep
@@ -869,7 +885,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
            // GIVEN a prior transition has run to ALTERNATE_BOUNCER
            bouncerRepository.setAlternateVisible(true)
            runTransitionAndSetWakefulness(
                    KeyguardState.LOCKSCREEN, KeyguardState.ALTERNATE_BOUNCER)
                KeyguardState.LOCKSCREEN,
                KeyguardState.ALTERNATE_BOUNCER
            )

            // GIVEN the primary bouncer isn't showing and device not sleeping
            bouncerRepository.setPrimaryShow(false)
@@ -980,7 +998,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
            // GIVEN a prior transition has run to PRIMARY_BOUNCER
            bouncerRepository.setPrimaryShow(true)
            runTransitionAndSetWakefulness(
                    KeyguardState.DREAMING_LOCKSCREEN_HOSTED, KeyguardState.PRIMARY_BOUNCER)
                KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
                KeyguardState.PRIMARY_BOUNCER
            )

            // WHEN the primary bouncer stops showing and lockscreen hosted dream still active
            bouncerRepository.setPrimaryShow(false)
@@ -1160,6 +1180,57 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
            coroutineContext.cancelChildren()
        }

    @Test
    fun dreamingToOccluded() =
        testScope.runTest {
            // GIVEN a prior transition has run to DREAMING
            keyguardRepository.setDreaming(true)
            runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.DREAMING)
            runCurrent()

            // WHEN the keyguard is occluded and device wakes up and is no longer dreaming
            keyguardRepository.setDreaming(false)
            keyguardRepository.setKeyguardOccluded(true)
            powerInteractor.setAwakeForTest()
            runCurrent()

            val info =
                withArgCaptor<TransitionInfo> {
                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                }
            // THEN a transition to OCCLUDED should occur
            assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor")
            assertThat(info.from).isEqualTo(KeyguardState.DREAMING)
            assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
            assertThat(info.animator).isNotNull()

            coroutineContext.cancelChildren()
        }

    @Test
    fun lockscreenToOccluded() =
        testScope.runTest {
            // GIVEN a prior transition has run to LOCKSCREEN
            runTransitionAndSetWakefulness(KeyguardState.GONE, KeyguardState.LOCKSCREEN)
            runCurrent()

            // WHEN the keyguard is occluded
            keyguardRepository.setKeyguardOccluded(true)
            runCurrent()

            val info =
                withArgCaptor<TransitionInfo> {
                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                }
            // THEN a transition to OCCLUDED should occur
            assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
            assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
            assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
            assertThat(info.animator).isNotNull()

            coroutineContext.cancelChildren()
        }

    @Test
    fun aodToOccluded() =
        testScope.runTest {