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

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

Merge "Use a Choreographer to schedule animation and drawing."

parents 731b939d 96e942da
Loading
Loading
Loading
Loading
+120 −110
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.AndroidRuntimeException;
import android.view.Choreographer;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AnimationUtils;
import android.view.animation.LinearInterpolator;
@@ -45,17 +46,10 @@ public class ValueAnimator extends Animator {
     * Internal constants
     */

    /*
     * The default amount of time in ms between animation frames
     */
    private static final long DEFAULT_FRAME_DELAY = 10;

    /**
     * Messages sent to timing handler: START is sent when an animation first begins, FRAME is sent
     * by the handler to itself to process the next animation frame
     * Messages sent to timing handler: START is sent when an animation first begins.
     */
    static final int ANIMATION_START = 0;
    static final int ANIMATION_FRAME = 1;

    /**
     * Values used with internal variable mPlayingState to indicate the current state of an
@@ -162,9 +156,6 @@ public class ValueAnimator extends Animator {
    // The amount of time in ms to delay starting the animation after start() is called
    private long mStartDelay = 0;

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

    // The number of times the animation will repeat. The default is 0, which means the animation
    // will play only once
    private int mRepeatCount = 0;
@@ -511,7 +502,8 @@ public class ValueAnimator extends Animator {
     * animations possible.
     *
     */
    private static class AnimationHandler extends Handler {
    private static class AnimationHandler extends Handler
            implements Choreographer.OnAnimateListener {
        // The per-thread list of all active animations
        private final ArrayList<ValueAnimator> mAnimations = new ArrayList<ValueAnimator>();

@@ -526,40 +518,41 @@ public class ValueAnimator extends Animator {
        private final ArrayList<ValueAnimator> mEndingAnims = new ArrayList<ValueAnimator>();
        private final ArrayList<ValueAnimator> mReadyAnims = new ArrayList<ValueAnimator>();

        private final Choreographer mChoreographer;
        private boolean mIsChoreographed;

        private AnimationHandler() {
            mChoreographer = Choreographer.getInstance();
        }

        /**
         * There are only two messages that we care about: ANIMATION_START and
         * ANIMATION_FRAME. The START message is sent when an animation's start()
         * method is called. It cannot start synchronously when start() is called
         * 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.
         * The FRAME message is the one that is sent over and over while there are any
         * active animations to process.
         */
        @Override
        public void handleMessage(Message msg) {
            boolean callAgain = true;
            ArrayList<ValueAnimator> animations = mAnimations;
            ArrayList<ValueAnimator> delayedAnims = mDelayedAnims;
            switch (msg.what) {
                // TODO: should we avoid sending frame message when starting if we
                // were already running?
                case ANIMATION_START:
                    ArrayList<ValueAnimator> pendingAnimations = mPendingAnimations;
                    if (animations.size() > 0 || delayedAnims.size() > 0) {
                        callAgain = false;
                    doAnimationStart();
                    break;
            }
                    // pendingAnims holds any animations that have requested to be started
                    // We're going to clear sPendingAnimations, but starting animation may
        }

        private void doAnimationStart() {
            // 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
                    // starting triggers another starting). So we loop until sPendingAnimations
            // starting triggers another starting). So we loop until mPendingAnimations
            // is empty.
                    while (pendingAnimations.size() > 0) {
            while (mPendingAnimations.size() > 0) {
                ArrayList<ValueAnimator> pendingCopy =
                                (ArrayList<ValueAnimator>) pendingAnimations.clone();
                        pendingAnimations.clear();
                        (ArrayList<ValueAnimator>) mPendingAnimations.clone();
                mPendingAnimations.clear();
                int count = pendingCopy.size();
                for (int i = 0; i < count; ++i) {
                    ValueAnimator anim = pendingCopy.get(i);
@@ -567,48 +560,48 @@ public class ValueAnimator extends Animator {
                    if (anim.mStartDelay == 0) {
                        anim.startAnimation(this);
                    } else {
                                delayedAnims.add(anim);
                        mDelayedAnims.add(anim);
                    }
                }
            }
                    // fall through to process first frame of new animations
                case ANIMATION_FRAME:
            doAnimationFrame();
        }

        private void doAnimationFrame() {
            // currentTime holds the common time for all animations processed
            // during this frame
            long currentTime = AnimationUtils.currentAnimationTimeMillis();
                    ArrayList<ValueAnimator> readyAnims = mReadyAnims;
                    ArrayList<ValueAnimator> endingAnims = mEndingAnims;

            // First, process animations currently sitting on the delayed queue, adding
            // them to the active animations if they are ready
                    int numDelayedAnims = delayedAnims.size();
            int numDelayedAnims = mDelayedAnims.size();
            for (int i = 0; i < numDelayedAnims; ++i) {
                        ValueAnimator anim = delayedAnims.get(i);
                ValueAnimator anim = mDelayedAnims.get(i);
                if (anim.delayedAnimationFrame(currentTime)) {
                            readyAnims.add(anim);
                    mReadyAnims.add(anim);
                }
            }
                    int numReadyAnims = readyAnims.size();
            int numReadyAnims = mReadyAnims.size();
            if (numReadyAnims > 0) {
                for (int i = 0; i < numReadyAnims; ++i) {
                            ValueAnimator anim = readyAnims.get(i);
                    ValueAnimator anim = mReadyAnims.get(i);
                    anim.startAnimation(this);
                    anim.mRunning = true;
                            delayedAnims.remove(anim);
                    mDelayedAnims.remove(anim);
                }
                        readyAnims.clear();
                mReadyAnims.clear();
            }

            // Now process all active animations. The return value from animationFrame()
            // tells the handler whether it should now be ended
                    int numAnims = animations.size();
            int numAnims = mAnimations.size();
            int i = 0;
            while (i < numAnims) {
                        ValueAnimator anim = animations.get(i);
                ValueAnimator anim = mAnimations.get(i);
                if (anim.animationFrame(currentTime)) {
                            endingAnims.add(anim);
                    mEndingAnims.add(anim);
                }
                        if (animations.size() == numAnims) {
                if (mAnimations.size() == numAnims) {
                    ++i;
                } else {
                    // An animation might be canceled or ended by client code
@@ -619,25 +612,36 @@ public class ValueAnimator extends Animator {
                    // but that entails garbage and processing overhead that would
                    // be nice to avoid.
                    --numAnims;
                            endingAnims.remove(anim);
                    mEndingAnims.remove(anim);
                }
            }
                    if (endingAnims.size() > 0) {
                        for (i = 0; i < endingAnims.size(); ++i) {
                            endingAnims.get(i).endAnimation(this);
            if (mEndingAnims.size() > 0) {
                for (i = 0; i < mEndingAnims.size(); ++i) {
                    mEndingAnims.get(i).endAnimation(this);
                }
                        endingAnims.clear();
                mEndingAnims.clear();
            }

                    // If there are still active or delayed animations, call the handler again
                    // after the frameDelay
                    if (callAgain && (!animations.isEmpty() || !delayedAnims.isEmpty())) {
                        sendEmptyMessageDelayed(ANIMATION_FRAME, Math.max(0, sFrameDelay -
                            (AnimationUtils.currentAnimationTimeMillis() - currentTime)));
            // If there are still active or delayed animations, schedule a future call to
            // onAnimate to process the next frame of the animations.
            if (!mAnimations.isEmpty() || !mDelayedAnims.isEmpty()) {
                if (!mIsChoreographed) {
                    mIsChoreographed = true;
                    mChoreographer.addOnAnimateListener(this);
                }
                mChoreographer.scheduleAnimation();
            } else {
                if (mIsChoreographed) {
                    mIsChoreographed = false;
                    mChoreographer.removeOnAnimateListener(this);
                }
                    break;
            }
        }

        @Override
        public void onAnimate() {
            doAnimationFrame();
        }
    }

    /**
@@ -667,10 +671,13 @@ public class ValueAnimator extends Animator {
     * function because the same delay will be applied to all animations, since they are all
     * run off of a single timing loop.
     *
     * The frame delay may be ignored when the animation system uses an external timing
     * source, such as the display refresh rate (vsync), to govern animations.
     *
     * @return the requested time between frames, in milliseconds
     */
    public static long getFrameDelay() {
        return sFrameDelay;
        return Choreographer.getFrameDelay();
    }

    /**
@@ -680,10 +687,13 @@ public class ValueAnimator extends Animator {
     * function because the same delay will be applied to all animations, since they are all
     * run off of a single timing loop.
     *
     * The frame delay may be ignored when the animation system uses an external timing
     * source, such as the display refresh rate (vsync), to govern animations.
     *
     * @param frameDelay the requested time between frames, in milliseconds
     */
    public static void setFrameDelay(long frameDelay) {
        sFrameDelay = frameDelay;
        Choreographer.setFrameDelay(frameDelay);
    }

    /**
+380 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.view;

import com.android.internal.util.ArrayUtils;

import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.util.Log;

/**
 * Coodinates animations and drawing for UI on a particular thread.
 * @hide
 */
public final class Choreographer extends Handler {
    private static final String TAG = "Choreographer";
    private static final boolean DEBUG = false;

    // The default amount of time in ms between animation frames.
    // When vsync is not enabled, we want to have some idea of how long we should
    // wait before posting the next animation message.  It is important that the
    // default value be less than the true inter-frame delay on all devices to avoid
    // situations where we might skip frames by waiting too long (we must compensate
    // for jitter and hardware variations).  Regardless of this value, the animation
    // and display loop is ultimately rate-limited by how fast new graphics buffers can
    // be dequeued.
    private static final long DEFAULT_FRAME_DELAY = 10;

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

    // Thread local storage for the choreographer.
    private static final ThreadLocal<Choreographer> sThreadInstance =
            new ThreadLocal<Choreographer>() {
        @Override
        protected Choreographer initialValue() {
            Looper looper = Looper.myLooper();
            if (looper == null) {
                throw new IllegalStateException("The current thread must have a looper!");
            }
            return new Choreographer(looper);
        }
    };

    // System property to enable/disable vsync for animations and drawing.
    // Enabled by default.
    private static final boolean USE_VSYNC = SystemProperties.getBoolean(
            "debug.choreographer.vsync", true);

    // System property to enable/disable the use of the vsync / animation timer
    // for drawing rather than drawing immediately.
    // Enabled by default.
    private static final boolean USE_ANIMATION_TIMER_FOR_DRAW = SystemProperties.getBoolean(
            "debug.choreographer.animdraw", true);

    private static final int MSG_DO_ANIMATION = 0;
    private static final int MSG_DO_DRAW = 1;

    private final Looper mLooper;

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

    private boolean mAnimationScheduled;
    private boolean mDrawScheduled;
    private FrameDisplayEventReceiver mFrameDisplayEventReceiver;
    private long mLastAnimationTime;
    private long mLastDrawTime;

    private Choreographer(Looper looper) {
        super(looper);
        mLooper = looper;
        mLastAnimationTime = Long.MIN_VALUE;
        mLastDrawTime = Long.MIN_VALUE;
    }

    /**
     * Gets the choreographer for this thread.
     * Must be called on the UI thread.
     *
     * @return The choreographer for this thread.
     * @throws IllegalStateException if the thread does not have a looper.
     */
    public static Choreographer getInstance() {
        return sThreadInstance.get();
    }

    /**
     * The amount of time, in milliseconds, between each frame of the animation. This is a
     * requested time that the animation will attempt to honor, but the actual delay between
     * frames may be different, depending on system load and capabilities. This is a static
     * function because the same delay will be applied to all animations, since they are all
     * run off of a single timing loop.
     *
     * The frame delay may be ignored when the animation system uses an external timing
     * source, such as the display refresh rate (vsync), to govern animations.
     *
     * @return the requested time between frames, in milliseconds
     */
    public static long getFrameDelay() {
        return sFrameDelay;
    }

    /**
     * The amount of time, in milliseconds, between each frame of the animation. This is a
     * requested time that the animation will attempt to honor, but the actual delay between
     * frames may be different, depending on system load and capabilities. This is a static
     * function because the same delay will be applied to all animations, since they are all
     * run off of a single timing loop.
     *
     * The frame delay may be ignored when the animation system uses an external timing
     * source, such as the display refresh rate (vsync), to govern animations.
     *
     * @param frameDelay the requested time between frames, in milliseconds
     */
    public static void setFrameDelay(long frameDelay) {
        sFrameDelay = frameDelay;
    }

    /**
     * Schedules animation (and drawing) to occur on the next frame synchronization boundary.
     * Must be called on the UI thread.
     */
    public void scheduleAnimation() {
        if (!mAnimationScheduled) {
            mAnimationScheduled = true;
            if (USE_VSYNC) {
                if (DEBUG) {
                    Log.d(TAG, "Scheduling vsync for animation.");
                }
                if (mFrameDisplayEventReceiver == null) {
                    mFrameDisplayEventReceiver = new FrameDisplayEventReceiver(mLooper);
                }
                mFrameDisplayEventReceiver.scheduleVsync();
            } 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.");
                }
                sendEmptyMessageAtTime(MSG_DO_ANIMATION, nextAnimationTime);
            }
        }
    }

    /**
     * Schedules drawing to occur on the next frame synchronization boundary.
     * Must be called on the UI thread.
     */
    public void scheduleDraw() {
        if (!mDrawScheduled) {
            mDrawScheduled = true;
            if (USE_ANIMATION_TIMER_FOR_DRAW) {
                scheduleAnimation();
            } else {
                if (DEBUG) {
                    Log.d(TAG, "Scheduling draw immediately.");
                }
                sendEmptyMessage(MSG_DO_DRAW);
            }
        }
    }

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

    private void doAnimation() {
        if (mAnimationScheduled) {
            mAnimationScheduled = false;

            final long 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;
            if (listeners != null) {
                for (int i = 0; i < listeners.length; i++) {
                    listeners[i].onAnimate();
                }
            }

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

        if (USE_ANIMATION_TIMER_FOR_DRAW) {
            doDraw();
        }
    }

    private void doDraw() {
        if (mDrawScheduled) {
            mDrawScheduled = false;

            final long 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;
            if (listeners != null) {
                for (int i = 0; i < listeners.length; i++) {
                    listeners[i].onDraw();
                }
            }

            if (DEBUG) {
                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.
     */
    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);
        }

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

    /**
     * Removes an animation listener.
     * Must be called on the UI thread.
     *
     * @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);
        }

        mOnAnimateListeners = ArrayUtils.removeElement(OnAnimateListener.class,
                mOnAnimateListeners, listener);
        stopTimingLoopIfNoListeners();
    }

    /**
     * Adds a draw listener.
     * Must be called on the UI thread.
     *
     * @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);
        }

        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);
        }

        mOnDrawListeners = ArrayUtils.removeElement(OnDrawListener.class,
                mOnDrawListeners, listener);
        stopTimingLoopIfNoListeners();
    }

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

            if (mAnimationScheduled) {
                mAnimationScheduled = false;
                if (!USE_VSYNC) {
                    removeMessages(MSG_DO_ANIMATION);
                }
            }

            if (mDrawScheduled) {
                mDrawScheduled = false;
                if (!USE_ANIMATION_TIMER_FOR_DRAW) {
                    removeMessages(MSG_DO_DRAW);
                }
            }

            if (mFrameDisplayEventReceiver != null) {
                mFrameDisplayEventReceiver.dispose();
                mFrameDisplayEventReceiver = null;
            }
        }
    }

    /**
     * Listens for animation frame timing events.
     */
    public static interface OnAnimateListener {
        /**
         * Called to animate properties before drawing the frame.
         */
        public void onAnimate();
    }

    /**
     * Listens for draw frame timing events.
     */
    public static interface OnDrawListener {
        /**
         * Called to draw the frame.
         */
        public void onDraw();
    }

    private final class FrameDisplayEventReceiver extends DisplayEventReceiver {
        public FrameDisplayEventReceiver(Looper looper) {
            super(looper);
        }

        @Override
        public void onVsync(long timestampNanos, int frame) {
            doAnimation();
        }
    }
}
+197 −200

File changed.

Preview size limit exceeded, changes collapsed.

+52 −1
Original line number Diff line number Diff line
@@ -17,7 +17,6 @@
package com.android.internal.util;

import java.lang.reflect.Array;
import java.util.Collection;

// XXX these should be changed to reflect the actual memory allocator we use.
// it looks like right now objects want to be powers of 2 minus 8
@@ -142,4 +141,56 @@ public class ArrayUtils
        }
        return false;
    }

    /**
     * Appends an element to a copy of the array and returns the copy.
     * @param array The original array, or null to represent an empty array.
     * @param element The element to add.
     * @return A new array that contains all of the elements of the original array
     * with the specified element added at the end.
     */
    @SuppressWarnings("unchecked")
    public static <T> T[] appendElement(Class<T> kind, T[] array, T element) {
        final T[] result;
        final int end;
        if (array != null) {
            end = array.length;
            result = (T[])Array.newInstance(kind, end + 1);
            System.arraycopy(array, 0, result, 0, end);
        } else {
            end = 0;
            result = (T[])Array.newInstance(kind, 1);
        }
        result[end] = element;
        return result;
    }

    /**
     * Removes an element from a copy of the array and returns the copy.
     * If the element is not present, then the original array is returned unmodified.
     * @param array The original array, or null to represent an empty array.
     * @param element The element to remove.
     * @return A new array that contains all of the elements of the original array
     * except the first copy of the specified element removed.  If the specified element
     * was not present, then returns the original array.  Returns null if the result
     * would be an empty array.
     */
    @SuppressWarnings("unchecked")
    public static <T> T[] removeElement(Class<T> kind, T[] array, T element) {
        if (array != null) {
            final int length = array.length;
            for (int i = 0; i < length; i++) {
                if (array[i] == element) {
                    if (length == 1) {
                        return null;
                    }
                    T[] result = (T[])Array.newInstance(kind, length - 1);
                    System.arraycopy(array, 0, result, 0, i);
                    System.arraycopy(array, i + 1, result, i, length - i - 1);
                    return result;
                }
            }
        }
        return array;
    }
}
+5 −2
Original line number Diff line number Diff line
@@ -86,8 +86,11 @@ public abstract class AnimationThread extends Thread {
        try {
            Handler_Delegate.setCallback(new IHandlerCallback() {
                public void sendMessageAtTime(Handler handler, Message msg, long uptimeMillis) {
                    if (msg.what == ValueAnimator.ANIMATION_START ||
                            msg.what == ValueAnimator.ANIMATION_FRAME) {
                    if (msg.what == ValueAnimator.ANIMATION_START /*||
                            FIXME: The ANIMATION_FRAME message no longer exists.  Instead,
                            the animation timing loop is based on a Choreographer object
                            that schedules animation and drawing frames.
                            msg.what == ValueAnimator.ANIMATION_FRAME*/) {
                        mQueue.add(new MessageBundle(handler, msg, uptimeMillis));
                    } else {
                        // just ignore.