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

Commit bd60c11b authored by Josh Tsuji's avatar Josh Tsuji
Browse files

Clear manual bouncer transitionId on CANCELED steps.

This ID can get stuck if an external signal causes a PRIMARY_BOUNCER cancellation, and then no further transitions to PRIMARY_BOUNCER can be started.

Also, update FakeKeyguardTransitionRepository to add CANCELED steps so we can add a test case for this issue.

Bug: 372691566
Test: atest FromLockscreenTransitionInteractorTest
Flag: EXEMPT bugfix
Change-Id: I411b53f1713177e335de2a3c49501ebcdf8e9c7a
parent 6f38d94c
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -81,7 +81,7 @@ class FromDreamingTransitionInteractorTest(flags: FlagsParameterization?) : Sysu
            this.fakeKeyguardTransitionRepository =
                FakeKeyguardTransitionRepository(
                    // This test sends transition steps manually in the test cases.
                    sendTransitionStepsOnStartTransition = false,
                    initiallySendTransitionStepsOnStartTransition = false,
                    testScope = testScope,
                )

+77 −5
Original line number Diff line number Diff line
@@ -27,15 +27,13 @@ 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.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepositorySpy
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.StatusBarState.KEYGUARD
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat as assertThatRepository
import com.android.systemui.kosmos.testScope
import com.android.systemui.shade.data.repository.FlingInfo
import com.android.systemui.shade.data.repository.fakeShadeRepository
@@ -48,6 +46,8 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.reset
import org.mockito.Mockito.spy
import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat as assertThatRepository

@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -55,7 +55,9 @@ import org.mockito.Mockito.reset
class FromLockscreenTransitionInteractorTest : SysuiTestCase() {
    private val kosmos =
        testKosmos().apply {
            this.keyguardTransitionRepository = fakeKeyguardTransitionRepositorySpy
            this.fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository(
                testScope = testScope,
            ))
        }

    private val testScope = kosmos.testScope
@@ -66,7 +68,7 @@ class FromLockscreenTransitionInteractorTest : SysuiTestCase() {

    @Before
    fun setup() {
        transitionRepository = kosmos.fakeKeyguardTransitionRepositorySpy
        transitionRepository = kosmos.fakeKeyguardTransitionRepository
    }

    @Test
@@ -302,4 +304,74 @@ class FromLockscreenTransitionInteractorTest : SysuiTestCase() {
                    to = KeyguardState.LOCKSCREEN,
                )
        }

    /**
     * External signals can cause us to transition from PRIMARY_BOUNCER -> * while a manual
     * transition is in progress. This test was added after a bug that caused the manual transition
     * ID to get stuck in this scenario, preventing subsequent transitions to PRIMARY_BOUNCER.
     */
    @Test
    fun testExternalTransitionAwayFromBouncer_transitionIdNotStuck() =
        testScope.runTest {
            underTest.start()
            keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
            keyguardRepository.setKeyguardDismissible(false)
            shadeRepository.setLegacyShadeTracking(true)
            keyguardRepository.setKeyguardOccluded(false)
            runCurrent()

            reset(transitionRepository)

            // Disable automatic sending of transition steps so we can send steps through RUNNING
            // to simulate a cancellation.
            transitionRepository.sendTransitionStepsOnStartTransition = false
            shadeRepository.setLegacyShadeExpansion(0.5f)
            runCurrent()

            assertThatRepository(transitionRepository)
                .startedTransition(
                    from = KeyguardState.LOCKSCREEN,
                    to = KeyguardState.PRIMARY_BOUNCER,
                )

            // Partially transition to PRIMARY_BOUNCER.
            transitionRepository.sendTransitionSteps(
                from = KeyguardState.LOCKSCREEN,
                to = KeyguardState.PRIMARY_BOUNCER,
                throughTransitionState = TransitionState.RUNNING,
                testScope = testScope,
            )

            // Start a transition to GONE, which will cancel LS -> BOUNCER.
            transitionRepository.sendTransitionSteps(
                from = KeyguardState.PRIMARY_BOUNCER,
                to = KeyguardState.GONE,
                testScope = testScope,
            )

            // Go to AOD, then LOCKSCREEN.
            transitionRepository.sendTransitionSteps(
                from = KeyguardState.GONE,
                to = KeyguardState.AOD,
                testScope = testScope,
            )
            transitionRepository.sendTransitionSteps(
                from = KeyguardState.AOD,
                to = KeyguardState.LOCKSCREEN,
                testScope = testScope,
            )

            reset(transitionRepository)

            // Start a swipe up to the bouncer, and verify that we started a transition to
            // PRIMARY_BOUNCER, verifying the transition ID did not get stuck.
            shadeRepository.setLegacyShadeExpansion(0.25f)
            runCurrent()

            assertThatRepository(transitionRepository)
                .startedTransition(
                    from = KeyguardState.LOCKSCREEN,
                    to = KeyguardState.PRIMARY_BOUNCER,
                )
        }
}
+19 −5
Original line number Diff line number Diff line
@@ -19,7 +19,6 @@ package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
import android.util.MathUtils
import com.android.app.animation.Interpolators
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.Flags.communalSceneKtfRefactor
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.dagger.SysUISingleton
@@ -40,17 +39,19 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.util.kotlin.sample
import java.util.UUID
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
import java.util.UUID
import javax.inject.Inject
import com.android.app.tracing.coroutines.launchTraced as launch

