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

Commit d7c69cc4 authored by Jordan Demeulenaere's avatar Jordan Demeulenaere Committed by Android (Google) Code Review
Browse files

Merge "Delay TransitionAnimator.onAnimationEnd by one frame (1/4)" into main

parents f44fb22b 3b64f537
Loading
Loading
Loading
Loading
+27 −18
Original line number Diff line number Diff line
@@ -44,6 +44,7 @@ import com.android.app.animation.Interpolators
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.policy.ScreenDecorationsUtils
import com.android.systemui.Flags.activityTransitionUseLargestWindow
import java.util.concurrent.Executor
import kotlin.math.roundToInt

private const val TAG = "ActivityTransitionAnimator"
@@ -52,14 +53,19 @@ private const val TAG = "ActivityTransitionAnimator"
 * A class that allows activities to be started in a seamless way from a view that is transforming
 * nicely into the starting window.
 */
class ActivityTransitionAnimator(
class ActivityTransitionAnimator
@JvmOverloads
constructor(
    /** The executor that runs on the main thread. */
    private val mainExecutor: Executor,

    /** The animator used when animating a View into an app. */
    private val transitionAnimator: TransitionAnimator = DEFAULT_TRANSITION_ANIMATOR,
    private val transitionAnimator: TransitionAnimator = defaultTransitionAnimator(mainExecutor),

    /** The animator used when animating a Dialog into an app. */
    // TODO(b/218989950): Remove this animator and instead set the duration of the dim fade out to
    // TIMINGS.contentBeforeFadeOutDuration.
    private val dialogToAppAnimator: TransitionAnimator = DEFAULT_DIALOG_TO_APP_ANIMATOR,
    private val dialogToAppAnimator: TransitionAnimator = defaultDialogToAppAnimator(mainExecutor),

    /**
     * Whether we should disable the WindowManager timeout. This should be set to true in tests
@@ -100,10 +106,6 @@ class ActivityTransitionAnimator(
        // TODO(b/288507023): Remove this flag.
        @JvmField val DEBUG_TRANSITION_ANIMATION = Build.IS_DEBUGGABLE

        private val DEFAULT_TRANSITION_ANIMATOR = TransitionAnimator(TIMINGS, INTERPOLATORS)
        private val DEFAULT_DIALOG_TO_APP_ANIMATOR =
            TransitionAnimator(DIALOG_TIMINGS, INTERPOLATORS)

        /** Durations & interpolators for the navigation bar fading in & out. */
        private const val ANIMATION_DURATION_NAV_FADE_IN = 266L
        private const val ANIMATION_DURATION_NAV_FADE_OUT = 133L
@@ -121,6 +123,14 @@ class ActivityTransitionAnimator(
         * cancelled by WM.
         */
        private const val LONG_TRANSITION_TIMEOUT = 5_000L

        private fun defaultTransitionAnimator(mainExecutor: Executor): TransitionAnimator {
            return TransitionAnimator(mainExecutor, TIMINGS, INTERPOLATORS)
        }

        private fun defaultDialogToAppAnimator(mainExecutor: Executor): TransitionAnimator {
            return TransitionAnimator(mainExecutor, DIALOG_TIMINGS, INTERPOLATORS)
        }
    }

    /**
@@ -257,9 +267,7 @@ class ActivityTransitionAnimator(

    private fun Controller.callOnIntentStartedOnMainThread(willAnimate: Boolean) {
        if (Looper.myLooper() != Looper.getMainLooper()) {
            this.transitionContainer.context.mainExecutor.execute {
                callOnIntentStartedOnMainThread(willAnimate)
            }
            mainExecutor.execute { callOnIntentStartedOnMainThread(willAnimate) }
        } else {
            if (DEBUG_TRANSITION_ANIMATION) {
                Log.d(
@@ -479,12 +487,10 @@ class ActivityTransitionAnimator(
        controller: Controller,
        callback: Callback,
        /** The animator to use to animate the window transition. */
        transitionAnimator: TransitionAnimator = DEFAULT_TRANSITION_ANIMATOR,
        transitionAnimator: TransitionAnimator,
        /** Listener for animation lifecycle events. */
        listener: Listener? = null
    ) : IRemoteAnimationRunner.Stub() {
        private val context = controller.transitionContainer.context

        // This is being passed across IPC boundaries and cycles (through PendingIntentRecords,
        // etc.) are possible. So we need to make sure we drop any references that might
        // transitively cause leaks when we're done with animation.
@@ -493,11 +499,12 @@ class ActivityTransitionAnimator(
        init {
            delegate =
                AnimationDelegate(
                    mainExecutor,
                    controller,
                    callback,
                    DelegatingAnimationCompletionListener(listener, this::dispose),
                    transitionAnimator,
                    disableWmTimeout
                    disableWmTimeout,
                )
        }

@@ -510,7 +517,7 @@ class ActivityTransitionAnimator(
            finishedCallback: IRemoteAnimationFinishedCallback?
        ) {
            val delegate = delegate
            context.mainExecutor.execute {
            mainExecutor.execute {
                if (delegate == null) {
                    Log.i(TAG, "onAnimationStart called after completion")
                    // Animation started too late and timed out already. We need to still
@@ -525,7 +532,7 @@ class ActivityTransitionAnimator(
        @BinderThread
        override fun onAnimationCancelled() {
            val delegate = delegate
            context.mainExecutor.execute {
            mainExecutor.execute {
                delegate ?: Log.wtf(TAG, "onAnimationCancelled called after completion")
                delegate?.onAnimationCancelled()
            }
@@ -535,19 +542,21 @@ class ActivityTransitionAnimator(
        fun dispose() {
            // Drop references to animation controller once we're done with the animation
            // to avoid leaking.
            context.mainExecutor.execute { delegate = null }
            mainExecutor.execute { delegate = null }
        }
    }

    class AnimationDelegate
    @JvmOverloads
    constructor(
        private val mainExecutor: Executor,
        private val controller: Controller,
        private val callback: Callback,
        /** Listener for animation lifecycle events. */
        private val listener: Listener? = null,
        /** The animator to use to animate the window transition. */
        private val transitionAnimator: TransitionAnimator = DEFAULT_TRANSITION_ANIMATOR,
        private val transitionAnimator: TransitionAnimator =
            defaultTransitionAnimator(mainExecutor),

        /**
         * Whether we should disable the WindowManager timeout. This should be set to true in tests
+11 −19
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ import com.android.internal.jank.Cuj.CujType
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.util.maybeForceFullscreen
import com.android.systemui.util.registerAnimationOnBackInvoked
import java.util.concurrent.Executor
import kotlin.math.roundToInt

private const val TAG = "DialogTransitionAnimator"
@@ -55,10 +56,16 @@ private const val TAG = "DialogTransitionAnimator"
class DialogTransitionAnimator
@JvmOverloads
constructor(
    private val mainExecutor: Executor,
    private val callback: Callback,
    private val interactionJankMonitor: InteractionJankMonitor,
    private val featureFlags: AnimationFeatureFlags,
    private val transitionAnimator: TransitionAnimator = TransitionAnimator(TIMINGS, INTERPOLATORS),
    private val transitionAnimator: TransitionAnimator =
        TransitionAnimator(
            mainExecutor,
            TIMINGS,
            INTERPOLATORS,
        ),
    private val isForTesting: Boolean = false,
) {
    private companion object {
@@ -937,25 +944,10 @@ private class AnimatedDialog(
                }

                override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
                    // onLaunchAnimationEnd is called by an Animator at the end of the animation,
                    // on a Choreographer animation tick. The following calls will move the animated
                    // content from the dialog overlay back to its original position, and this
                    // change must be reflected in the next frame given that we then sync the next
                    // frame of both the content and dialog ViewRoots. However, in case that content
                    // is rendered by Compose, whose compositions are also scheduled on a
                    // Choreographer frame, any state change made *right now* won't be reflected in
                    // the next frame given that a Choreographer frame can't schedule another and
                    // have it happen in the same frame. So we post the forwarded calls to
                    // [Controller.onLaunchAnimationEnd], leaving this Choreographer frame, ensuring
                    // that the move of the content back to its original window will be reflected in
                    // the next frame right after [onLaunchAnimationEnd] is called.
                    dialog.context.mainExecutor.execute {
                    startController.onTransitionAnimationEnd(isExpandingFullyAbove)
                    endController.onTransitionAnimationEnd(isExpandingFullyAbove)

                    onLaunchAnimationEnd()
                }
                }

                override fun onTransitionAnimationProgress(
                    state: TransitionAnimator.State,
+26 −5
Original line number Diff line number Diff line
@@ -31,12 +31,17 @@ import android.view.animation.Interpolator
import androidx.annotation.VisibleForTesting
import com.android.app.animation.Interpolators.LINEAR
import com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary
import java.util.concurrent.Executor
import kotlin.math.roundToInt

private const val TAG = "TransitionAnimator"

/** A base class to animate a window (activity or dialog) launch to or return from a view . */
class TransitionAnimator(private val timings: Timings, private val interpolators: Interpolators) {
class TransitionAnimator(
    private val mainExecutor: Executor,
    private val timings: Timings,
    private val interpolators: Interpolators,
) {
    companion object {
        internal const val DEBUG = false
        private val SRC_MODE = PorterDuffXfermode(PorterDuff.Mode.SRC)
@@ -351,6 +356,21 @@ class TransitionAnimator(private val timings: Timings, private val interpolators
                    if (DEBUG) {
                        Log.d(TAG, "Animation ended")
                    }

                    // onAnimationEnd is called at the end of the animation, on a Choreographer
                    // animation tick. During dialog launches, the following calls will move the
                    // animated content from the dialog overlay back to its original position, and
                    // this change must be reflected in the next frame given that we then sync the
                    // next frame of both the content and dialog ViewRoots. During SysUI activity
                    // launches, we will instantly collapse the shade at the end of the transition.
                    // However, if those are rendered by Compose, whose compositions are also
                    // scheduled on a Choreographer frame, any state change made *right now* won't
                    // be reflected in the next frame given that a Choreographer frame can't
                    // schedule another and have it happen in the same frame. So we post the
                    // forwarded calls to [Controller.onLaunchAnimationEnd] in the main executor,
                    // leaving this Choreographer frame, ensuring that any state change applied by
                    // onTransitionAnimationEnd() will be reflected in the same frame.
                    mainExecutor.execute {
                        controller.onTransitionAnimationEnd(isExpandingFullyAbove)
                        transitionContainerOverlay.remove(windowBackgroundLayer)

@@ -359,6 +379,7 @@ class TransitionAnimator(private val timings: Timings, private val interpolators
                        }
                    }
                }
            }
        )

        animator.addUpdateListener { animation ->
+8 −5
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import com.android.systemui.animation.DialogTransitionAnimator;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpHandler;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
@@ -56,10 +57,10 @@ import com.android.systemui.statusbar.notification.collection.render.Notificatio
import com.android.systemui.statusbar.phone.CentralSurfacesImpl;
import com.android.systemui.statusbar.phone.ManagedProfileController;
import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl;
import com.android.systemui.statusbar.phone.ui.StatusBarIconList;
import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback;
import com.android.systemui.statusbar.phone.ui.StatusBarIconController;
import com.android.systemui.statusbar.phone.ui.StatusBarIconControllerImpl;
import com.android.systemui.statusbar.phone.ui.StatusBarIconList;
import com.android.systemui.statusbar.policy.KeyguardStateController;

import dagger.Binds;
@@ -209,14 +210,16 @@ public interface CentralSurfacesDependenciesModule {
    /** */
    @Provides
    @SysUISingleton
    static ActivityTransitionAnimator provideActivityTransitionAnimator() {
        return new ActivityTransitionAnimator();
    static ActivityTransitionAnimator provideActivityTransitionAnimator(
            @Main Executor mainExecutor) {
        return new ActivityTransitionAnimator(mainExecutor);
    }

    /** */
    @Provides
    @SysUISingleton
    static DialogTransitionAnimator provideDialogTransitionAnimator(IDreamManager dreamManager,
    static DialogTransitionAnimator provideDialogTransitionAnimator(@Main Executor mainExecutor,
            IDreamManager dreamManager,
            KeyguardStateController keyguardStateController,
            Lazy<AlternateBouncerInteractor> alternateBouncerInteractor,
            InteractionJankMonitor interactionJankMonitor,
@@ -243,7 +246,7 @@ public interface CentralSurfacesDependenciesModule {
            }
        };
        return new DialogTransitionAnimator(
                callback, interactionJankMonitor, animationFeatureFlags);
                mainExecutor, callback, interactionJankMonitor, animationFeatureFlags);
    }

    /** */
+4 −2
Original line number Diff line number Diff line
@@ -46,7 +46,8 @@ import org.mockito.junit.MockitoJUnit
@RunWithLooper
class ActivityTransitionAnimatorTest : SysuiTestCase() {
    private val transitionContainer = LinearLayout(mContext)
    private val testTransitionAnimator = fakeTransitionAnimator()
    private val mainExecutor = context.mainExecutor
    private val testTransitionAnimator = fakeTransitionAnimator(mainExecutor)
    @Mock lateinit var callback: ActivityTransitionAnimator.Callback
    @Mock lateinit var listener: ActivityTransitionAnimator.Listener
    @Spy private val controller = TestTransitionAnimatorController(transitionContainer)
@@ -59,9 +60,10 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
    fun setup() {
        activityTransitionAnimator =
            ActivityTransitionAnimator(
                mainExecutor,
                testTransitionAnimator,
                testTransitionAnimator,
                disableWmTimeout = true
                disableWmTimeout = true,
            )
        activityTransitionAnimator.callback = callback
        activityTransitionAnimator.addListener(listener)
Loading