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

Commit 2dd8bf53 authored by Jordan Demeulenaere's avatar Jordan Demeulenaere
Browse files

Ensure that transitions are started only once

This CL ensures that a single transition can not be started multiple
times.

This uncovered a bug introduced in ag/29024667: linked transitions would
run() their originalTransition rather than simply awaiting for them.
This later unveiled that we were using an incorrect coroutineScope {}
for linked transitions, as we would wait for all linked transitions to
finish before actually re-running the transition again in the original
STLState.

I didn't add more tests for linked transitions because I plan to remove
them in ag/30320655 anyways.

Bug: 376438969
Test: atest SceneTransitionLayoutStateTest
Flag: com.android.systemui.scene_container
Change-Id: Ie91771bea7e2d1db869b302305093283a2604943
parent a33c539a
Loading
Loading
Loading
Loading
+5 −5
Original line number Diff line number Diff line
@@ -372,12 +372,12 @@ internal class MutableSceneTransitionLayoutStateImpl(

            // Handle transition links.
            previousTransition?.let { cancelActiveTransitionLinks(it) }
            if (stateLinks.isNotEmpty()) {
                coroutineScope { setupTransitionLinks(transition) }
            }
            coroutineScope {
                setupTransitionLinks(transition)

                // Run the transition until it is finished.
            transition.run()
                transition.runInternal()
            }
        } finally {
            finishTransition(transition)
        }
+22 −1
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import com.android.compose.animation.scene.TransformationSpecImpl
import com.android.compose.animation.scene.TransitionKey
import com.android.compose.animation.scene.transition.link.LinkedTransition
import com.android.compose.animation.scene.transition.link.StateLink
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.launch

/** The state associated to a [SceneTransitionLayout] at some specific point in time. */
@@ -283,6 +284,12 @@ sealed interface TransitionState {
        /** The map of active links that connects this transition to other transitions. */
        internal val activeTransitionLinks = mutableMapOf<StateLink, LinkedTransition>()

        /** Whether this transition was already started. */
        private var wasStarted = false

        /** A completable to [await] this transition. */
        private val completable = CompletableDeferred<Unit>()

        init {
            check(fromContent != toContent)
            check(
@@ -328,7 +335,7 @@ sealed interface TransitionState {
        }

        /** Run this transition and return once it is finished. */
        abstract suspend fun run()
        protected abstract suspend fun run()

        /**
         * Freeze this transition state so that neither [currentScene] nor [currentOverlays] will
@@ -341,6 +348,20 @@ sealed interface TransitionState {
         */
        abstract fun freezeAndAnimateToCurrentState()

        /** Wait for this transition to finish. */
        internal suspend fun await() = completable.await()

        internal suspend fun runInternal() {
            check(!wasStarted) { "A Transition can be started only once." }
            wasStarted = true

            try {
                run()
            } finally {
                completable.complete(Unit)
            }
        }

        internal fun updateOverscrollSpecs(
            fromSpec: OverscrollSpecImpl?,
            toSpec: OverscrollSpecImpl?,
+1 −1
Original line number Diff line number Diff line
@@ -50,7 +50,7 @@ internal class LinkedTransition(
        get() = originalTransition.progressVelocity

    override suspend fun run() {
        originalTransition.run()
        originalTransition.await()
    }

    override fun freezeAndAnimateToCurrentState() {
+12 −0
Original line number Diff line number Diff line
@@ -42,6 +42,7 @@ import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.consumeAsFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertThrows
import org.junit.Rule
@@ -777,4 +778,15 @@ class SceneTransitionLayoutStateTest {
        assertThat(transition.progressTo(SceneA)).isEqualTo(1f - 0.2f)
        assertThrows(IllegalArgumentException::class.java) { transition.progressTo(SceneC) }
    }

    @Test
    fun transitionCanBeStartedOnlyOnce() = runTest {
        val state = MutableSceneTransitionLayoutState(SceneA)
        val transition = transition(from = SceneA, to = SceneB)

        state.startTransitionImmediately(backgroundScope, transition)
        assertThrows(IllegalStateException::class.java) {
            runBlocking { state.startTransition(transition) }
        }
    }
}