• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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