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

Commit fef3d62b authored by Jeff Brown's avatar Jeff Brown Committed by Android (Google) Code Review
Browse files

Merge "Add support for posting Runnables to the Choreographer."

parents 472ea606 96858857
Loading
Loading
Loading
Loading
+312 −126
Original line number Diff line number Diff line
@@ -37,7 +37,7 @@ import android.util.Log;
 *
 * @hide
 */
public final class Choreographer extends Handler {
public final class Choreographer {
    private static final String TAG = "Choreographer";
    private static final boolean DEBUG = false;

@@ -92,10 +92,16 @@ public final class Choreographer extends Handler {
    private final Object mLock = new Object();

    private final Looper mLooper;
    private final FrameHandler mHandler;

    private Callback mCallbackPool;

    private OnAnimateListener[] mOnAnimateListeners;
    private OnDrawListener[] mOnDrawListeners;

    private Callback mOnAnimateCallbacks;
    private Callback mOnDrawCallbacks;

    private boolean mAnimationScheduled;
    private boolean mDrawScheduled;
    private boolean mFrameDisplayEventReceiverNeeded;
@@ -104,8 +110,8 @@ public final class Choreographer extends Handler {
    private long mLastDrawTime;

    private Choreographer(Looper looper) {
        super(looper);
        mLooper = looper;
        mHandler = new FrameHandler(looper);
        mLastAnimationTime = Long.MIN_VALUE;
        mLastDrawTime = Long.MIN_VALUE;
    }
@@ -158,12 +164,13 @@ public final class Choreographer extends Handler {
     */
    public void scheduleAnimation() {
        synchronized (mLock) {
            scheduleAnimationLocked();
            scheduleAnimationLocked(false);
        }
    }

    private void scheduleAnimationLocked() {
        if (!mAnimationScheduled) {
    private void scheduleAnimationLocked(boolean force) {
        if (!mAnimationScheduled
                && (force || mOnAnimateListeners != null || mOnAnimateCallbacks != null)) {
            mAnimationScheduled = true;
            if (USE_VSYNC) {
                if (DEBUG) {
@@ -176,13 +183,14 @@ public final class Choreographer extends Handler {
                if (!mFrameDisplayEventReceiverNeeded) {
                    mFrameDisplayEventReceiverNeeded = true;
                    if (mFrameDisplayEventReceiver != null) {
                        removeMessages(MSG_DO_DISPOSE_RECEIVER);
                        mHandler.removeMessages(MSG_DO_DISPOSE_RECEIVER);
                    }
                }
                if (isRunningOnLooperThreadLocked()) {
                    doScheduleVsyncLocked();
                } else {
                    sendMessageAtFrontOfQueue(obtainMessage(MSG_DO_SCHEDULE_VSYNC));
                    mHandler.sendMessageAtFrontOfQueue(
                            mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC));
                }
            } else {
                final long now = SystemClock.uptimeMillis();
@@ -190,7 +198,7 @@ public final class Choreographer extends Handler {
                if (DEBUG) {
                    Log.d(TAG, "Scheduling animation in " + (nextAnimationTime - now) + " ms.");
                }
                sendEmptyMessageAtTime(MSG_DO_ANIMATION, nextAnimationTime);
                mHandler.sendEmptyMessageAtTime(MSG_DO_ANIMATION, nextAnimationTime);
            }
        }
    }
@@ -212,16 +220,21 @@ public final class Choreographer extends Handler {
     */
    public void scheduleDraw() {
        synchronized (mLock) {
            if (!mDrawScheduled) {
            scheduleDrawLocked();
        }
    }

    private void scheduleDrawLocked() {
        if (!mDrawScheduled
                && (mOnDrawListeners != null || mOnDrawCallbacks != null)) {
            mDrawScheduled = true;
            if (USE_ANIMATION_TIMER_FOR_DRAW) {
                    scheduleAnimationLocked();
                scheduleAnimationLocked(true);
            } else {
                if (DEBUG) {
                    Log.d(TAG, "Scheduling draw immediately.");
                }
                    sendEmptyMessage(MSG_DO_DRAW);
                }
                mHandler.sendEmptyMessage(MSG_DO_DRAW);
            }
        }
    }
@@ -237,25 +250,165 @@ public final class Choreographer extends Handler {
        }
    }

    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case MSG_DO_ANIMATION:
                doAnimation();
                break;
            case MSG_DO_DRAW:
                doDraw();
                break;
            case MSG_DO_SCHEDULE_VSYNC:
                doScheduleVsync();
                break;
            case MSG_DO_DISPOSE_RECEIVER:
                doDisposeReceiver();
                break;
    /**
     * Adds an animation listener.
     *
     * @param listener The listener to add.
     */
    public void addOnAnimateListener(OnAnimateListener listener) {
        if (listener == null) {
            throw new IllegalArgumentException("listener must not be null");
        }

        if (DEBUG) {
            Log.d(TAG, "Adding onAnimate listener: " + listener);
        }

    private void doAnimation() {
        synchronized (mLock) {
            mOnAnimateListeners = ArrayUtils.appendElement(OnAnimateListener.class,
                    mOnAnimateListeners, listener);
        }
    }

    /**
     * Removes an animation listener.
     *
     * @param listener The listener to remove.
     */
    public void removeOnAnimateListener(OnAnimateListener listener) {
        if (listener == null) {
            throw new IllegalArgumentException("listener must not be null");
        }

        if (DEBUG) {
            Log.d(TAG, "Removing onAnimate listener: " + listener);
        }

        synchronized (mLock) {
            mOnAnimateListeners = ArrayUtils.removeElement(OnAnimateListener.class,
                    mOnAnimateListeners, listener);
            stopTimingLoopIfNoListenersOrCallbacksLocked();
        }
    }

    /**
     * Adds a draw listener.
     *
     * @param listener The listener to add.
     */
    public void addOnDrawListener(OnDrawListener listener) {
        if (listener == null) {
            throw new IllegalArgumentException("listener must not be null");
        }

        if (DEBUG) {
            Log.d(TAG, "Adding onDraw listener: " + listener);
        }

        synchronized (mLock) {
            mOnDrawListeners = ArrayUtils.appendElement(OnDrawListener.class,
                    mOnDrawListeners, listener);
        }
    }

    /**
     * Removes a draw listener.
     * Must be called on the UI thread.
     *
     * @param listener The listener to remove.
     */
    public void removeOnDrawListener(OnDrawListener listener) {
        if (listener == null) {
            throw new IllegalArgumentException("listener must not be null");
        }

        if (DEBUG) {
            Log.d(TAG, "Removing onDraw listener: " + listener);
        }

        synchronized (mLock) {
            mOnDrawListeners = ArrayUtils.removeElement(OnDrawListener.class,
                    mOnDrawListeners, listener);
            stopTimingLoopIfNoListenersOrCallbacksLocked();
        }
    }


    /**
     * Posts a callback to run on the next animation cycle and schedules an animation cycle.
     * The callback only runs once and then is automatically removed.
     *
     * @param runnable The callback to run during the next animation cycle.
     *
     * @see #removeOnAnimateCallback
     */
    public void postOnAnimateCallback(Runnable runnable) {
        if (runnable == null) {
            throw new IllegalArgumentException("runnable must not be null");
        }
        synchronized (mLock) {
            mOnAnimateCallbacks = addCallbackLocked(mOnAnimateCallbacks, runnable);
            scheduleAnimationLocked(false);
        }
    }

    /**
     * Removes an animation callback.
     * Does nothing if the specified animation callback has not been posted or has already
     * been removed.
     *
     * @param runnable The animation callback to remove.
     *
     * @see #postOnAnimateCallback
     */
    public void removeOnAnimateCallback(Runnable runnable) {
        if (runnable == null) {
            throw new IllegalArgumentException("runnable must not be null");
        }
        synchronized (mLock) {
            mOnAnimateCallbacks = removeCallbackLocked(mOnAnimateCallbacks, runnable);
            stopTimingLoopIfNoListenersOrCallbacksLocked();
        }
    }

    /**
     * Posts a callback to run on the next draw cycle and schedules a draw cycle.
     * The callback only runs once and then is automatically removed.
     *
     * @param runnable The callback to run during the next draw cycle.
     *
     * @see #removeOnDrawCallback
     */
    public void postOnDrawCallback(Runnable runnable) {
        if (runnable == null) {
            throw new IllegalArgumentException("runnable must not be null");
        }
        synchronized (mLock) {
            mOnDrawCallbacks = addCallbackLocked(mOnDrawCallbacks, runnable);
            scheduleDrawLocked();
        }
    }

    /**
     * Removes a draw callback.
     * Does nothing if the specified draw callback has not been posted or has already
     * been removed.
     *
     * @param runnable The draw callback to remove.
     *
     * @see #postOnDrawCallback
     */
    public void removeOnDrawCallback(Runnable runnable) {
        if (runnable == null) {
            throw new IllegalArgumentException("runnable must not be null");
        }
        synchronized (mLock) {
            mOnDrawCallbacks = removeCallbackLocked(mOnDrawCallbacks, runnable);
            stopTimingLoopIfNoListenersOrCallbacksLocked();
        }
    }

    void doAnimation() {
        doAnimationInner();

        if (USE_ANIMATION_TIMER_FOR_DRAW) {
@@ -263,9 +416,10 @@ public final class Choreographer extends Handler {
        }
    }

    private void doAnimationInner() {
    void doAnimationInner() {
        final long start;
        final OnAnimateListener[] listeners;
        final Callback callbacks;
        synchronized (mLock) {
            if (!mAnimationScheduled) {
                return; // no work to do
@@ -280,6 +434,8 @@ public final class Choreographer extends Handler {
            mLastAnimationTime = start;

            listeners = mOnAnimateListeners;
            callbacks = mOnAnimateCallbacks;
            mOnAnimateCallbacks = null;
        }

        if (listeners != null) {
@@ -288,14 +444,22 @@ public final class Choreographer extends Handler {
            }
        }

        if (callbacks != null) {
            runCallbacks(callbacks);
            synchronized (mLock) {
                recycleCallbacksLocked(callbacks);
            }
        }

        if (DEBUG) {
            Log.d(TAG, "Animation took " + (SystemClock.uptimeMillis() - start) + " ms.");
        }
    }

    private void doDraw() {
    void doDraw() {
        final long start;
        final OnDrawListener[] listeners;
        final Callback callbacks;
        synchronized (mLock) {
            if (!mDrawScheduled) {
                return; // no work to do
@@ -310,6 +474,8 @@ public final class Choreographer extends Handler {
            mLastDrawTime = start;

            listeners = mOnDrawListeners;
            callbacks = mOnDrawCallbacks;
            mOnDrawCallbacks = null;
        }

        if (listeners != null) {
@@ -318,12 +484,19 @@ public final class Choreographer extends Handler {
            }
        }

        if (callbacks != null) {
            runCallbacks(callbacks);
            synchronized (mLock) {
                recycleCallbacksLocked(callbacks);
            }
        }

        if (DEBUG) {
            Log.d(TAG, "Draw took " + (SystemClock.uptimeMillis() - start) + " ms.");
        }
    }

    private void doScheduleVsync() {
    void doScheduleVsync() {
        synchronized (mLock) {
            doScheduleVsyncLocked();
        }
@@ -338,7 +511,7 @@ public final class Choreographer extends Handler {
        }
    }

    private void doDisposeReceiver() {
    void doDisposeReceiver() {
        synchronized (mLock) {
            if (!mFrameDisplayEventReceiverNeeded && mFrameDisplayEventReceiver != null) {
                mFrameDisplayEventReceiver.dispose();
@@ -347,127 +520,111 @@ public final class Choreographer extends Handler {
        }
    }

    /**
     * Adds an animation listener.
     *
     * @param listener The listener to add.
     */
    public void addOnAnimateListener(OnAnimateListener listener) {
        if (listener == null) {
            throw new IllegalArgumentException("listener must not be null");
        }

    private void stopTimingLoopIfNoListenersOrCallbacksLocked() {
        if (mOnAnimateListeners == null && mOnDrawListeners == null
                && mOnAnimateCallbacks == null && mOnDrawCallbacks == null) {
            if (DEBUG) {
            Log.d(TAG, "Adding onAnimate listener: " + listener);
        }

        synchronized (mLock) {
            mOnAnimateListeners = ArrayUtils.appendElement(OnAnimateListener.class,
                    mOnAnimateListeners, listener);
        }
                Log.d(TAG, "Stopping timing loop.");
            }

    /**
     * Removes an animation listener.
     *
     * @param listener The listener to remove.
     */
    public void removeOnAnimateListener(OnAnimateListener listener) {
        if (listener == null) {
            throw new IllegalArgumentException("listener must not be null");
            if (mAnimationScheduled) {
                mAnimationScheduled = false;
                if (USE_VSYNC) {
                    mHandler.removeMessages(MSG_DO_SCHEDULE_VSYNC);
                } else {
                    mHandler.removeMessages(MSG_DO_ANIMATION);
                }

        if (DEBUG) {
            Log.d(TAG, "Removing onAnimate listener: " + listener);
            }

        synchronized (mLock) {
            mOnAnimateListeners = ArrayUtils.removeElement(OnAnimateListener.class,
                    mOnAnimateListeners, listener);
            stopTimingLoopIfNoListenersLocked();
            if (mDrawScheduled) {
                mDrawScheduled = false;
                if (!USE_ANIMATION_TIMER_FOR_DRAW) {
                    mHandler.removeMessages(MSG_DO_DRAW);
                }
            }

    /**
     * Adds a draw listener.
     *
     * @param listener The listener to add.
     */
    public void addOnDrawListener(OnDrawListener listener) {
        if (listener == null) {
            throw new IllegalArgumentException("listener must not be null");
            // Post a message to dispose the display event receiver if we haven't needed
            // it again after a certain amount of time has elapsed.  Another reason to
            // defer disposal is that it is possible for use to attempt to dispose the
            // receiver while handling a vsync event that it dispatched, which might
            // cause a few problems...
            if (mFrameDisplayEventReceiverNeeded) {
                mFrameDisplayEventReceiverNeeded = false;
                if (mFrameDisplayEventReceiver != null) {
                    mHandler.sendEmptyMessageDelayed(MSG_DO_DISPOSE_RECEIVER,
                            DISPOSE_RECEIVER_DELAY);
                }

        if (DEBUG) {
            Log.d(TAG, "Adding onDraw listener: " + listener);
            }

        synchronized (mLock) {
            mOnDrawListeners = ArrayUtils.appendElement(OnDrawListener.class,
                    mOnDrawListeners, listener);
        }
    }

    /**
     * Removes a draw listener.
     * Must be called on the UI thread.
     *
     * @param listener The listener to remove.
     */
    public void removeOnDrawListener(OnDrawListener listener) {
        if (listener == null) {
            throw new IllegalArgumentException("listener must not be null");
    private boolean isRunningOnLooperThreadLocked() {
        return Looper.myLooper() == mLooper;
    }

        if (DEBUG) {
            Log.d(TAG, "Removing onDraw listener: " + listener);
    private Callback addCallbackLocked(Callback head, Runnable runnable) {
        Callback callback = obtainCallbackLocked(runnable);
        if (head == null) {
            return callback;
        }

        synchronized (mLock) {
            mOnDrawListeners = ArrayUtils.removeElement(OnDrawListener.class,
                    mOnDrawListeners, listener);
            stopTimingLoopIfNoListenersLocked();
        Callback tail = head;
        while (tail.next != null) {
            tail = tail.next;
        }
        tail.next = callback;
        return head;
    }

    private void stopTimingLoopIfNoListenersLocked() {
        if (mOnDrawListeners == null && mOnAnimateListeners == null) {
            if (DEBUG) {
                Log.d(TAG, "Stopping timing loop.");
    private Callback removeCallbackLocked(Callback head, Runnable runnable) {
        Callback predecessor = null;
        for (Callback callback = head; callback != null;) {
            final Callback next = callback.next;
            if (callback.runnable == runnable) {
                if (predecessor != null) {
                    predecessor.next = next;
                } else {
                    head = next;
                }

            if (mAnimationScheduled) {
                mAnimationScheduled = false;
                if (USE_VSYNC) {
                    removeMessages(MSG_DO_SCHEDULE_VSYNC);
                recycleCallbackLocked(callback);
            } else {
                    removeMessages(MSG_DO_ANIMATION);
                predecessor = callback;
            }
            callback = next;
        }
        return head;
    }

            if (mDrawScheduled) {
                mDrawScheduled = false;
                if (!USE_ANIMATION_TIMER_FOR_DRAW) {
                    removeMessages(MSG_DO_DRAW);
    private void runCallbacks(Callback head) {
        while (head != null) {
            head.runnable.run();
            head = head.next;
        }
    }

            // Post a message to dispose the display event receiver if we haven't needed
            // it again after a certain amount of time has elapsed.  Another reason to
            // defer disposal is that it is possible for use to attempt to dispose the
            // receiver while handling a vsync event that it dispatched, which might
            // cause a few problems...
            if (mFrameDisplayEventReceiverNeeded) {
                mFrameDisplayEventReceiverNeeded = false;
                if (mFrameDisplayEventReceiver != null) {
                    sendEmptyMessageDelayed(MSG_DO_DISPOSE_RECEIVER, DISPOSE_RECEIVER_DELAY);
    private void recycleCallbacksLocked(Callback head) {
        while (head != null) {
            final Callback next = head.next;
            recycleCallbackLocked(head);
            head = next;
        }
    }

    private Callback obtainCallbackLocked(Runnable runnable) {
        Callback callback = mCallbackPool;
        if (callback == null) {
            callback = new Callback();
        } else {
            mCallbackPool = callback.next;
            callback.next = null;
        }
        callback.runnable = runnable;
        return callback;
    }

    private boolean isRunningOnLooperThreadLocked() {
        return Looper.myLooper() == mLooper;
    private void recycleCallbackLocked(Callback callback) {
        callback.runnable = null;
        callback.next = mCallbackPool;
        mCallbackPool = callback;
    }

    /**
@@ -490,6 +647,30 @@ public final class Choreographer extends Handler {
        public void onDraw();
    }

    private final class FrameHandler extends Handler {
        public FrameHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_DO_ANIMATION:
                    doAnimation();
                    break;
                case MSG_DO_DRAW:
                    doDraw();
                    break;
                case MSG_DO_SCHEDULE_VSYNC:
                    doScheduleVsync();
                    break;
                case MSG_DO_DISPOSE_RECEIVER:
                    doDisposeReceiver();
                    break;
            }
        }
    }

    private final class FrameDisplayEventReceiver extends DisplayEventReceiver {
        public FrameDisplayEventReceiver(Looper looper) {
            super(looper);
@@ -500,4 +681,9 @@ public final class Choreographer extends Handler {
            doAnimation();
        }
    }

    private static final class Callback {
        public Callback next;
        public Runnable runnable;
    }
}