1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.animation; 18 19 import android.os.SystemClock; 20 import android.util.ArrayMap; 21 import android.view.Choreographer; 22 23 import java.util.ArrayList; 24 25 /** 26 * This custom, static handler handles the timing pulse that is shared by all active 27 * ValueAnimators. This approach ensures that the setting of animation values will happen on the 28 * same thread that animations start on, and that all animations will share the same times for 29 * calculating their values, which makes synchronizing animations possible. 30 * 31 * The handler uses the Choreographer by default for doing periodic callbacks. A custom 32 * AnimationFrameCallbackProvider can be set on the handler to provide timing pulse that 33 * may be independent of UI frame update. This could be useful in testing. 34 * 35 * @hide 36 */ 37 public class AnimationHandler { 38 /** 39 * Internal per-thread collections used to avoid set collisions as animations start and end 40 * while being processed. 41 * @hide 42 */ 43 private final ArrayMap<AnimationFrameCallback, Long> mDelayedCallbackStartTime = 44 new ArrayMap<>(); 45 private final ArrayList<AnimationFrameCallback> mAnimationCallbacks = 46 new ArrayList<>(); 47 private final ArrayList<AnimationFrameCallback> mCommitCallbacks = 48 new ArrayList<>(); 49 private AnimationFrameCallbackProvider mProvider; 50 51 private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() { 52 @Override 53 public void doFrame(long frameTimeNanos) { 54 doAnimationFrame(getProvider().getFrameTime()); 55 if (mAnimationCallbacks.size() > 0) { 56 getProvider().postFrameCallback(this); 57 } 58 } 59 }; 60 61 public final static ThreadLocal<AnimationHandler> sAnimatorHandler = new ThreadLocal<>(); 62 private boolean mListDirty = false; 63 getInstance()64 public static AnimationHandler getInstance() { 65 if (sAnimatorHandler.get() == null) { 66 sAnimatorHandler.set(new AnimationHandler()); 67 } 68 return sAnimatorHandler.get(); 69 } 70 71 /** 72 * By default, the Choreographer is used to provide timing for frame callbacks. A custom 73 * provider can be used here to provide different timing pulse. 74 */ setProvider(AnimationFrameCallbackProvider provider)75 public void setProvider(AnimationFrameCallbackProvider provider) { 76 if (provider == null) { 77 mProvider = new MyFrameCallbackProvider(); 78 } else { 79 mProvider = provider; 80 } 81 } 82 getProvider()83 private AnimationFrameCallbackProvider getProvider() { 84 if (mProvider == null) { 85 mProvider = new MyFrameCallbackProvider(); 86 } 87 return mProvider; 88 } 89 90 /** 91 * Register to get a callback on the next frame after the delay. 92 */ addAnimationFrameCallback(final AnimationFrameCallback callback, long delay)93 public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) { 94 if (mAnimationCallbacks.size() == 0) { 95 getProvider().postFrameCallback(mFrameCallback); 96 } 97 if (!mAnimationCallbacks.contains(callback)) { 98 mAnimationCallbacks.add(callback); 99 } 100 101 if (delay > 0) { 102 mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay)); 103 } 104 } 105 106 /** 107 * Register to get a one shot callback for frame commit timing. Frame commit timing is the 108 * time *after* traversals are done, as opposed to the animation frame timing, which is 109 * before any traversals. This timing can be used to adjust the start time of an animation 110 * when expensive traversals create big delta between the animation frame timing and the time 111 * that animation is first shown on screen. 112 * 113 * Note this should only be called when the animation has already registered to receive 114 * animation frame callbacks. This callback will be guaranteed to happen *after* the next 115 * animation frame callback. 116 */ addOneShotCommitCallback(final AnimationFrameCallback callback)117 public void addOneShotCommitCallback(final AnimationFrameCallback callback) { 118 if (!mCommitCallbacks.contains(callback)) { 119 mCommitCallbacks.add(callback); 120 } 121 } 122 123 /** 124 * Removes the given callback from the list, so it will no longer be called for frame related 125 * timing. 126 */ removeCallback(AnimationFrameCallback callback)127 public void removeCallback(AnimationFrameCallback callback) { 128 mCommitCallbacks.remove(callback); 129 mDelayedCallbackStartTime.remove(callback); 130 int id = mAnimationCallbacks.indexOf(callback); 131 if (id >= 0) { 132 mAnimationCallbacks.set(id, null); 133 mListDirty = true; 134 } 135 } 136 doAnimationFrame(long frameTime)137 private void doAnimationFrame(long frameTime) { 138 long currentTime = SystemClock.uptimeMillis(); 139 final int size = mAnimationCallbacks.size(); 140 for (int i = 0; i < size; i++) { 141 final AnimationFrameCallback callback = mAnimationCallbacks.get(i); 142 if (callback == null) { 143 continue; 144 } 145 if (isCallbackDue(callback, currentTime)) { 146 callback.doAnimationFrame(frameTime); 147 if (mCommitCallbacks.contains(callback)) { 148 getProvider().postCommitCallback(new Runnable() { 149 @Override 150 public void run() { 151 commitAnimationFrame(callback, getProvider().getFrameTime()); 152 } 153 }); 154 } 155 } 156 } 157 cleanUpList(); 158 } 159 commitAnimationFrame(AnimationFrameCallback callback, long frameTime)160 private void commitAnimationFrame(AnimationFrameCallback callback, long frameTime) { 161 if (!mDelayedCallbackStartTime.containsKey(callback) && 162 mCommitCallbacks.contains(callback)) { 163 callback.commitAnimationFrame(frameTime); 164 mCommitCallbacks.remove(callback); 165 } 166 } 167 168 /** 169 * Remove the callbacks from mDelayedCallbackStartTime once they have passed the initial delay 170 * so that they can start getting frame callbacks. 171 * 172 * @return true if they have passed the initial delay or have no delay, false otherwise. 173 */ isCallbackDue(AnimationFrameCallback callback, long currentTime)174 private boolean isCallbackDue(AnimationFrameCallback callback, long currentTime) { 175 Long startTime = mDelayedCallbackStartTime.get(callback); 176 if (startTime == null) { 177 return true; 178 } 179 if (startTime < currentTime) { 180 mDelayedCallbackStartTime.remove(callback); 181 return true; 182 } 183 return false; 184 } 185 186 /** 187 * Return the number of callbacks that have registered for frame callbacks. 188 */ getAnimationCount()189 public static int getAnimationCount() { 190 AnimationHandler handler = sAnimatorHandler.get(); 191 if (handler == null) { 192 return 0; 193 } 194 return handler.getCallbackSize(); 195 } 196 setFrameDelay(long delay)197 public static void setFrameDelay(long delay) { 198 getInstance().getProvider().setFrameDelay(delay); 199 } 200 getFrameDelay()201 public static long getFrameDelay() { 202 return getInstance().getProvider().getFrameDelay(); 203 } 204 autoCancelBasedOn(ObjectAnimator objectAnimator)205 void autoCancelBasedOn(ObjectAnimator objectAnimator) { 206 for (int i = mAnimationCallbacks.size() - 1; i >= 0; i--) { 207 AnimationFrameCallback cb = mAnimationCallbacks.get(i); 208 if (cb == null) { 209 continue; 210 } 211 if (objectAnimator.shouldAutoCancel(cb)) { 212 ((Animator) mAnimationCallbacks.get(i)).cancel(); 213 } 214 } 215 } 216 cleanUpList()217 private void cleanUpList() { 218 if (mListDirty) { 219 for (int i = mAnimationCallbacks.size() - 1; i >= 0; i--) { 220 if (mAnimationCallbacks.get(i) == null) { 221 mAnimationCallbacks.remove(i); 222 } 223 } 224 mListDirty = false; 225 } 226 } 227 getCallbackSize()228 private int getCallbackSize() { 229 int count = 0; 230 int size = mAnimationCallbacks.size(); 231 for (int i = size - 1; i >= 0; i--) { 232 if (mAnimationCallbacks.get(i) != null) { 233 count++; 234 } 235 } 236 return count; 237 } 238 239 /** 240 * Default provider of timing pulse that uses Choreographer for frame callbacks. 241 */ 242 private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider { 243 244 final Choreographer mChoreographer = Choreographer.getInstance(); 245 246 @Override postFrameCallback(Choreographer.FrameCallback callback)247 public void postFrameCallback(Choreographer.FrameCallback callback) { 248 mChoreographer.postFrameCallback(callback); 249 } 250 251 @Override postCommitCallback(Runnable runnable)252 public void postCommitCallback(Runnable runnable) { 253 mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, runnable, null); 254 } 255 256 @Override getFrameTime()257 public long getFrameTime() { 258 return mChoreographer.getFrameTime(); 259 } 260 261 @Override getFrameDelay()262 public long getFrameDelay() { 263 return Choreographer.getFrameDelay(); 264 } 265 266 @Override setFrameDelay(long delay)267 public void setFrameDelay(long delay) { 268 Choreographer.setFrameDelay(delay); 269 } 270 } 271 272 /** 273 * Callbacks that receives notifications for animation timing and frame commit timing. 274 */ 275 interface AnimationFrameCallback { 276 /** 277 * Run animation based on the frame time. 278 * @param frameTime The frame start time, in the {@link SystemClock#uptimeMillis()} time 279 * base. 280 * @return if the animation has finished. 281 */ doAnimationFrame(long frameTime)282 boolean doAnimationFrame(long frameTime); 283 284 /** 285 * This notifies the callback of frame commit time. Frame commit time is the time after 286 * traversals happen, as opposed to the normal animation frame time that is before 287 * traversals. This is used to compensate expensive traversals that happen as the 288 * animation starts. When traversals take a long time to complete, the rendering of the 289 * initial frame will be delayed (by a long time). But since the startTime of the 290 * animation is set before the traversal, by the time of next frame, a lot of time would 291 * have passed since startTime was set, the animation will consequently skip a few frames 292 * to respect the new frameTime. By having the commit time, we can adjust the start time to 293 * when the first frame was drawn (after any expensive traversals) so that no frames 294 * will be skipped. 295 * 296 * @param frameTime The frame time after traversals happen, if any, in the 297 * {@link SystemClock#uptimeMillis()} time base. 298 */ commitAnimationFrame(long frameTime)299 void commitAnimationFrame(long frameTime); 300 } 301 302 /** 303 * The intention for having this interface is to increase the testability of ValueAnimator. 304 * Specifically, we can have a custom implementation of the interface below and provide 305 * timing pulse without using Choreographer. That way we could use any arbitrary interval for 306 * our timing pulse in the tests. 307 * 308 * @hide 309 */ 310 public interface AnimationFrameCallbackProvider { postFrameCallback(Choreographer.FrameCallback callback)311 void postFrameCallback(Choreographer.FrameCallback callback); postCommitCallback(Runnable runnable)312 void postCommitCallback(Runnable runnable); getFrameTime()313 long getFrameTime(); getFrameDelay()314 long getFrameDelay(); setFrameDelay(long delay)315 void setFrameDelay(long delay); 316 } 317 } 318