• 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.util.ArrayMap;
21  import android.view.Choreographer;
22  
23  import java.util.ArrayList;
24  
25  /**
26   * This custom, static handler handles the timing pulse that is shared by all active
27   * ValueAnimators. This approach ensures that the setting of animation values will happen on the
28   * same thread that animations start on, and that all animations will share the same times for
29   * calculating their values, which makes synchronizing animations possible.
30   *
31   * The handler uses the Choreographer by default for doing periodic callbacks. A custom
32   * AnimationFrameCallbackProvider can be set on the handler to provide timing pulse that
33   * may be independent of UI frame update. This could be useful in testing.
34   *
35   * @hide
36   */
37  public class AnimationHandler {
38      /**
39       * Internal per-thread collections used to avoid set collisions as animations start and end
40       * while being processed.
41       * @hide
42       */
43      private final ArrayMap<AnimationFrameCallback, Long> mDelayedCallbackStartTime =
44              new ArrayMap<>();
45      private final ArrayList<AnimationFrameCallback> mAnimationCallbacks =
46              new ArrayList<>();
47      private final ArrayList<AnimationFrameCallback> mCommitCallbacks =
48              new ArrayList<>();
49      private AnimationFrameCallbackProvider mProvider;
50  
51      private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
52          @Override
53          public void doFrame(long frameTimeNanos) {
54              doAnimationFrame(getProvider().getFrameTime());
55              if (mAnimationCallbacks.size() > 0) {
56                  getProvider().postFrameCallback(this);
57              }
58          }
59      };
60  
61      public final static ThreadLocal<AnimationHandler> sAnimatorHandler = new ThreadLocal<>();
62      private boolean mListDirty = false;
63  
getInstance()64      public static AnimationHandler getInstance() {
65          if (sAnimatorHandler.get() == null) {
66              sAnimatorHandler.set(new AnimationHandler());
67          }
68          return sAnimatorHandler.get();
69      }
70  
71      /**
72       * By default, the Choreographer is used to provide timing for frame callbacks. A custom
73       * provider can be used here to provide different timing pulse.
74       */
setProvider(AnimationFrameCallbackProvider provider)75      public void setProvider(AnimationFrameCallbackProvider provider) {
76          if (provider == null) {
77              mProvider = new MyFrameCallbackProvider();
78          } else {
79              mProvider = provider;
80          }
81      }
82  
getProvider()83      private AnimationFrameCallbackProvider getProvider() {
84          if (mProvider == null) {
85              mProvider = new MyFrameCallbackProvider();
86          }
87          return mProvider;
88      }
89  
90      /**
91       * Register to get a callback on the next frame after the delay.
92       */
addAnimationFrameCallback(final AnimationFrameCallback callback, long delay)93      public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
94          if (mAnimationCallbacks.size() == 0) {
95              getProvider().postFrameCallback(mFrameCallback);
96          }
97          if (!mAnimationCallbacks.contains(callback)) {
98              mAnimationCallbacks.add(callback);
99          }
100  
101          if (delay > 0) {
102              mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
103          }
104      }
105  
106      /**
107       * Register to get a one shot callback for frame commit timing. Frame commit timing is the
108       * time *after* traversals are done, as opposed to the animation frame timing, which is
109       * before any traversals. This timing can be used to adjust the start time of an animation
110       * when expensive traversals create big delta between the animation frame timing and the time
111       * that animation is first shown on screen.
112       *
113       * Note this should only be called when the animation has already registered to receive
114       * animation frame callbacks. This callback will be guaranteed to happen *after* the next
115       * animation frame callback.
116       */
addOneShotCommitCallback(final AnimationFrameCallback callback)117      public void addOneShotCommitCallback(final AnimationFrameCallback callback) {
118          if (!mCommitCallbacks.contains(callback)) {
119              mCommitCallbacks.add(callback);
120          }
121      }
122  
123      /**
124       * Removes the given callback from the list, so it will no longer be called for frame related
125       * timing.
126       */
removeCallback(AnimationFrameCallback callback)127      public void removeCallback(AnimationFrameCallback callback) {
128          mCommitCallbacks.remove(callback);
129          mDelayedCallbackStartTime.remove(callback);
130          int id = mAnimationCallbacks.indexOf(callback);
131          if (id >= 0) {
132              mAnimationCallbacks.set(id, null);
133              mListDirty = true;
134          }
135      }
136  
doAnimationFrame(long frameTime)137      private void doAnimationFrame(long frameTime) {
138          int size = mAnimationCallbacks.size();
139          long currentTime = SystemClock.uptimeMillis();
140          for (int i = 0; i < size; i++) {
141              final AnimationFrameCallback callback = mAnimationCallbacks.get(i);
142              if (callback == null) {
143                  continue;
144              }
145              if (isCallbackDue(callback, currentTime)) {
146                  callback.doAnimationFrame(frameTime);
147                  if (mCommitCallbacks.contains(callback)) {
148                      getProvider().postCommitCallback(new Runnable() {
149                          @Override
150                          public void run() {
151                              commitAnimationFrame(callback, getProvider().getFrameTime());
152                          }
153                      });
154                  }
155              }
156          }
157          cleanUpList();
158      }
159  
commitAnimationFrame(AnimationFrameCallback callback, long frameTime)160      private void commitAnimationFrame(AnimationFrameCallback callback, long frameTime) {
161          if (!mDelayedCallbackStartTime.containsKey(callback) &&
162                  mCommitCallbacks.contains(callback)) {
163              callback.commitAnimationFrame(frameTime);
164              mCommitCallbacks.remove(callback);
165          }
166      }
167  
168      /**
169       * Remove the callbacks from mDelayedCallbackStartTime once they have passed the initial delay
170       * so that they can start getting frame callbacks.
171       *
172       * @return true if they have passed the initial delay or have no delay, false otherwise.
173       */
isCallbackDue(AnimationFrameCallback callback, long currentTime)174      private boolean isCallbackDue(AnimationFrameCallback callback, long currentTime) {
175          Long startTime = mDelayedCallbackStartTime.get(callback);
176          if (startTime == null) {
177              return true;
178          }
179          if (startTime < currentTime) {
180              mDelayedCallbackStartTime.remove(callback);
181              return true;
182          }
183          return false;
184      }
185  
186      /**
187       * Return the number of callbacks that have registered for frame callbacks.
188       */
getAnimationCount()189      public static int getAnimationCount() {
190          AnimationHandler handler = sAnimatorHandler.get();
191          if (handler == null) {
192              return 0;
193          }
194          return handler.getCallbackSize();
195      }
196  
setFrameDelay(long delay)197      public static void setFrameDelay(long delay) {
198          getInstance().getProvider().setFrameDelay(delay);
199      }
200  
getFrameDelay()201      public static long getFrameDelay() {
202          return getInstance().getProvider().getFrameDelay();
203      }
204  
autoCancelBasedOn(ObjectAnimator objectAnimator)205      void autoCancelBasedOn(ObjectAnimator objectAnimator) {
206          for (int i = mAnimationCallbacks.size() - 1; i >= 0; i--) {
207              AnimationFrameCallback cb = mAnimationCallbacks.get(i);
208              if (cb == null) {
209                  continue;
210              }
211              if (objectAnimator.shouldAutoCancel(cb)) {
212                  ((Animator) mAnimationCallbacks.get(i)).cancel();
213              }
214          }
215      }
216  
cleanUpList()217      private void cleanUpList() {
218          if (mListDirty) {
219              for (int i = mAnimationCallbacks.size() - 1; i >= 0; i--) {
220                  if (mAnimationCallbacks.get(i) == null) {
221                      mAnimationCallbacks.remove(i);
222                  }
223              }
224              mListDirty = false;
225          }
226      }
227  
getCallbackSize()228      private int getCallbackSize() {
229          int count = 0;
230          int size = mAnimationCallbacks.size();
231          for (int i = size - 1; i >= 0; i--) {
232              if (mAnimationCallbacks.get(i) != null) {
233                  count++;
234              }
235          }
236          return count;
237      }
238  
239      /**
240       * Default provider of timing pulse that uses Choreographer for frame callbacks.
241       */
242      private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider {
243  
244          final Choreographer mChoreographer = Choreographer.getInstance();
245  
246          @Override
postFrameCallback(Choreographer.FrameCallback callback)247          public void postFrameCallback(Choreographer.FrameCallback callback) {
248              mChoreographer.postFrameCallback(callback);
249          }
250  
251          @Override
postCommitCallback(Runnable runnable)252          public void postCommitCallback(Runnable runnable) {
253              mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, runnable, null);
254          }
255  
256          @Override
getFrameTime()257          public long getFrameTime() {
258              return mChoreographer.getFrameTime();
259          }
260  
261          @Override
getFrameDelay()262          public long getFrameDelay() {
263              return Choreographer.getFrameDelay();
264          }
265  
266          @Override
setFrameDelay(long delay)267          public void setFrameDelay(long delay) {
268              Choreographer.setFrameDelay(delay);
269          }
270      }
271  
272      /**
273       * Callbacks that receives notifications for animation timing and frame commit timing.
274       */
275      interface AnimationFrameCallback {
276          /**
277           * Run animation based on the frame time.
278           * @param frameTime The frame start time, in the {@link SystemClock#uptimeMillis()} time
279           *                  base.
280           */
doAnimationFrame(long frameTime)281          void doAnimationFrame(long frameTime);
282  
283          /**
284           * This notifies the callback of frame commit time. Frame commit time is the time after
285           * traversals happen, as opposed to the normal animation frame time that is before
286           * traversals. This is used to compensate expensive traversals that happen as the
287           * animation starts. When traversals take a long time to complete, the rendering of the
288           * initial frame will be delayed (by a long time). But since the startTime of the
289           * animation is set before the traversal, by the time of next frame, a lot of time would
290           * have passed since startTime was set, the animation will consequently skip a few frames
291           * to respect the new frameTime. By having the commit time, we can adjust the start time to
292           * when the first frame was drawn (after any expensive traversals) so that no frames
293           * will be skipped.
294           *
295           * @param frameTime The frame time after traversals happen, if any, in the
296           *                  {@link SystemClock#uptimeMillis()} time base.
297           */
commitAnimationFrame(long frameTime)298          void commitAnimationFrame(long frameTime);
299      }
300  
301      /**
302       * The intention for having this interface is to increase the testability of ValueAnimator.
303       * Specifically, we can have a custom implementation of the interface below and provide
304       * timing pulse without using Choreographer. That way we could use any arbitrary interval for
305       * our timing pulse in the tests.
306       *
307       * @hide
308       */
309      public interface AnimationFrameCallbackProvider {
postFrameCallback(Choreographer.FrameCallback callback)310          void postFrameCallback(Choreographer.FrameCallback callback);
postCommitCallback(Runnable runnable)311          void postCommitCallback(Runnable runnable);
getFrameTime()312          long getFrameTime();
getFrameDelay()313          long getFrameDelay();
setFrameDelay(long delay)314          void setFrameDelay(long delay);
315      }
316  }
317