Loading packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt +17 −0 Original line number Diff line number Diff line Loading @@ -35,6 +35,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.OFF import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.util.kotlin.pairwise import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi Loading @@ -46,6 +47,7 @@ import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn /** Encapsulates business-logic related to the keyguard transitions. */ @OptIn(ExperimentalCoroutinesApi::class) Loading Loading @@ -205,6 +207,21 @@ constructor( .map { step -> step.to } .shareIn(scope, SharingStarted.Eagerly, replay = 1) /** * A pair of the most recent STARTED step, and the transition step immediately preceding it. The * transition framework enforces that the previous step is either a CANCELED or FINISHED step, * and that the previous step was *to* the state the STARTED step is *from*. * * This flow can be used to access the previous step to determine whether it was CANCELED or * FINISHED. In the case of a CANCELED step, we can also figure out which state we were coming * from when we were canceled. */ val startedStepWithPrecedingStep = transitions .pairwise() .filter { it.newValue.transitionState == TransitionState.STARTED } .stateIn(scope, SharingStarted.Eagerly, null) /** * The last [KeyguardState] to which we [TransitionState.FINISHED] a transition. * Loading packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt +22 −12 Original line number Diff line number Diff line Loading @@ -19,7 +19,9 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.BiometricUnlockModel import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow Loading Loading @@ -121,19 +123,27 @@ constructor( * want to know if the AOD/clock/notifs/etc. are visible. */ val lockscreenVisibility: Flow<Boolean> = combine( transitionInteractor.startedKeyguardTransitionStep, transitionInteractor.finishedKeyguardState, ) { startedStep, finishedState -> // If we finished the transition, use the finished state. If we're running a // transition, use the state we're transitioning FROM. This can be different from // the last finished state if a transition is interrupted. For example, if we were // transitioning from GONE to AOD and then started AOD -> LOCKSCREEN mid-transition, // we want to immediately use the visibility for AOD (lockscreenVisibility=true) // even though the lastFinishedState is still GONE (lockscreenVisibility=false). if (finishedState == startedStep.to) finishedState else startedStep.from transitionInteractor.currentKeyguardState .sample(transitionInteractor.startedStepWithPrecedingStep, ::Pair) .map { (currentState, startedWithPrev) -> val startedFromStep = startedWithPrev?.previousValue val startedStep = startedWithPrev?.newValue val returningToGoneAfterCancellation = startedStep?.to == KeyguardState.GONE && startedFromStep?.transitionState == TransitionState.CANCELED && startedFromStep.from == KeyguardState.GONE if (!returningToGoneAfterCancellation) { // By default, apply the lockscreen visibility of the current state. KeyguardState.lockscreenVisibleInState(currentState) } else { // If we're transitioning to GONE after a prior canceled transition from GONE, // then this is the camera launch transition from an asleep state back to GONE. // We don't want to show the lockscreen since we're aborting the lock and going // back to GONE. KeyguardState.lockscreenVisibleInState(KeyguardState.GONE) } } .map(KeyguardState::lockscreenVisibleInState) .distinctUntilChanged() /** Loading packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt +319 −0 Original line number Diff line number Diff line Loading @@ -375,4 +375,323 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { values ) } @Test fun testLockscreenVisibility_usesFromState_ifCanceled() = testScope.runTest { val values by collectValues(underTest.lockscreenVisibility) transitionRepository.sendTransitionSteps( from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE, testScope ) runCurrent() assertEquals( listOf( // Initially should be true, as we start in LOCKSCREEN. true, // Then, false, since we finish in GONE. false, ), values ) transitionRepository.sendTransitionStep( TransitionStep( transitionState = TransitionState.STARTED, from = KeyguardState.GONE, to = KeyguardState.AOD, ) ) transitionRepository.sendTransitionStep( TransitionStep( transitionState = TransitionState.RUNNING, from = KeyguardState.GONE, to = KeyguardState.AOD, ) ) runCurrent() assertEquals( listOf( true, // Should remain false as we transition from GONE. false, ), values ) transitionRepository.sendTransitionStep( TransitionStep( transitionState = TransitionState.CANCELED, from = KeyguardState.GONE, to = KeyguardState.AOD, ) ) transitionRepository.sendTransitionStep( TransitionStep( transitionState = TransitionState.STARTED, from = KeyguardState.AOD, to = KeyguardState.LOCKSCREEN, ) ) runCurrent() assertEquals( listOf( true, false, // If we cancel and then go from LS -> GONE, we should immediately flip to the // visibility of the from state (LS). true, ), values ) transitionRepository.sendTransitionStep( TransitionStep( transitionState = TransitionState.FINISHED, from = KeyguardState.AOD, to = KeyguardState.LOCKSCREEN, ) ) runCurrent() assertEquals( listOf( true, false, true, ), values ) } /** * Tests the special case for insecure camera launch. CANCELING a transition from GONE and then * STARTING a transition back to GONE should never show the lockscreen, even though the current * state during the AOD/isAsleep -> GONE transition is AOD (where lockscreen visibility = true). */ @Test fun testLockscreenVisibility_falseDuringTransitionToGone_fromCanceledGone() = testScope.runTest { val values by collectValues(underTest.lockscreenVisibility) transitionRepository.sendTransitionSteps( from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE, testScope ) runCurrent() assertEquals( listOf( true, // Not visible since we're GONE. false, ), values ) transitionRepository.sendTransitionStep( TransitionStep( transitionState = TransitionState.STARTED, from = KeyguardState.GONE, to = KeyguardState.AOD, ) ) runCurrent() transitionRepository.sendTransitionStep( TransitionStep( transitionState = TransitionState.RUNNING, from = KeyguardState.GONE, to = KeyguardState.AOD, ) ) runCurrent() transitionRepository.sendTransitionStep( TransitionStep( transitionState = TransitionState.CANCELED, from = KeyguardState.GONE, to = KeyguardState.AOD, ) ) runCurrent() transitionRepository.sendTransitionStep( TransitionStep( transitionState = TransitionState.STARTED, from = KeyguardState.AOD, to = KeyguardState.GONE, ) ) runCurrent() transitionRepository.sendTransitionStep( TransitionStep( transitionState = TransitionState.RUNNING, from = KeyguardState.AOD, to = KeyguardState.GONE, ) ) runCurrent() transitionRepository.sendTransitionStep( TransitionStep( transitionState = TransitionState.FINISHED, from = KeyguardState.AOD, to = KeyguardState.GONE, ) ) runCurrent() assertEquals( listOf( true, // Remains not visible from GONE -> AOD (canceled) -> AOD since we never // FINISHED in AOD, and special-case handling for the insecure camera launch // ensures that we use the lockscreen visibility for GONE (false) if we're // STARTED to GONE after a CANCELED from GONE. false, ), values ) transitionRepository.sendTransitionSteps( from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN, testScope, ) assertEquals( listOf( true, false, // Make sure there's no stuck overrides or something - we should make lockscreen // visible again once we're finished in LOCKSCREEN. true, ), values ) } /** */ @Test fun testLockscreenVisibility_trueDuringTransitionToGone_fromNotCanceledGone() = testScope.runTest { val values by collectValues(underTest.lockscreenVisibility) transitionRepository.sendTransitionSteps( from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE, testScope ) runCurrent() assertEquals( listOf( true, // Not visible when finished in GONE. false, ), values ) transitionRepository.sendTransitionStep( TransitionStep( transitionState = TransitionState.STARTED, from = KeyguardState.GONE, to = KeyguardState.AOD, ) ) runCurrent() transitionRepository.sendTransitionStep( TransitionStep( transitionState = TransitionState.RUNNING, from = KeyguardState.GONE, to = KeyguardState.AOD, ) ) runCurrent() assertEquals( listOf( true, // Still not visible during GONE -> AOD. false, ), values ) transitionRepository.sendTransitionStep( TransitionStep( transitionState = TransitionState.FINISHED, from = KeyguardState.GONE, to = KeyguardState.AOD, ) ) runCurrent() assertEquals( listOf( true, false, // Visible now that we're FINISHED in AOD. true ), values ) transitionRepository.sendTransitionStep( TransitionStep( transitionState = TransitionState.STARTED, from = KeyguardState.AOD, to = KeyguardState.GONE, ) ) runCurrent() transitionRepository.sendTransitionStep( TransitionStep( transitionState = TransitionState.RUNNING, from = KeyguardState.AOD, to = KeyguardState.GONE, ) ) runCurrent() assertEquals( listOf( true, false, // Remains visible from AOD during transition. true ), values ) transitionRepository.sendTransitionStep( TransitionStep( transitionState = TransitionState.FINISHED, from = KeyguardState.AOD, to = KeyguardState.GONE, ) ) runCurrent() assertEquals( listOf( true, false, true, // Until we're finished in GONE again. false ), values ) } } Loading
packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt +17 −0 Original line number Diff line number Diff line Loading @@ -35,6 +35,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.OFF import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.util.kotlin.pairwise import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi Loading @@ -46,6 +47,7 @@ import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn /** Encapsulates business-logic related to the keyguard transitions. */ @OptIn(ExperimentalCoroutinesApi::class) Loading Loading @@ -205,6 +207,21 @@ constructor( .map { step -> step.to } .shareIn(scope, SharingStarted.Eagerly, replay = 1) /** * A pair of the most recent STARTED step, and the transition step immediately preceding it. The * transition framework enforces that the previous step is either a CANCELED or FINISHED step, * and that the previous step was *to* the state the STARTED step is *from*. * * This flow can be used to access the previous step to determine whether it was CANCELED or * FINISHED. In the case of a CANCELED step, we can also figure out which state we were coming * from when we were canceled. */ val startedStepWithPrecedingStep = transitions .pairwise() .filter { it.newValue.transitionState == TransitionState.STARTED } .stateIn(scope, SharingStarted.Eagerly, null) /** * The last [KeyguardState] to which we [TransitionState.FINISHED] a transition. * Loading
packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt +22 −12 Original line number Diff line number Diff line Loading @@ -19,7 +19,9 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.BiometricUnlockModel import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow Loading Loading @@ -121,19 +123,27 @@ constructor( * want to know if the AOD/clock/notifs/etc. are visible. */ val lockscreenVisibility: Flow<Boolean> = combine( transitionInteractor.startedKeyguardTransitionStep, transitionInteractor.finishedKeyguardState, ) { startedStep, finishedState -> // If we finished the transition, use the finished state. If we're running a // transition, use the state we're transitioning FROM. This can be different from // the last finished state if a transition is interrupted. For example, if we were // transitioning from GONE to AOD and then started AOD -> LOCKSCREEN mid-transition, // we want to immediately use the visibility for AOD (lockscreenVisibility=true) // even though the lastFinishedState is still GONE (lockscreenVisibility=false). if (finishedState == startedStep.to) finishedState else startedStep.from transitionInteractor.currentKeyguardState .sample(transitionInteractor.startedStepWithPrecedingStep, ::Pair) .map { (currentState, startedWithPrev) -> val startedFromStep = startedWithPrev?.previousValue val startedStep = startedWithPrev?.newValue val returningToGoneAfterCancellation = startedStep?.to == KeyguardState.GONE && startedFromStep?.transitionState == TransitionState.CANCELED && startedFromStep.from == KeyguardState.GONE if (!returningToGoneAfterCancellation) { // By default, apply the lockscreen visibility of the current state. KeyguardState.lockscreenVisibleInState(currentState) } else { // If we're transitioning to GONE after a prior canceled transition from GONE, // then this is the camera launch transition from an asleep state back to GONE. // We don't want to show the lockscreen since we're aborting the lock and going // back to GONE. KeyguardState.lockscreenVisibleInState(KeyguardState.GONE) } } .map(KeyguardState::lockscreenVisibleInState) .distinctUntilChanged() /** Loading
packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt +319 −0 Original line number Diff line number Diff line Loading @@ -375,4 +375,323 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { values ) } @Test fun testLockscreenVisibility_usesFromState_ifCanceled() = testScope.runTest { val values by collectValues(underTest.lockscreenVisibility) transitionRepository.sendTransitionSteps( from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE, testScope ) runCurrent() assertEquals( listOf( // Initially should be true, as we start in LOCKSCREEN. true, // Then, false, since we finish in GONE. false, ), values ) transitionRepository.sendTransitionStep( TransitionStep( transitionState = TransitionState.STARTED, from = KeyguardState.GONE, to = KeyguardState.AOD, ) ) transitionRepository.sendTransitionStep( TransitionStep( transitionState = TransitionState.RUNNING, from = KeyguardState.GONE, to = KeyguardState.AOD, ) ) runCurrent() assertEquals( listOf( true, // Should remain false as we transition from GONE. false, ), values ) transitionRepository.sendTransitionStep( TransitionStep( transitionState = TransitionState.CANCELED, from = KeyguardState.GONE, to = KeyguardState.AOD, ) ) transitionRepository.sendTransitionStep( TransitionStep( transitionState = TransitionState.STARTED, from = KeyguardState.AOD, to = KeyguardState.LOCKSCREEN, ) ) runCurrent() assertEquals( listOf( true, false, // If we cancel and then go from LS -> GONE, we should immediately flip to the // visibility of the from state (LS). true, ), values ) transitionRepository.sendTransitionStep( TransitionStep( transitionState = TransitionState.FINISHED, from = KeyguardState.AOD, to = KeyguardState.LOCKSCREEN, ) ) runCurrent() assertEquals( listOf( true, false, true, ), values ) } /** * Tests the special case for insecure camera launch. CANCELING a transition from GONE and then * STARTING a transition back to GONE should never show the lockscreen, even though the current * state during the AOD/isAsleep -> GONE transition is AOD (where lockscreen visibility = true). */ @Test fun testLockscreenVisibility_falseDuringTransitionToGone_fromCanceledGone() = testScope.runTest { val values by collectValues(underTest.lockscreenVisibility) transitionRepository.sendTransitionSteps( from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE, testScope ) runCurrent() assertEquals( listOf( true, // Not visible since we're GONE. false, ), values ) transitionRepository.sendTransitionStep( TransitionStep( transitionState = TransitionState.STARTED, from = KeyguardState.GONE, to = KeyguardState.AOD, ) ) runCurrent() transitionRepository.sendTransitionStep( TransitionStep( transitionState = TransitionState.RUNNING, from = KeyguardState.GONE, to = KeyguardState.AOD, ) ) runCurrent() transitionRepository.sendTransitionStep( TransitionStep( transitionState = TransitionState.CANCELED, from = KeyguardState.GONE, to = KeyguardState.AOD, ) ) runCurrent() transitionRepository.sendTransitionStep( TransitionStep( transitionState = TransitionState.STARTED, from = KeyguardState.AOD, to = KeyguardState.GONE, ) ) runCurrent() transitionRepository.sendTransitionStep( TransitionStep( transitionState = TransitionState.RUNNING, from = KeyguardState.AOD, to = KeyguardState.GONE, ) ) runCurrent() transitionRepository.sendTransitionStep( TransitionStep( transitionState = TransitionState.FINISHED, from = KeyguardState.AOD, to = KeyguardState.GONE, ) ) runCurrent() assertEquals( listOf( true, // Remains not visible from GONE -> AOD (canceled) -> AOD since we never // FINISHED in AOD, and special-case handling for the insecure camera launch // ensures that we use the lockscreen visibility for GONE (false) if we're // STARTED to GONE after a CANCELED from GONE. false, ), values ) transitionRepository.sendTransitionSteps( from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN, testScope, ) assertEquals( listOf( true, false, // Make sure there's no stuck overrides or something - we should make lockscreen // visible again once we're finished in LOCKSCREEN. true, ), values ) } /** */ @Test fun testLockscreenVisibility_trueDuringTransitionToGone_fromNotCanceledGone() = testScope.runTest { val values by collectValues(underTest.lockscreenVisibility) transitionRepository.sendTransitionSteps( from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE, testScope ) runCurrent() assertEquals( listOf( true, // Not visible when finished in GONE. false, ), values ) transitionRepository.sendTransitionStep( TransitionStep( transitionState = TransitionState.STARTED, from = KeyguardState.GONE, to = KeyguardState.AOD, ) ) runCurrent() transitionRepository.sendTransitionStep( TransitionStep( transitionState = TransitionState.RUNNING, from = KeyguardState.GONE, to = KeyguardState.AOD, ) ) runCurrent() assertEquals( listOf( true, // Still not visible during GONE -> AOD. false, ), values ) transitionRepository.sendTransitionStep( TransitionStep( transitionState = TransitionState.FINISHED, from = KeyguardState.GONE, to = KeyguardState.AOD, ) ) runCurrent() assertEquals( listOf( true, false, // Visible now that we're FINISHED in AOD. true ), values ) transitionRepository.sendTransitionStep( TransitionStep( transitionState = TransitionState.STARTED, from = KeyguardState.AOD, to = KeyguardState.GONE, ) ) runCurrent() transitionRepository.sendTransitionStep( TransitionStep( transitionState = TransitionState.RUNNING, from = KeyguardState.AOD, to = KeyguardState.GONE, ) ) runCurrent() assertEquals( listOf( true, false, // Remains visible from AOD during transition. true ), values ) transitionRepository.sendTransitionStep( TransitionStep( transitionState = TransitionState.FINISHED, from = KeyguardState.AOD, to = KeyguardState.GONE, ) ) runCurrent() assertEquals( listOf( true, false, true, // Until we're finished in GONE again. false ), values ) } }