@SysUISingleton
class FromLockscreenTransitionInteractor
@@ -258,6 +259,19 @@ constructor(
                }
            }
        }

        // Ensure that transitionId is nulled out if external signals cause a PRIMARY_BOUNCER
        // transition to be canceled.
        scope.launch {
            transitionInteractor.transitions
                .filter {
                    it.transitionState == TransitionState.CANCELED &&
                        it.to == KeyguardState.PRIMARY_BOUNCER
                }
                .collect {
                    transitionId = null
                }
        }
    }

    fun dismissKeyguard() {
+30 −5
Original line number Diff line number Diff line
@@ -52,6 +52,15 @@ import kotlinx.coroutines.test.runCurrent
class FakeKeyguardTransitionRepository(
    private val initInLockscreen: Boolean = true,

    /**
     * Initial value for [FakeKeyguardTransitionRepository.sendTransitionStepsOnStartTransition].
     * This needs to be configurable in the constructor since some transitions are triggered on
     * init, before a test has the chance to set sendTransitionStepsOnStartTransition to false.
     */
    private val initiallySendTransitionStepsOnStartTransition: Boolean = true,
    private val testScope: TestScope,
) : KeyguardTransitionRepository {

    /**
     * If true, calls to [startTransition] will automatically emit STARTED, RUNNING, and FINISHED
     * transition steps from/to the given states.
@@ -64,11 +73,9 @@ class FakeKeyguardTransitionRepository(
     *
     * If your test needs to make assertions at specific points between STARTED/FINISHED, or if it's
     * difficult to set up all of the conditions to make the transition interactors actually call
     * startTransition, then construct a FakeKeyguardTransitionRepository with this value false.
     * startTransition, set this value to false.
     */
    private val sendTransitionStepsOnStartTransition: Boolean = true,
    private val testScope: TestScope,
) : KeyguardTransitionRepository {
    var sendTransitionStepsOnStartTransition = initiallySendTransitionStepsOnStartTransition

    private val _transitions =
        MutableSharedFlow<TransitionStep>(replay = 3, onBufferOverflow = BufferOverflow.DROP_OLDEST)
@@ -77,7 +84,11 @@ class FakeKeyguardTransitionRepository(
    @Inject
    constructor(
        testScope: TestScope
    ) : this(initInLockscreen = true, sendTransitionStepsOnStartTransition = true, testScope)
    ) : this(
        initInLockscreen = true,
        initiallySendTransitionStepsOnStartTransition = true,
        testScope
    )

    private val _currentTransitionInfo: MutableStateFlow<TransitionInfo> =
        MutableStateFlow(
@@ -176,6 +187,20 @@ class FakeKeyguardTransitionRepository(
        testScheduler: TestCoroutineScheduler,
        throughTransitionState: TransitionState = TransitionState.FINISHED,
    ) {
        val lastStep = _transitions.replayCache.lastOrNull()
        if (lastStep != null && lastStep.transitionState != TransitionState.FINISHED) {
            sendTransitionStep(
                step =
                TransitionStep(
                    transitionState = TransitionState.CANCELED,
                    from = lastStep.from,
                    to = lastStep.to,
                    value = 0f,
                )
            )
            testScheduler.runCurrent()
        }

        sendTransitionStep(
            step =
                TransitionStep(