• 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.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