1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.chrome.browser.layouts.animation;
6 
7 import static org.chromium.base.ContextUtils.getApplicationContext;
8 
9 import android.animation.Animator;
10 import android.animation.TimeInterpolator;
11 import android.animation.ValueAnimator;
12 import android.provider.Settings;
13 
14 import androidx.annotation.IntDef;
15 import androidx.annotation.NonNull;
16 import androidx.annotation.Nullable;
17 import androidx.annotation.VisibleForTesting;
18 
19 import org.chromium.base.Log;
20 import org.chromium.base.ObserverList;
21 import org.chromium.base.supplier.Supplier;
22 import org.chromium.components.browser_ui.widget.animation.Interpolators;
23 import org.chromium.ui.modelutil.PropertyModel;
24 import org.chromium.ui.modelutil.PropertyModel.WritableFloatPropertyKey;
25 
26 import java.lang.annotation.Retention;
27 import java.lang.annotation.RetentionPolicy;
28 import java.lang.ref.WeakReference;
29 import java.util.ArrayList;
30 
31 /**
32  * An animator that can be used for animations in the Browser Compositor.
33  */
34 public class CompositorAnimator extends Animator {
35     private static final String TAG = "CompositorAnimator";
36 
37     /** The different states that this animator can be in. */
38     @IntDef({AnimationState.STARTED, AnimationState.RUNNING, AnimationState.CANCELED,
39             AnimationState.ENDED})
40     @Retention(RetentionPolicy.SOURCE)
41     private @interface AnimationState {
42         int STARTED = 0;
43         int RUNNING = 1;
44         int CANCELED = 2;
45         int ENDED = 3;
46     }
47 
48     /**
49      * The scale honoring Settings.Global.ANIMATOR_DURATION_SCALE. Use static to reduce updating.
50      * See {@link ValueAnimator}.
51      **/
52     @VisibleForTesting
53     public static float sDurationScale = 1;
54 
55     /** The {@link CompositorAnimationHandler} running the animation. */
56     private final WeakReference<CompositorAnimationHandler> mHandler;
57 
58     /** The list of listeners for events through the life of an animation. */
59     private final ObserverList<AnimatorListener> mListeners = new ObserverList<>();
60 
61     /** The list of frame update listeners for this animation. */
62     private final ArrayList<AnimatorUpdateListener> mAnimatorUpdateListeners = new ArrayList<>();
63 
64     /**
65      * A cached copy of the list of {@link AnimatorUpdateListener}s to prevent allocating a new list
66      * every update.
67      */
68     private final ArrayList<AnimatorUpdateListener> mCachedList = new ArrayList<>();
69 
70     /** The time interpolator for the animator. */
71     private TimeInterpolator mTimeInterpolator;
72 
73     /**
74      * The amount of time in ms that has passed since the animation has started. This includes any
75      * delay that was set.
76      */
77     private long mTimeSinceStartMs;
78 
79     /**
80      * The fraction that the animation is complete. This number is in the range [0, 1] and accounts
81      * for the set time interpolator.
82      */
83     private float mAnimatedFraction;
84 
85     /** The value that the animation should start with (ending at {@link #mEndValue}). */
86     private Supplier<Float> mStartValue;
87 
88     /** The value that the animation will transition to (starting at {@link #mStartValue}). */
89     private Supplier<Float> mEndValue;
90 
91     /** The duration of the animation in ms. */
92     private long mDurationMs;
93 
94     /**
95      * The animator's start delay in ms. Once {@link #start()} is called, updates are not sent until
96      * this time has passed.
97      */
98     private long mStartDelayMs;
99 
100     /** The current state of the animation. */
101     @AnimationState
102     private int mAnimationState = AnimationState.ENDED;
103 
104     /**
105      * Whether the animation ended because of frame updates. This is used to determine if any
106      * listeners need to be updated one more time.
107      */
108     private boolean mDidUpdateToCompletion;
109 
110     /**
111      * A utility for creating a basic animator.
112      * @param handler The {@link CompositorAnimationHandler} responsible for running the animation.
113      * @param startValue The starting animation value.
114      * @param endValue The end animation value.
115      * @param durationMs The duration of the animation in ms.
116      * @param listener An update listener if specific actions need to be performed.
117      * @return A {@link CompositorAnimator} for the property.
118      */
ofFloat(CompositorAnimationHandler handler, float startValue, float endValue, long durationMs, @Nullable AnimatorUpdateListener listener)119     public static CompositorAnimator ofFloat(CompositorAnimationHandler handler, float startValue,
120             float endValue, long durationMs, @Nullable AnimatorUpdateListener listener) {
121         CompositorAnimator animator = new CompositorAnimator(handler);
122         animator.setValues(startValue, endValue);
123         if (listener != null) animator.addUpdateListener(listener);
124         animator.setDuration(durationMs);
125         return animator;
126     }
127 
128     /**
129      * A utility for creating a basic animator.
130      * @param handler The {@link CompositorAnimationHandler} responsible for running the animation.
131      * @param target The object to modify.
132      * @param property The property of the object to modify.
133      * @param startValue The starting animation value.
134      * @param endValue The end animation value.
135      * @param durationMs The duration of the animation in ms.
136      * @param interpolator The time interpolator for the animation.
137      * @return A {@link CompositorAnimator} for the property.
138      */
ofFloatProperty(CompositorAnimationHandler handler, final T target, final FloatProperty<T> property, float startValue, float endValue, long durationMs, TimeInterpolator interpolator)139     public static <T> CompositorAnimator ofFloatProperty(CompositorAnimationHandler handler,
140             final T target, final FloatProperty<T> property, float startValue, float endValue,
141             long durationMs, TimeInterpolator interpolator) {
142         CompositorAnimator animator = new CompositorAnimator(handler);
143         animator.setValues(startValue, endValue);
144         animator.setDuration(durationMs);
145         animator.addUpdateListener(
146                 (CompositorAnimator a) -> property.setValue(target, a.getAnimatedValue()));
147         animator.setInterpolator(interpolator);
148         return animator;
149     }
150 
151     /**
152      * A utility for creating a basic animator.
153      * @param handler The {@link CompositorAnimationHandler} responsible for running the animation.
154      * @param target The object to modify.
155      * @param property The property of the object to modify.
156      * @param startValue The {@link Supplier} of the starting animation value.
157      * @param endValue The {@link Supplier} of the end animation value.
158      * @param durationMs The duration of the animation in ms.
159      * @param interpolator The time interpolator for the animation.
160      * @return A {@link CompositorAnimator} for the property.
161      */
ofFloatProperty(CompositorAnimationHandler handler, final T target, final FloatProperty<T> property, Supplier<Float> startValue, Supplier<Float> endValue, long durationMs, TimeInterpolator interpolator)162     public static <T> CompositorAnimator ofFloatProperty(CompositorAnimationHandler handler,
163             final T target, final FloatProperty<T> property, Supplier<Float> startValue,
164             Supplier<Float> endValue, long durationMs, TimeInterpolator interpolator) {
165         CompositorAnimator animator = new CompositorAnimator(handler);
166         animator.setValues(startValue, endValue);
167         animator.setDuration(durationMs);
168         animator.addUpdateListener(
169                 (CompositorAnimator a) -> property.setValue(target, a.getAnimatedValue()));
170         animator.setInterpolator(interpolator);
171         return animator;
172     }
173 
174     /**
175      * A utility for creating a basic animator.
176      * @param handler The {@link CompositorAnimationHandler} responsible for running the animation.
177      * @param target The object to modify.
178      * @param property The property of the object to modify.
179      * @param startValue The starting animation value.
180      * @param endValue The end animation value.
181      * @param durationMs The duration of the animation in ms.
182      * @return A {@link CompositorAnimator} for the property.
183      */
ofFloatProperty(CompositorAnimationHandler handler, final T target, final FloatProperty<T> property, float startValue, float endValue, long durationMs)184     public static <T> CompositorAnimator ofFloatProperty(CompositorAnimationHandler handler,
185             final T target, final FloatProperty<T> property, float startValue, float endValue,
186             long durationMs) {
187         return ofFloatProperty(handler, target, property, startValue, endValue, durationMs,
188                 Interpolators.DECELERATE_INTERPOLATOR);
189     }
190 
191     /**
192      * A utility for creating a basic animator.
193      * @param handler The {@link CompositorAnimationHandler} responsible for running the animation.
194      * @param target The object to modify.
195      * @param property The property of the object to modify.
196      * @param startValue The {@link Supplier} of the starting animation value.
197      * @param endValue The {@link Supplier} of the end animation value.
198      * @param durationMs The duration of the animation in ms.
199      * @return A {@link CompositorAnimator} for the property.
200      */
ofFloatProperty(CompositorAnimationHandler handler, final T target, final FloatProperty<T> property, Supplier<Float> startValue, Supplier<Float> endValue, long durationMs)201     public static <T> CompositorAnimator ofFloatProperty(CompositorAnimationHandler handler,
202             final T target, final FloatProperty<T> property, Supplier<Float> startValue,
203             Supplier<Float> endValue, long durationMs) {
204         return ofFloatProperty(handler, target, property, startValue, endValue, durationMs,
205                 Interpolators.DECELERATE_INTERPOLATOR);
206     }
207 
208     /**
209      * Create a {@link CompositorAnimator} to animate the {@link WritableFloatPropertyKey}.
210      * @param handler The {@link CompositorAnimationHandler} responsible for running the animation.
211      * @param model The {@link PropertyModel} to modify.
212      * @param key The {@link WritableFloatPropertyKey} in the model to update.
213      * @param startValue The {@link Supplier} of the starting animation value.
214      * @param endValue The {@link Supplier} of the end animation value.
215      * @param durationMs The duration of the animation in ms.
216      * @param interpolator The time interpolator for the animation.
217      * @return {@link CompositorAnimator} for animating the {@link WritableFloatPropertyKey}.
218      */
ofWritableFloatPropertyKey(CompositorAnimationHandler handler, final PropertyModel model, WritableFloatPropertyKey key, Supplier<Float> startValue, Supplier<Float> endValue, long durationMs, TimeInterpolator interpolator)219     public static CompositorAnimator ofWritableFloatPropertyKey(CompositorAnimationHandler handler,
220             final PropertyModel model, WritableFloatPropertyKey key, Supplier<Float> startValue,
221             Supplier<Float> endValue, long durationMs, TimeInterpolator interpolator) {
222         CompositorAnimator animator = new CompositorAnimator(handler);
223         animator.setValues(startValue, endValue);
224         animator.setDuration(durationMs);
225         animator.addUpdateListener((CompositorAnimator a) -> model.set(key, a.getAnimatedValue()));
226         animator.setInterpolator(interpolator);
227         return animator;
228     }
229 
230     /**
231      * Create a {@link CompositorAnimator} to animate the {@link WritableFloatPropertyKey}.
232      * @param handler The {@link CompositorAnimationHandler} responsible for running the animation.
233      * @param model The {@link PropertyModel} to modify.
234      * @param key The {@link WritableFloatPropertyKey} in the model to update.
235      * @param startValue The starting animation value.
236      * @param endValue The end animation value.
237      * @param durationMs The duration of the animation in ms.
238      * @param interpolator The time interpolator for the animation.
239      * @return {@link CompositorAnimator} for animating the {@link WritableFloatPropertyKey}.
240      */
ofWritableFloatPropertyKey(CompositorAnimationHandler handler, final PropertyModel model, WritableFloatPropertyKey key, float startValue, float endValue, long durationMs, TimeInterpolator interpolator)241     public static CompositorAnimator ofWritableFloatPropertyKey(CompositorAnimationHandler handler,
242             final PropertyModel model, WritableFloatPropertyKey key, float startValue,
243             float endValue, long durationMs, TimeInterpolator interpolator) {
244         return ofWritableFloatPropertyKey(
245                 handler, model, key, () -> startValue, () -> endValue, durationMs, interpolator);
246     }
247 
248     /**
249      * Create a {@link CompositorAnimator} to animate the {@link WritableFloatPropertyKey}.
250      * @param handler The {@link CompositorAnimationHandler} responsible for running the animation.
251      * @param model The {@link PropertyModel} to modify.
252      * @param key The {@link WritableFloatPropertyKey} in the model to update.
253      * @param startValue The starting animation value.
254      * @param endValue The end animation value.
255      * @param durationMs The duration of the animation in ms.
256      * @return {@link CompositorAnimator} for animating the {@link WritableFloatPropertyKey}.
257      */
ofWritableFloatPropertyKey(CompositorAnimationHandler handler, final PropertyModel model, WritableFloatPropertyKey key, float startValue, float endValue, long durationMs)258     public static CompositorAnimator ofWritableFloatPropertyKey(CompositorAnimationHandler handler,
259             final PropertyModel model, WritableFloatPropertyKey key, float startValue,
260             float endValue, long durationMs) {
261         return ofWritableFloatPropertyKey(handler, model, key, startValue, endValue, durationMs,
262                 Interpolators.DECELERATE_INTERPOLATOR);
263     }
264 
265     /** An interface for listening for frames of an animation. */
266     public interface AnimatorUpdateListener {
267         /**
268          * A notification of the occurrence of another frame of the animation.
269          * @param animator The animator that was updated.
270          */
onAnimationUpdate(CompositorAnimator animator)271         void onAnimationUpdate(CompositorAnimator animator);
272     }
273 
274     /**
275      * Create a new animator for the current context.
276      * @param handler The {@link CompositorAnimationHandler} responsible for running the animation.
277      */
CompositorAnimator(@onNull CompositorAnimationHandler handler)278     public CompositorAnimator(@NonNull CompositorAnimationHandler handler) {
279         mHandler = new WeakReference<>(handler);
280 
281         // The default interpolator is decelerate; this mimics the existing ChromeAnimation
282         // behavior.
283         mTimeInterpolator = Interpolators.DECELERATE_INTERPOLATOR;
284 
285         // By default, animate for 0 to 1.
286         setValues(0, 1);
287 
288         // Try to update from the system setting, but not too frequently.
289         sDurationScale = Settings.Global.getFloat(getApplicationContext().getContentResolver(),
290                 Settings.Global.ANIMATOR_DURATION_SCALE, sDurationScale);
291         if (sDurationScale != 1) {
292             Log.i(TAG, "Settings.Global.ANIMATOR_DURATION_SCALE = %f", sDurationScale);
293         }
294     }
295 
getScaledDuration()296     private long getScaledDuration() {
297         return (long) (mDurationMs * sDurationScale);
298     }
299 
300     /**
301      * Push an update to the animation. This should be called while the start delay is active and
302      * assumes that the animated object is at the starting position when {@link #start} is called.
303      * @param deltaTimeMs The time since the previous frame.
304      */
doAnimationFrame(long deltaTimeMs)305     public void doAnimationFrame(long deltaTimeMs) {
306         mTimeSinceStartMs += deltaTimeMs;
307 
308         // Clamp to the animator's duration, taking into account the start delay.
309         long finalTimeMs = Math.min(
310                 (long) (mTimeSinceStartMs - mStartDelayMs * sDurationScale), getScaledDuration());
311 
312         // Wait until the start delay has passed.
313         if (finalTimeMs < 0) return;
314 
315         // In the case where duration is 0, the animation is complete.
316         mAnimatedFraction = 1;
317         if (getScaledDuration() > 0) {
318             mAnimatedFraction =
319                     mTimeInterpolator.getInterpolation(finalTimeMs / (float) getScaledDuration());
320         }
321 
322         // Push update to listeners.
323         mCachedList.addAll(mAnimatorUpdateListeners);
324         for (int i = 0; i < mCachedList.size(); i++) mCachedList.get(i).onAnimationUpdate(this);
325         mCachedList.clear();
326 
327         if (finalTimeMs == getScaledDuration()) {
328             mDidUpdateToCompletion = true;
329             end();
330         }
331     }
332 
333     /**
334      * @return The animated fraction after being passed through the time interpolator, if set.
335      */
336     @VisibleForTesting
getAnimatedFraction()337     float getAnimatedFraction() {
338         return mAnimatedFraction;
339     }
340 
341     /**
342      * Add a listener for frame occurrences.
343      * @param listener The listener to add.
344      */
addUpdateListener(AnimatorUpdateListener listener)345     public void addUpdateListener(AnimatorUpdateListener listener) {
346         mAnimatorUpdateListeners.add(listener);
347     }
348 
349     /**
350      * @return Whether or not the animation has ended after being started. If the animation is
351      *         started after ending, this value will be reset to true.
352      */
hasEnded()353     public boolean hasEnded() {
354         return mAnimationState == AnimationState.ENDED;
355     }
356 
357     /**
358      * Set the values to animate between.
359      * @param start The value to begin the animation with.
360      * @param end The value to end the animation at.
361      */
setValues(float start, float end)362     void setValues(float start, float end) {
363         setValues(() -> start, () -> end);
364     }
365 
366     /**
367      * Set the values to animate between.
368      * @param start The value to begin the animation with.
369      * @param end The value to end the animation at.
370      */
371     @VisibleForTesting
setValues(Supplier<Float> start, Supplier<Float> end)372     void setValues(Supplier<Float> start, Supplier<Float> end) {
373         mStartValue = start;
374         mEndValue = end;
375     }
376 
377     /**
378      * @return The current value between the floats set by {@link #setValues(float, float)}.
379      */
getAnimatedValue()380     public float getAnimatedValue() {
381         return mStartValue.get() + (getAnimatedFraction() * (mEndValue.get() - mStartValue.get()));
382     }
383 
384     @Override
addListener(AnimatorListener listener)385     public void addListener(AnimatorListener listener) {
386         mListeners.addObserver(listener);
387     }
388 
389     @Override
removeListener(AnimatorListener listener)390     public void removeListener(AnimatorListener listener) {
391         mListeners.removeObserver(listener);
392     }
393 
394     @Override
removeAllListeners()395     public void removeAllListeners() {
396         mListeners.clear();
397         mAnimatorUpdateListeners.clear();
398     }
399 
400     @Override
401     @SuppressWarnings("unchecked")
start()402     public void start() {
403         if (mAnimationState != AnimationState.ENDED) return;
404 
405         super.start();
406         mAnimationState = AnimationState.RUNNING;
407         mDidUpdateToCompletion = false;
408         CompositorAnimationHandler handler = mHandler.get();
409         if (handler != null) handler.registerAndStartAnimator(this);
410         mTimeSinceStartMs = 0;
411 
412         for (AnimatorListener listener : mListeners) listener.onAnimationStart(this);
413     }
414 
415     @Override
416     @SuppressWarnings("unchecked")
cancel()417     public void cancel() {
418         if (mAnimationState == AnimationState.ENDED) return;
419 
420         mAnimationState = AnimationState.CANCELED;
421 
422         super.cancel();
423 
424         for (AnimatorListener listener : mListeners) listener.onAnimationCancel(this);
425 
426         end();
427     }
428 
429     @Override
430     @SuppressWarnings("unchecked")
end()431     public void end() {
432         if (mAnimationState == AnimationState.ENDED) return;
433 
434         super.end();
435         boolean wasCanceled = mAnimationState == AnimationState.CANCELED;
436         mAnimationState = AnimationState.ENDED;
437 
438         // If the animation was ended early but not canceled, push one last update to the listeners.
439         if (!mDidUpdateToCompletion && !wasCanceled) {
440             mAnimatedFraction = 1f;
441             for (AnimatorUpdateListener listener : mAnimatorUpdateListeners) {
442                 listener.onAnimationUpdate(this);
443             }
444         }
445 
446         for (AnimatorListener listener : mListeners) listener.onAnimationEnd(this);
447     }
448 
449     @Override
getStartDelay()450     public long getStartDelay() {
451         return mStartDelayMs;
452     }
453 
454     @Override
setStartDelay(long startDelayMs)455     public void setStartDelay(long startDelayMs) {
456         if (startDelayMs < 0) startDelayMs = 0;
457         mStartDelayMs = startDelayMs;
458     }
459 
460     @Override
setDuration(long durationMs)461     public CompositorAnimator setDuration(long durationMs) {
462         if (durationMs < 0) durationMs = 0;
463         mDurationMs = durationMs;
464         return this;
465     }
466 
467     @Override
getDuration()468     public long getDuration() {
469         return mDurationMs;
470     }
471 
472     @Override
setInterpolator(TimeInterpolator timeInterpolator)473     public void setInterpolator(TimeInterpolator timeInterpolator) {
474         assert timeInterpolator != null;
475         mTimeInterpolator = timeInterpolator;
476     }
477 
478     @Override
isRunning()479     public boolean isRunning() {
480         return mAnimationState == AnimationState.RUNNING;
481     }
482 }
483