blob: 7778c350146c6f31235266194f87937656039ce1 [file] [log] [blame]
Doris Liu3618d302015-08-14 11:11:08 -07001/*
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
17package android.animation;
18
19import android.os.SystemClock;
20import android.util.ArrayMap;
21import android.view.Choreographer;
22
23import 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
Doris Liuf310e882015-08-27 13:40:07 -070032 * AnimationFrameCallbackProvider can be set on the handler to provide timing pulse that
Doris Liu3618d302015-08-14 11:11:08 -070033 * may be independent of UI frame update. This could be useful in testing.
34 *
35 * @hide
36 */
37public 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
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 */
75 public void setProvider(AnimationFrameCallbackProvider provider) {
76 if (provider == null) {
77 mProvider = new MyFrameCallbackProvider();
78 } else {
79 mProvider = provider;
80 }
81 }
82
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 */
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 */
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 */
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
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
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 */
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 */
189 public static int getAnimationCount() {
190 AnimationHandler handler = sAnimatorHandler.get();
191 if (handler == null) {
192 return 0;
193 }
194 return handler.getCallbackSize();
195 }
196
197 public static void setFrameDelay(long delay) {
198 getInstance().getProvider().setFrameDelay(delay);
199 }
200
201 public static long getFrameDelay() {
202 return getInstance().getProvider().getFrameDelay();
203 }
204
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
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
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
247 public void postFrameCallback(Choreographer.FrameCallback callback) {
248 mChoreographer.postFrameCallback(callback);
249 }
250
251 @Override
252 public void postCommitCallback(Runnable runnable) {
253 mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, runnable, null);
254 }
255
256 @Override
257 public long getFrameTime() {
258 return mChoreographer.getFrameTime();
259 }
260
261 @Override
262 public long getFrameDelay() {
263 return Choreographer.getFrameDelay();
264 }
265
266 @Override
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 */
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 */
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 {
310 void postFrameCallback(Choreographer.FrameCallback callback);
311 void postCommitCallback(Runnable runnable);
312 long getFrameTime();
313 long getFrameDelay();
314 void setFrameDelay(long delay);
315 }
316}