diff options
-rwxr-xr-x | core/java/android/animation/ValueAnimator.java | 230 | ||||
-rw-r--r-- | core/java/android/view/Choreographer.java | 380 | ||||
-rw-r--r-- | core/java/android/view/ViewRootImpl.java | 397 | ||||
-rw-r--r-- | core/java/com/android/internal/util/ArrayUtils.java | 53 | ||||
-rw-r--r-- | tools/layoutlib/bridge/src/android/animation/AnimationThread.java | 7 |
5 files changed, 754 insertions, 313 deletions
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java index 4f63165..9bf1634 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 0000000..0fdd105 --- /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 95c473c..2dd7ddf 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 3d22929..edeb2a8 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 2b5e4fa..af83c61 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. |