1 /* 2 * Copyright (C) 2018 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 androidx.core.animation; 18 19 import android.view.Choreographer; 20 21 import java.util.ArrayList; 22 23 /** 24 * This custom, static handler handles the timing pulse that is shared by all active 25 * ValueAnimators. This approach ensures that the setting of animation values will happen on the 26 * same thread that animations start on, and that all animations will share the same times for 27 * calculating their values, which makes synchronizing animations possible. 28 * 29 * The handler uses the Choreographer by default for doing periodic callbacks. A custom 30 * AnimationFrameCallbackProvider can be set on the handler to provide timing pulse that 31 * may be independent of UI frame update. This could be useful in testing. 32 */ 33 class AnimationHandler { 34 /** 35 * Callbacks that receives notifications for animation timing 36 */ 37 interface AnimationFrameCallback { 38 /** 39 * Run animation based on the frame time. 40 * @param frameTime The frame start time 41 */ doAnimationFrame(long frameTime)42 boolean doAnimationFrame(long frameTime); 43 } 44 45 /** 46 * This method notifies all the on-going animations of the new frame, so that 47 * they can update animation values as needed. 48 */ onAnimationFrame(long frameTime)49 void onAnimationFrame(long frameTime) { 50 AnimationHandler.this.doAnimationFrame(frameTime); 51 if (mAnimationCallbacks.size() > 0) { 52 mProvider.postFrameCallback(); 53 } 54 } 55 /** 56 * Internal per-thread collections used to avoid set collisions as animations start and end 57 * while being processed. 58 */ 59 60 public static final ThreadLocal<AnimationHandler> sAnimationHandler = new ThreadLocal<>(); 61 private static AnimationHandler sTestHandler = null; 62 private final AnimationFrameCallbackProvider mProvider; 63 private final ArrayList<AnimationFrameCallback> mAnimationCallbacks = new ArrayList<>(); 64 boolean mListDirty = false; 65 AnimationHandler(AnimationFrameCallbackProvider provider)66 AnimationHandler(AnimationFrameCallbackProvider provider) { 67 if (provider == null) { 68 mProvider = new FrameCallbackProvider16(); 69 } else { 70 mProvider = provider; 71 } 72 } 73 getInstance()74 public static AnimationHandler getInstance() { 75 if (sTestHandler != null) { 76 return sTestHandler; 77 } 78 if (sAnimationHandler.get() == null) { 79 sAnimationHandler.set(new AnimationHandler(null)); 80 } 81 return sAnimationHandler.get(); 82 } 83 setTestHandler(AnimationHandler handler)84 static void setTestHandler(AnimationHandler handler) { 85 sTestHandler = handler; 86 } 87 setFrameDelay(long frameDelay)88 void setFrameDelay(long frameDelay) { 89 mProvider.setFrameDelay(frameDelay); 90 } 91 getFrameDelay()92 long getFrameDelay() { 93 return mProvider.getFrameDelay(); 94 } 95 96 /** 97 * Register to get a callback on the next frame after the delay. 98 */ addAnimationFrameCallback(final AnimationFrameCallback callback)99 void addAnimationFrameCallback(final AnimationFrameCallback callback) { 100 if (mAnimationCallbacks.size() == 0) { 101 mProvider.postFrameCallback(); 102 } 103 if (!mAnimationCallbacks.contains(callback)) { 104 mAnimationCallbacks.add(callback); 105 } 106 mProvider.onNewCallbackAdded(callback); 107 } 108 109 /** 110 * Removes the given callback from the list, so it will no longer be called for frame related 111 * timing. 112 */ removeCallback(AnimationFrameCallback callback)113 public void removeCallback(AnimationFrameCallback callback) { 114 int id = mAnimationCallbacks.indexOf(callback); 115 if (id >= 0) { 116 mAnimationCallbacks.set(id, null); 117 mListDirty = true; 118 } 119 } 120 autoCancelBasedOn(ObjectAnimator objectAnimator)121 void autoCancelBasedOn(ObjectAnimator objectAnimator) { 122 for (int i = mAnimationCallbacks.size() - 1; i >= 0; i--) { 123 AnimationFrameCallback cb = mAnimationCallbacks.get(i); 124 if (cb == null) { 125 continue; 126 } 127 if (objectAnimator.shouldAutoCancel(cb)) { 128 ((Animator) mAnimationCallbacks.get(i)).cancel(); 129 } 130 } 131 } 132 doAnimationFrame(long frameTime)133 private void doAnimationFrame(long frameTime) { 134 for (int i = 0; i < mAnimationCallbacks.size(); i++) { 135 final AnimationFrameCallback callback = mAnimationCallbacks.get(i); 136 if (callback == null) { 137 continue; 138 } 139 callback.doAnimationFrame(frameTime); 140 } 141 cleanUpList(); 142 } 143 cleanUpList()144 private void cleanUpList() { 145 if (mListDirty) { 146 for (int i = mAnimationCallbacks.size() - 1; i >= 0; i--) { 147 if (mAnimationCallbacks.get(i) == null) { 148 mAnimationCallbacks.remove(i); 149 } 150 } 151 mListDirty = false; 152 } 153 } 154 getCallbackSize()155 private int getCallbackSize() { 156 int count = 0; 157 int size = mAnimationCallbacks.size(); 158 for (int i = size - 1; i >= 0; i--) { 159 if (mAnimationCallbacks.get(i) != null) { 160 count++; 161 } 162 } 163 return count; 164 } 165 166 /** 167 * Return the number of callbacks that have registered for frame callbacks. 168 */ getAnimationCount()169 public static int getAnimationCount() { 170 AnimationHandler handler = AnimationHandler.getInstance(); 171 if (handler == null) { 172 return 0; 173 } 174 return handler.getCallbackSize(); 175 } 176 177 /** 178 * Default provider of timing pulse that uses Choreographer for frame callbacks. 179 */ 180 private class FrameCallbackProvider16 implements AnimationFrameCallbackProvider, 181 Choreographer.FrameCallback { 182 FrameCallbackProvider16()183 FrameCallbackProvider16() { 184 } 185 186 @Override doFrame(long frameTimeNanos)187 public void doFrame(long frameTimeNanos) { 188 onAnimationFrame(frameTimeNanos / 1000000); 189 } 190 191 @Override onNewCallbackAdded(AnimationFrameCallback callback)192 public void onNewCallbackAdded(AnimationFrameCallback callback) {} 193 194 @Override postFrameCallback()195 public void postFrameCallback() { 196 Choreographer.getInstance().postFrameCallback(this); 197 } 198 199 @Override setFrameDelay(long delay)200 public void setFrameDelay(long delay) { 201 android.animation.ValueAnimator.setFrameDelay(delay); 202 } 203 204 @Override getFrameDelay()205 public long getFrameDelay() { 206 return android.animation.ValueAnimator.getFrameDelay(); 207 } 208 } 209 210 /** 211 * The intention for having this interface is to increase the testability of ValueAnimator. 212 * Specifically, we can have a custom implementation of the interface below and provide 213 * timing pulse without using Choreographer. That way we could use any arbitrary interval for 214 * our timing pulse in the tests. 215 */ 216 interface AnimationFrameCallbackProvider { 217 onNewCallbackAdded(AnimationFrameCallback callback)218 void onNewCallbackAdded(AnimationFrameCallback callback); 219 postFrameCallback()220 void postFrameCallback(); 221 setFrameDelay(long delay)222 void setFrameDelay(long delay); 223 getFrameDelay()224 long getFrameDelay(); 225 } 226 } 227