summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Jeff Brown <jeffbrown@google.com> 2011-11-30 19:55:01 -0800
committer Jeff Brown <jeffbrown@google.com> 2011-12-05 16:39:59 -0800
commit96e942dabeeaaa9ab6df3a870668c6fe53d930da (patch)
treec2bee431f14b90422586d0dc1e7d77256474fadb
parent0a0a1248cfc03940174cbd9af677bafd7280a3bc (diff)
Use a Choreographer to schedule animation and drawing.
Both animations and drawing need to march to the beat of the same drum, but the animation system doesn't know abgout the view system and vice-versa so neither one can drive the other. We introduce the Choreographer as a drummer to keep everyone in time and ensure a magnificent performance. This patch enabled VSync based animations and drawing by default. Two system properties are provided for testing purposes to control the behavior. "debug.choreographer.vsync": Enables vsync based animation timing. Defaults to true. When false, animations are timed by posting delayed messages to a message queue in the same way they used to be before this patch. "debug.choreographer.animdraw": Enables the use of the animation timer to drive drawing such that drawing is synchronized with animations (in other words, with vsync or the timing loop). Defaults to true. When false, layout traversals and drawing are posted to the message queue for execution without any delay or synchronization in the same way they used to be before this patch. Stubbed out part of the layoutlib animation code because it depends on the old timing loop (opened bug 5712395) Change-Id: I186d9518648e89bc3e809e393e9a9148bbbecc4d
-rwxr-xr-xcore/java/android/animation/ValueAnimator.java230
-rw-r--r--core/java/android/view/Choreographer.java380
-rw-r--r--core/java/android/view/ViewRootImpl.java397
-rw-r--r--core/java/com/android/internal/util/ArrayUtils.java53
-rw-r--r--tools/layoutlib/bridge/src/android/animation/AnimationThread.java7
5 files changed, 754 insertions, 313 deletions
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index 4f63165e7250..9bf1634c5a55 100755
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -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,118 +518,130 @@ 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;
- }
- // pendingAnims holds any animations that have requested to be started
- // We're going to clear sPendingAnimations, 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
- // is empty.
- while (pendingAnimations.size() > 0) {
- ArrayList<ValueAnimator> pendingCopy =
- (ArrayList<ValueAnimator>) pendingAnimations.clone();
- pendingAnimations.clear();
- int count = pendingCopy.size();
- for (int i = 0; i < count; ++i) {
- ValueAnimator anim = pendingCopy.get(i);
- // If the animation has a startDelay, place it on the delayed list
- if (anim.mStartDelay == 0) {
- anim.startAnimation(this);
- } else {
- delayedAnims.add(anim);
- }
- }
- }
- // fall through to process first frame of new animations
- case ANIMATION_FRAME:
- // 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();
- for (int i = 0; i < numDelayedAnims; ++i) {
- ValueAnimator anim = delayedAnims.get(i);
- if (anim.delayedAnimationFrame(currentTime)) {
- readyAnims.add(anim);
- }
- }
- int numReadyAnims = readyAnims.size();
- if (numReadyAnims > 0) {
- for (int i = 0; i < numReadyAnims; ++i) {
- ValueAnimator anim = readyAnims.get(i);
- anim.startAnimation(this);
- anim.mRunning = true;
- delayedAnims.remove(anim);
- }
- readyAnims.clear();
- }
+ doAnimationStart();
+ break;
+ }
+ }
- // Now process all active animations. The return value from animationFrame()
- // tells the handler whether it should now be ended
- int numAnims = animations.size();
- int i = 0;
- while (i < numAnims) {
- ValueAnimator anim = animations.get(i);
- if (anim.animationFrame(currentTime)) {
- endingAnims.add(anim);
- }
- if (animations.size() == numAnims) {
- ++i;
- } else {
- // An animation might be canceled or ended by client code
- // during the animation frame. Check to see if this happened by
- // seeing whether the current index is the same as it was before
- // calling animationFrame(). Another approach would be to copy
- // animations to a temporary list and process that list instead,
- // but that entails garbage and processing overhead that would
- // be nice to avoid.
- --numAnims;
- endingAnims.remove(anim);
- }
- }
- if (endingAnims.size() > 0) {
- for (i = 0; i < endingAnims.size(); ++i) {
- endingAnims.get(i).endAnimation(this);
- }
- endingAnims.clear();
+ 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 mPendingAnimations
+ // is empty.
+ while (mPendingAnimations.size() > 0) {
+ ArrayList<ValueAnimator> pendingCopy =
+ (ArrayList<ValueAnimator>) mPendingAnimations.clone();
+ mPendingAnimations.clear();
+ int count = pendingCopy.size();
+ for (int i = 0; i < count; ++i) {
+ ValueAnimator anim = pendingCopy.get(i);
+ // If the animation has a startDelay, place it on the delayed list
+ if (anim.mStartDelay == 0) {
+ anim.startAnimation(this);
+ } else {
+ mDelayedAnims.add(anim);
}
+ }
+ }
+ doAnimationFrame();
+ }
- // 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)));
- }
- break;
+ private void doAnimationFrame() {
+ // currentTime holds the common time for all animations processed
+ // during this frame
+ long currentTime = AnimationUtils.currentAnimationTimeMillis();
+
+ // First, process animations currently sitting on the delayed queue, adding
+ // them to the active animations if they are ready
+ int numDelayedAnims = mDelayedAnims.size();
+ for (int i = 0; i < numDelayedAnims; ++i) {
+ ValueAnimator anim = mDelayedAnims.get(i);
+ if (anim.delayedAnimationFrame(currentTime)) {
+ mReadyAnims.add(anim);
+ }
+ }
+ int numReadyAnims = mReadyAnims.size();
+ if (numReadyAnims > 0) {
+ for (int i = 0; i < numReadyAnims; ++i) {
+ ValueAnimator anim = mReadyAnims.get(i);
+ anim.startAnimation(this);
+ anim.mRunning = true;
+ mDelayedAnims.remove(anim);
+ }
+ mReadyAnims.clear();
+ }
+
+ // Now process all active animations. The return value from animationFrame()
+ // tells the handler whether it should now be ended
+ int numAnims = mAnimations.size();
+ int i = 0;
+ while (i < numAnims) {
+ ValueAnimator anim = mAnimations.get(i);
+ if (anim.animationFrame(currentTime)) {
+ mEndingAnims.add(anim);
+ }
+ if (mAnimations.size() == numAnims) {
+ ++i;
+ } else {
+ // An animation might be canceled or ended by client code
+ // during the animation frame. Check to see if this happened by
+ // seeing whether the current index is the same as it was before
+ // calling animationFrame(). Another approach would be to copy
+ // animations to a temporary list and process that list instead,
+ // but that entails garbage and processing overhead that would
+ // be nice to avoid.
+ --numAnims;
+ mEndingAnims.remove(anim);
+ }
+ }
+ if (mEndingAnims.size() > 0) {
+ for (i = 0; i < mEndingAnims.size(); ++i) {
+ mEndingAnims.get(i).endAnimation(this);
+ }
+ mEndingAnims.clear();
+ }
+
+ // 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);
+ }
}
}
+
+ @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);
}
/**
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
new file mode 100644
index 000000000000..0fdd1058b7fb
--- /dev/null
+++ b/core/java/android/view/Choreographer.java
@@ -0,0 +1,380 @@
+/*
+ * 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();
+ }
+ }
+}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 95c473cde284..2dd7ddf3a81a 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -96,7 +96,8 @@ import java.util.List;
*/
@SuppressWarnings({"EmptyCatchBlock", "PointlessBooleanExpression"})
public final class ViewRootImpl extends Handler implements ViewParent,
- View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
+ View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks,
+ Choreographer.OnDrawListener {
private static final String TAG = "ViewRootImpl";
private static final boolean DBG = false;
private static final boolean LOCAL_LOGV = false;
@@ -110,7 +111,6 @@ public final class ViewRootImpl extends Handler implements ViewParent,
private static final boolean DEBUG_IMF = false || LOCAL_LOGV;
private static final boolean DEBUG_CONFIGURATION = false || LOCAL_LOGV;
private static final boolean DEBUG_FPS = false;
- private static final boolean WATCH_POINTER = false;
/**
* Set this system property to true to force the view hierarchy to render
@@ -201,13 +201,14 @@ public final class ViewRootImpl extends Handler implements ViewParent,
InputQueue.Callback mInputQueueCallback;
InputQueue mInputQueue;
FallbackEventHandler mFallbackEventHandler;
+ Choreographer mChoreographer;
final Rect mTempRect; // used in the transaction to not thrash the heap.
final Rect mVisRect; // used to retrieve visible rect of focused view.
boolean mTraversalScheduled;
long mLastTraversalFinishedTimeNanos;
- long mLastDrawDurationNanos;
+ long mLastDrawFinishedTimeNanos;
boolean mWillDrawSoon;
boolean mLayoutRequested;
boolean mFirst;
@@ -225,7 +226,7 @@ public final class ViewRootImpl extends Handler implements ViewParent,
// Input event queue.
QueuedInputEvent mFirstPendingInputEvent;
QueuedInputEvent mCurrentInputEvent;
- boolean mProcessInputEventsPending;
+ boolean mProcessInputEventsScheduled;
boolean mWindowAttributesChanged = false;
int mWindowAttributesChangesFlag = 0;
@@ -374,6 +375,7 @@ public final class ViewRootImpl extends Handler implements ViewParent,
mFallbackEventHandler = PolicyManager.makeNewFallbackEventHandler(context);
mProfileRendering = Boolean.parseBoolean(
SystemProperties.get(PROPERTY_PROFILE_RENDERING, "false"));
+ mChoreographer = Choreographer.getInstance();
}
public static void addFirstDrawHandler(Runnable callback) {
@@ -425,6 +427,8 @@ public final class ViewRootImpl extends Handler implements ViewParent,
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
+ mChoreographer.addOnDrawListener(this);
+
mView = view;
mFallbackEventHandler.setView(view);
mWindowAttributes.copyFrom(attrs);
@@ -794,23 +798,19 @@ public final class ViewRootImpl extends Handler implements ViewParent,
public void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
-
- //noinspection ConstantConditions
- if (ViewDebug.DEBUG_LATENCY && mLastTraversalFinishedTimeNanos != 0) {
- final long now = System.nanoTime();
- Log.d(TAG, "Latency: Scheduled traversal, it has been "
- + ((now - mLastTraversalFinishedTimeNanos) * 0.000001f)
- + "ms since the last traversal finished.");
- }
-
- sendEmptyMessage(DO_TRAVERSAL);
+ mChoreographer.scheduleDraw();
}
}
public void unscheduleTraversals() {
+ mTraversalScheduled = false;
+ }
+
+ @Override
+ public void onDraw() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
- removeMessages(DO_TRAVERSAL);
+ doTraversal();
}
}
@@ -847,12 +847,45 @@ public final class ViewRootImpl extends Handler implements ViewParent,
}
}
+ private void doTraversal() {
+ doProcessInputEvents();
+
+ if (mProfile) {
+ Debug.startMethodTracing("ViewAncestor");
+ }
+
+ final long traversalStartTime;
+ if (ViewDebug.DEBUG_LATENCY) {
+ traversalStartTime = System.nanoTime();
+ if (mLastTraversalFinishedTimeNanos != 0) {
+ Log.d(ViewDebug.DEBUG_LATENCY_TAG, "Starting performTraversals(); it has been "
+ + ((traversalStartTime - mLastTraversalFinishedTimeNanos) * 0.000001f)
+ + "ms since the last traversals finished.");
+ } else {
+ Log.d(ViewDebug.DEBUG_LATENCY_TAG, "Starting performTraversals().");
+ }
+ }
+
+ performTraversals();
+
+ if (ViewDebug.DEBUG_LATENCY) {
+ long now = System.nanoTime();
+ Log.d(ViewDebug.DEBUG_LATENCY_TAG, "performTraversals() took "
+ + ((now - traversalStartTime) * 0.000001f)
+ + "ms.");
+ mLastTraversalFinishedTimeNanos = now;
+ }
+
+ if (mProfile) {
+ Debug.stopMethodTracing();
+ mProfile = false;
+ }
+ }
+
private void performTraversals() {
// cache mView since it is used so much below...
final View host = mView;
- processInputEvents();
-
if (DBG) {
System.out.println("======================================");
System.out.println("performTraversals");
@@ -862,10 +895,8 @@ public final class ViewRootImpl extends Handler implements ViewParent,
if (host == null || !mAdded)
return;
- mTraversalScheduled = false;
mWillDrawSoon = true;
boolean windowSizeMayChange = false;
- boolean fullRedrawNeeded = mFullRedrawNeeded;
boolean newSurface = false;
boolean surfaceChanged = false;
WindowManager.LayoutParams lp = mWindowAttributes;
@@ -890,7 +921,7 @@ public final class ViewRootImpl extends Handler implements ViewParent,
CompatibilityInfo compatibilityInfo = mCompatibilityInfo.get();
if (compatibilityInfo.supportsScreen() == mLastInCompatMode) {
params = lp;
- fullRedrawNeeded = true;
+ mFullRedrawNeeded = true;
mLayoutRequested = true;
if (mLastInCompatMode) {
params.flags &= ~WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW;
@@ -905,7 +936,7 @@ public final class ViewRootImpl extends Handler implements ViewParent,
Rect frame = mWinFrame;
if (mFirst) {
- fullRedrawNeeded = true;
+ mFullRedrawNeeded = true;
mLayoutRequested = true;
if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL) {
@@ -949,7 +980,7 @@ public final class ViewRootImpl extends Handler implements ViewParent,
if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
if (DEBUG_ORIENTATION) Log.v(TAG,
"View " + host + " resized to: " + frame);
- fullRedrawNeeded = true;
+ mFullRedrawNeeded = true;
mLayoutRequested = true;
windowSizeMayChange = true;
}
@@ -1287,7 +1318,7 @@ public final class ViewRootImpl extends Handler implements ViewParent,
// before actually drawing them, so it can display then
// all at once.
newSurface = true;
- fullRedrawNeeded = true;
+ mFullRedrawNeeded = true;
mPreviousTransparentRegion.setEmpty();
if (mAttachInfo.mHardwareRenderer != null) {
@@ -1323,7 +1354,7 @@ public final class ViewRootImpl extends Handler implements ViewParent,
}
} else if (surfaceGenerationId != mSurface.getGenerationId() &&
mSurfaceHolder == null && mAttachInfo.mHardwareRenderer != null) {
- fullRedrawNeeded = true;
+ mFullRedrawNeeded = true;
try {
mAttachInfo.mHardwareRenderer.updateSurface(mHolder);
} catch (Surface.OutOfResourcesException e) {
@@ -1609,6 +1640,11 @@ public final class ViewRootImpl extends Handler implements ViewParent,
}
}
+ // Remember if we must report the next draw.
+ if ((relayoutResult & WindowManagerImpl.RELAYOUT_RES_FIRST_TIME) != 0) {
+ mReportNextDraw = true;
+ }
+
boolean cancelDraw = attachInfo.mTreeObserver.dispatchOnPreDraw() ||
viewVisibility != View.VISIBLE;
@@ -1619,42 +1655,8 @@ public final class ViewRootImpl extends Handler implements ViewParent,
}
mPendingTransitions.clear();
}
- mFullRedrawNeeded = false;
-
- final long drawStartTime;
- if (ViewDebug.DEBUG_LATENCY) {
- drawStartTime = System.nanoTime();
- }
-
- draw(fullRedrawNeeded);
-
- if (ViewDebug.DEBUG_LATENCY) {
- mLastDrawDurationNanos = System.nanoTime() - drawStartTime;
- }
- if ((relayoutResult&WindowManagerImpl.RELAYOUT_RES_FIRST_TIME) != 0
- || mReportNextDraw) {
- if (LOCAL_LOGV) {
- Log.v(TAG, "FINISHED DRAWING: " + mWindowAttributes.getTitle());
- }
- mReportNextDraw = false;
- if (mSurfaceHolder != null && mSurface.isValid()) {
- mSurfaceHolderCallback.surfaceRedrawNeeded(mSurfaceHolder);
- SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
- if (callbacks != null) {
- for (SurfaceHolder.Callback c : callbacks) {
- if (c instanceof SurfaceHolder.Callback2) {
- ((SurfaceHolder.Callback2)c).surfaceRedrawNeeded(
- mSurfaceHolder);
- }
- }
- }
- }
- try {
- sWindowSession.finishDrawing(mWindow);
- } catch (RemoteException e) {
- }
- }
+ performDraw();
} else {
// End any pending transitions on this non-visible window
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
@@ -1663,14 +1665,6 @@ public final class ViewRootImpl extends Handler implements ViewParent,
}
mPendingTransitions.clear();
}
- // We were supposed to report when we are done drawing. Since we canceled the
- // draw, remember it here.
- if ((relayoutResult&WindowManagerImpl.RELAYOUT_RES_FIRST_TIME) != 0) {
- mReportNextDraw = true;
- }
- if (fullRedrawNeeded) {
- mFullRedrawNeeded = true;
- }
if (viewVisibility == View.VISIBLE) {
// Try again
@@ -1814,6 +1808,56 @@ public final class ViewRootImpl extends Handler implements ViewParent,
}
}
+ private void performDraw() {
+ final long drawStartTime;
+ if (ViewDebug.DEBUG_LATENCY) {
+ drawStartTime = System.nanoTime();
+ if (mLastDrawFinishedTimeNanos != 0) {
+ Log.d(ViewDebug.DEBUG_LATENCY_TAG, "Starting draw(); it has been "
+ + ((drawStartTime - mLastDrawFinishedTimeNanos) * 0.000001f)
+ + "ms since the last draw finished.");
+ } else {
+ Log.d(ViewDebug.DEBUG_LATENCY_TAG, "Starting draw().");
+ }
+ }
+
+ final boolean fullRedrawNeeded = mFullRedrawNeeded;
+ mFullRedrawNeeded = false;
+ draw(fullRedrawNeeded);
+
+ if (ViewDebug.DEBUG_LATENCY) {
+ long now = System.nanoTime();
+ Log.d(ViewDebug.DEBUG_LATENCY_TAG, "performDraw() took "
+ + ((now - drawStartTime) * 0.000001f)
+ + "ms.");
+ mLastDrawFinishedTimeNanos = now;
+ }
+
+ if (mReportNextDraw) {
+ mReportNextDraw = false;
+
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "FINISHED DRAWING: " + mWindowAttributes.getTitle());
+ }
+ if (mSurfaceHolder != null && mSurface.isValid()) {
+ mSurfaceHolderCallback.surfaceRedrawNeeded(mSurfaceHolder);
+ SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
+ if (callbacks != null) {
+ for (SurfaceHolder.Callback c : callbacks) {
+ if (c instanceof SurfaceHolder.Callback2) {
+ ((SurfaceHolder.Callback2)c).surfaceRedrawNeeded(
+ mSurfaceHolder);
+ }
+ }
+ }
+ }
+ try {
+ sWindowSession.finishDrawing(mWindow);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
private void draw(boolean fullRedrawNeeded) {
Surface surface = mSurface;
if (surface == null || !surface.isValid()) {
@@ -1852,8 +1896,9 @@ public final class ViewRootImpl extends Handler implements ViewParent,
mCurScrollY = yoff;
fullRedrawNeeded = true;
}
- float appScale = mAttachInfo.mApplicationScale;
- boolean scalingRequired = mAttachInfo.mScalingRequired;
+
+ final float appScale = mAttachInfo.mApplicationScale;
+ final boolean scalingRequired = mAttachInfo.mScalingRequired;
int resizeAlpha = 0;
if (mResizeBuffer != null) {
@@ -1868,7 +1913,7 @@ public final class ViewRootImpl extends Handler implements ViewParent,
}
}
- Rect dirty = mDirty;
+ final Rect dirty = mDirty;
if (mSurfaceHolder != null) {
// The app owns the surface, we won't draw.
dirty.setEmpty();
@@ -1886,35 +1931,6 @@ public final class ViewRootImpl extends Handler implements ViewParent,
dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
}
- if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
- if (!dirty.isEmpty() || mIsAnimating) {
- mIsAnimating = false;
- mHardwareYOffset = yoff;
- mResizeAlpha = resizeAlpha;
-
- mCurrentDirty.set(dirty);
- mCurrentDirty.union(mPreviousDirty);
- mPreviousDirty.set(dirty);
- dirty.setEmpty();
-
- Rect currentDirty = mCurrentDirty;
- if (animating) {
- currentDirty = null;
- }
-
- if (mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this, currentDirty)) {
- mPreviousDirty.set(0, 0, mWidth, mHeight);
- }
- }
-
- if (animating) {
- mFullRedrawNeeded = true;
- scheduleTraversals();
- }
-
- return;
- }
-
if (DEBUG_ORIENTATION || DEBUG_DRAW) {
Log.v(TAG, "Draw " + mView + "/"
+ mWindowAttributes.getTitle()
@@ -1925,64 +1941,79 @@ public final class ViewRootImpl extends Handler implements ViewParent,
}
if (!dirty.isEmpty() || mIsAnimating) {
- Canvas canvas;
- try {
- int left = dirty.left;
- int top = dirty.top;
- int right = dirty.right;
- int bottom = dirty.bottom;
-
- final long lockCanvasStartTime;
- if (ViewDebug.DEBUG_LATENCY) {
- lockCanvasStartTime = System.nanoTime();
- }
+ if (mAttachInfo.mHardwareRenderer != null
+ && mAttachInfo.mHardwareRenderer.isEnabled()) {
+ // Draw with hardware renderer.
+ mIsAnimating = false;
+ mHardwareYOffset = yoff;
+ mResizeAlpha = resizeAlpha;
- canvas = surface.lockCanvas(dirty);
+ mCurrentDirty.set(dirty);
+ mCurrentDirty.union(mPreviousDirty);
+ mPreviousDirty.set(dirty);
+ dirty.setEmpty();
- if (ViewDebug.DEBUG_LATENCY) {
- long now = System.nanoTime();
- Log.d(TAG, "Latency: Spent "
- + ((now - lockCanvasStartTime) * 0.000001f)
- + "ms waiting for surface.lockCanvas()");
+ if (mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this,
+ animating ? null : mCurrentDirty)) {
+ mPreviousDirty.set(0, 0, mWidth, mHeight);
}
+ } else {
+ // Draw with software renderer.
+ Canvas canvas;
+ try {
+ int left = dirty.left;
+ int top = dirty.top;
+ int right = dirty.right;
+ int bottom = dirty.bottom;
+
+ final long lockCanvasStartTime;
+ if (ViewDebug.DEBUG_LATENCY) {
+ lockCanvasStartTime = System.nanoTime();
+ }
- if (left != dirty.left || top != dirty.top || right != dirty.right ||
- bottom != dirty.bottom) {
- mAttachInfo.mIgnoreDirtyState = true;
- }
+ canvas = mSurface.lockCanvas(dirty);
- // TODO: Do this in native
- canvas.setDensity(mDensity);
- } catch (Surface.OutOfResourcesException e) {
- Log.e(TAG, "OutOfResourcesException locking surface", e);
- try {
- if (!sWindowSession.outOfMemory(mWindow)) {
- Slog.w(TAG, "No processes killed for memory; killing self");
- Process.killProcess(Process.myPid());
+ if (ViewDebug.DEBUG_LATENCY) {
+ long now = System.nanoTime();
+ Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- lockCanvas() took "
+ + ((now - lockCanvasStartTime) * 0.000001f) + "ms");
}
- } catch (RemoteException ex) {
- }
- mLayoutRequested = true; // ask wm for a new surface next time.
- return;
- } catch (IllegalArgumentException e) {
- Log.e(TAG, "IllegalArgumentException locking surface", e);
- // Don't assume this is due to out of memory, it could be
- // something else, and if it is something else then we could
- // kill stuff (or ourself) for no reason.
- mLayoutRequested = true; // ask wm for a new surface next time.
- return;
- }
- try {
- if (!dirty.isEmpty() || mIsAnimating) {
- long startTime = 0L;
+ if (left != dirty.left || top != dirty.top || right != dirty.right ||
+ bottom != dirty.bottom) {
+ mAttachInfo.mIgnoreDirtyState = true;
+ }
+ // TODO: Do this in native
+ canvas.setDensity(mDensity);
+ } catch (Surface.OutOfResourcesException e) {
+ Log.e(TAG, "OutOfResourcesException locking surface", e);
+ try {
+ if (!sWindowSession.outOfMemory(mWindow)) {
+ Slog.w(TAG, "No processes killed for memory; killing self");
+ Process.killProcess(Process.myPid());
+ }
+ } catch (RemoteException ex) {
+ }
+ mLayoutRequested = true; // ask wm for a new surface next time.
+ return;
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "IllegalArgumentException locking surface", e);
+ // Don't assume this is due to out of memory, it could be
+ // something else, and if it is something else then we could
+ // kill stuff (or ourself) for no reason.
+ mLayoutRequested = true; // ask wm for a new surface next time.
+ return;
+ }
+
+ try {
if (DEBUG_ORIENTATION || DEBUG_DRAW) {
Log.v(TAG, "Surface " + surface + " drawing to bitmap w="
+ canvas.getWidth() + ", h=" + canvas.getHeight());
//canvas.drawARGB(255, 255, 0, 0);
}
+ long startTime = 0L;
if (ViewDebug.DEBUG_PROFILE_DRAWING) {
startTime = SystemClock.elapsedRealtime();
}
@@ -2045,23 +2076,23 @@ public final class ViewRootImpl extends Handler implements ViewParent,
if (ViewDebug.DEBUG_PROFILE_DRAWING) {
EventLog.writeEvent(60000, SystemClock.elapsedRealtime() - startTime);
}
- }
- } finally {
- final long unlockCanvasAndPostStartTime;
- if (ViewDebug.DEBUG_LATENCY) {
- unlockCanvasAndPostStartTime = System.nanoTime();
- }
+ } finally {
+ final long unlockCanvasAndPostStartTime;
+ if (ViewDebug.DEBUG_LATENCY) {
+ unlockCanvasAndPostStartTime = System.nanoTime();
+ }
- surface.unlockCanvasAndPost(canvas);
+ surface.unlockCanvasAndPost(canvas);
- if (ViewDebug.DEBUG_LATENCY) {
- long now = System.nanoTime();
- Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- unlockCanvasAndPost() took "
- + ((now - unlockCanvasAndPostStartTime) * 0.000001f) + "ms");
- }
+ if (ViewDebug.DEBUG_LATENCY) {
+ long now = System.nanoTime();
+ Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- unlockCanvasAndPost() took "
+ + ((now - unlockCanvasAndPostStartTime) * 0.000001f) + "ms");
+ }
- if (LOCAL_LOGV) {
- Log.v(TAG, "Surface " + surface + " unlockCanvasAndPost");
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "Surface " + surface + " unlockCanvasAndPost");
+ }
}
}
}
@@ -2297,6 +2328,8 @@ public final class ViewRootImpl extends Handler implements ViewParent,
mInputChannel.dispose();
mInputChannel = null;
}
+
+ mChoreographer.removeOnDrawListener(this);
}
void updateConfiguration(Configuration config, boolean force) {
@@ -2351,7 +2384,6 @@ public final class ViewRootImpl extends Handler implements ViewParent,
}
}
- public final static int DO_TRAVERSAL = 1000;
public final static int DIE = 1001;
public final static int RESIZED = 1002;
public final static int RESIZED_REPORT = 1003;
@@ -2380,8 +2412,6 @@ public final class ViewRootImpl extends Handler implements ViewParent,
@Override
public String getMessageName(Message message) {
switch (message.what) {
- case DO_TRAVERSAL:
- return "DO_TRAVERSAL";
case DIE:
return "DIE";
case RESIZED:
@@ -2445,45 +2475,12 @@ public final class ViewRootImpl extends Handler implements ViewParent,
info.target.invalidate(info.left, info.top, info.right, info.bottom);
info.release();
break;
- case DO_TRAVERSAL:
- if (mProfile) {
- Debug.startMethodTracing("ViewAncestor");
- }
-
- final long traversalStartTime;
- if (ViewDebug.DEBUG_LATENCY) {
- traversalStartTime = System.nanoTime();
- mLastDrawDurationNanos = 0;
- if (mLastTraversalFinishedTimeNanos != 0) {
- Log.d(ViewDebug.DEBUG_LATENCY_TAG, "Starting performTraversals(); it has been "
- + ((traversalStartTime - mLastTraversalFinishedTimeNanos) * 0.000001f)
- + "ms since the last traversals finished.");
- } else {
- Log.d(ViewDebug.DEBUG_LATENCY_TAG, "Starting performTraversals().");
- }
- }
-
- performTraversals();
-
- if (ViewDebug.DEBUG_LATENCY) {
- long now = System.nanoTime();
- Log.d(ViewDebug.DEBUG_LATENCY_TAG, "performTraversals() took "
- + ((now - traversalStartTime) * 0.000001f)
- + "ms.");
- mLastTraversalFinishedTimeNanos = now;
- }
-
- if (mProfile) {
- Debug.stopMethodTracing();
- mProfile = false;
- }
- break;
case IME_FINISHED_EVENT:
handleImeFinishedEvent(msg.arg1, msg.arg2 != 0);
break;
case DO_PROCESS_INPUT_EVENTS:
- mProcessInputEventsPending = false;
- processInputEvents();
+ mProcessInputEventsScheduled = false;
+ doProcessInputEvents();
break;
case DISPATCH_APP_VISIBILITY:
handleAppVisibility(msg.arg1 != 0);
@@ -3782,13 +3779,13 @@ public final class ViewRootImpl extends Handler implements ViewParent,
}
private void scheduleProcessInputEvents() {
- if (!mProcessInputEventsPending) {
- mProcessInputEventsPending = true;
+ if (!mProcessInputEventsScheduled) {
+ mProcessInputEventsScheduled = true;
sendEmptyMessage(DO_PROCESS_INPUT_EVENTS);
}
}
- void processInputEvents() {
+ private void doProcessInputEvents() {
while (mCurrentInputEvent == null && mFirstPendingInputEvent != null) {
QueuedInputEvent q = mFirstPendingInputEvent;
mFirstPendingInputEvent = q.mNext;
@@ -3799,8 +3796,8 @@ public final class ViewRootImpl extends Handler implements ViewParent,
// We are done processing all input events that we can process right now
// so we can clear the pending flag immediately.
- if (mProcessInputEventsPending) {
- mProcessInputEventsPending = false;
+ if (mProcessInputEventsScheduled) {
+ mProcessInputEventsScheduled = false;
removeMessages(DO_PROCESS_INPUT_EVENTS);
}
}
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index 3d229295ca7a..edeb2a8d7bc1 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -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;
+ }
}
diff --git a/tools/layoutlib/bridge/src/android/animation/AnimationThread.java b/tools/layoutlib/bridge/src/android/animation/AnimationThread.java
index 2b5e4fad1f0a..af83c61b2241 100644
--- a/tools/layoutlib/bridge/src/android/animation/AnimationThread.java
+++ b/tools/layoutlib/bridge/src/android/animation/AnimationThread.java
@@ -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.