1 /*
2  * Copyright (C) 2017 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 package com.android.launcher3.anim;
17 
18 import static com.android.launcher3.anim.Interpolators.LINEAR;
19 import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
20 
21 import android.animation.Animator;
22 import android.animation.Animator.AnimatorListener;
23 import android.animation.AnimatorListenerAdapter;
24 import android.animation.AnimatorSet;
25 import android.animation.TimeInterpolator;
26 import android.animation.ValueAnimator;
27 import android.util.Log;
28 
29 import androidx.dynamicanimation.animation.DynamicAnimation;
30 import androidx.dynamicanimation.animation.SpringAnimation;
31 
32 import java.util.ArrayList;
33 import java.util.Collections;
34 import java.util.HashSet;
35 import java.util.List;
36 import java.util.Set;
37 
38 /**
39  * Helper class to control the playback of an {@link AnimatorSet}, with custom interpolators
40  * and durations.
41  *
42  * Note: The implementation does not support start delays on child animations or
43  * sequential playbacks.
44  */
45 public abstract class AnimatorPlaybackController implements ValueAnimator.AnimatorUpdateListener {
46 
47     private static final String TAG = "AnimatorPlaybackCtrler";
48     private static boolean DEBUG = false;
49 
wrap(AnimatorSet anim, long duration)50     public static AnimatorPlaybackController wrap(AnimatorSet anim, long duration) {
51         return wrap(anim, duration, null);
52     }
53 
54     /**
55      * Creates an animation controller for the provided animation.
56      * The actual duration does not matter as the animation is manually controlled. It just
57      * needs to be larger than the total number of pixels so that we don't have jittering due
58      * to float (animation-fraction * total duration) to int conversion.
59      */
wrap(AnimatorSet anim, long duration, Runnable onCancelRunnable)60     public static AnimatorPlaybackController wrap(AnimatorSet anim, long duration,
61             Runnable onCancelRunnable) {
62 
63         /**
64          * TODO: use {@link AnimatorSet#setCurrentPlayTime(long)} once b/68382377 is fixed.
65          */
66         return new AnimatorPlaybackControllerVL(anim, duration, onCancelRunnable);
67     }
68 
69     private final ValueAnimator mAnimationPlayer;
70     private final long mDuration;
71 
72     protected final AnimatorSet mAnim;
73     private Set<SpringAnimation> mSprings;
74 
75     protected float mCurrentFraction;
76     private Runnable mEndAction;
77 
78     protected boolean mTargetCancelled = false;
79     protected Runnable mOnCancelRunnable;
80 
81     private OnAnimationEndDispatcher mEndListener;
82     private DynamicAnimation.OnAnimationEndListener mSpringEndListener;
83     // We need this variable to ensure the end listener is called immediately, otherwise we run into
84     // issues where the callback interferes with the states of the swipe detector.
85     private boolean mSkipToEnd = false;
86 
AnimatorPlaybackController(AnimatorSet anim, long duration, Runnable onCancelRunnable)87     protected AnimatorPlaybackController(AnimatorSet anim, long duration,
88             Runnable onCancelRunnable) {
89         mAnim = anim;
90         mDuration = duration;
91         mOnCancelRunnable = onCancelRunnable;
92 
93         mAnimationPlayer = ValueAnimator.ofFloat(0, 1);
94         mAnimationPlayer.setInterpolator(LINEAR);
95         mEndListener = new OnAnimationEndDispatcher();
96         mAnimationPlayer.addListener(mEndListener);
97         mAnimationPlayer.addUpdateListener(this);
98 
99         mAnim.addListener(new AnimatorListenerAdapter() {
100             @Override
101             public void onAnimationCancel(Animator animation) {
102                 mTargetCancelled = true;
103                 if (mOnCancelRunnable != null) {
104                     mOnCancelRunnable.run();
105                     mOnCancelRunnable = null;
106                 }
107             }
108 
109             @Override
110             public void onAnimationEnd(Animator animation) {
111                 mTargetCancelled = false;
112                 mOnCancelRunnable = null;
113             }
114 
115             @Override
116             public void onAnimationStart(Animator animation) {
117                 mTargetCancelled = false;
118             }
119         });
120 
121         mSprings = new HashSet<>();
122         mSpringEndListener = (animation, canceled, value, velocity1) -> {
123             if (canceled) {
124                 mEndListener.onAnimationCancel(mAnimationPlayer);
125             } else {
126                 mEndListener.onAnimationEnd(mAnimationPlayer);
127             }
128         };
129     }
130 
getTarget()131     public AnimatorSet getTarget() {
132         return mAnim;
133     }
134 
getDuration()135     public long getDuration() {
136         return mDuration;
137     }
138 
getInterpolator()139     public TimeInterpolator getInterpolator() {
140         return mAnim.getInterpolator() != null ? mAnim.getInterpolator() : LINEAR;
141     }
142 
143     /**
144      * Starts playing the animation forward from current position.
145      */
start()146     public void start() {
147         mAnimationPlayer.setFloatValues(mCurrentFraction, 1);
148         mAnimationPlayer.setDuration(clampDuration(1 - mCurrentFraction));
149         mAnimationPlayer.start();
150     }
151 
152     /**
153      * Starts playing the animation backwards from current position
154      */
reverse()155     public void reverse() {
156         mAnimationPlayer.setFloatValues(mCurrentFraction, 0);
157         mAnimationPlayer.setDuration(clampDuration(mCurrentFraction));
158         mAnimationPlayer.start();
159     }
160 
161     /**
162      * Pauses the currently playing animation.
163      */
pause()164     public void pause() {
165         mAnimationPlayer.cancel();
166     }
167 
168     /**
169      * Returns the underlying animation used for controlling the set.
170      */
getAnimationPlayer()171     public ValueAnimator getAnimationPlayer() {
172         return mAnimationPlayer;
173     }
174 
175     /**
176      * Sets the current animation position and updates all the child animators accordingly.
177      */
setPlayFraction(float fraction)178     public abstract void setPlayFraction(float fraction);
179 
getProgressFraction()180     public float getProgressFraction() {
181         return mCurrentFraction;
182     }
183 
getInterpolatedProgress()184     public float getInterpolatedProgress() {
185         return getInterpolator().getInterpolation(mCurrentFraction);
186     }
187 
188     /**
189      * Sets the action to be called when the animation is completed. Also clears any
190      * previously set action.
191      */
setEndAction(Runnable runnable)192     public void setEndAction(Runnable runnable) {
193         mEndAction = runnable;
194     }
195 
196     @Override
onAnimationUpdate(ValueAnimator valueAnimator)197     public void onAnimationUpdate(ValueAnimator valueAnimator) {
198         setPlayFraction((float) valueAnimator.getAnimatedValue());
199     }
200 
clampDuration(float fraction)201     protected long clampDuration(float fraction) {
202         float playPos = mDuration * fraction;
203         if (playPos <= 0) {
204             return 0;
205         } else {
206             return Math.min((long) playPos, mDuration);
207         }
208     }
209 
210     /**
211      * Starts playback and sets the spring.
212      */
dispatchOnStartWithVelocity(float end, float velocity)213     public void dispatchOnStartWithVelocity(float end, float velocity) {
214         if (!QUICKSTEP_SPRINGS.get()) {
215             dispatchOnStart();
216             return;
217         }
218 
219         if (DEBUG) Log.d(TAG, "dispatchOnStartWithVelocity#end=" + end + ", velocity=" + velocity);
220 
221         for (Animator a : mAnim.getChildAnimations()) {
222             if (a instanceof SpringObjectAnimator) {
223                 if (DEBUG) Log.d(TAG, "Found springAnimator=" + a);
224                 SpringObjectAnimator springAnimator = (SpringObjectAnimator) a;
225                 mSprings.add(springAnimator.getSpring());
226                 springAnimator.startSpring(end, velocity, mSpringEndListener);
227             }
228         }
229 
230         dispatchOnStart();
231     }
232 
dispatchOnStart()233     public void dispatchOnStart() {
234         dispatchOnStartRecursively(mAnim);
235     }
236 
dispatchOnStartRecursively(Animator animator)237     private void dispatchOnStartRecursively(Animator animator) {
238         List<AnimatorListener> listeners = animator instanceof SpringObjectAnimator
239                 ? nonNullList(((SpringObjectAnimator) animator).getObjectAnimatorListeners())
240                 : nonNullList(animator.getListeners());
241 
242         for (AnimatorListener l : listeners) {
243             l.onAnimationStart(animator);
244         }
245 
246         if (animator instanceof AnimatorSet) {
247             for (Animator anim : nonNullList(((AnimatorSet) animator).getChildAnimations())) {
248                 dispatchOnStartRecursively(anim);
249             }
250         }
251     }
252 
253     /**
254      * Sets mOnCancelRunnable = null before dispatching the cancel and restoring the runnable. This
255      * is intended to be used only if you need to cancel but want to defer cleaning up yourself.
256      */
dispatchOnCancelWithoutCancelRunnable()257     public void dispatchOnCancelWithoutCancelRunnable() {
258         Runnable onCancel = mOnCancelRunnable;
259         setOnCancelRunnable(null);
260         dispatchOnCancel();
261         setOnCancelRunnable(onCancel);
262     }
263 
dispatchOnCancel()264     public void dispatchOnCancel() {
265         dispatchOnCancelRecursively(mAnim);
266     }
267 
dispatchOnCancelRecursively(Animator animator)268     private void dispatchOnCancelRecursively(Animator animator) {
269         for (AnimatorListener l : nonNullList(animator.getListeners())) {
270             l.onAnimationCancel(animator);
271         }
272 
273         if (animator instanceof AnimatorSet) {
274             for (Animator anim : nonNullList(((AnimatorSet) animator).getChildAnimations())) {
275                 dispatchOnCancelRecursively(anim);
276             }
277         }
278     }
279 
dispatchSetInterpolator(TimeInterpolator interpolator)280     public void dispatchSetInterpolator(TimeInterpolator interpolator) {
281         dispatchSetInterpolatorRecursively(mAnim, interpolator);
282     }
283 
dispatchSetInterpolatorRecursively(Animator anim, TimeInterpolator interpolator)284     private void dispatchSetInterpolatorRecursively(Animator anim, TimeInterpolator interpolator) {
285         anim.setInterpolator(interpolator);
286         if (anim instanceof AnimatorSet) {
287             for (Animator child : nonNullList(((AnimatorSet) anim).getChildAnimations())) {
288                 dispatchSetInterpolatorRecursively(child, interpolator);
289             }
290         }
291     }
292 
setOnCancelRunnable(Runnable runnable)293     public void setOnCancelRunnable(Runnable runnable) {
294         mOnCancelRunnable = runnable;
295     }
296 
skipToEnd()297     public void skipToEnd() {
298         mSkipToEnd = true;
299         for (SpringAnimation spring : mSprings) {
300             if (spring.canSkipToEnd()) {
301                 spring.skipToEnd();
302             }
303         }
304         mAnimationPlayer.end();
305         mSkipToEnd = false;
306     }
307 
308     public static class AnimatorPlaybackControllerVL extends AnimatorPlaybackController {
309 
310         private final ValueAnimator[] mChildAnimations;
311 
AnimatorPlaybackControllerVL(AnimatorSet anim, long duration, Runnable onCancelRunnable)312         private AnimatorPlaybackControllerVL(AnimatorSet anim, long duration,
313                 Runnable onCancelRunnable) {
314             super(anim, duration, onCancelRunnable);
315 
316             // Build animation list
317             ArrayList<ValueAnimator> childAnims = new ArrayList<>();
318             getAnimationsRecur(mAnim, childAnims);
319             mChildAnimations = childAnims.toArray(new ValueAnimator[childAnims.size()]);
320         }
321 
getAnimationsRecur(AnimatorSet anim, ArrayList<ValueAnimator> out)322         private void getAnimationsRecur(AnimatorSet anim, ArrayList<ValueAnimator> out) {
323             long forceDuration = anim.getDuration();
324             TimeInterpolator forceInterpolator = anim.getInterpolator();
325             for (Animator child : anim.getChildAnimations()) {
326                 if (forceDuration > 0) {
327                     child.setDuration(forceDuration);
328                 }
329                 if (forceInterpolator != null) {
330                     child.setInterpolator(forceInterpolator);
331                 }
332                 if (child instanceof ValueAnimator) {
333                     out.add((ValueAnimator) child);
334                 } else if (child instanceof AnimatorSet) {
335                     getAnimationsRecur((AnimatorSet) child, out);
336                 } else {
337                     throw new RuntimeException("Unknown animation type " + child);
338                 }
339             }
340         }
341 
342         @Override
setPlayFraction(float fraction)343         public void setPlayFraction(float fraction) {
344             mCurrentFraction = fraction;
345             // Let the animator report the progress but don't apply the progress to child
346             // animations if it has been cancelled.
347             if (mTargetCancelled) {
348                 return;
349             }
350             long playPos = clampDuration(fraction);
351             for (ValueAnimator anim : mChildAnimations) {
352                 anim.setCurrentPlayTime(Math.min(playPos, anim.getDuration()));
353             }
354         }
355     }
356 
isAnySpringRunning()357     private boolean isAnySpringRunning() {
358         for (SpringAnimation spring : mSprings) {
359             if (spring.isRunning()) {
360                 return true;
361             }
362         }
363         return false;
364     }
365 
366     /**
367      * Only dispatches the on end actions once the animator and all springs have completed running.
368      */
369     private class OnAnimationEndDispatcher extends AnimationSuccessListener {
370 
371         boolean mAnimatorDone = false;
372         boolean mSpringsDone = false;
373         boolean mDispatched = false;
374 
375         @Override
onAnimationStart(Animator animation)376         public void onAnimationStart(Animator animation) {
377             mCancelled = false;
378             mDispatched = false;
379         }
380 
381         @Override
onAnimationSuccess(Animator animator)382         public void onAnimationSuccess(Animator animator) {
383             if (mSprings.isEmpty()) {
384                 mSpringsDone = mAnimatorDone = true;
385             }
386             if (isAnySpringRunning()) {
387                 mAnimatorDone = true;
388             } else {
389                 mSpringsDone = true;
390             }
391 
392             // We wait for the spring (if any) to finish running before completing the end callback.
393             if (!mDispatched && (mSkipToEnd || (mAnimatorDone && mSpringsDone))) {
394                 dispatchOnEndRecursively(mAnim);
395                 if (mEndAction != null) {
396                     mEndAction.run();
397                 }
398                 mDispatched = true;
399             }
400         }
401 
dispatchOnEndRecursively(Animator animator)402         private void dispatchOnEndRecursively(Animator animator) {
403             for (AnimatorListener l : nonNullList(animator.getListeners())) {
404                 l.onAnimationEnd(animator);
405             }
406 
407             if (animator instanceof AnimatorSet) {
408                 for (Animator anim : nonNullList(((AnimatorSet) animator).getChildAnimations())) {
409                     dispatchOnEndRecursively(anim);
410                 }
411             }
412         }
413     }
414 
nonNullList(ArrayList<T> list)415     private static <T> List<T> nonNullList(ArrayList<T> list) {
416         return list == null ? Collections.emptyList() : list;
417     }
418 }
419