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.annotation.Nullable; 20 import android.os.SystemClock; 21 import android.os.SystemProperties; 22 import android.util.ArrayMap; 23 import android.util.Log; 24 import android.view.Choreographer; 25 26 import java.lang.ref.WeakReference; 27 import java.util.ArrayList; 28 29 /** 30 * This custom, static handler handles the timing pulse that is shared by all active 31 * ValueAnimators. This approach ensures that the setting of animation values will happen on the 32 * same thread that animations start on, and that all animations will share the same times for 33 * calculating their values, which makes synchronizing animations possible. 34 * 35 * The handler uses the Choreographer by default for doing periodic callbacks. A custom 36 * AnimationFrameCallbackProvider can be set on the handler to provide timing pulse that 37 * may be independent of UI frame update. This could be useful in testing. 38 * 39 * @hide 40 */ 41 public class AnimationHandler { 42 43 private static final String TAG = "AnimationHandler"; 44 private static final boolean LOCAL_LOGV = false; 45 46 /** 47 * Internal per-thread collections used to avoid set collisions as animations start and end 48 * while being processed. 49 */ 50 private final ArrayMap<AnimationFrameCallback, Long> mDelayedCallbackStartTime = 51 new ArrayMap<>(); 52 private final ArrayList<AnimationFrameCallback> mAnimationCallbacks = 53 new ArrayList<>(); 54 private final ArrayList<AnimationFrameCallback> mCommitCallbacks = 55 new ArrayList<>(); 56 private AnimationFrameCallbackProvider mProvider; 57 58 // Static flag which allows the pausing behavior to be globally disabled/enabled. 59 private static boolean sAnimatorPausingEnabled = isPauseBgAnimationsEnabledInSystemProperties(); 60 61 // Static flag which prevents the system property from overriding sAnimatorPausingEnabled field. 62 private static boolean sOverrideAnimatorPausingSystemProperty = false; 63 64 /** 65 * This paused list is used to store animators forcibly paused when the activity 66 * went into the background (to avoid unnecessary background processing work). 67 * These animators should be resume()'d when the activity returns to the foreground. 68 */ 69 private final ArrayList<Animator> mPausedAnimators = new ArrayList<>(); 70 71 /** 72 * This structure is used to store the currently active objects (ViewRootImpls or 73 * WallpaperService.Engines) in the process. Each of these objects sends a request to 74 * AnimationHandler when it goes into the background (request to pause) or foreground 75 * (request to resume). Because all animators are managed by AnimationHandler on the same 76 * thread, it should only ever pause animators when *all* requestors are in the background. 77 * This list tracks the background/foreground state of all requestors and only ever 78 * pauses animators when all items are in the background (false). To simplify, we only ever 79 * store visible (foreground) requestors; if the set size reaches zero, there are no 80 * objects in the foreground and it is time to pause animators. 81 */ 82 private final ArrayList<WeakReference<Object>> mAnimatorRequestors = new ArrayList<>(); 83 84 /** 85 * The callbacks which will invoke {@link Animator#notifyEndListeners(boolean)} on next frame. 86 * It is only used if {@link Animator#setPostNotifyEndListenerEnabled(boolean)} sets true. 87 */ 88 private ArrayList<Runnable> mPendingEndAnimationListeners; 89 90 /** 91 * The value of {@link Choreographer#getVsyncId()} at the last animation frame. 92 * It is only used if {@link Animator#setPostNotifyEndListenerEnabled(boolean)} sets true. 93 */ 94 private long mLastAnimationFrameVsyncId; 95 96 /** 97 * The value of {@link Choreographer#getVsyncId()} when calling 98 * {@link Animator#notifyEndListeners(boolean)}. 99 * It is only used if {@link Animator#setPostNotifyEndListenerEnabled(boolean)} sets true. 100 */ 101 private long mEndAnimationFrameVsyncId; 102 103 private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() { 104 @Override 105 public void doFrame(long frameTimeNanos) { 106 doAnimationFrame(getProvider().getFrameTime()); 107 if (mAnimationCallbacks.size() > 0) { 108 getProvider().postFrameCallback(this); 109 } 110 } 111 }; 112 113 public static final ThreadLocal<AnimationHandler> sAnimatorHandler = new ThreadLocal<>(); 114 private static AnimationHandler sTestHandler = null; 115 private boolean mListDirty = false; 116 getInstance()117 public static AnimationHandler getInstance() { 118 if (sTestHandler != null) { 119 return sTestHandler; 120 } 121 AnimationHandler animatorHandler = sAnimatorHandler.get(); 122 if (animatorHandler == null) { 123 animatorHandler = new AnimationHandler(); 124 sAnimatorHandler.set(animatorHandler); 125 } 126 return animatorHandler; 127 } 128 129 /** 130 * Sets an instance that will be returned by {@link #getInstance()} on every thread. 131 * @return the previously active test handler, if any. 132 * @hide 133 */ setTestHandler(@ullable AnimationHandler handler)134 public static @Nullable AnimationHandler setTestHandler(@Nullable AnimationHandler handler) { 135 AnimationHandler oldHandler = sTestHandler; 136 sTestHandler = handler; 137 return oldHandler; 138 } 139 140 /** 141 * System property that controls the behavior of pausing infinite animators when an app 142 * is moved to the background. 143 * 144 * @return the value of 'framework.pause_bg_animations.enabled' system property 145 */ isPauseBgAnimationsEnabledInSystemProperties()146 private static boolean isPauseBgAnimationsEnabledInSystemProperties() { 147 if (sOverrideAnimatorPausingSystemProperty) return sAnimatorPausingEnabled; 148 return SystemProperties 149 .getBoolean("framework.pause_bg_animations.enabled", true); 150 } 151 152 /** 153 * Disable the default behavior of pausing infinite animators when 154 * apps go into the background. 155 * 156 * @param enable Enable (default behavior) or disable background pausing behavior. 157 */ setAnimatorPausingEnabled(boolean enable)158 public static void setAnimatorPausingEnabled(boolean enable) { 159 sAnimatorPausingEnabled = enable; 160 } 161 162 /** 163 * Prevents the setAnimatorPausingEnabled behavior from being overridden 164 * by the 'framework.pause_bg_animations.enabled' system property value. 165 * 166 * This is for testing purposes only. 167 * 168 * @param enable Enable or disable (default behavior) overriding the system 169 * property. 170 */ setOverrideAnimatorPausingSystemProperty(boolean enable)171 public static void setOverrideAnimatorPausingSystemProperty(boolean enable) { 172 sOverrideAnimatorPausingSystemProperty = enable; 173 } 174 175 /** 176 * This is called when a window goes away. We should remove 177 * it from the requestors list to ensure that we are counting requests correctly and not 178 * tracking obsolete+enabled requestors. 179 */ removeRequestor(Object requestor)180 public static void removeRequestor(Object requestor) { 181 getInstance().requestAnimatorsEnabledImpl(false, requestor); 182 if (LOCAL_LOGV) { 183 Log.v(TAG, "removeRequestor for " + requestor); 184 } 185 } 186 187 /** 188 * This method is called from ViewRootImpl or WallpaperService when either a window is no 189 * longer visible (enable == false) or when a window becomes visible (enable == true). 190 * If animators are not properly disabled when activities are backgrounded, it can lead to 191 * unnecessary processing, particularly for infinite animators, as the system will continue 192 * to pulse timing events even though the results are not visible. As a workaround, we 193 * pause all un-paused infinite animators, and resume them when any window in the process 194 * becomes visible. 195 */ requestAnimatorsEnabled(boolean enable, Object requestor)196 public static void requestAnimatorsEnabled(boolean enable, Object requestor) { 197 getInstance().requestAnimatorsEnabledImpl(enable, requestor); 198 } 199 requestAnimatorsEnabledImpl(boolean enable, Object requestor)200 private void requestAnimatorsEnabledImpl(boolean enable, Object requestor) { 201 boolean wasEmpty = mAnimatorRequestors.isEmpty(); 202 setAnimatorPausingEnabled(isPauseBgAnimationsEnabledInSystemProperties()); 203 synchronized (mAnimatorRequestors) { 204 // Only store WeakRef objects to avoid leaks 205 if (enable) { 206 // First, check whether such a reference is already on the list 207 WeakReference<Object> weakRef = null; 208 for (int i = mAnimatorRequestors.size() - 1; i >= 0; --i) { 209 WeakReference<Object> ref = mAnimatorRequestors.get(i); 210 Object referent = ref.get(); 211 if (referent == requestor) { 212 weakRef = ref; 213 } else if (referent == null) { 214 // Remove any reference that has been cleared 215 mAnimatorRequestors.remove(i); 216 } 217 } 218 if (weakRef == null) { 219 weakRef = new WeakReference<>(requestor); 220 mAnimatorRequestors.add(weakRef); 221 } 222 } else { 223 for (int i = mAnimatorRequestors.size() - 1; i >= 0; --i) { 224 WeakReference<Object> ref = mAnimatorRequestors.get(i); 225 Object referent = ref.get(); 226 if (referent == requestor || referent == null) { 227 // remove requested item or item that has been cleared 228 mAnimatorRequestors.remove(i); 229 } 230 } 231 // If a reference to the requestor wasn't in the list, nothing to remove 232 } 233 } 234 if (!sAnimatorPausingEnabled) { 235 // Resume any animators that have been paused in the meantime, otherwise noop 236 // Leave logic above so that if pausing gets re-enabled, the state of the requestors 237 // list is valid 238 resumeAnimators(); 239 return; 240 } 241 boolean isEmpty = mAnimatorRequestors.isEmpty(); 242 if (wasEmpty != isEmpty) { 243 // only paused/resume animators if there was a visibility change 244 if (!isEmpty) { 245 // If any requestors are enabled, resume currently paused animators 246 resumeAnimators(); 247 } else { 248 // Wait before pausing to avoid thrashing animator state for temporary backgrounding 249 Choreographer.getInstance().postFrameCallbackDelayed(mPauser, 250 Animator.getBackgroundPauseDelay()); 251 } 252 } 253 if (LOCAL_LOGV) { 254 Log.v(TAG, (enable ? "enable" : "disable") + " animators for " + requestor 255 + " with pauseDelay of " + Animator.getBackgroundPauseDelay()); 256 for (int i = 0; i < mAnimatorRequestors.size(); ++i) { 257 Log.v(TAG, "animatorRequestors " + i + " = " 258 + mAnimatorRequestors.get(i) + " with referent " 259 + mAnimatorRequestors.get(i).get()); 260 } 261 } 262 } 263 resumeAnimators()264 private void resumeAnimators() { 265 Choreographer.getInstance().removeFrameCallback(mPauser); 266 for (int i = mPausedAnimators.size() - 1; i >= 0; --i) { 267 mPausedAnimators.get(i).resume(); 268 } 269 mPausedAnimators.clear(); 270 } 271 272 private Choreographer.FrameCallback mPauser = frameTimeNanos -> { 273 if (mAnimatorRequestors.size() > 0) { 274 // something enabled animators since this callback was scheduled - bail 275 return; 276 } 277 for (int i = 0; i < mAnimationCallbacks.size(); ++i) { 278 AnimationFrameCallback callback = mAnimationCallbacks.get(i); 279 if (callback instanceof Animator) { 280 Animator animator = ((Animator) callback); 281 if (animator.getTotalDuration() == Animator.DURATION_INFINITE 282 && !animator.isPaused()) { 283 mPausedAnimators.add(animator); 284 animator.pause(); 285 } 286 } 287 } 288 }; 289 290 /** 291 * By default, the Choreographer is used to provide timing for frame callbacks. A custom 292 * provider can be used here to provide different timing pulse. 293 */ setProvider(AnimationFrameCallbackProvider provider)294 public void setProvider(AnimationFrameCallbackProvider provider) { 295 if (provider == null) { 296 mProvider = new MyFrameCallbackProvider(); 297 } else { 298 mProvider = provider; 299 } 300 } 301 getProvider()302 private AnimationFrameCallbackProvider getProvider() { 303 if (mProvider == null) { 304 mProvider = new MyFrameCallbackProvider(); 305 } 306 return mProvider; 307 } 308 309 /** 310 * Register to get a callback on the next frame after the delay. 311 */ addAnimationFrameCallback(final AnimationFrameCallback callback, long delay)312 public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) { 313 if (mAnimationCallbacks.size() == 0) { 314 getProvider().postFrameCallback(mFrameCallback); 315 } 316 if (!mAnimationCallbacks.contains(callback)) { 317 mAnimationCallbacks.add(callback); 318 } 319 320 if (delay > 0) { 321 mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay)); 322 } 323 } 324 325 /** 326 * Register to get a one shot callback for frame commit timing. Frame commit timing is the 327 * time *after* traversals are done, as opposed to the animation frame timing, which is 328 * before any traversals. This timing can be used to adjust the start time of an animation 329 * when expensive traversals create big delta between the animation frame timing and the time 330 * that animation is first shown on screen. 331 * 332 * Note this should only be called when the animation has already registered to receive 333 * animation frame callbacks. This callback will be guaranteed to happen *after* the next 334 * animation frame callback. 335 */ addOneShotCommitCallback(final AnimationFrameCallback callback)336 public void addOneShotCommitCallback(final AnimationFrameCallback callback) { 337 if (!mCommitCallbacks.contains(callback)) { 338 mCommitCallbacks.add(callback); 339 } 340 } 341 342 /** 343 * Removes the given callback from the list, so it will no longer be called for frame related 344 * timing. 345 */ removeCallback(AnimationFrameCallback callback)346 public void removeCallback(AnimationFrameCallback callback) { 347 mCommitCallbacks.remove(callback); 348 mDelayedCallbackStartTime.remove(callback); 349 int id = mAnimationCallbacks.indexOf(callback); 350 if (id >= 0) { 351 mAnimationCallbacks.set(id, null); 352 mListDirty = true; 353 } 354 } 355 356 /** 357 * Returns the vsyncId of last animation frame if the given {@param currentVsyncId} matches 358 * the vsyncId from the end callback of animation. Otherwise it returns the given vsyncId. 359 * It only takes effect if {@link #postEndAnimationCallback(Runnable)} is called. 360 */ getLastAnimationFrameVsyncId(long currentVsyncId)361 public long getLastAnimationFrameVsyncId(long currentVsyncId) { 362 return currentVsyncId == mEndAnimationFrameVsyncId && mLastAnimationFrameVsyncId != 0 363 ? mLastAnimationFrameVsyncId : currentVsyncId; 364 } 365 366 /** Runs the given callback on next frame to notify the end of the animation. */ postEndAnimationCallback(Runnable notifyEndAnimation)367 public void postEndAnimationCallback(Runnable notifyEndAnimation) { 368 if (mPendingEndAnimationListeners == null) { 369 mPendingEndAnimationListeners = new ArrayList<>(); 370 } 371 mPendingEndAnimationListeners.add(notifyEndAnimation); 372 if (mPendingEndAnimationListeners.size() > 1) { 373 return; 374 } 375 final Choreographer choreographer = Choreographer.getInstance(); 376 mLastAnimationFrameVsyncId = choreographer.getVsyncId(); 377 getProvider().postFrameCallback(frame -> { 378 mEndAnimationFrameVsyncId = choreographer.getVsyncId(); 379 // The animation listeners can only get vsyncId of last animation frame in this frame 380 // by getLastAnimationFrameVsyncId(currentVsyncId). 381 while (mPendingEndAnimationListeners.size() > 0) { 382 mPendingEndAnimationListeners.remove(0).run(); 383 } 384 mEndAnimationFrameVsyncId = 0; 385 mLastAnimationFrameVsyncId = 0; 386 }); 387 } 388 removePendingEndAnimationCallback(Runnable notifyEndAnimation)389 void removePendingEndAnimationCallback(Runnable notifyEndAnimation) { 390 if (mPendingEndAnimationListeners != null) { 391 mPendingEndAnimationListeners.remove(notifyEndAnimation); 392 } 393 } 394 doAnimationFrame(long frameTime)395 private void doAnimationFrame(long frameTime) { 396 long currentTime = SystemClock.uptimeMillis(); 397 final int size = mAnimationCallbacks.size(); 398 for (int i = 0; i < size; i++) { 399 final AnimationFrameCallback callback = mAnimationCallbacks.get(i); 400 if (callback == null) { 401 continue; 402 } 403 if (isCallbackDue(callback, currentTime)) { 404 callback.doAnimationFrame(frameTime); 405 if (mCommitCallbacks.contains(callback)) { 406 getProvider().postCommitCallback(new Runnable() { 407 @Override 408 public void run() { 409 commitAnimationFrame(callback, getProvider().getFrameTime()); 410 } 411 }); 412 } 413 } 414 } 415 cleanUpList(); 416 } 417 commitAnimationFrame(AnimationFrameCallback callback, long frameTime)418 private void commitAnimationFrame(AnimationFrameCallback callback, long frameTime) { 419 if (!mDelayedCallbackStartTime.containsKey(callback) && 420 mCommitCallbacks.contains(callback)) { 421 callback.commitAnimationFrame(frameTime); 422 mCommitCallbacks.remove(callback); 423 } 424 } 425 426 /** 427 * Remove the callbacks from mDelayedCallbackStartTime once they have passed the initial delay 428 * so that they can start getting frame callbacks. 429 * 430 * @return true if they have passed the initial delay or have no delay, false otherwise. 431 */ isCallbackDue(AnimationFrameCallback callback, long currentTime)432 private boolean isCallbackDue(AnimationFrameCallback callback, long currentTime) { 433 Long startTime = mDelayedCallbackStartTime.get(callback); 434 if (startTime == null) { 435 return true; 436 } 437 if (startTime < currentTime) { 438 mDelayedCallbackStartTime.remove(callback); 439 return true; 440 } 441 return false; 442 } 443 444 /** 445 * Return the number of callbacks that have registered for frame callbacks. 446 */ getAnimationCount()447 public static int getAnimationCount() { 448 AnimationHandler handler = sTestHandler; 449 if (handler == null) { 450 handler = sAnimatorHandler.get(); 451 } 452 if (handler == null) { 453 return 0; 454 } 455 return handler.getCallbackSize(); 456 } 457 setFrameDelay(long delay)458 public static void setFrameDelay(long delay) { 459 getInstance().getProvider().setFrameDelay(delay); 460 } 461 getFrameDelay()462 public static long getFrameDelay() { 463 return getInstance().getProvider().getFrameDelay(); 464 } 465 autoCancelBasedOn(ObjectAnimator objectAnimator)466 void autoCancelBasedOn(ObjectAnimator objectAnimator) { 467 for (int i = mAnimationCallbacks.size() - 1; i >= 0; i--) { 468 AnimationFrameCallback cb = mAnimationCallbacks.get(i); 469 if (cb == null) { 470 continue; 471 } 472 if (objectAnimator.shouldAutoCancel(cb)) { 473 ((Animator) mAnimationCallbacks.get(i)).cancel(); 474 } 475 } 476 } 477 cleanUpList()478 private void cleanUpList() { 479 if (mListDirty) { 480 for (int i = mAnimationCallbacks.size() - 1; i >= 0; i--) { 481 if (mAnimationCallbacks.get(i) == null) { 482 mAnimationCallbacks.remove(i); 483 } 484 } 485 mListDirty = false; 486 } 487 } 488 getCallbackSize()489 private int getCallbackSize() { 490 int count = 0; 491 int size = mAnimationCallbacks.size(); 492 for (int i = size - 1; i >= 0; i--) { 493 if (mAnimationCallbacks.get(i) != null) { 494 count++; 495 } 496 } 497 return count; 498 } 499 500 /** 501 * Default provider of timing pulse that uses Choreographer for frame callbacks. 502 */ 503 private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider { 504 505 final Choreographer mChoreographer = Choreographer.getInstance(); 506 507 @Override postFrameCallback(Choreographer.FrameCallback callback)508 public void postFrameCallback(Choreographer.FrameCallback callback) { 509 mChoreographer.postFrameCallback(callback); 510 } 511 512 @Override postCommitCallback(Runnable runnable)513 public void postCommitCallback(Runnable runnable) { 514 mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, runnable, null); 515 } 516 517 @Override getFrameTime()518 public long getFrameTime() { 519 return mChoreographer.getFrameTime(); 520 } 521 522 @Override getFrameDelay()523 public long getFrameDelay() { 524 return Choreographer.getFrameDelay(); 525 } 526 527 @Override setFrameDelay(long delay)528 public void setFrameDelay(long delay) { 529 Choreographer.setFrameDelay(delay); 530 } 531 } 532 533 /** 534 * Callbacks that receives notifications for animation timing and frame commit timing. 535 * @hide 536 */ 537 public interface AnimationFrameCallback { 538 /** 539 * Run animation based on the frame time. 540 * @param frameTime The frame start time, in the {@link SystemClock#uptimeMillis()} time 541 * base. 542 * @return if the animation has finished. 543 */ doAnimationFrame(long frameTime)544 boolean doAnimationFrame(long frameTime); 545 546 /** 547 * This notifies the callback of frame commit time. Frame commit time is the time after 548 * traversals happen, as opposed to the normal animation frame time that is before 549 * traversals. This is used to compensate expensive traversals that happen as the 550 * animation starts. When traversals take a long time to complete, the rendering of the 551 * initial frame will be delayed (by a long time). But since the startTime of the 552 * animation is set before the traversal, by the time of next frame, a lot of time would 553 * have passed since startTime was set, the animation will consequently skip a few frames 554 * to respect the new frameTime. By having the commit time, we can adjust the start time to 555 * when the first frame was drawn (after any expensive traversals) so that no frames 556 * will be skipped. 557 * 558 * @param frameTime The frame time after traversals happen, if any, in the 559 * {@link SystemClock#uptimeMillis()} time base. 560 */ commitAnimationFrame(long frameTime)561 void commitAnimationFrame(long frameTime); 562 } 563 564 /** 565 * The intention for having this interface is to increase the testability of ValueAnimator. 566 * Specifically, we can have a custom implementation of the interface below and provide 567 * timing pulse without using Choreographer. That way we could use any arbitrary interval for 568 * our timing pulse in the tests. 569 * 570 * @hide 571 */ 572 public interface AnimationFrameCallbackProvider { postFrameCallback(Choreographer.FrameCallback callback)573 void postFrameCallback(Choreographer.FrameCallback callback); postCommitCallback(Runnable runnable)574 void postCommitCallback(Runnable runnable); getFrameTime()575 long getFrameTime(); getFrameDelay()576 long getFrameDelay(); setFrameDelay(long delay)577 void setFrameDelay(long delay); 578 } 579 } 580