Loading core/java/android/animation/AnimationHandler.java +52 −0 Original line number Diff line number Diff line Loading @@ -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) { Loading Loading @@ -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(); Loading core/java/android/animation/Animator.java +39 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package android.animation; import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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 Loading Loading @@ -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. Loading core/java/android/animation/AnimatorSet.java +8 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading core/java/android/animation/ValueAnimator.java +10 −3 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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. Loading core/java/com/android/internal/jank/FrameTracker.java +3 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading
core/java/android/animation/AnimationHandler.java +52 −0 Original line number Diff line number Diff line Loading @@ -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) { Loading Loading @@ -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(); Loading
core/java/android/animation/Animator.java +39 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package android.animation; import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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 Loading Loading @@ -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. Loading
core/java/android/animation/AnimatorSet.java +8 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading
core/java/android/animation/ValueAnimator.java +10 −3 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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. Loading
core/java/com/android/internal/jank/FrameTracker.java +3 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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