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

Commit e685cf0a authored by Riddle Hsu's avatar Riddle Hsu
Browse files

Add the ability to adjust the timing animation end listener

No behavior change unless someone enables this.

Originally, when calling onAnimationEnd, the listeners are called
before drawing the last animation frame. If the listener is slow,
there may have jank at the end of animation.

If setPostNotifyEndListenerEnabled is enabled, the most common
animators: ValueAnimator and AnimatorSet will run the end listeners
on the next frame of last animation frame. So the implementation
of callback won't delay the animation frame.

Also make FrameTracker exclude the frame of end callback from
jank data if it is the frame after the last animation frame.

Bug: 300035126
Flag: EXEMPT disabled by default
Test: atest FrameworksCoreTests:FrameTrackerTest# \
            testEndAnimationWithLastFrameSyncId
      atest FrameworksCoreTests:ValueAnimatorTests# \
            testPostNotifyEndListener
Change-Id: I0550d951c65fe0e03e81bce883b3b33a796ae532
parent 8fa39887
Loading
Loading
Loading
Loading
+52 −0
Original line number Diff line number Diff line
@@ -81,6 +81,25 @@ public class AnimationHandler {
     */
    private final ArrayList<WeakReference<Object>> mAnimatorRequestors = new ArrayList<>();

    /**
     * The callbacks which will invoke {@link Animator#notifyEndListeners(boolean)} on next frame.
     * It is only used if {@link Animator#setPostNotifyEndListenerEnabled(boolean)} sets true.
     */
    private ArrayList<Runnable> mPendingEndAnimationListeners;

    /**
     * The value of {@link Choreographer#getVsyncId()} at the last animation frame.
     * It is only used if {@link Animator#setPostNotifyEndListenerEnabled(boolean)} sets true.
     */
    private long mLastAnimationFrameVsyncId;

    /**
     * The value of {@link Choreographer#getVsyncId()} when calling
     * {@link Animator#notifyEndListeners(boolean)}.
     * It is only used if {@link Animator#setPostNotifyEndListenerEnabled(boolean)} sets true.
     */
    private long mEndAnimationFrameVsyncId;

    private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
        @Override
        public void doFrame(long frameTimeNanos) {
@@ -332,6 +351,39 @@ public class AnimationHandler {
        }
    }

    /**
     * Returns the vsyncId of last animation frame if the given {@param currentVsyncId} matches
     * the vsyncId from the end callback of animation. Otherwise it returns the given vsyncId.
     * It only takes effect if {@link #postEndAnimationCallback(Runnable)} is called.
     */
    public long getLastAnimationFrameVsyncId(long currentVsyncId) {
        return currentVsyncId == mEndAnimationFrameVsyncId && mLastAnimationFrameVsyncId != 0
                ? mLastAnimationFrameVsyncId : currentVsyncId;
    }

    /** Runs the given callback on next frame to notify the end of the animation. */
    public void postEndAnimationCallback(Runnable notifyEndAnimation) {
        if (mPendingEndAnimationListeners == null) {
            mPendingEndAnimationListeners = new ArrayList<>();
        }
        mPendingEndAnimationListeners.add(notifyEndAnimation);
        if (mPendingEndAnimationListeners.size() > 1) {
            return;
        }
        final Choreographer choreographer = Choreographer.getInstance();
        mLastAnimationFrameVsyncId = choreographer.getVsyncId();
        getProvider().postFrameCallback(frame -> {
            mEndAnimationFrameVsyncId = choreographer.getVsyncId();
            // The animation listeners can only get vsyncId of last animation frame in this frame
            // by getLastAnimationFrameVsyncId(currentVsyncId).
            while (mPendingEndAnimationListeners.size() > 0) {
                mPendingEndAnimationListeners.remove(0).run();
            }
            mEndAnimationFrameVsyncId = 0;
            mLastAnimationFrameVsyncId = 0;
        });
    }

    private void doAnimationFrame(long frameTime) {
        long currentTime = SystemClock.uptimeMillis();
        final int size = mAnimationCallbacks.size();
+39 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package android.animation;

import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
@@ -23,6 +24,7 @@ import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo.Config;
import android.content.res.ConstantState;
import android.os.Build;
import android.os.Trace;
import android.util.LongArray;

import java.util.ArrayList;
@@ -72,6 +74,13 @@ public abstract class Animator implements Cloneable {
     */
    private static long sBackgroundPauseDelay = 1000;

    /**
     * If true, when the animation plays normally to the end, the callback
     * {@link AnimatorListener#onAnimationEnd(Animator)} will be scheduled on the next frame.
     * It is to avoid the last animation frame being delayed by the implementation of listeners.
     */
    static boolean sPostNotifyEndListenerEnabled;

    /**
     * A cache of the values in a list. Used so that when calling the list, we have a copy
     * of it in case the list is modified while iterating. The array can be reused to avoid
@@ -123,6 +132,14 @@ public abstract class Animator implements Cloneable {
        AnimationHandler.setOverrideAnimatorPausingSystemProperty(!enable);
    }

    /**
     * @see #sPostNotifyEndListenerEnabled
     * @hide
     */
    public static void setPostNotifyEndListenerEnabled(boolean enable) {
        sPostNotifyEndListenerEnabled = enable;
    }

    /**
     * Starts this animation. If the animation has a nonzero startDelay, the animation will start
     * running after that delay elapses. A non-delayed animation will have its initial
@@ -635,6 +652,28 @@ public abstract class Animator implements Cloneable {
        }
    }

    void notifyEndListenersFromEndAnimation(boolean isReversing, boolean postNotifyEndListener) {
        if (postNotifyEndListener) {
            AnimationHandler.getInstance().postEndAnimationCallback(
                    () -> completeEndAnimation(isReversing, "postNotifyAnimEnd"));
        } else {
            completeEndAnimation(isReversing, "notifyAnimEnd");
        }
    }

    @CallSuper
    void completeEndAnimation(boolean isReversing, String notifyListenerTraceName) {
        final boolean useTrace = mListeners != null && Trace.isTagEnabled(Trace.TRACE_TAG_VIEW);
        if (useTrace) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, notifyListenerTraceName
                    + "-" + getClass().getSimpleName());
        }
        notifyEndListeners(isReversing);
        if (useTrace) {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

    /**
     * Calls <code>call</code> for every item in <code>list</code> with <code>animator</code> and
     * <code>isReverse</code> as parameters.
+8 −1
Original line number Diff line number Diff line
@@ -1442,6 +1442,8 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
    }

    private void endAnimation() {
        final boolean postNotifyEndListener = sPostNotifyEndListenerEnabled && mListeners != null
                && mLastFrameTime > 0;
        mStarted = false;
        mLastFrameTime = -1;
        mFirstFrame = -1;
@@ -1453,7 +1455,12 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim

        // No longer receive callbacks
        removeAnimationCallback();
        notifyEndListeners(mReversing);
        notifyEndListenersFromEndAnimation(mReversing, postNotifyEndListener);
    }

    @Override
    void completeEndAnimation(boolean isReversing, String notifyListenerTraceName) {
        super.completeEndAnimation(isReversing, notifyListenerTraceName);
        removeAnimationEndListener();
        mSelfPulse = true;
        mReversing = false;
+10 −3
Original line number Diff line number Diff line
@@ -1289,6 +1289,8 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio
        if (mAnimationEndRequested) {
            return;
        }
        final boolean postNotifyEndListener = sPostNotifyEndListenerEnabled && mListeners != null
                && mLastFrameTime > 0;
        removeAnimationCallback();

        mAnimationEndRequested = true;
@@ -1303,15 +1305,20 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio
        mStartTime = -1;
        mRunning = false;
        mStarted = false;
        notifyEndListeners(mReversing);
        // mReversing needs to be reset *after* notifying the listeners for the end callbacks.
        mReversing = false;
        notifyEndListenersFromEndAnimation(mReversing, postNotifyEndListener);
        if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
            Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, getNameForTrace(),
                    System.identityHashCode(this));
        }
    }

    @Override
    void completeEndAnimation(boolean isReversing, String notifyListenerTraceName) {
        super.completeEndAnimation(isReversing, notifyListenerTraceName);
        // mReversing needs to be reset *after* notifying the listeners for the end callbacks.
        mReversing = false;
    }

    /**
     * Called internally to start an animation by adding it to the active animations list. Must be
     * called on the UI thread.
+3 −1
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import static com.android.internal.jank.InteractionJankMonitor.ACTION_SESSION_CA
import static com.android.internal.jank.InteractionJankMonitor.ACTION_SESSION_END;
import static com.android.internal.jank.InteractionJankMonitor.EXECUTOR_TASK_TIMEOUT;

import android.animation.AnimationHandler;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -344,7 +345,8 @@ public class FrameTracker implements HardwareRendererObserver.OnFrameMetricsAvai
    @UiThread
    public boolean end(@Reasons int reason) {
        if (mCancelled || mEndVsyncId != INVALID_ID) return false;
        mEndVsyncId = mChoreographer.getVsyncId();
        mEndVsyncId = AnimationHandler.getInstance().getLastAnimationFrameVsyncId(
                mChoreographer.getVsyncId());
        // Cancel the session if:
        // 1. The session begins and ends at the same vsync id.
        // 2. The session never begun.
Loading