diff options
2 files changed, 1059 insertions, 403 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt index 4f7d9444020c..6828041eff5a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt @@ -21,23 +21,24 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectValues -import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN +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.CANCELED import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING import com.android.systemui.keyguard.shared.model.TransitionState.STARTED import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import junit.framework.Assert.assertEquals -import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest -import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -46,18 +47,11 @@ import org.junit.runner.RunWith @kotlinx.coroutines.ExperimentalCoroutinesApi class KeyguardTransitionInteractorTest : SysuiTestCase() { - private lateinit var underTest: KeyguardTransitionInteractor - private lateinit var repository: FakeKeyguardTransitionRepository - private val testScope = TestScope() - - @Before - fun setUp() { - repository = FakeKeyguardTransitionRepository() - underTest = KeyguardTransitionInteractorFactory.create( - scope = testScope.backgroundScope, - repository = repository, - ).keyguardTransitionInteractor - } + val kosmos = testKosmos() + + val underTest = kosmos.keyguardTransitionInteractor + val repository = kosmos.fakeKeyguardTransitionRepository + val testScope = kosmos.testScope @Test fun transitionCollectorsReceivesOnlyAppropriateEvents() = runTest { @@ -114,49 +108,51 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { } @Test - fun finishedKeyguardStateTests() = testScope.runTest { - val finishedSteps by collectValues(underTest.finishedKeyguardState) - runCurrent() - val steps = mutableListOf<TransitionStep>() - - steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0f, STARTED)) - steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0.5f, RUNNING)) - steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 1f, FINISHED)) - steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0f, STARTED)) - steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0.9f, RUNNING)) - steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 1f, FINISHED)) - steps.add(TransitionStep(AOD, GONE, 1f, STARTED)) - - steps.forEach { - repository.sendTransitionStep(it) + fun finishedKeyguardStateTests() = + testScope.runTest { + val finishedSteps by collectValues(underTest.finishedKeyguardState) runCurrent() + val steps = mutableListOf<TransitionStep>() + + steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0f, STARTED)) + steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0.5f, RUNNING)) + steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 1f, FINISHED)) + steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0f, STARTED)) + steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0.9f, RUNNING)) + steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 1f, FINISHED)) + steps.add(TransitionStep(AOD, GONE, 1f, STARTED)) + + steps.forEach { + repository.sendTransitionStep(it) + runCurrent() + } + + assertThat(finishedSteps).isEqualTo(listOf(LOCKSCREEN, PRIMARY_BOUNCER, AOD)) } - assertThat(finishedSteps).isEqualTo(listOf(LOCKSCREEN, PRIMARY_BOUNCER, AOD)) - } - @Test - fun startedKeyguardStateTests() = testScope.runTest { - val startedStates by collectValues(underTest.startedKeyguardState) - runCurrent() - val steps = mutableListOf<TransitionStep>() - - steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0f, STARTED)) - steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0.5f, RUNNING)) - steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 1f, FINISHED)) - steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0f, STARTED)) - steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0.9f, RUNNING)) - steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 1f, FINISHED)) - steps.add(TransitionStep(AOD, GONE, 1f, STARTED)) - - steps.forEach { - repository.sendTransitionStep(it) + fun startedKeyguardStateTests() = + testScope.runTest { + val startedStates by collectValues(underTest.startedKeyguardState) runCurrent() + val steps = mutableListOf<TransitionStep>() + + steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0f, STARTED)) + steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0.5f, RUNNING)) + steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 1f, FINISHED)) + steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0f, STARTED)) + steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0.9f, RUNNING)) + steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 1f, FINISHED)) + steps.add(TransitionStep(AOD, GONE, 1f, STARTED)) + + steps.forEach { + repository.sendTransitionStep(it) + runCurrent() + } + + assertThat(startedStates).isEqualTo(listOf(LOCKSCREEN, PRIMARY_BOUNCER, AOD, GONE)) } - assertThat(startedStates).isEqualTo(listOf(LOCKSCREEN, PRIMARY_BOUNCER, AOD, GONE)) - } - @Test fun finishedKeyguardTransitionStepTests() = runTest { val finishedSteps by collectValues(underTest.finishedKeyguardTransitionStep) @@ -178,7 +174,7 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { // Ignore the default state. assertThat(finishedSteps.subList(1, finishedSteps.size)) - .isEqualTo(listOf(steps[2], steps[5])) + .isEqualTo(listOf(steps[2], steps[5])) } @Test @@ -233,500 +229,1067 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { } @Test - fun isInTransitionToState() = testScope.runTest { - val results by collectValues(underTest.isInTransitionToState(GONE)) + fun isInTransitionToAnyState() = + testScope.runTest { + val inTransition by collectValues(underTest.isInTransitionToAnyState) + + assertEquals( + listOf( + true, // The repo is seeded with a transition from OFF to LOCKSCREEN. + false, + ), + inTransition + ) + + sendSteps( + TransitionStep(LOCKSCREEN, GONE, 0f, STARTED), + ) + + assertEquals( + listOf( + true, + false, + true, + ), + inTransition + ) + + sendSteps( + TransitionStep(LOCKSCREEN, GONE, 0.5f, RUNNING), + ) + + assertEquals( + listOf( + true, + false, + true, + ), + inTransition + ) + + sendSteps( + TransitionStep(LOCKSCREEN, GONE, 1f, FINISHED), + ) + + assertEquals( + listOf( + true, + false, + true, + false, + ), + inTransition + ) + } + + @Test + fun isInTransitionToAnyState_finishedStateIsStartedStateAfterCancels() = + testScope.runTest { + val inTransition by collectValues(underTest.isInTransitionToAnyState) + + assertEquals( + listOf( + true, + false, + ), + inTransition + ) + + // Start FINISHED in GONE. + sendSteps( + TransitionStep(LOCKSCREEN, GONE, 0f, STARTED), + TransitionStep(LOCKSCREEN, GONE, 0.5f, RUNNING), + TransitionStep(LOCKSCREEN, GONE, 1f, FINISHED), + ) + + assertEquals( + listOf( + true, + false, + true, + false, + ), + inTransition + ) + + sendSteps( + TransitionStep(GONE, DOZING, 0f, STARTED), + ) + + assertEquals( + listOf( + true, + false, + true, + false, + true, + ), + inTransition + ) + + sendSteps( + TransitionStep(GONE, DOZING, 0.5f, RUNNING), + TransitionStep(GONE, DOZING, 0.6f, CANCELED), + TransitionStep(DOZING, LOCKSCREEN, 0f, STARTED), + TransitionStep(DOZING, LOCKSCREEN, 0.5f, RUNNING), + TransitionStep(DOZING, LOCKSCREEN, 0.6f, CANCELED), + TransitionStep(LOCKSCREEN, GONE, 0f, STARTED), + ) + + assertEquals( + listOf( + true, + false, + true, + false, + // We should have been in transition throughout the entire transition, including + // both cancellations, and we should still be in transition despite now + // transitioning to GONE, the state we're also FINISHED in. + true, + ), + inTransition + ) + + sendSteps( + TransitionStep(LOCKSCREEN, GONE, 0.5f, RUNNING), + TransitionStep(LOCKSCREEN, GONE, 1f, FINISHED), + ) + + assertEquals( + listOf( + true, + false, + true, + false, + true, + false, + ), + inTransition + ) + } + + @Test + fun isInTransitionToState() = + testScope.runTest { + val results by collectValues(underTest.isInTransitionToState(GONE)) - sendSteps( + sendSteps( TransitionStep(AOD, DOZING, 0f, STARTED), TransitionStep(AOD, DOZING, 0.5f, RUNNING), TransitionStep(AOD, DOZING, 1f, FINISHED), - ) - + ) - assertThat(results).isEqualTo(listOf( - false, - )) + assertThat(results) + .isEqualTo( + listOf( + false, + ) + ) - sendSteps( + sendSteps( TransitionStep(DOZING, GONE, 0f, STARTED), - ) + ) - assertThat(results).isEqualTo(listOf( - false, - true, - )) + assertThat(results) + .isEqualTo( + listOf( + false, + true, + ) + ) - sendSteps( + sendSteps( TransitionStep(DOZING, GONE, 0f, RUNNING), - ) + ) - assertThat(results).isEqualTo(listOf( - false, - true, - )) + assertThat(results) + .isEqualTo( + listOf( + false, + true, + ) + ) - sendSteps( + sendSteps( TransitionStep(DOZING, GONE, 0f, FINISHED), - ) + ) - assertThat(results).isEqualTo(listOf( - false, - true, - false, - )) + assertThat(results) + .isEqualTo( + listOf( + false, + true, + false, + ) + ) - sendSteps( + sendSteps( TransitionStep(GONE, DOZING, 0f, STARTED), TransitionStep(GONE, DOZING, 0f, RUNNING), TransitionStep(GONE, DOZING, 1f, FINISHED), - ) + ) - assertThat(results).isEqualTo(listOf( - false, - true, - false, - )) + assertThat(results) + .isEqualTo( + listOf( + false, + true, + false, + ) + ) - sendSteps( + sendSteps( TransitionStep(DOZING, GONE, 0f, STARTED), TransitionStep(DOZING, GONE, 0f, RUNNING), - ) - - assertThat(results).isEqualTo(listOf( - false, - true, - false, - true, - )) - } + ) + + assertThat(results) + .isEqualTo( + listOf( + false, + true, + false, + true, + ) + ) + } @Test - fun isInTransitionFromState() = testScope.runTest { - val results by collectValues(underTest.isInTransitionFromState(DOZING)) + fun isInTransitionFromState() = + testScope.runTest { + val results by collectValues(underTest.isInTransitionFromState(DOZING)) - sendSteps( + sendSteps( TransitionStep(AOD, DOZING, 0f, STARTED), TransitionStep(AOD, DOZING, 0.5f, RUNNING), TransitionStep(AOD, DOZING, 1f, FINISHED), - ) - + ) - assertThat(results).isEqualTo(listOf( - false, - )) + assertThat(results) + .isEqualTo( + listOf( + false, + ) + ) - sendSteps( + sendSteps( TransitionStep(DOZING, GONE, 0f, STARTED), - ) + ) - assertThat(results).isEqualTo(listOf( - false, - true, - )) + assertThat(results) + .isEqualTo( + listOf( + false, + true, + ) + ) - sendSteps( + sendSteps( TransitionStep(DOZING, GONE, 0f, RUNNING), - ) + ) - assertThat(results).isEqualTo(listOf( - false, - true, - )) + assertThat(results) + .isEqualTo( + listOf( + false, + true, + ) + ) - sendSteps( + sendSteps( TransitionStep(DOZING, GONE, 0f, FINISHED), - ) + ) - assertThat(results).isEqualTo(listOf( - false, - true, - false, - )) + assertThat(results) + .isEqualTo( + listOf( + false, + true, + false, + ) + ) - sendSteps( + sendSteps( TransitionStep(GONE, DOZING, 0f, STARTED), TransitionStep(GONE, DOZING, 0f, RUNNING), TransitionStep(GONE, DOZING, 1f, FINISHED), - ) + ) - assertThat(results).isEqualTo(listOf( - false, - true, - false, - )) + assertThat(results) + .isEqualTo( + listOf( + false, + true, + false, + ) + ) - sendSteps( + sendSteps( TransitionStep(DOZING, GONE, 0f, STARTED), TransitionStep(DOZING, GONE, 0f, RUNNING), - ) - - assertThat(results).isEqualTo(listOf( - false, - true, - false, - true, - )) - } + ) + + assertThat(results) + .isEqualTo( + listOf( + false, + true, + false, + true, + ) + ) + } @Test - fun isInTransitionFromStateWhere() = testScope.runTest { - val results by collectValues(underTest.isInTransitionFromStateWhere { - it == DOZING - }) + fun isInTransitionFromStateWhere() = + testScope.runTest { + val results by collectValues(underTest.isInTransitionFromStateWhere { it == DOZING }) - sendSteps( + sendSteps( TransitionStep(AOD, DOZING, 0f, STARTED), TransitionStep(AOD, DOZING, 0.5f, RUNNING), TransitionStep(AOD, DOZING, 1f, FINISHED), - ) - + ) - assertThat(results).isEqualTo(listOf( - false, - )) + assertThat(results) + .isEqualTo( + listOf( + false, + ) + ) - sendSteps( + sendSteps( TransitionStep(DOZING, GONE, 0f, STARTED), - ) + ) - assertThat(results).isEqualTo(listOf( - false, - true, - )) + assertThat(results) + .isEqualTo( + listOf( + false, + true, + ) + ) - sendSteps( + sendSteps( TransitionStep(DOZING, GONE, 0f, RUNNING), - ) + ) - assertThat(results).isEqualTo(listOf( - false, - true, - )) + assertThat(results) + .isEqualTo( + listOf( + false, + true, + ) + ) - sendSteps( + sendSteps( TransitionStep(DOZING, GONE, 0f, FINISHED), - ) + ) - assertThat(results).isEqualTo(listOf( - false, - true, - false, - )) + assertThat(results) + .isEqualTo( + listOf( + false, + true, + false, + ) + ) - sendSteps( + sendSteps( TransitionStep(GONE, DOZING, 0f, STARTED), TransitionStep(GONE, DOZING, 0f, RUNNING), TransitionStep(GONE, DOZING, 1f, FINISHED), - ) + ) - assertThat(results).isEqualTo(listOf( - false, - true, - false, - )) + assertThat(results) + .isEqualTo( + listOf( + false, + true, + false, + ) + ) - sendSteps( + sendSteps( TransitionStep(DOZING, GONE, 0f, STARTED), TransitionStep(DOZING, GONE, 0f, RUNNING), - ) - - assertThat(results).isEqualTo(listOf( - false, - true, - false, - true, - )) - } + ) + + assertThat(results) + .isEqualTo( + listOf( + false, + true, + false, + true, + ) + ) + } @Test - fun isInTransitionWhere() = testScope.runTest { - val results by collectValues(underTest.isInTransitionWhere( - fromStatePredicate = { it == DOZING }, - toStatePredicate = { it == GONE }, - )) + fun isInTransitionWhere() = + testScope.runTest { + val results by + collectValues( + underTest.isInTransitionWhere( + fromStatePredicate = { it == DOZING }, + toStatePredicate = { it == GONE }, + ) + ) - sendSteps( + sendSteps( TransitionStep(AOD, DOZING, 0f, STARTED), TransitionStep(AOD, DOZING, 0.5f, RUNNING), TransitionStep(AOD, DOZING, 1f, FINISHED), - ) - + ) - assertThat(results).isEqualTo(listOf( - false, - )) + assertThat(results) + .isEqualTo( + listOf( + false, + ) + ) - sendSteps( + sendSteps( TransitionStep(DOZING, GONE, 0f, STARTED), - ) + ) - assertThat(results).isEqualTo(listOf( - false, - true, - )) + assertThat(results) + .isEqualTo( + listOf( + false, + true, + ) + ) - sendSteps( + sendSteps( TransitionStep(DOZING, GONE, 0f, RUNNING), - ) + ) - assertThat(results).isEqualTo(listOf( - false, - true, - )) + assertThat(results) + .isEqualTo( + listOf( + false, + true, + ) + ) - sendSteps( + sendSteps( TransitionStep(DOZING, GONE, 0f, FINISHED), - ) + ) - assertThat(results).isEqualTo(listOf( - false, - true, - false, - )) + assertThat(results) + .isEqualTo( + listOf( + false, + true, + false, + ) + ) - sendSteps( + sendSteps( TransitionStep(GONE, DOZING, 0f, STARTED), TransitionStep(GONE, DOZING, 0f, RUNNING), TransitionStep(GONE, DOZING, 1f, FINISHED), - ) + ) - assertThat(results).isEqualTo(listOf( - false, - true, - false, - )) + assertThat(results) + .isEqualTo( + listOf( + false, + true, + false, + ) + ) - sendSteps( + sendSteps( TransitionStep(DOZING, GONE, 0f, STARTED), TransitionStep(DOZING, GONE, 0f, RUNNING), - ) - - assertThat(results).isEqualTo(listOf( - false, - true, - false, - true, - )) - } + ) + + assertThat(results) + .isEqualTo( + listOf( + false, + true, + false, + true, + ) + ) + } @Test - fun isFinishedInStateWhere() = testScope.runTest { - val results by collectValues(underTest.isFinishedInStateWhere { it == GONE } ) + fun isInTransitionWhere_withCanceledStep() = + testScope.runTest { + val results by + collectValues( + underTest.isInTransitionWhere( + fromStatePredicate = { it == DOZING }, + toStatePredicate = { it == GONE }, + ) + ) - sendSteps( + sendSteps( TransitionStep(AOD, DOZING, 0f, STARTED), TransitionStep(AOD, DOZING, 0.5f, RUNNING), TransitionStep(AOD, DOZING, 1f, FINISHED), - ) + ) - assertThat(results).isEqualTo(listOf( - false, // Finished in DOZING, not GONE. - )) + assertThat(results) + .isEqualTo( + listOf( + false, + ) + ) - sendSteps(TransitionStep(DOZING, GONE, 0f, STARTED)) + sendSteps( + TransitionStep(DOZING, GONE, 0f, STARTED), + ) - assertThat(results).isEqualTo(listOf( - false, - )) + assertThat(results) + .isEqualTo( + listOf( + false, + true, + ) + ) - sendSteps(TransitionStep(DOZING, GONE, 0f, RUNNING)) + sendSteps( + TransitionStep(DOZING, GONE, 0f, RUNNING), + ) - assertThat(results).isEqualTo(listOf( - false, - )) + assertThat(results) + .isEqualTo( + listOf( + false, + true, + ) + ) - sendSteps(TransitionStep(DOZING, GONE, 1f, FINISHED)) + sendSteps( + TransitionStep(DOZING, GONE, 0f, CANCELED), + ) - assertThat(results).isEqualTo(listOf( - false, - true, - )) + assertThat(results) + .isEqualTo( + listOf( + false, + true, + ) + ) - sendSteps( + sendSteps( TransitionStep(GONE, DOZING, 0f, STARTED), TransitionStep(GONE, DOZING, 0f, RUNNING), - ) + TransitionStep(GONE, DOZING, 1f, FINISHED), + ) - assertThat(results).isEqualTo(listOf( - false, - true, - )) + assertThat(results) + .isEqualTo( + listOf( + false, + true, + false, + ) + ) + + sendSteps( + TransitionStep(DOZING, GONE, 0f, STARTED), + TransitionStep(DOZING, GONE, 0f, RUNNING), + ) + + assertThat(results) + .isEqualTo( + listOf( + false, + true, + false, + true, + ) + ) + } + + @Test + fun isFinishedInStateWhere() = + testScope.runTest { + val results by collectValues(underTest.isFinishedInStateWhere { it == GONE }) + + sendSteps( + TransitionStep(AOD, DOZING, 0f, STARTED), + TransitionStep(AOD, DOZING, 0.5f, RUNNING), + TransitionStep(AOD, DOZING, 1f, FINISHED), + ) + + assertThat(results) + .isEqualTo( + listOf( + false, // Finished in DOZING, not GONE. + ) + ) + + sendSteps(TransitionStep(DOZING, GONE, 0f, STARTED)) + + assertThat(results) + .isEqualTo( + listOf( + false, + ) + ) + + sendSteps(TransitionStep(DOZING, GONE, 0f, RUNNING)) + + assertThat(results) + .isEqualTo( + listOf( + false, + ) + ) + + sendSteps(TransitionStep(DOZING, GONE, 1f, FINISHED)) - sendSteps(TransitionStep(GONE, DOZING, 1f, FINISHED)) + assertThat(results) + .isEqualTo( + listOf( + false, + true, + ) + ) - assertThat(results).isEqualTo(listOf( - false, - true, - false, - )) + sendSteps( + TransitionStep(GONE, DOZING, 0f, STARTED), + TransitionStep(GONE, DOZING, 0f, RUNNING), + ) - sendSteps( + assertThat(results) + .isEqualTo( + listOf( + false, + true, + ) + ) + + sendSteps(TransitionStep(GONE, DOZING, 1f, FINISHED)) + + assertThat(results) + .isEqualTo( + listOf( + false, + true, + false, + ) + ) + + sendSteps( TransitionStep(DOZING, GONE, 0f, STARTED), TransitionStep(DOZING, GONE, 0f, RUNNING), - ) - - assertThat(results).isEqualTo(listOf( - false, - true, - false, - )) - - sendSteps(TransitionStep(DOZING, GONE, 1f, FINISHED)) - - assertThat(results).isEqualTo(listOf( - false, - true, - false, - true, - )) - } + ) + + assertThat(results) + .isEqualTo( + listOf( + false, + true, + false, + ) + ) + + sendSteps(TransitionStep(DOZING, GONE, 1f, FINISHED)) + + assertThat(results) + .isEqualTo( + listOf( + false, + true, + false, + true, + ) + ) + } @Test - fun isFinishedInState() = testScope.runTest { - val results by collectValues(underTest.isFinishedInState(GONE)) + fun isFinishedInState() = + testScope.runTest { + val results by collectValues(underTest.isFinishedInState(GONE)) - sendSteps( + sendSteps( TransitionStep(AOD, DOZING, 0f, STARTED), TransitionStep(AOD, DOZING, 0.5f, RUNNING), TransitionStep(AOD, DOZING, 1f, FINISHED), - ) + ) - assertThat(results).isEqualTo(listOf( - false, // Finished in DOZING, not GONE. - )) + assertThat(results) + .isEqualTo( + listOf( + false, // Finished in DOZING, not GONE. + ) + ) - sendSteps(TransitionStep(DOZING, GONE, 0f, STARTED)) + sendSteps(TransitionStep(DOZING, GONE, 0f, STARTED)) - assertThat(results).isEqualTo(listOf( - false, - )) + assertThat(results) + .isEqualTo( + listOf( + false, + ) + ) - sendSteps(TransitionStep(DOZING, GONE, 0f, RUNNING)) + sendSteps(TransitionStep(DOZING, GONE, 0f, RUNNING)) - assertThat(results).isEqualTo(listOf( - false, - )) + assertThat(results) + .isEqualTo( + listOf( + false, + ) + ) - sendSteps(TransitionStep(DOZING, GONE, 1f, FINISHED)) + sendSteps(TransitionStep(DOZING, GONE, 1f, FINISHED)) - assertThat(results).isEqualTo(listOf( - false, - true, - )) + assertThat(results) + .isEqualTo( + listOf( + false, + true, + ) + ) - sendSteps( + sendSteps( TransitionStep(GONE, DOZING, 0f, STARTED), TransitionStep(GONE, DOZING, 0f, RUNNING), - ) + ) - assertThat(results).isEqualTo(listOf( - false, - true, - )) + assertThat(results) + .isEqualTo( + listOf( + false, + true, + ) + ) - sendSteps(TransitionStep(GONE, DOZING, 1f, FINISHED)) + sendSteps(TransitionStep(GONE, DOZING, 1f, FINISHED)) - assertThat(results).isEqualTo(listOf( - false, - true, - false, - )) + assertThat(results) + .isEqualTo( + listOf( + false, + true, + false, + ) + ) - sendSteps( + sendSteps( TransitionStep(DOZING, GONE, 0f, STARTED), TransitionStep(DOZING, GONE, 0f, RUNNING), - ) - - assertThat(results).isEqualTo(listOf( - false, - true, - false, - )) - - sendSteps(TransitionStep(DOZING, GONE, 1f, FINISHED)) - - assertThat(results).isEqualTo(listOf( - false, - true, - false, - true, - )) - } + ) + + assertThat(results) + .isEqualTo( + listOf( + false, + true, + false, + ) + ) + + sendSteps(TransitionStep(DOZING, GONE, 1f, FINISHED)) + + assertThat(results) + .isEqualTo( + listOf( + false, + true, + false, + true, + ) + ) + } @Test - fun finishedKeyguardState_emitsAgainIfCancelledAndReversed() = testScope.runTest { - val finishedStates by collectValues(underTest.finishedKeyguardState) + fun finishedKeyguardState_emitsAgainIfCancelledAndReversed() = + testScope.runTest { + val finishedStates by collectValues(underTest.finishedKeyguardState) - // We default FINISHED in LOCKSCREEN. - assertEquals(listOf( - LOCKSCREEN - ), finishedStates) + // We default FINISHED in LOCKSCREEN. + assertEquals(listOf(LOCKSCREEN), finishedStates) - sendSteps( + sendSteps( TransitionStep(LOCKSCREEN, AOD, 0f, STARTED), TransitionStep(LOCKSCREEN, AOD, 0.5f, RUNNING), TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED), - ) + ) - // We're FINISHED in AOD. - assertEquals(listOf( - LOCKSCREEN, - AOD, - ), finishedStates) + // We're FINISHED in AOD. + assertEquals( + listOf( + LOCKSCREEN, + AOD, + ), + finishedStates + ) - // Transition back to LOCKSCREEN. - sendSteps( + // Transition back to LOCKSCREEN. + sendSteps( TransitionStep(AOD, LOCKSCREEN, 0f, STARTED), TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING), TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED), - ) + ) - // We're FINISHED in LOCKSCREEN. - assertEquals(listOf( - LOCKSCREEN, - AOD, - LOCKSCREEN, - ), finishedStates) + // We're FINISHED in LOCKSCREEN. + assertEquals( + listOf( + LOCKSCREEN, + AOD, + LOCKSCREEN, + ), + finishedStates + ) - sendSteps( + sendSteps( TransitionStep(LOCKSCREEN, GONE, 0f, STARTED), TransitionStep(LOCKSCREEN, GONE, 0.5f, RUNNING), - ) + ) - // We've STARTED a transition to GONE but not yet finished it so we're still FINISHED in - // LOCKSCREEN. - assertEquals(listOf( - LOCKSCREEN, - AOD, - LOCKSCREEN, - ), finishedStates) + // We've STARTED a transition to GONE but not yet finished it so we're still FINISHED in + // LOCKSCREEN. + assertEquals( + listOf( + LOCKSCREEN, + AOD, + LOCKSCREEN, + ), + finishedStates + ) - sendSteps( + sendSteps( TransitionStep(LOCKSCREEN, GONE, 0.6f, CANCELED), - ) + ) - // We've CANCELED a transition to GONE, we're still FINISHED in LOCKSCREEN. - assertEquals(listOf( - LOCKSCREEN, - AOD, - LOCKSCREEN, - ), finishedStates) + // We've CANCELED a transition to GONE, we're still FINISHED in LOCKSCREEN. + assertEquals( + listOf( + LOCKSCREEN, + AOD, + LOCKSCREEN, + ), + finishedStates + ) - sendSteps( + sendSteps( TransitionStep(GONE, LOCKSCREEN, 0.6f, STARTED), TransitionStep(GONE, LOCKSCREEN, 0.9f, RUNNING), TransitionStep(GONE, LOCKSCREEN, 1f, FINISHED), - ) - - // Expect another emission of LOCKSCREEN, as we have FINISHED a second transition to - // LOCKSCREEN after the cancellation. - assertEquals(listOf( - LOCKSCREEN, - AOD, - LOCKSCREEN, - LOCKSCREEN, - ), finishedStates) - } + ) + + // Expect another emission of LOCKSCREEN, as we have FINISHED a second transition to + // LOCKSCREEN after the cancellation. + assertEquals( + listOf( + LOCKSCREEN, + AOD, + LOCKSCREEN, + LOCKSCREEN, + ), + finishedStates + ) + } + + @Test + fun testCurrentState() = + testScope.runTest { + val currentStates by collectValues(underTest.currentKeyguardState) + + // We init the repo with a transition from OFF -> LOCKSCREEN. + assertEquals( + listOf( + OFF, + LOCKSCREEN, + ), + currentStates + ) + + sendSteps( + TransitionStep(LOCKSCREEN, AOD, 0f, STARTED), + ) + + // The current state should continue to be LOCKSCREEN as we transition to AOD. + assertEquals( + listOf( + OFF, + LOCKSCREEN, + ), + currentStates + ) + + sendSteps( + TransitionStep(LOCKSCREEN, AOD, 0.5f, RUNNING), + ) + + // The current state should continue to be LOCKSCREEN as we transition to AOD. + assertEquals( + listOf( + OFF, + LOCKSCREEN, + ), + currentStates + ) + + sendSteps( + TransitionStep(LOCKSCREEN, AOD, 0.6f, CANCELED), + ) + + // Once CANCELED, we're still currently in LOCKSCREEN... + assertEquals( + listOf( + OFF, + LOCKSCREEN, + ), + currentStates + ) + + sendSteps( + TransitionStep(AOD, LOCKSCREEN, 0.6f, STARTED), + ) + + // ...until STARTING back to LOCKSCREEN, at which point the "current" state should be + // the + // one we're transitioning from, despite never FINISHING in that state. + assertEquals( + listOf( + OFF, + LOCKSCREEN, + AOD, + ), + currentStates + ) + + sendSteps( + TransitionStep(AOD, LOCKSCREEN, 0.8f, RUNNING), + TransitionStep(AOD, LOCKSCREEN, 0.8f, FINISHED), + ) + + // FINSHING in LOCKSCREEN should update the current state to LOCKSCREEN. + assertEquals( + listOf( + OFF, + LOCKSCREEN, + AOD, + LOCKSCREEN, + ), + currentStates + ) + } + + @Test + fun testCurrentState_multipleCancellations_backToLastFinishedState() = + testScope.runTest { + val currentStates by collectValues(underTest.currentKeyguardState) + + // We init the repo with a transition from OFF -> LOCKSCREEN. + assertEquals( + listOf( + OFF, + LOCKSCREEN, + ), + currentStates + ) + + sendSteps( + TransitionStep(LOCKSCREEN, GONE, 0f, STARTED), + TransitionStep(LOCKSCREEN, GONE, 0.5f, RUNNING), + TransitionStep(LOCKSCREEN, GONE, 1f, FINISHED), + ) + + assertEquals( + listOf( + // Default transition from OFF -> LOCKSCREEN + OFF, + LOCKSCREEN, + // Transitioned to GONE + GONE, + ), + currentStates + ) + + sendSteps( + TransitionStep(GONE, DOZING, 0f, STARTED), + TransitionStep(GONE, DOZING, 0.5f, RUNNING), + TransitionStep(GONE, DOZING, 0.6f, CANCELED), + ) + + assertEquals( + listOf( + OFF, + LOCKSCREEN, + GONE, + // Current state should not be DOZING until the post-cancelation transition is + // STARTED + ), + currentStates + ) + + sendSteps( + TransitionStep(DOZING, LOCKSCREEN, 0f, STARTED), + ) + + assertEquals( + listOf( + OFF, + LOCKSCREEN, + GONE, + // DOZING -> LS STARTED, DOZING is now the current state. + DOZING, + ), + currentStates + ) + + sendSteps( + TransitionStep(DOZING, LOCKSCREEN, 0.5f, RUNNING), + TransitionStep(DOZING, LOCKSCREEN, 0.6f, CANCELED), + ) + + assertEquals( + listOf( + OFF, + LOCKSCREEN, + GONE, + DOZING, + ), + currentStates + ) + + sendSteps( + TransitionStep(LOCKSCREEN, GONE, 0f, STARTED), + ) + + assertEquals( + listOf( + OFF, + LOCKSCREEN, + GONE, + DOZING, + // LS -> GONE STARTED, LS is now the current state. + LOCKSCREEN, + ), + currentStates + ) + + sendSteps( + TransitionStep(LOCKSCREEN, GONE, 0.5f, RUNNING), + TransitionStep(LOCKSCREEN, GONE, 1f, FINISHED), + ) + + assertEquals( + listOf( + OFF, + LOCKSCREEN, + GONE, + DOZING, + LOCKSCREEN, + // FINISHED in GONE, GONE is now the current state. + GONE, + ), + currentStates + ) + } private suspend fun sendSteps(vararg steps: TransitionStep) { steps.forEach { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt index f7d1543e4650..e0c3fea7e7ee 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt @@ -36,17 +36,19 @@ import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.shareIn /** Encapsulates business-logic related to the keyguard transitions. */ +@OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton class KeyguardTransitionInteractor @Inject @@ -181,29 +183,121 @@ constructor( val finishedKeyguardTransitionStep: Flow<TransitionStep> = repository.transitions.filter { step -> step.transitionState == TransitionState.FINISHED } - /** The destination state of the last started transition. */ + /** The destination state of the last [TransitionState.STARTED] transition. */ val startedKeyguardState: SharedFlow<KeyguardState> = startedKeyguardTransitionStep .map { step -> step.to } .shareIn(scope, SharingStarted.Eagerly, replay = 1) - /** The last completed [KeyguardState] transition */ + /** + * The last [KeyguardState] to which we [TransitionState.FINISHED] a transition. + * + * WARNING: This will NOT emit a value if a transition is CANCELED, and will also not emit a + * value when a subsequent transition is STARTED. It will *only* emit once we have finally + * FINISHED in a state. This can have unintuitive implications. + * + * For example, if we're transitioning from GONE -> DOZING, and that transition is CANCELED in + * favor of a DOZING -> LOCKSCREEN transition, the FINISHED state is still GONE, and will remain + * GONE throughout the DOZING -> LOCKSCREEN transition until the DOZING -> LOCKSCREEN transition + * finishes (at which point we'll be FINISHED in LOCKSCREEN). + * + * Since there's no real limit to how many consecutive transitions can be canceled, it's even + * possible for the FINISHED state to be the same as the STARTED state while still + * transitioning. + * + * For example: + * 1. We're finished in GONE. + * 2. The user presses the power button, starting a GONE -> DOZING transition. We're still + * FINISHED in GONE. + * 3. The user changes their mind, pressing the power button to wake up; this starts a DOZING -> + * LOCKSCREEN transition. We're still FINISHED in GONE. + * 4. The user quickly swipes away the lockscreen prior to DOZING -> LOCKSCREEN finishing; this + * starts a LOCKSCREEN -> GONE transition. We're still FINISHED in GONE, but we've also + * STARTED a transition *to* GONE. + * 5. We'll emit KeyguardState.GONE again once the transition finishes. + * + * If you just need to know when we eventually settle into a state, this flow is likely + * sufficient. However, if you're having issues with state *during* transitions started after + * one or more canceled transitions, you probably need to use [currentKeyguardState]. + */ val finishedKeyguardState: SharedFlow<KeyguardState> = finishedKeyguardTransitionStep .map { step -> step.to } .shareIn(scope, SharingStarted.Eagerly, replay = 1) /** - * Whether we're currently in a transition to a new [KeyguardState] and haven't yet completed - * it. + * The [KeyguardState] we're currently in. + * + * If we're not in transition, this is simply the [finishedKeyguardState]. If we're in + * transition, this is the state we're transitioning *from*. + * + * Absent CANCELED transitions, [currentKeyguardState] and [finishedKeyguardState] are always + * identical - if a transition FINISHES in a given state, the subsequent state we START a + * transition *from* would always be that same previously FINISHED state. + * + * However, if a transition is CANCELED, the next transition will START from a state we never + * FINISHED in. For example, if we transition from GONE -> DOZING, but CANCEL that transition in + * favor of DOZING -> LOCKSCREEN, we've STARTED a transition *from* DOZING despite never + * FINISHING in DOZING. Thus, the current state will be DOZING but the FINISHED state will still + * be GONE. + * + * In this example, if there was DOZING-related state that needs to be set up in order to + * properly render a DOZING -> LOCKSCREEN transition, it would never be set up if we were + * listening for [finishedKeyguardState] to emit DOZING. However, [currentKeyguardState] would + * emit DOZING immediately upon STARTING DOZING -> LOCKSCREEN, allowing us to set up the state. + * + * Whether you want to use [currentKeyguardState] or [finishedKeyguardState] depends on your + * specific use case and how you want to handle cancellations. In general, if you're dealing + * with state/UI present across multiple [KeyguardState]s, you probably want + * [currentKeyguardState]. If you're dealing with state/UI encapsulated within a single state, + * you likely want [finishedKeyguardState]. + * + * As an example, let's say you want to animate in a message on the lockscreen UI after waking + * up, and that TextView is not involved in animations between states. You'd want to collect + * [finishedKeyguardState], so you'll only animate it in once we're settled on the lockscreen. + * If you use [currentKeyguardState] in this case, a DOZING -> LOCKSCREEN transition that is + * interrupted by a LOCKSCREEN -> GONE transition would cause the message to become visible + * immediately upon LOCKSCREEN -> GONE STARTING, as the current state would become LOCKSCREEN in + * that case. That's likely not what you want. + * + * On the other hand, let's say you're animating the smartspace from alpha 0f to 1f during + * DOZING -> LOCKSCREEN, but the transition is interrupted by LOCKSCREEN -> GONE. LS -> GONE + * needs the smartspace to be alpha=1f so that it can play the shared-element unlock animation. + * In this case, we'd want to collect [currentKeyguardState] and ensure the smartspace is + * visible when the current state is LOCKSCREEN. If you use [finishedKeyguardState] in this + * case, the smartspace will never be set to alpha = 1f and you'll have a half-faded smartspace + * during the LS -> GONE transition. + * + * If you need special-case handling for cancellations (such as conditional handling depending + * on which [KeyguardState] was canceled) you can collect [canceledKeyguardTransitionStep] + * directly. + * + * As a helpful footnote, here's the values of [finishedKeyguardState] and + * [currentKeyguardState] during a sequence with two cancellations: + * 1. We're FINISHED in GONE. currentKeyguardState=GONE; finishedKeyguardState=GONE. + * 2. We START a transition from GONE -> DOZING. currentKeyguardState=GONE; + * finishedKeyguardState=GONE. + * 3. We CANCEL this transition and START a transition from DOZING -> LOCKSCREEN. + * currentKeyguardState=DOZING; finishedKeyguardState=GONE. + * 4. We subsequently also CANCEL DOZING -> LOCKSCREEN and START LOCKSCREEN -> GONE. + * currentKeyguardState=LOCKSCREEN finishedKeyguardState=GONE. + * 5. LOCKSCREEN -> GONE is allowed to FINISH. currentKeyguardState=GONE; + * finishedKeyguardState=GONE. */ - val isInTransitionToAnyState = - combine( - startedKeyguardTransitionStep, - finishedKeyguardState, - ) { startedStep, finishedState -> - startedStep.to != finishedState - } + val currentKeyguardState: SharedFlow<KeyguardState> = + repository.transitions + .mapLatest { + if (it.transitionState == TransitionState.FINISHED) { + it.to + } else { + it.from + } + } + .distinctUntilChanged() + .shareIn(scope, SharingStarted.Eagerly, replay = 1) + + /** Whether we've currently STARTED a transition and haven't yet FINISHED it. */ + val isInTransitionToAnyState = isInTransitionWhere({ true }, { true }) /** * The amount of transition into or out of the given [KeyguardState]. @@ -293,13 +387,12 @@ constructor( fromStatePredicate: (KeyguardState) -> Boolean, toStatePredicate: (KeyguardState) -> Boolean, ): Flow<Boolean> { - return combine( - startedKeyguardTransitionStep, - finishedKeyguardState, - ) { startedStep, finishedState -> - fromStatePredicate(startedStep.from) && - toStatePredicate(startedStep.to) && - finishedState != startedStep.to + return repository.transitions + .filter { it.transitionState != TransitionState.CANCELED } + .mapLatest { + it.transitionState != TransitionState.FINISHED && + fromStatePredicate(it.from) && + toStatePredicate(it.to) } .distinctUntilChanged() } |