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

Commit 022308a7 authored by Mike Schneider's avatar Mike Schneider
Browse files

Immediately activate / deactivate managed motion values on creation

In the initial implementation, the ManagedMotionValue would return NaN
until the next frame was rendered. Now, the motion value immediately
activates from the current frames' values (iff the collection itself
is active)

Test: Unit tests
Bug: 404975104
Flag: com.android.systemui.scene_container
Change-Id: I5d21bce46d16bc2f15087c622d65eb5973ef42e7
parent af260059
Loading
Loading
Loading
Loading
+17 −28
Original line number Diff line number Diff line
@@ -70,7 +70,12 @@ class MotionValueCollection(
     * [MotionValueCollection] is kept active.
     */
    fun create(spec: () -> MotionSpec, label: String? = null): ManagedMotionValue {
        return ManagedMotionComputation(this, spec, label).also { managedComputations.add(it) }
        return ManagedMotionComputation(this, spec, label).also {
            if (isActive) {
                it.onActivate()
            }
            managedComputations.add(it)
        }
    }

    /**
@@ -96,12 +101,7 @@ class MotionValueCollection(
            var capturedGestureDragOffset = currentGestureDragOffset
            var capturedDirection = currentDirection

            val activeComputations = mutableSetOf<ManagedMotionComputation>()
            managedComputations.forEach {
                it.onActivate()
                activeComputations.add(it)
            }
            activeComputationCount = activeComputations.size
            managedComputations.forEach { it.onActivate() }

            try {
                isAnimating = true
@@ -115,17 +115,6 @@ class MotionValueCollection(
                while (true) {

                    withFrameNanos { frameTimeNanos ->
                        val addedComputations = managedComputations - activeComputations
                        val removedComputations = activeComputations - managedComputations
                        addedComputations.forEach {
                            it.onActivate()
                            activeComputations.add(it)
                        }
                        removedComputations.forEach {
                            it.onDeactivate()
                            activeComputations.remove(it)
                        }
                        activeComputationCount = activeComputations.size
                        frameCount++

                        currentAnimationTimeNanos = frameTimeNanos
@@ -137,7 +126,7 @@ class MotionValueCollection(
                        currentDirection = gestureContext.direction
                        currentGestureDragOffset = gestureContext.dragOffset

                        activeComputations.forEach { it.onFrameStart() }
                        managedComputations.forEach { it.onFrameStart() }
                    }

                    // At this point, the complete frame is done (including layout, drawing and
@@ -152,7 +141,7 @@ class MotionValueCollection(
                    // re-computation if the current state is being read before the next frame).

                    var scheduleNextFrame = false
                    activeComputations.forEach {
                    managedComputations.forEach {
                        if (it.onFrameEnd(isAnimatingUninterrupted)) {
                            scheduleNextFrame = true
                        }
@@ -181,7 +170,9 @@ class MotionValueCollection(
                    }

                    isAnimating = false
                    activeComputations.forEach { it.debugInspector?.isAnimating = false }
                    managedComputations.forEach { it.debugInspector?.isAnimating = false }
                    val activeComputations = managedComputations.toSet()

                    snapshotFlow {
                            val hasComputations =
                                activeComputations.isNotEmpty() || managedComputations.isNotEmpty()
@@ -197,12 +188,11 @@ class MotionValueCollection(
                        }
                        .first { it }
                    isAnimating = true
                    activeComputations.forEach { it.debugInspector?.isAnimating = true }
                    managedComputations.forEach { it.debugInspector?.isAnimating = true }
                }
            } finally {
                isActive = false
                activeComputations.forEach { it.onDeactivate() }
                activeComputationCount = 0
                managedComputations.forEach { it.onDeactivate() }
            }
        }
    }
@@ -241,10 +231,6 @@ class MotionValueCollection(
    var frameCount = 0
        private set

    @VisibleForTesting
    var activeComputationCount = 0
        private set

    @VisibleForTesting
    // Note - this is public so that its accessible by the mechanics:testing library
    val managedMotionValues: Set<ManagedMotionValue>
@@ -252,6 +238,7 @@ class MotionValueCollection(

    internal fun onDispose(toDispose: ManagedMotionComputation) {
        managedComputations.remove(toDispose)
        toDispose.onDeactivate()
    }
}

@@ -399,6 +386,8 @@ internal class ManagedMotionComputation(
        capturedAnimation = currentComputedValues.animation
        capturedSpringState = currentSpringState

        onFrameStart()

        debugInspector?.isAnimating = true
        debugInspector?.isActive = true
    }
+48 −31
Original line number Diff line number Diff line
@@ -69,18 +69,52 @@ class MotionValueCollectionLifecycleTest :
    }

    @Test
    fun keepRunning_activatesExisting() = runTest {
    fun create_withoutKeepRunning_remainsInactive() = runTest {
        val input = mutableFloatStateOf(1f)
        val underTest = MotionValueCollection(input::value, FakeGestureContext)

        rule.setContent {}

        assertThat(underTest.isActive).isFalse()

        val motionValue = underTest.create({ MotionSpec.Identity })
        assertThat(motionValue.output).isNaN()
        val inspector = motionValue.debugInspector()
        assertThat(inspector.isActive).isFalse()
    }

    @Test
    fun create_whileKeepRunning_isActivatedImmediately() = runTest {
        val input = mutableFloatStateOf(1f)
        val underTest = MotionValueCollection(input::value, FakeGestureContext)

        rule.setContent { LaunchedEffect(Unit) { underTest.keepRunning() } }
        rule.awaitIdle()

        assertThat(underTest.isActive).isTrue()
        assertThat(underTest.managedMotionValues.size).isEqualTo(0)

        val motionValue = underTest.create({ MotionSpec.Identity })
        assertThat(motionValue.output).isEqualTo(1f)
        val inspector = motionValue.debugInspector()
        assertThat(inspector.isActive).isTrue()
    }

    @Test
    fun keepRunning_activatesAlreadyCreated() = runTest {
        val input = mutableFloatStateOf(0f)
        val underTest = MotionValueCollection(input::value, FakeGestureContext)

        val inspector = underTest.create({ MotionSpec.Identity }).debugInspector()
        val motionValue = underTest.create({ MotionSpec.Identity })
        val inspector = motionValue.debugInspector()

        assertThat(underTest.frameCount).isEqualTo(0)
        assertThat(underTest.isActive).isFalse()
        assertThat(underTest.isAnimating).isFalse()
        assertThat(underTest.activeComputationCount).isEqualTo(0)
        assertThat(underTest.managedMotionValues.size).isEqualTo(1)
        assertThat(inspector.isActive).isFalse()
        assertThat(inspector.isAnimating).isFalse()
        assertThat(motionValue.output).isNaN()

        rule.setContent { LaunchedEffect(Unit) { underTest.keepRunning() } }

@@ -89,9 +123,10 @@ class MotionValueCollectionLifecycleTest :
        assertThat(underTest.frameCount).isEqualTo(1)
        assertThat(underTest.isActive).isTrue()
        assertThat(underTest.isAnimating).isFalse()
        assertThat(underTest.activeComputationCount).isEqualTo(1)
        assertThat(underTest.managedMotionValues.size).isEqualTo(1)
        assertThat(inspector.isActive).isTrue()
        assertThat(inspector.isAnimating).isFalse()
        assertThat(motionValue.output).isFinite()
    }

    @Test
@@ -109,7 +144,7 @@ class MotionValueCollectionLifecycleTest :
        assertThat(underTest.frameCount).isEqualTo(1)
        assertThat(underTest.isActive).isTrue()
        assertThat(underTest.isAnimating).isFalse()
        assertThat(underTest.activeComputationCount).isEqualTo(1)
        assertThat(underTest.managedMotionValues.size).isEqualTo(1)
        assertThat(inspector.isActive).isTrue()
        assertThat(inspector.isAnimating).isFalse()

@@ -119,7 +154,7 @@ class MotionValueCollectionLifecycleTest :
        assertThat(underTest.frameCount).isEqualTo(2)
        assertThat(underTest.isActive).isTrue()
        assertThat(underTest.isAnimating).isFalse()
        assertThat(underTest.activeComputationCount).isEqualTo(0)
        assertThat(underTest.managedMotionValues.size).isEqualTo(0)
        assertThat(inspector.isActive).isFalse()
        assertThat(inspector.isAnimating).isFalse()
    }
@@ -138,32 +173,14 @@ class MotionValueCollectionLifecycleTest :

        assertThat(underTest.isActive).isFalse()
        assertThat(inspector.isActive).isFalse()
        assertThat(underTest.activeComputationCount).isEqualTo(0)
        assertThat(underTest.managedMotionValues.size).isEqualTo(1)

        motionValue.dispose()
        rule.awaitIdle()

        assertThat(underTest.isActive).isFalse()
        assertThat(inspector.isActive).isFalse()
        assertThat(underTest.activeComputationCount).isEqualTo(0)
    }

    @Test
    fun keepRunning_activatesNew() = runTest {
        val input = mutableFloatStateOf(0f)
        val underTest = MotionValueCollection(input::value, FakeGestureContext)

        rule.setContent { LaunchedEffect(Unit) { underTest.keepRunning() } }
        rule.awaitIdle()

        assertThat(underTest.isActive).isTrue()
        assertThat(underTest.activeComputationCount).isEqualTo(0)

        val inspector = underTest.create({ MotionSpec.Identity }).debugInspector()
        rule.awaitIdle()

        assertThat(underTest.activeComputationCount).isEqualTo(1)
        assertThat(inspector.isActive).isTrue()
        assertThat(underTest.managedMotionValues.size).isEqualTo(0)
    }

    @Test
@@ -180,21 +197,21 @@ class MotionValueCollectionLifecycleTest :
        rule.awaitIdle()

        assertThat(underTest.isActive).isTrue()
        assertThat(underTest.activeComputationCount).isEqualTo(2)
        assertThat(underTest.managedMotionValues.size).isEqualTo(2)
        assertThat(inspector1.isActive).isTrue()
        assertThat(inspector2.isActive).isTrue()

        mv1.dispose()
        rule.awaitIdle()

        assertThat(underTest.activeComputationCount).isEqualTo(1)
        assertThat(underTest.managedMotionValues.size).isEqualTo(1)
        assertThat(inspector1.isActive).isFalse()
        assertThat(inspector2.isActive).isTrue()

        mv2.dispose()
        rule.awaitIdle()

        assertThat(underTest.activeComputationCount).isEqualTo(0)
        assertThat(underTest.managedMotionValues.size).isEqualTo(0)
        assertThat(inspector1.isActive).isFalse()
        assertThat(inspector2.isActive).isFalse()
    }
@@ -215,14 +232,14 @@ class MotionValueCollectionLifecycleTest :

        assertThat(underTest.isActive).isTrue()
        assertThat(inspector.isActive).isTrue()
        assertThat(underTest.activeComputationCount).isEqualTo(1)
        assertThat(underTest.managedMotionValues.size).isEqualTo(1)

        keepRunning.value = false
        rule.awaitIdle()

        assertThat(underTest.isActive).isFalse()
        assertThat(inspector.isActive).isFalse()
        assertThat(underTest.activeComputationCount).isEqualTo(0)
        assertThat(underTest.managedMotionValues.size).isEqualTo(1)
    }

    @Test