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