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

Commit 7ae9d5fa authored by Jeff Brown's avatar Jeff Brown
Browse files

Use the Choreographer for Drawable animations.

Change-Id: Ifcbf33434bf3c32d1900fd0b3f5bde004604ce8a
parent d5f07990
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -645,7 +645,7 @@ public class ValueAnimator extends Animator {
            // onAnimate to process the next frame of the animations.
            if (!mAnimationScheduled
                    && (!mAnimations.isEmpty() || !mDelayedAnims.isEmpty())) {
                mChoreographer.postAnimationCallback(this);
                mChoreographer.postAnimationCallback(this, null);
                mAnimationScheduled = true;
            }
        }
+185 −97
Original line number Diff line number Diff line
@@ -81,8 +81,8 @@ public final class Choreographer {
    private static final int MSG_DO_ANIMATION = 0;
    private static final int MSG_DO_DRAW = 1;
    private static final int MSG_DO_SCHEDULE_VSYNC = 2;
    private static final int MSG_POST_DELAYED_ANIMATION = 3;
    private static final int MSG_POST_DELAYED_DRAW = 4;
    private static final int MSG_DO_SCHEDULE_ANIMATION = 3;
    private static final int MSG_DO_SCHEDULE_DRAW = 4;

    private final Object mLock = new Object();

@@ -151,135 +151,159 @@ public final class Choreographer {
        sFrameDelay = frameDelay;
    }

    /**
     * Subtracts typical frame delay time from a delay interval in milliseconds.
     *
     * This method can be used to compensate for animation delay times that have baked
     * in assumptions about the frame delay.  For example, it's quite common for code to
     * assume a 60Hz frame time and bake in a 16ms delay.  When we call
     * {@link #postAnimationCallbackDelayed} we want to know how long to wait before
     * posting the animation callback but let the animation timer take care of the remaining
     * frame delay time.
     *
     * This method is somewhat conservative about how much of the frame delay it
     * subtracts.  It uses the same value returned by {@link #getFrameDelay} which by
     * default is 10ms even though many parts of the system assume 16ms.  Consequently,
     * we might still wait 6ms before posting an animation callback that we want to run
     * on the next frame, but this is much better than waiting a whole 16ms and likely
     * missing the deadline.
     *
     * @param delayMillis The original delay time including an assumed frame delay.
     * @return The adjusted delay time with the assumed frame delay subtracted out.
     */
    public static long subtractFrameDelay(long delayMillis) {
        final long frameDelay = sFrameDelay;
        return delayMillis <= frameDelay ? 0 : delayMillis - frameDelay;
    }

    /**
     * Posts a callback to run on the next animation cycle.
     * The callback only runs once and then is automatically removed.
     *
     * @param runnable The callback to run during the next animation cycle.
     * @param action The callback action to run during the next animation cycle.
     * @param token The callback token, or null if none.
     *
     * @see #removeAnimationCallback
     */
    public void postAnimationCallback(Runnable runnable) {
        if (runnable == null) {
            throw new IllegalArgumentException("runnable must not be null");
        }
        postAnimationCallbackUnchecked(runnable);
    }

    private void postAnimationCallbackUnchecked(Runnable runnable) {
        synchronized (mLock) {
            mAnimationCallbacks = addCallbackLocked(mAnimationCallbacks, runnable);
            scheduleAnimationLocked();
        }
    public void postAnimationCallback(Runnable action, Object token) {
        postAnimationCallbackDelayed(action, token, 0);
    }

    /**
     * Posts a callback to run on the next animation cycle following the specified delay.
     * The callback only runs once and then is automatically removed.
     *
     * @param runnable The callback to run during the next animation cycle following
     * @param action The callback action to run during the next animation cycle after
     * the specified delay.
     * @param token The callback token, or null if none.
     * @param delayMillis The delay time in milliseconds.
     *
     * @see #removeAnimationCallback
     */
    public void postAnimationCallbackDelayed(Runnable runnable, long delayMillis) {
        if (runnable == null) {
            throw new IllegalArgumentException("runnable must not be null");
    public void postAnimationCallbackDelayed(Runnable action, Object token, long delayMillis) {
        if (action == null) {
            throw new IllegalArgumentException("action must not be null");
        }
        if (delayMillis <= 0) {
            postAnimationCallbackUnchecked(runnable);

        synchronized (mLock) {
            final long now = SystemClock.uptimeMillis();
            final long dueTime = now + delayMillis;
            mAnimationCallbacks = addCallbackLocked(mAnimationCallbacks, dueTime, action, token);

            if (dueTime <= now) {
                scheduleAnimationLocked(now);
            } else {
            Message msg = mHandler.obtainMessage(MSG_POST_DELAYED_ANIMATION, runnable);
            mHandler.sendMessageDelayed(msg, delayMillis);
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_ANIMATION, action);
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
    }

    /**
     * Removes animation callbacks for the specified runnable.
     * Does nothing if the specified animation callback has not been posted or has already
     * been removed.
     * Removes animation callbacks that have the specified action and token.
     *
     * @param runnable The animation callback to remove.
     * @param action The action property of the callbacks to remove, or null to remove
     * callbacks with any action.
     * @param token The token property of the callbacks to remove, or null to remove
     * callbacks with any token.
     *
     * @see #postAnimationCallback
     * @see #postAnimationCallbackDelayed
     */
    public void removeAnimationCallbacks(Runnable runnable) {
        if (runnable == null) {
            throw new IllegalArgumentException("runnable must not be null");
        }
    public void removeAnimationCallbacks(Runnable action, Object token) {
        synchronized (mLock) {
            mAnimationCallbacks = removeCallbacksLocked(mAnimationCallbacks, runnable);
            mAnimationCallbacks = removeCallbacksLocked(mAnimationCallbacks, action, token);
            if (action != null && token == null) {
                mHandler.removeMessages(MSG_DO_SCHEDULE_ANIMATION, action);
            }
        }
        mHandler.removeMessages(MSG_POST_DELAYED_ANIMATION, runnable);
    }

    /**
     * Posts a callback to run on the next draw cycle.
     * The callback only runs once and then is automatically removed.
     *
     * @param runnable The callback to run during the next draw cycle.
     * @param action The callback action to run during the next draw cycle.
     * @param token The callback token, or null if none.
     *
     * @see #removeDrawCallback
     */
    public void postDrawCallback(Runnable runnable) {
        if (runnable == null) {
            throw new IllegalArgumentException("runnable must not be null");
        }
        postDrawCallbackUnchecked(runnable);
    }

    private void postDrawCallbackUnchecked(Runnable runnable) {
        synchronized (mLock) {
            mDrawCallbacks = addCallbackLocked(mDrawCallbacks, runnable);
            scheduleDrawLocked();
        }
    public void postDrawCallback(Runnable action, Object token) {
        postDrawCallbackDelayed(action, token, 0);
    }

    /**
     * Posts a callback to run on the next draw cycle following the specified delay.
     * The callback only runs once and then is automatically removed.
     *
     * @param runnable The callback to run during the next draw cycle following
     * @param action The callback action to run during the next animation cycle after
     * the specified delay.
     * @param token The callback token, or null if none.
     * @param delayMillis The delay time in milliseconds.
     *
     * @see #removeDrawCallback
     */
    public void postDrawCallbackDelayed(Runnable runnable, long delayMillis) {
        if (runnable == null) {
            throw new IllegalArgumentException("runnable must not be null");
    public void postDrawCallbackDelayed(Runnable action, Object token, long delayMillis) {
        if (action == null) {
            throw new IllegalArgumentException("action must not be null");
        }
        if (delayMillis <= 0) {
            postDrawCallbackUnchecked(runnable);

        synchronized (mLock) {
            final long now = SystemClock.uptimeMillis();
            final long dueTime = now + delayMillis;
            mDrawCallbacks = addCallbackLocked(mDrawCallbacks, dueTime, action, token);
            scheduleDrawLocked(now);

            if (dueTime <= now) {
                scheduleDrawLocked(now);
            } else {
            Message msg = mHandler.obtainMessage(MSG_POST_DELAYED_DRAW, runnable);
            mHandler.sendMessageDelayed(msg, delayMillis);
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_DRAW, action);
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
    }

    /**
     * Removes draw callbacks for the specified runnable.
     * Does nothing if the specified draw callback has not been posted or has already
     * been removed.
     * Removes draw callbacks that have the specified action and token.
     *
     * @param runnable The draw callback to remove.
     * @param action The action property of the callbacks to remove, or null to remove
     * callbacks with any action.
     * @param token The token property of the callbacks to remove, or null to remove
     * callbacks with any token.
     *
     * @see #postDrawCallback
     * @see #postDrawCallbackDelayed
     */
    public void removeDrawCallbacks(Runnable runnable) {
        if (runnable == null) {
            throw new IllegalArgumentException("runnable must not be null");
        }
    public void removeDrawCallbacks(Runnable action, Object token) {
        synchronized (mLock) {
            mDrawCallbacks = removeCallbacksLocked(mDrawCallbacks, runnable);
            mDrawCallbacks = removeCallbacksLocked(mDrawCallbacks, action, token);
            if (action != null && token == null) {
                mHandler.removeMessages(MSG_DO_SCHEDULE_DRAW, action);
            }
        }
        mHandler.removeMessages(MSG_POST_DELAYED_DRAW, runnable);
    }

    private void scheduleAnimationLocked() {
    private void scheduleAnimationLocked(long now) {
        if (!mAnimationScheduled) {
            mAnimationScheduled = true;
            if (USE_VSYNC) {
@@ -291,14 +315,13 @@ public final class Choreographer {
                // otherwise post a message to schedule the vsync from the UI thread
                // as soon as possible.
                if (isRunningOnLooperThreadLocked()) {
                    doScheduleVsyncLocked();
                    scheduleVsyncLocked();
                } else {
                    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
                    msg.setAsynchronous(true);
                    mHandler.sendMessageAtFrontOfQueue(msg);
                }
            } else {
                final long now = SystemClock.uptimeMillis();
                final long nextAnimationTime = Math.max(mLastAnimationTime + sFrameDelay, now);
                if (DEBUG) {
                    Log.d(TAG, "Scheduling animation in " + (nextAnimationTime - now) + " ms.");
@@ -310,18 +333,18 @@ public final class Choreographer {
        }
    }

    private void scheduleDrawLocked() {
    private void scheduleDrawLocked(long now) {
        if (!mDrawScheduled) {
            mDrawScheduled = true;
            if (USE_ANIMATION_TIMER_FOR_DRAW) {
                scheduleAnimationLocked();
                scheduleAnimationLocked(now);
            } else {
                if (DEBUG) {
                    Log.d(TAG, "Scheduling draw immediately.");
                }
                Message msg = mHandler.obtainMessage(MSG_DO_DRAW);
                msg.setAsynchronous(true);
                mHandler.sendMessage(msg);
                mHandler.sendMessageAtTime(msg, now);
            }
        }
    }
@@ -336,7 +359,7 @@ public final class Choreographer {

    void doAnimationInner() {
        final long start;
        final Callback callbacks;
        Callback callbacks;
        synchronized (mLock) {
            if (!mAnimationScheduled) {
                return; // no work to do
@@ -351,7 +374,23 @@ public final class Choreographer {
            mLastAnimationTime = start;

            callbacks = mAnimationCallbacks;
            mAnimationCallbacks = null;
            if (callbacks != null) {
                if (callbacks.dueTime > start) {
                    callbacks = null;
                } else {
                    Callback predecessor = callbacks;
                    Callback successor = predecessor.next;
                    while (successor != null) {
                        if (successor.dueTime > start) {
                            predecessor.next = null;
                            break;
                        }
                        predecessor = successor;
                        successor = successor.next;
                    }
                    mAnimationCallbacks = successor;
                }
            }
        }

        if (callbacks != null) {
@@ -368,7 +407,7 @@ public final class Choreographer {

    void doDraw() {
        final long start;
        final Callback callbacks;
        Callback callbacks;
        synchronized (mLock) {
            if (!mDrawScheduled) {
                return; // no work to do
@@ -383,7 +422,23 @@ public final class Choreographer {
            mLastDrawTime = start;

            callbacks = mDrawCallbacks;
            mDrawCallbacks = null;
            if (callbacks != null) {
                if (callbacks.dueTime > start) {
                    callbacks = null;
                } else {
                    Callback predecessor = callbacks;
                    Callback successor = predecessor.next;
                    while (successor != null) {
                        if (successor.dueTime > start) {
                            predecessor.next = null;
                            break;
                        }
                        predecessor = successor;
                        successor = successor.next;
                    }
                    mDrawCallbacks = successor;
                }
            }
        }

        if (callbacks != null) {
@@ -400,38 +455,66 @@ public final class Choreographer {

    void doScheduleVsync() {
        synchronized (mLock) {
            doScheduleVsyncLocked();
            if (mAnimationScheduled) {
                scheduleVsyncLocked();
            }
        }
    }

    private void doScheduleVsyncLocked() {
        if (mAnimationScheduled) {
            mDisplayEventReceiver.scheduleVsync();
    void doScheduleAnimation() {
        synchronized (mLock) {
            final long now = SystemClock.uptimeMillis();
            if (mAnimationCallbacks != null && mAnimationCallbacks.dueTime <= now) {
                scheduleAnimationLocked(now);
            }
        }
    }

    void doScheduleDraw() {
        synchronized (mLock) {
            final long now = SystemClock.uptimeMillis();
            if (mDrawCallbacks != null && mDrawCallbacks.dueTime <= now) {
                scheduleDrawLocked(now);
            }
        }
    }

    private void scheduleVsyncLocked() {
        mDisplayEventReceiver.scheduleVsync();
    }

    private boolean isRunningOnLooperThreadLocked() {
        return Looper.myLooper() == mLooper;
    }

    private Callback addCallbackLocked(Callback head, Runnable runnable) {
        Callback callback = obtainCallbackLocked(runnable);
    private Callback addCallbackLocked(Callback head,
            long dueTime, Runnable action, Object token) {
        Callback callback = obtainCallbackLocked(dueTime, action, token);
        if (head == null) {
            return callback;
        }
        Callback tail = head;
        while (tail.next != null) {
            tail = tail.next;
        Callback entry = head;
        if (dueTime < entry.dueTime) {
            callback.next = entry;
            return callback;
        }
        while (entry.next != null) {
            if (dueTime < entry.next.dueTime) {
                callback.next = entry.next;
                break;
            }
            entry = entry.next;
        }
        tail.next = callback;
        entry.next = callback;
        return head;
    }

    private Callback removeCallbacksLocked(Callback head, Runnable runnable) {
    private Callback removeCallbacksLocked(Callback head, Runnable action, Object token) {
        Callback predecessor = null;
        for (Callback callback = head; callback != null;) {
            final Callback next = callback.next;
            if (callback.runnable == runnable) {
            if ((action == null || callback.action == action)
                    && (token == null || callback.token == token)) {
                if (predecessor != null) {
                    predecessor.next = next;
                } else {
@@ -448,7 +531,7 @@ public final class Choreographer {

    private void runCallbacks(Callback head) {
        while (head != null) {
            head.runnable.run();
            head.action.run();
            head = head.next;
        }
    }
@@ -461,7 +544,7 @@ public final class Choreographer {
        }
    }

    private Callback obtainCallbackLocked(Runnable runnable) {
    private Callback obtainCallbackLocked(long dueTime, Runnable action, Object token) {
        Callback callback = mCallbackPool;
        if (callback == null) {
            callback = new Callback();
@@ -469,12 +552,15 @@ public final class Choreographer {
            mCallbackPool = callback.next;
            callback.next = null;
        }
        callback.runnable = runnable;
        callback.dueTime = dueTime;
        callback.action = action;
        callback.token = token;
        return callback;
    }

    private void recycleCallbackLocked(Callback callback) {
        callback.runnable = null;
        callback.action = null;
        callback.token = null;
        callback.next = mCallbackPool;
        mCallbackPool = callback;
    }
@@ -496,11 +582,11 @@ public final class Choreographer {
                case MSG_DO_SCHEDULE_VSYNC:
                    doScheduleVsync();
                    break;
                case MSG_POST_DELAYED_ANIMATION:
                    postAnimationCallbackUnchecked((Runnable)msg.obj);
                case MSG_DO_SCHEDULE_ANIMATION:
                    doScheduleAnimation();
                    break;
                case MSG_POST_DELAYED_DRAW:
                    postDrawCallbackUnchecked((Runnable)msg.obj);
                case MSG_DO_SCHEDULE_DRAW:
                    doScheduleDraw();
                    break;
            }
        }
@@ -519,6 +605,8 @@ public final class Choreographer {

    private static final class Callback {
        public Callback next;
        public Runnable runnable;
        public long dueTime;
        public Runnable action;
        public Object token;
    }
}
+53 −4
Original line number Diff line number Diff line
@@ -8774,6 +8774,52 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
        return true;
    }
    /**
     * <p>Causes the Runnable to execute on the next animation time step.
     * The runnable will be run on the user interface thread.</p>
     *
     * <p>This method can be invoked from outside of the UI thread
     * only when this View is attached to a window.</p>
     *
     * @param action The Runnable that will be executed.
     *
     * @hide
     */
    public void postOnAnimation(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            attachInfo.mViewRootImpl.mChoreographer.postAnimationCallback(action, null);
        } else {
            // Assume that post will succeed later
            ViewRootImpl.getRunQueue().post(action);
        }
    }
    /**
     * <p>Causes the Runnable to execute on the next animation time step,
     * after the specified amount of time elapses.
     * The runnable will be run on the user interface thread.</p>
     *
     * <p>This method can be invoked from outside of the UI thread
     * only when this View is attached to a window.</p>
     *
     * @param action The Runnable that will be executed.
     * @param delayMillis The delay (in milliseconds) until the Runnable
     *        will be executed.
     *
     * @hide
     */
    public void postOnAnimationDelayed(Runnable action, long delayMillis) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            attachInfo.mViewRootImpl.mChoreographer.postAnimationCallbackDelayed(
                    action, null, delayMillis);
        } else {
            // Assume that post will succeed later
            ViewRootImpl.getRunQueue().postDelayed(action, delayMillis);
        }
    }
    /**
     * <p>Removes the specified Runnable from the message queue.</p>
     * 
@@ -8791,6 +8837,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            attachInfo.mHandler.removeCallbacks(action);
            attachInfo.mViewRootImpl.mChoreographer.removeAnimationCallbacks(action, null);
        } else {
            // Assume that post will succeed later
            ViewRootImpl.getRunQueue().removeCallbacks(action);
@@ -11837,10 +11884,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
     */
    public void scheduleDrawable(Drawable who, Runnable what, long when) {
        if (verifyDrawable(who) && what != null) {
            final long delay = when - SystemClock.uptimeMillis();
            if (mAttachInfo != null) {
                mAttachInfo.mHandler.postAtTime(what, who, when);
                mAttachInfo.mViewRootImpl.mChoreographer.postAnimationCallbackDelayed(
                        what, who, Choreographer.subtractFrameDelay(delay));
            } else {
                ViewRootImpl.getRunQueue().postDelayed(what, when - SystemClock.uptimeMillis());
                ViewRootImpl.getRunQueue().postDelayed(what, delay);
            }
        }
    }
@@ -11854,7 +11903,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
    public void unscheduleDrawable(Drawable who, Runnable what) {
        if (verifyDrawable(who) && what != null) {
            if (mAttachInfo != null) {
                mAttachInfo.mHandler.removeCallbacks(what, who);
                mAttachInfo.mViewRootImpl.mChoreographer.removeAnimationCallbacks(what, who);
            } else {
                ViewRootImpl.getRunQueue().removeCallbacks(what);
            }
@@ -11872,7 +11921,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
     */
    public void unscheduleDrawable(Drawable who) {
        if (mAttachInfo != null) {
            mAttachInfo.mHandler.removeCallbacksAndMessages(who);
            mAttachInfo.mViewRootImpl.mChoreographer.removeAnimationCallbacks(null, who);
        }
    }
+4 −4
Original line number Diff line number Diff line
@@ -867,7 +867,7 @@ public final class ViewRootImpl implements ViewParent,
    void scheduleFrame() {
        if (!mFrameScheduled) {
            mFrameScheduled = true;
            mChoreographer.postDrawCallback(mFrameRunnable);
            mChoreographer.postDrawCallback(mFrameRunnable, null);
        }
    }

@@ -876,7 +876,7 @@ public final class ViewRootImpl implements ViewParent,

        if (mFrameScheduled) {
            mFrameScheduled = false;
            mChoreographer.removeDrawCallbacks(mFrameRunnable);
            mChoreographer.removeDrawCallbacks(mFrameRunnable, null);
        }
    }

@@ -4027,7 +4027,7 @@ public final class ViewRootImpl implements ViewParent,
                }

                if (mPosted && mViews.isEmpty() && mViewRects.isEmpty()) {
                    mChoreographer.removeAnimationCallbacks(this);
                    mChoreographer.removeAnimationCallbacks(this, null);
                    mPosted = false;
                }
            }
@@ -4068,7 +4068,7 @@ public final class ViewRootImpl implements ViewParent,

        private void postIfNeededLocked() {
            if (!mPosted) {
                mChoreographer.postAnimationCallback(this);
                mChoreographer.postAnimationCallback(this, null);
                mPosted = true;
            }
        }
+1 −1
Original line number Diff line number Diff line
@@ -9157,7 +9157,7 @@ public class WindowManagerService extends IWindowManager.Stub

    void scheduleAnimationLocked() {
        if (!mAnimationScheduled) {
            mChoreographer.postAnimationCallback(mAnimationRunnable);
            mChoreographer.postAnimationCallback(mAnimationRunnable, null);
            mAnimationScheduled = true;
        }
    }