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

Commit d99ca786 authored by Jeff Brown's avatar Jeff Brown Committed by Android Git Automerger
Browse files

am e19d5f02: Merge "Use choreographer frame time to schedule animations." into jb-dev

* commit 'e19d5f02':
  Use choreographer frame time to schedule animations.
parents 57caa12a e19d5f02
Loading
Loading
Loading
Loading
+0 −10
Original line number Diff line number Diff line
@@ -14,16 +14,6 @@ public class TimeAnimator extends ValueAnimator {

    @Override
    boolean animationFrame(long currentTime) {
        if (mPlayingState == STOPPED) {
            mPlayingState = RUNNING;
            if (mSeekTime < 0) {
                mStartTime = currentTime;
            } else {
                mStartTime = currentTime - mSeekTime;
                // Now that we're playing, reset the seek time
                mSeekTime = -1;
            }
        }
        if (mListener != null) {
            long totalTime = currentTime - mStartTime;
            long deltaTime = (mPreviousTime < 0) ? 0 : (currentTime - mPreviousTime);
+45 −51
Original line number Diff line number Diff line
@@ -55,11 +55,6 @@ public class ValueAnimator extends Animator {
     */
    private static float sDurationScale = 1.0f;

    /**
     * Messages sent to timing handler: START is sent when an animation first begins.
     */
    static final int ANIMATION_START = 0;

    /**
     * Values used with internal variable mPlayingState to indicate the current state of an
     * animation.
@@ -504,7 +499,7 @@ public class ValueAnimator extends Animator {
            mPlayingState = SEEKED;
        }
        mStartTime = currentTime - playTime;
        animationFrame(currentTime);
        doAnimationFrame(currentTime);
    }

    /**
@@ -528,8 +523,9 @@ public class ValueAnimator extends Animator {
     * the same times for calculating their values, which makes synchronizing
     * animations possible.
     *
     * The handler uses the Choreographer for executing periodic callbacks.
     */
    private static class AnimationHandler extends Handler implements Runnable {
    private static class AnimationHandler implements Runnable {
        // The per-thread list of all active animations
        private final ArrayList<ValueAnimator> mAnimations = new ArrayList<ValueAnimator>();

@@ -552,34 +548,13 @@ public class ValueAnimator extends Animator {
        }

        /**
         * The START message is sent when an animation's start()  method is called.
         * It cannot start synchronously when start() is called
         * because the call may be on the wrong thread, and it would also not be
         * synchronized with other animations because it would not start on a common
         * timing pulse. So each animation sends a START message to the handler, which
         * causes the handler to place the animation on the active animations queue and
         * start processing frames for that animation.
         * Start animating on the next frame.
         */
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case ANIMATION_START:
                    // If there are already active animations, or if another ANIMATION_START
                    // message was processed during this frame, then the pending list may already
                    // have been cleared. If that's the case, we've already processed the
                    // active animations for this frame - don't do it again.
                    if (mPendingAnimations.size() > 0) {
                        doAnimationFrame();
                    }
                    break;
            }
        public void start() {
            scheduleAnimation();
        }

        private void doAnimationFrame() {
            // currentTime holds the common time for all animations processed
            // during this frame
            long currentTime = AnimationUtils.currentAnimationTimeMillis();

        private void doAnimationFrame(long frameTime) {
            // mPendingAnimations holds any animations that have requested to be started
            // We're going to clear mPendingAnimations, but starting animation may
            // cause more to be added to the pending list (for example, if one animation
@@ -605,7 +580,7 @@ public class ValueAnimator extends Animator {
            int numDelayedAnims = mDelayedAnims.size();
            for (int i = 0; i < numDelayedAnims; ++i) {
                ValueAnimator anim = mDelayedAnims.get(i);
                if (anim.delayedAnimationFrame(currentTime)) {
                if (anim.delayedAnimationFrame(frameTime)) {
                    mReadyAnims.add(anim);
                }
            }
@@ -626,7 +601,7 @@ public class ValueAnimator extends Animator {
            int i = 0;
            while (i < numAnims) {
                ValueAnimator anim = mAnimations.get(i);
                if (anim.animationFrame(currentTime)) {
                if (anim.doAnimationFrame(frameTime)) {
                    mEndingAnims.add(anim);
                }
                if (mAnimations.size() == numAnims) {
@@ -652,10 +627,8 @@ public class ValueAnimator extends Animator {

            // If there are still active or delayed animations, schedule a future call to
            // onAnimate to process the next frame of the animations.
            if (!mAnimationScheduled
                    && (!mAnimations.isEmpty() || !mDelayedAnims.isEmpty())) {
                mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null);
                mAnimationScheduled = true;
            if (!mAnimations.isEmpty() || !mDelayedAnims.isEmpty()) {
                scheduleAnimation();
            }
        }

@@ -663,7 +636,14 @@ public class ValueAnimator extends Animator {
        @Override
        public void run() {
            mAnimationScheduled = false;
            doAnimationFrame();
            doAnimationFrame(mChoreographer.getFrameTime());
        }

        private void scheduleAnimation() {
            if (!mAnimationScheduled) {
                mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null);
                mAnimationScheduled = true;
            }
        }
    }

@@ -935,7 +915,7 @@ public class ValueAnimator extends Animator {
            mRunning = true;
            notifyStartListeners();
        }
        animationHandler.sendEmptyMessage(ANIMATION_START);
        animationHandler.start();
    }

    @Override
@@ -1098,17 +1078,6 @@ public class ValueAnimator extends Animator {
     */
    boolean animationFrame(long currentTime) {
        boolean done = false;

        if (mPlayingState == STOPPED) {
            mPlayingState = RUNNING;
            if (mSeekTime < 0) {
                mStartTime = currentTime;
            } else {
                mStartTime = currentTime - mSeekTime;
                // Now that we're playing, reset the seek time
                mSeekTime = -1;
            }
        }
        switch (mPlayingState) {
        case RUNNING:
        case SEEKED:
@@ -1143,6 +1112,31 @@ public class ValueAnimator extends Animator {
        return done;
    }

    /**
     * Processes a frame of the animation, adjusting the start time if needed.
     *
     * @param frameTime The frame time.
     * @return true if the animation has ended.
     */
    final boolean doAnimationFrame(long frameTime) {
        if (mPlayingState == STOPPED) {
            mPlayingState = RUNNING;
            if (mSeekTime < 0) {
                mStartTime = frameTime;
            } else {
                mStartTime = frameTime - mSeekTime;
                // Now that we're playing, reset the seek time
                mSeekTime = -1;
            }
        }
        // The frame time might be before the start time during the first frame of
        // an animation.  The "current time" must always be on or after the start
        // time to avoid animating frames at negative time intervals.  In practice, this
        // is very rare and only happens when seeking backwards.
        final long currentTime = Math.max(frameTime, mStartTime);
        return animationFrame(currentTime);
    }

    /**
     * Returns the current animation fraction, which is the elapsed/interpolated fraction used in
     * the most recent frame update on the animation.
+66 −14
Original line number Diff line number Diff line
@@ -69,6 +69,12 @@ public final class Choreographer {
    private static final boolean USE_VSYNC = SystemProperties.getBoolean(
            "debug.choreographer.vsync", true);

    // Enable/disable using the frame time instead of returning now.
    private static final boolean USE_FRAME_TIME = SystemProperties.getBoolean(
            "debug.choreographer.frametime", true);

    private static final long NANOS_PER_MS = 1000000;

    private static final int MSG_DO_FRAME = 0;
    private static final int MSG_DO_SCHEDULE_VSYNC = 1;
    private static final int MSG_DO_SCHEDULE_CALLBACK = 2;
@@ -84,7 +90,8 @@ public final class Choreographer {
    private final CallbackQueue[] mCallbackQueues;

    private boolean mFrameScheduled;
    private long mLastFrameTime;
    private boolean mCallbacksRunning;
    private long mLastFrameTimeNanos;

    /**
     * Callback type: Input callback.  Runs first.
@@ -108,7 +115,7 @@ public final class Choreographer {
        mLooper = looper;
        mHandler = new FrameHandler(looper);
        mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper) : null;
        mLastFrameTime = Long.MIN_VALUE;
        mLastFrameTimeNanos = Long.MIN_VALUE;

        mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
        for (int i = 0; i <= CALLBACK_LAST; i++) {
@@ -270,6 +277,40 @@ public final class Choreographer {
        }
    }

    /**
     * Gets the time when the current frame started.  The frame time should be used
     * instead of {@link SystemClock#uptimeMillis()} to synchronize animations.
     * This helps to reduce inter-frame jitter because the frame time is fixed at the
     * time the frame was scheduled to start, regardless of when the animations or
     * drawing code actually ran.
     *
     * This method should only be called from within a callback.
     *
     * @return The frame start time, in the {@link SystemClock#uptimeMillis()} time base.
     *
     * @throws IllegalStateException if no frame is in progress.
     */
    public long getFrameTime() {
        return getFrameTimeNanos() / NANOS_PER_MS;
    }

    /**
     * Same as {@link #getFrameTime()} but with nanosecond precision.
     *
     * @return The frame start time, in the {@link System#nanoTime()} time base.
     *
     * @throws IllegalStateException if no frame is in progress.
     */
    public long getFrameTimeNanos() {
        synchronized (mLock) {
            if (!mCallbacksRunning) {
                throw new IllegalStateException("This method must only be called as "
                        + "part of a callback while a frame is in progress.");
            }
            return USE_FRAME_TIME ? mLastFrameTimeNanos : System.nanoTime();
        }
    }

    private void scheduleFrameLocked(long now) {
        if (!mFrameScheduled) {
            mFrameScheduled = true;
@@ -289,7 +330,8 @@ public final class Choreographer {
                    mHandler.sendMessageAtFrontOfQueue(msg);
                }
            } else {
                final long nextFrameTime = Math.max(mLastFrameTime + sFrameDelay, now);
                final long nextFrameTime = Math.max(
                        mLastFrameTimeNanos / NANOS_PER_MS + sFrameDelay, now);
                if (DEBUG) {
                    Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
                }
@@ -300,13 +342,18 @@ public final class Choreographer {
        }
    }

    void doFrame(int frame) {
    void doFrame(long timestampNanos, int frame) {
        synchronized (mLock) {
            if (!mFrameScheduled) {
                return; // no work to do
            }
            mFrameScheduled = false;
            mLastFrameTime = SystemClock.uptimeMillis();
            mLastFrameTimeNanos = timestampNanos;
        }

        final long startNanos;
        if (DEBUG) {
            startNanos = System.nanoTime();
        }

        doCallbacks(Choreographer.CALLBACK_INPUT);
@@ -314,20 +361,24 @@ public final class Choreographer {
        doCallbacks(Choreographer.CALLBACK_TRAVERSAL);

        if (DEBUG) {
            final long endNanos = System.nanoTime();
            Log.d(TAG, "Frame " + frame + ": Finished, took "
                    + (SystemClock.uptimeMillis() - mLastFrameTime) + " ms.");
                    + (endNanos - startNanos) * 0.000001f + " ms, latency "
                    + (startNanos - timestampNanos) * 0.000001f + " ms.");
        }
    }

    void doCallbacks(int callbackType) {
        final long start;
        Callback callbacks;
        synchronized (mLock) {
            start = SystemClock.uptimeMillis();
            callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(start);
            final long now = SystemClock.uptimeMillis();
            callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(now);
            if (callbacks == null) {
                return;
            }

        if (callbacks != null) {
            mCallbacksRunning = true;
        }
        try {
            for (Callback c = callbacks; c != null; c = c.next) {
                if (DEBUG) {
                    Log.d(TAG, "RunCallback: type=" + callbackType
@@ -336,8 +387,9 @@ public final class Choreographer {
                }
                c.action.run();
            }

        } finally {
            synchronized (mLock) {
                mCallbacksRunning = false;
                do {
                    final Callback next = callbacks.next;
                    recycleCallbackLocked(callbacks);
@@ -404,7 +456,7 @@ public final class Choreographer {
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_DO_FRAME:
                    doFrame(0);
                    doFrame(System.nanoTime(), 0);
                    break;
                case MSG_DO_SCHEDULE_VSYNC:
                    doScheduleVsync();
@@ -423,7 +475,7 @@ public final class Choreographer {

        @Override
        public void onVsync(long timestampNanos, int frame) {
            doFrame(frame);
            doFrame(timestampNanos, frame);
        }
    }