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 private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() { 85 @Override 86 public void doFrame(long frameTimeNanos) { 87 doAnimationFrame(getProvider().getFrameTime()); 88 if (mAnimationCallbacks.size() > 0) { 89 getProvider().postFrameCallback(this); 90 } 91 } 92 }; 93 94 public final static ThreadLocal<AnimationHandler> sAnimatorHandler = new ThreadLocal<>(); 95 private static AnimationHandler sTestHandler = null; 96 private boolean mListDirty = false; 97 getInstance()98 public static AnimationHandler getInstance() { 99 if (sTestHandler != null) { 100 return sTestHandler; 101 } 102 if (sAnimatorHandler.get() == null) { 103 sAnimatorHandler.set(new AnimationHandler()); 104 } 105 return sAnimatorHandler.get(); 106 } 107 108 /** 109 * Sets an instance that will be returned by {@link #getInstance()} on every thread. 110 * @return the previously active test handler, if any. 111 * @hide 112 */ setTestHandler(@ullable AnimationHandler handler)113 public static @Nullable AnimationHandler setTestHandler(@Nullable AnimationHandler handler) { 114 AnimationHandler oldHandler = sTestHandler; 115 sTestHandler = handler; 116 return oldHandler; 117 } 118 119 /** 120 * System property that controls the behavior of pausing infinite animators when an app 121 * is moved to the background. 122 * 123 * @return the value of 'framework.pause_bg_animations.enabled' system property 124 */ isPauseBgAnimationsEnabledInSystemProperties()125 private static boolean isPauseBgAnimationsEnabledInSystemProperties() { 126 if (sOverrideAnimatorPausingSystemProperty) return sAnimatorPausingEnabled; 127 return SystemProperties 128 .getBoolean("framework.pause_bg_animations.enabled", true); 129 } 130 131 /** 132 * Disable the default behavior of pausing infinite animators when 133 * apps go into the background. 134 * 135 * @param enable Enable (default behavior) or disable background pausing behavior. 136 */ setAnimatorPausingEnabled(boolean enable)137 public static void setAnimatorPausingEnabled(boolean enable) { 138 sAnimatorPausingEnabled = enable; 139 } 140 141 /** 142 * Prevents the setAnimatorPausingEnabled behavior from being overridden 143 * by the 'framework.pause_bg_animations.enabled' system property value. 144 * 145 * This is for testing purposes only. 146 * 147 * @param enable Enable or disable (default behavior) overriding the system 148 * property. 149 */ setOverrideAnimatorPausingSystemProperty(boolean enable)150 public static void setOverrideAnimatorPausingSystemProperty(boolean enable) { 151 sOverrideAnimatorPausingSystemProperty = enable; 152 } 153 154 /** 155 * This is called when a window goes away. We should remove 156 * it from the requestors list to ensure that we are counting requests correctly and not 157 * tracking obsolete+enabled requestors. 158 */ removeRequestor(Object requestor)159 public static void removeRequestor(Object requestor) { 160 getInstance().requestAnimatorsEnabledImpl(false, requestor); 161 if (LOCAL_LOGV) { 162 Log.v(TAG, "removeRequestor for " + requestor); 163 } 164 } 165 166 /** 167 * This method is called from ViewRootImpl or WallpaperService when either a window is no 168 * longer visible (enable == false) or when a window becomes visible (enable == true). 169 * If animators are not properly disabled when activities are backgrounded, it can lead to 170 * unnecessary processing, particularly for infinite animators, as the system will continue 171 * to pulse timing events even though the results are not visible. As a workaround, we 172 * pause all un-paused infinite animators, and resume them when any window in the process 173 * becomes visible. 174 */ requestAnimatorsEnabled(boolean enable, Object requestor)175 public static void requestAnimatorsEnabled(boolean enable, Object requestor) { 176 getInstance().requestAnimatorsEnabledImpl(enable, requestor); 177 } 178 requestAnimatorsEnabledImpl(boolean enable, Object requestor)179 private void requestAnimatorsEnabledImpl(boolean enable, Object requestor) { 180 boolean wasEmpty = mAnimatorRequestors.isEmpty(); 181 setAnimatorPausingEnabled(isPauseBgAnimationsEnabledInSystemProperties()); 182 synchronized (mAnimatorRequestors) { 183 // Only store WeakRef objects to avoid leaks 184 if (enable) { 185 // First, check whether such a reference is already on the list 186 WeakReference<Object> weakRef = null; 187 for (int i = mAnimatorRequestors.size() - 1; i >= 0; --i) { 188 WeakReference<Object> ref = mAnimatorRequestors.get(i); 189 Object referent = ref.get(); 190 if (referent == requestor) { 191 weakRef = ref; 192 } else if (referent == null) { 193 // Remove any reference that has been cleared 194 mAnimatorRequestors.remove(i); 195 } 196 } 197 if (weakRef == null) { 198 weakRef = new WeakReference<>(requestor); 199 mAnimatorRequestors.add(weakRef); 200 } 201 } else { 202 for (int i = mAnimatorRequestors.size() - 1; i >= 0; --i) { 203 WeakReference<Object> ref = mAnimatorRequestors.get(i); 204 Object referent = ref.get(); 205 if (referent == requestor || referent == null) { 206 // remove requested item or item that has been cleared 207 mAnimatorRequestors.remove(i); 208 } 209 } 210 // If a reference to the requestor wasn't in the list, nothing to remove 211 } 212 } 213 if (!sAnimatorPausingEnabled) { 214 // Resume any animators that have been paused in the meantime, otherwise noop 215 // Leave logic above so that if pausing gets re-enabled, the state of the requestors 216 // list is valid 217 resumeAnimators(); 218 return; 219 } 220 boolean isEmpty = mAnimatorRequestors.isEmpty(); 221 if (wasEmpty != isEmpty) { 222 // only paused/resume animators if there was a visibility change 223 if (!isEmpty) { 224 // If any requestors are enabled, resume currently paused animators 225 resumeAnimators(); 226 } else { 227 // Wait before pausing to avoid thrashing animator state for temporary backgrounding 228 Choreographer.getInstance().postFrameCallbackDelayed(mPauser, 229 Animator.getBackgroundPauseDelay()); 230 } 231 } 232 if (LOCAL_LOGV) { 233 Log.v(TAG, (enable ? "enable" : "disable") + " animators for " + requestor 234 + " with pauseDelay of " + Animator.getBackgroundPauseDelay()); 235 for (int i = 0; i < mAnimatorRequestors.size(); ++i) { 236 Log.v(TAG, "animatorRequestors " + i + " = " 237 + mAnimatorRequestors.get(i) + " with referent " 238 + mAnimatorRequestors.get(i).get()); 239 } 240 } 241 } 242 resumeAnimators()243 private void resumeAnimators() { 244 Choreographer.getInstance().removeFrameCallback(mPauser); 245 for (int i = mPausedAnimators.size() - 1; i >= 0; --i) { 246 mPausedAnimators.get(i).resume(); 247 } 248 mPausedAnimators.clear(); 249 } 250 251 private Choreographer.FrameCallback mPauser = frameTimeNanos -> { 252 if (mAnimatorRequestors.size() > 0) { 253 // something enabled animators since this callback was scheduled - bail 254 return; 255 } 256 for (int i = 0; i < mAnimationCallbacks.size(); ++i) { 257 AnimationFrameCallback callback = mAnimationCallbacks.get(i); 258 if (callback instanceof Animator) { 259 Animator animator = ((Animator) callback); 260 if (animator.getTotalDuration() == Animator.DURATION_INFINITE 261 && !animator.isPaused()) { 262 mPausedAnimators.add(animator); 263 animator.pause(); 264 } 265 } 266 } 267 }; 268 269 /** 270 * By default, the Choreographer is used to provide timing for frame callbacks. A custom 271 * provider can be used here to provide different timing pulse. 272 */ setProvider(AnimationFrameCallbackProvider provider)273 public void setProvider(AnimationFrameCallbackProvider provider) { 274 if (provider == null) { 275 mProvider = new MyFrameCallbackProvider(); 276 } else { 277 mProvider = provider; 278 } 279 } 280 getProvider()281 private AnimationFrameCallbackProvider getProvider() { 282 if (mProvider == null) { 283 mProvider = new MyFrameCallbackProvider(); 284 } 285 return mProvider; 286 } 287 288 /** 289 * Register to get a callback on the next frame after the delay. 290 */ addAnimationFrameCallback(final AnimationFrameCallback callback, long delay)291 public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) { 292 if (mAnimationCallbacks.size() == 0) { 293 getProvider().postFrameCallback(mFrameCallback); 294 } 295 if (!mAnimationCallbacks.contains(callback)) { 296 mAnimationCallbacks.add(callback); 297 } 298 299 if (delay > 0) { 300 mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay)); 301 } 302 } 303 304 /** 305 * Register to get a one shot callback for frame commit timing. Frame commit timing is the 306 * time *after* traversals are done, as opposed to the animation frame timing, which is 307 * before any traversals. This timing can be used to adjust the start time of an animation 308 * when expensive traversals create big delta between the animation frame timing and the time 309 * that animation is first shown on screen. 310 * 311 * Note this should only be called when the animation has already registered to receive 312 * animation frame callbacks. This callback will be guaranteed to happen *after* the next 313 * animation frame callback. 314 */ addOneShotCommitCallback(final AnimationFrameCallback callback)315 public void addOneShotCommitCallback(final AnimationFrameCallback callback) { 316 if (!mCommitCallbacks.contains(callback)) { 317 mCommitCallbacks.add(callback); 318 } 319 } 320 321 /** 322 * Removes the given callback from the list, so it will no longer be called for frame related 323 * timing. 324 */ removeCallback(AnimationFrameCallback callback)325 public void removeCallback(AnimationFrameCallback callback) { 326 mCommitCallbacks.remove(callback); 327 mDelayedCallbackStartTime.remove(callback); 328 int id = mAnimationCallbacks.indexOf(callback); 329 if (id >= 0) { 330 mAnimationCallbacks.set(id, null); 331 mListDirty = true; 332 } 333 } 334 doAnimationFrame(long frameTime)335 private void doAnimationFrame(long frameTime) { 336 long currentTime = SystemClock.uptimeMillis(); 337 final int size = mAnimationCallbacks.size(); 338 for (int i = 0; i < size; i++) { 339 final AnimationFrameCallback callback = mAnimationCallbacks.get(i); 340 if (callback == null) { 341 continue; 342 } 343 if (isCallbackDue(callback, currentTime)) { 344 callback.doAnimationFrame(frameTime); 345 if (mCommitCallbacks.contains(callback)) { 346 getProvider().postCommitCallback(new Runnable() { 347 @Override 348 public void run() { 349 commitAnimationFrame(callback, getProvider().getFrameTime()); 350 } 351 }); 352 } 353 } 354 } 355 cleanUpList(); 356 } 357 commitAnimationFrame(AnimationFrameCallback callback, long frameTime)358 private void commitAnimationFrame(AnimationFrameCallback callback, long frameTime) { 359 if (!mDelayedCallbackStartTime.containsKey(callback) && 360 mCommitCallbacks.contains(callback)) { 361 callback.commitAnimationFrame(frameTime); 362 mCommitCallbacks.remove(callback); 363 } 364 } 365 366 /** 367 * Remove the callbacks from mDelayedCallbackStartTime once they have passed the initial delay 368 * so that they can start getting frame callbacks. 369 * 370 * @return true if they have passed the initial delay or have no delay, false otherwise. 371 */ isCallbackDue(AnimationFrameCallback callback, long currentTime)372 private boolean isCallbackDue(AnimationFrameCallback callback, long currentTime) { 373 Long startTime = mDelayedCallbackStartTime.get(callback); 374 if (startTime == null) { 375 return true; 376 } 377 if (startTime < currentTime) { 378 mDelayedCallbackStartTime.remove(callback); 379 return true; 380 } 381 return false; 382 } 383 384 /** 385 * Return the number of callbacks that have registered for frame callbacks. 386 */ getAnimationCount()387 public static int getAnimationCount() { 388 AnimationHandler handler = sTestHandler; 389 if (handler == null) { 390 handler = sAnimatorHandler.get(); 391 } 392 if (handler == null) { 393 return 0; 394 } 395 return handler.getCallbackSize(); 396 } 397 setFrameDelay(long delay)398 public static void setFrameDelay(long delay) { 399 getInstance().getProvider().setFrameDelay(delay); 400 } 401 getFrameDelay()402 public static long getFrameDelay() { 403 return getInstance().getProvider().getFrameDelay(); 404 } 405 autoCancelBasedOn(ObjectAnimator objectAnimator)406 void autoCancelBasedOn(ObjectAnimator objectAnimator) { 407 for (int i = mAnimationCallbacks.size() - 1; i >= 0; i--) { 408 AnimationFrameCallback cb = mAnimationCallbacks.get(i); 409 if (cb == null) { 410 continue; 411 } 412 if (objectAnimator.shouldAutoCancel(cb)) { 413 ((Animator) mAnimationCallbacks.get(i)).cancel(); 414 } 415 } 416 } 417 cleanUpList()418 private void cleanUpList() { 419 if (mListDirty) { 420 for (int i = mAnimationCallbacks.size() - 1; i >= 0; i--) { 421 if (mAnimationCallbacks.get(i) == null) { 422 mAnimationCallbacks.remove(i); 423 } 424 } 425 mListDirty = false; 426 } 427 } 428 getCallbackSize()429 private int getCallbackSize() { 430 int count = 0; 431 int size = mAnimationCallbacks.size(); 432 for (int i = size - 1; i >= 0; i--) { 433 if (mAnimationCallbacks.get(i) != null) { 434 count++; 435 } 436 } 437 return count; 438 } 439 440 /** 441 * Default provider of timing pulse that uses Choreographer for frame callbacks. 442 */ 443 private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider { 444 445 final Choreographer mChoreographer = Choreographer.getInstance(); 446 447 @Override postFrameCallback(Choreographer.FrameCallback callback)448 public void postFrameCallback(Choreographer.FrameCallback callback) { 449 mChoreographer.postFrameCallback(callback); 450 } 451 452 @Override postCommitCallback(Runnable runnable)453 public void postCommitCallback(Runnable runnable) { 454 mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, runnable, null); 455 } 456 457 @Override getFrameTime()458 public long getFrameTime() { 459 return mChoreographer.getFrameTime(); 460 } 461 462 @Override getFrameDelay()463 public long getFrameDelay() { 464 return Choreographer.getFrameDelay(); 465 } 466 467 @Override setFrameDelay(long delay)468 public void setFrameDelay(long delay) { 469 Choreographer.setFrameDelay(delay); 470 } 471 } 472 473 /** 474 * Callbacks that receives notifications for animation timing and frame commit timing. 475 * @hide 476 */ 477 public interface AnimationFrameCallback { 478 /** 479 * Run animation based on the frame time. 480 * @param frameTime The frame start time, in the {@link SystemClock#uptimeMillis()} time 481 * base. 482 * @return if the animation has finished. 483 */ doAnimationFrame(long frameTime)484 boolean doAnimationFrame(long frameTime); 485 486 /** 487 * This notifies the callback of frame commit time. Frame commit time is the time after 488 * traversals happen, as opposed to the normal animation frame time that is before 489 * traversals. This is used to compensate expensive traversals that happen as the 490 * animation starts. When traversals take a long time to complete, the rendering of the 491 * initial frame will be delayed (by a long time). But since the startTime of the 492 * animation is set before the traversal, by the time of next frame, a lot of time would 493 * have passed since startTime was set, the animation will consequently skip a few frames 494 * to respect the new frameTime. By having the commit time, we can adjust the start time to 495 * when the first frame was drawn (after any expensive traversals) so that no frames 496 * will be skipped. 497 * 498 * @param frameTime The frame time after traversals happen, if any, in the 499 * {@link SystemClock#uptimeMillis()} time base. 500 */ commitAnimationFrame(long frameTime)501 void commitAnimationFrame(long frameTime); 502 } 503 504 /** 505 * The intention for having this interface is to increase the testability of ValueAnimator. 506 * Specifically, we can have a custom implementation of the interface below and provide 507 * timing pulse without using Choreographer. That way we could use any arbitrary interval for 508 * our timing pulse in the tests. 509 * 510 * @hide 511 */ 512 public interface AnimationFrameCallbackProvider { postFrameCallback(Choreographer.FrameCallback callback)513 void postFrameCallback(Choreographer.FrameCallback callback); postCommitCallback(Runnable runnable)514 void postCommitCallback(Runnable runnable); getFrameTime()515 long getFrameTime(); getFrameDelay()516 long getFrameDelay(); setFrameDelay(long delay)517 void setFrameDelay(long delay); 518 } 519 } 520