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

Commit e62bbe81 authored by Luca Zuccarini's avatar Luca Zuccarini
Browse files

A few fixes for animation takeovers.

1. Something changed in how the transition is created and we had a gap
   in the conversion that made Lanucher show up on top of the closing
   window. Fixed that by adding a new branch that checks for the opening
   window and puts it in the below layers.
2. The ordering of states already matches the apps thanks to the
   conversion inside OriginTransition, so we can extract the right state
   directly without relying on the deprecated `prefixOrderIndex`.
3. We now use the Coreographer's frame time to start the spring after
   the right amount of delay and correctly maintain the momentum while
   avoiding a stutter due to two identical frames.

Bug: 323863002
Flag: com.android.systemui.shared.return_animation_framework_library
Flag: com.android.systemui.shared.return_animation_framework_long_lived
Test: atest ActivityTransitionAnimatorTest TransitionAnimatorTest
Change-Id: I6cf2203d8f21bd236759449ef1ee4d39c7099e18
parent b7245a56
Loading
Loading
Loading
Loading
+12 −24
Original line number Diff line number Diff line
@@ -867,6 +867,9 @@ constructor(
                    ) {
                        // Raise closing task to "above" layer so it isn't covered.
                        t.setLayer(target.leash, aboveLayers - i)
                    } else if (TransitionUtil.isOpeningType(change.mode)) {
                        // Put into the "below" layer space.
                        t.setLayer(target.leash, belowLayers - i)
                    }
                } else if (TransitionInfo.isIndependent(change, info)) {
                    // Root tasks
@@ -1133,7 +1136,7 @@ constructor(
                // If a [controller.windowAnimatorState] exists, treat this like a takeover.
                takeOverAnimationInternal(
                    window,
                    startWindowStates = null,
                    startWindowState = null,
                    startTransaction = null,
                    callback,
                )
@@ -1148,22 +1151,23 @@ constructor(
            callback: IRemoteAnimationFinishedCallback?,
        ) {
            val window = setUpAnimation(apps, callback) ?: return
            takeOverAnimationInternal(window, startWindowStates, startTransaction, callback)
            val startWindowState = startWindowStates[apps!!.indexOf(window)]
            takeOverAnimationInternal(window, startWindowState, startTransaction, callback)
        }

        private fun takeOverAnimationInternal(
            window: RemoteAnimationTarget,
            startWindowStates: Array<WindowAnimationState>?,
            startWindowState: WindowAnimationState?,
            startTransaction: SurfaceControl.Transaction?,
            callback: IRemoteAnimationFinishedCallback?,
        ) {
            val useSpring =
                !controller.isLaunching && startWindowStates != null && startTransaction != null
                !controller.isLaunching && startWindowState != null && startTransaction != null
            startAnimation(
                window,
                navigationBar = null,
                useSpring,
                startWindowStates,
                startWindowState,
                startTransaction,
                callback,
            )
@@ -1273,7 +1277,7 @@ constructor(
            window: RemoteAnimationTarget,
            navigationBar: RemoteAnimationTarget? = null,
            useSpring: Boolean = false,
            startingWindowStates: Array<WindowAnimationState>? = null,
            startingWindowState: WindowAnimationState? = null,
            startTransaction: SurfaceControl.Transaction? = null,
            iCallback: IRemoteAnimationFinishedCallback? = null,
        ) {
@@ -1319,6 +1323,7 @@ constructor(

            val isExpandingFullyAbove =
                transitionAnimator.isExpandingFullyAbove(controller.transitionContainer, endState)
            val windowState = startingWindowState ?: controller.windowAnimatorState

            // We animate the opening window and delegate the view expansion to [this.controller].
            val delegate = this.controller
@@ -1341,18 +1346,6 @@ constructor(
                                }
                        }

                        // The states are sorted matching the changes inside the transition info.
                        // Using this info, the RemoteAnimationTargets are created, with their
                        // prefixOrderIndex fields in reverse order to that of changes. To extract
                        // the right state, we need to invert again.
                        val windowState =
                            if (startingWindowStates != null) {
                                startingWindowStates[
                                    startingWindowStates.size - window.prefixOrderIndex]
                            } else {
                                controller.windowAnimatorState
                            }

                        // TODO(b/323863002): use the timestamp and velocity to update the initial
                        //   position.
                        val bounds = windowState?.bounds
@@ -1441,12 +1434,6 @@ constructor(
                        delegate.onTransitionAnimationProgress(state, progress, linearProgress)
                    }
                }
            val windowState =
                if (startingWindowStates != null) {
                    startingWindowStates[startingWindowStates.size - window.prefixOrderIndex]
                } else {
                    controller.windowAnimatorState
                }
            val velocityPxPerS =
                if (longLivedReturnAnimationsEnabled() && windowState?.velocityPxPerMs != null) {
                    val xVelocityPxPerS = windowState.velocityPxPerMs.x * 1000
@@ -1465,6 +1452,7 @@ constructor(
                    fadeWindowBackgroundLayer = !controller.isBelowAnimatingWindow,
                    drawHole = !controller.isBelowAnimatingWindow,
                    startVelocity = velocityPxPerS,
                    startFrameTime = windowState?.timestamp ?: -1,
                )
        }

+48 −2
Original line number Diff line number Diff line
@@ -27,6 +27,8 @@ import android.graphics.drawable.GradientDrawable
import android.util.FloatProperty
import android.util.Log
import android.util.MathUtils
import android.util.TimeUtils
import android.view.Choreographer
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroupOverlay
@@ -366,6 +368,7 @@ class TransitionAnimator(
        @get:VisibleForTesting val springY: SpringAnimation,
        @get:VisibleForTesting val springScale: SpringAnimation,
        private val springState: SpringState,
        private val startFrameTime: Long,
        private val onAnimationStart: Runnable,
    ) : Animation {
        @get:VisibleForTesting
@@ -374,6 +377,42 @@ class TransitionAnimator(

        override fun start() {
            onAnimationStart.run()

            // If no start frame time is provided, we start the springs normally.
            if (startFrameTime < 0) {
                startSprings()
                return
            }

            // This function is not guaranteed to be called inside a frame. We try to access the
            // frame time immediately, but if we're not inside a frame this will throw an exception.
            // We must then post a callback to be run at the beginning of the next frame.
            try {
                initAndStartSprings(Choreographer.getInstance().frameTime)
            } catch (_: IllegalStateException) {
                Choreographer.getInstance().postFrameCallback { frameTimeNanos ->
                    initAndStartSprings(frameTimeNanos / TimeUtils.NANOS_PER_MS)
                }
            }
        }

        private fun initAndStartSprings(frameTime: Long) {
            // Initialize the spring as if it had started at the time that its start state
            // was created.
            springX.doAnimationFrame(startFrameTime)
            springY.doAnimationFrame(startFrameTime)
            springScale.doAnimationFrame(startFrameTime)
            // Move the spring time forward to the current frame, so it updates its internal state
            // following the initial momentum over the elapsed time.
            springX.doAnimationFrame(frameTime)
            springY.doAnimationFrame(frameTime)
            springScale.doAnimationFrame(frameTime)
            // Actually start the spring. We do this after the previous calls because the framework
            // doesn't like it when you call doAnimationFrame() after start() with an earlier time.
            startSprings()
        }

        private fun startSprings() {
            springX.start()
            springY.start()
            springScale.start()
@@ -471,7 +510,9 @@ class TransitionAnimator(
     * is true.
     *
     * If [startVelocity] (expressed in pixels per second) is not null, a multi-spring animation
     * using it for the initial momentum will be used instead of the default interpolators.
     * using it for the initial momentum will be used instead of the default interpolators. In this
     * case, [startFrameTime] (if non-negative) represents the frame time at which the springs
     * should be started.
     */
    fun startAnimation(
        controller: Controller,
@@ -480,6 +521,7 @@ class TransitionAnimator(
        fadeWindowBackgroundLayer: Boolean = true,
        drawHole: Boolean = false,
        startVelocity: PointF? = null,
        startFrameTime: Long = -1,
    ): Animation {
        if (!controller.isLaunching) assertReturnAnimations()
        if (startVelocity != null) assertLongLivedReturnAnimations()
@@ -502,6 +544,7 @@ class TransitionAnimator(
                fadeWindowBackgroundLayer,
                drawHole,
                startVelocity,
                startFrameTime,
            )
            .apply { start() }
    }
@@ -515,6 +558,7 @@ class TransitionAnimator(
        fadeWindowBackgroundLayer: Boolean = true,
        drawHole: Boolean = false,
        startVelocity: PointF? = null,
        startFrameTime: Long = -1,
    ): Animation {
        val transitionContainer = controller.transitionContainer
        val transitionContainerOverlay = transitionContainer.overlay
@@ -537,6 +581,7 @@ class TransitionAnimator(
                startState,
                endState,
                startVelocity,
                startFrameTime,
                windowBackgroundLayer,
                transitionContainer,
                transitionContainerOverlay,
@@ -722,6 +767,7 @@ class TransitionAnimator(
        startState: State,
        endState: State,
        startVelocity: PointF,
        startFrameTime: Long,
        windowBackgroundLayer: GradientDrawable,
        transitionContainer: View,
        transitionContainerOverlay: ViewGroupOverlay,
@@ -912,7 +958,7 @@ class TransitionAnimator(
                    }
                }

        return MultiSpringAnimation(springX, springY, springScale, springState) {
        return MultiSpringAnimation(springX, springY, springScale, springState, startFrameTime) {
            onAnimationStart(
                controller,
                isExpandingFullyAbove,