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

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

Merge "Make the Choreographer thread-safe."

parents 4651c0ea 87d0b03f
Loading
Loading
Loading
Loading
+88 −50
Original line number Diff line number Diff line
@@ -27,6 +27,10 @@ import android.util.Log;

/**
 * Coordinates animations and drawing for UI on a particular thread.
 *
 * This object is thread-safe.  Other threads can add and remove listeners
 * or schedule work to occur at a later time on the UI thread.
 *
 * @hide
 */
public final class Choreographer extends Handler {
@@ -44,7 +48,7 @@ public final class Choreographer extends Handler {
    private static final long DEFAULT_FRAME_DELAY = 10;

    // The number of milliseconds between animation frames.
    private static long sFrameDelay = DEFAULT_FRAME_DELAY;
    private static volatile long sFrameDelay = DEFAULT_FRAME_DELAY;

    // Thread local storage for the choreographer.
    private static final ThreadLocal<Choreographer> sThreadInstance =
@@ -75,6 +79,8 @@ public final class Choreographer extends Handler {
    private static final int MSG_DO_ANIMATION = 0;
    private static final int MSG_DO_DRAW = 1;

    private final Object mLock = new Object();

    private final Looper mLooper;

    private OnAnimateListener[] mOnAnimateListeners;
@@ -138,9 +144,14 @@ public final class Choreographer extends Handler {

    /**
     * Schedules animation (and drawing) to occur on the next frame synchronization boundary.
     * Must be called on the UI thread.
     */
    public void scheduleAnimation() {
        synchronized (mLock) {
            scheduleAnimationLocked();
        }
    }

    private void scheduleAnimationLocked() {
        if (!mAnimationScheduled) {
            mAnimationScheduled = true;
            if (USE_VSYNC) {
@@ -163,23 +174,26 @@ public final class Choreographer extends Handler {
    }

    /**
     * Return true if {@link #scheduleAnimation()} has been called but
     * Returns true if {@link #scheduleAnimation()} has been called but
     * {@link OnAnimateListener#onAnimate() OnAnimateListener.onAnimate()} has
     * not yet been called.
     */
    public boolean isAnimationScheduled() {
        synchronized (mLock) {
            return mAnimationScheduled;
        }
    }

    /**
     * Schedules drawing to occur on the next frame synchronization boundary.
     * Must be called on the UI thread.
     */
    public void scheduleDraw() {
        synchronized (mLock) {
            if (!mDrawScheduled) {
                mDrawScheduled = true;
                if (USE_ANIMATION_TIMER_FOR_DRAW) {
                scheduleAnimation();
                    scheduleAnimationLocked();
                } else {
                    if (DEBUG) {
                        Log.d(TAG, "Scheduling draw immediately.");
@@ -188,15 +202,18 @@ public final class Choreographer extends Handler {
                }
            }
        }
    }

    /**
     * Return true if {@link #scheduleDraw()} has been called but
     * Returns true if {@link #scheduleDraw()} has been called but
     * {@link OnDrawListener#onDraw() OnDrawListener.onDraw()} has
     * not yet been called.
     */
    public boolean isDrawScheduled() {
        synchronized (mLock) {
            return mDrawScheduled;
        }
    }

    @Override
    public void handleMessage(Message msg) {
@@ -211,17 +228,32 @@ public final class Choreographer extends Handler {
    }

    private void doAnimation() {
        if (mAnimationScheduled) {
        doAnimationInner();

        if (USE_ANIMATION_TIMER_FOR_DRAW) {
            doDraw();
        }
    }

    private void doAnimationInner() {
        final long start;
        final OnAnimateListener[] listeners;
        synchronized (mLock) {
            if (!mAnimationScheduled) {
                return; // no work to do
            }
            mAnimationScheduled = false;

            final long start = SystemClock.uptimeMillis();
            start = SystemClock.uptimeMillis();
            if (DEBUG) {
                Log.d(TAG, "Performing animation: " + Math.max(0, start - mLastAnimationTime)
                        + " ms have elapsed since previous animation.");
            }
            mLastAnimationTime = start;

            final OnAnimateListener[] listeners = mOnAnimateListeners;
            listeners = mOnAnimateListeners;
        }

        if (listeners != null) {
            for (int i = 0; i < listeners.length; i++) {
                listeners[i].onAnimate();
@@ -233,23 +265,25 @@ public final class Choreographer extends Handler {
        }
    }

        if (USE_ANIMATION_TIMER_FOR_DRAW) {
            doDraw();
        }
    }

    private void doDraw() {
        if (mDrawScheduled) {
        final long start;
        final OnDrawListener[] listeners;
        synchronized (mLock) {
            if (!mDrawScheduled) {
                return; // no work to do
            }
            mDrawScheduled = false;

            final long start = SystemClock.uptimeMillis();
            start = SystemClock.uptimeMillis();
            if (DEBUG) {
                Log.d(TAG, "Performing draw: " + Math.max(0, start - mLastDrawTime)
                        + " ms have elapsed since previous draw.");
            }
            mLastDrawTime = start;

            final OnDrawListener[] listeners = mOnDrawListeners;
            listeners = mOnDrawListeners;
        }

        if (listeners != null) {
            for (int i = 0; i < listeners.length; i++) {
                listeners[i].onDraw();
@@ -260,11 +294,9 @@ public final class Choreographer extends Handler {
            Log.d(TAG, "Draw took " + (SystemClock.uptimeMillis() - start) + " ms.");
        }
    }
    }

    /**
     * Adds an animation listener.
     * Must be called on the UI thread.
     *
     * @param listener The listener to add.
     */
@@ -277,13 +309,14 @@ public final class Choreographer extends Handler {
            Log.d(TAG, "Adding onAnimate listener: " + listener);
        }

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

    /**
     * Removes an animation listener.
     * Must be called on the UI thread.
     *
     * @param listener The listener to remove.
     */
@@ -296,14 +329,15 @@ public final class Choreographer extends Handler {
            Log.d(TAG, "Removing onAnimate listener: " + listener);
        }

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

    /**
     * Adds a draw listener.
     * Must be called on the UI thread.
     *
     * @param listener The listener to add.
     */
@@ -316,9 +350,11 @@ public final class Choreographer extends Handler {
            Log.d(TAG, "Adding onDraw listener: " + listener);
        }

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

    /**
     * Removes a draw listener.
@@ -335,12 +371,14 @@ public final class Choreographer extends Handler {
            Log.d(TAG, "Removing onDraw listener: " + listener);
        }

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

    private void stopTimingLoopIfNoListeners() {
    private void stopTimingLoopIfNoListenersLocked() {
        if (mOnDrawListeners == null && mOnAnimateListeners == null) {
            if (DEBUG) {
                Log.d(TAG, "Stopping timing loop.");