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