1 /*
2  * Copyright (C) 2014 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.view;
18 
19 import android.animation.Animator;
20 import android.animation.TimeInterpolator;
21 import android.animation.ValueAnimator;
22 import android.compat.annotation.UnsupportedAppUsage;
23 import android.graphics.CanvasProperty;
24 import android.graphics.Paint;
25 import android.graphics.RecordingCanvas;
26 import android.graphics.RenderNode;
27 import android.os.Handler;
28 import android.os.Looper;
29 import android.util.SparseIntArray;
30 
31 import com.android.internal.util.VirtualRefBasePtr;
32 import com.android.internal.view.animation.FallbackLUTInterpolator;
33 import com.android.internal.view.animation.HasNativeInterpolator;
34 import com.android.internal.view.animation.NativeInterpolatorFactory;
35 
36 import java.util.ArrayList;
37 
38 /**
39  * @hide
40  */
41 public class RenderNodeAnimator extends Animator {
42     // Keep in sync with enum RenderProperty in Animator.h
43     public static final int TRANSLATION_X = 0;
44     public static final int TRANSLATION_Y = 1;
45     public static final int TRANSLATION_Z = 2;
46     public static final int SCALE_X = 3;
47     public static final int SCALE_Y = 4;
48     public static final int ROTATION = 5;
49     public static final int ROTATION_X = 6;
50     public static final int ROTATION_Y = 7;
51     public static final int X = 8;
52     public static final int Y = 9;
53     public static final int Z = 10;
54     public static final int ALPHA = 11;
55     // The last value in the enum, used for array size initialization
56     public static final int LAST_VALUE = ALPHA;
57 
58     // Keep in sync with enum PaintFields in Animator.h
59     public static final int PAINT_STROKE_WIDTH = 0;
60 
61     /**
62      * Field for the Paint alpha channel, which should be specified as a value
63      * between 0 and 255.
64      */
65     public static final int PAINT_ALPHA = 1;
66 
67     // ViewPropertyAnimator uses a mask for its values, we need to remap them
68     // to the enum values here. RenderPropertyAnimator can't use the mask values
69     // directly as internally it uses a lookup table so it needs the values to
70     // be sequential starting from 0
71     private static final SparseIntArray sViewPropertyAnimatorMap = new SparseIntArray(15) {{
72         put(ViewPropertyAnimator.TRANSLATION_X, TRANSLATION_X);
73         put(ViewPropertyAnimator.TRANSLATION_Y, TRANSLATION_Y);
74         put(ViewPropertyAnimator.TRANSLATION_Z, TRANSLATION_Z);
75         put(ViewPropertyAnimator.SCALE_X, SCALE_X);
76         put(ViewPropertyAnimator.SCALE_Y, SCALE_Y);
77         put(ViewPropertyAnimator.ROTATION, ROTATION);
78         put(ViewPropertyAnimator.ROTATION_X, ROTATION_X);
79         put(ViewPropertyAnimator.ROTATION_Y, ROTATION_Y);
80         put(ViewPropertyAnimator.X, X);
81         put(ViewPropertyAnimator.Y, Y);
82         put(ViewPropertyAnimator.Z, Z);
83         put(ViewPropertyAnimator.ALPHA, ALPHA);
84     }};
85 
86     private VirtualRefBasePtr mNativePtr;
87 
88     private Handler mHandler;
89     private RenderNode mTarget;
90     private View mViewTarget;
91     private int mRenderProperty = -1;
92     private float mFinalValue;
93     private TimeInterpolator mInterpolator;
94 
95     private static final int STATE_PREPARE = 0;
96     private static final int STATE_DELAYED = 1;
97     private static final int STATE_RUNNING = 2;
98     private static final int STATE_FINISHED = 3;
99     private int mState = STATE_PREPARE;
100 
101     private long mUnscaledDuration = 300;
102     private long mUnscaledStartDelay = 0;
103     // If this is true, we will run any start delays on the UI thread. This is
104     // the safe default, and is necessary to ensure start listeners fire at
105     // the correct time. Animators created by RippleDrawable (the
106     // CanvasProperty<> ones) do not have this expectation, and as such will
107     // set this to false so that the renderthread handles the startdelay instead
108     private final boolean mUiThreadHandlesDelay;
109     private long mStartDelay = 0;
110     private long mStartTime;
111 
112     @UnsupportedAppUsage
mapViewPropertyToRenderProperty(int viewProperty)113     public static int mapViewPropertyToRenderProperty(int viewProperty) {
114         return sViewPropertyAnimatorMap.get(viewProperty);
115     }
116 
117     @UnsupportedAppUsage
RenderNodeAnimator(int property, float finalValue)118     public RenderNodeAnimator(int property, float finalValue) {
119         mRenderProperty = property;
120         mFinalValue = finalValue;
121         mUiThreadHandlesDelay = true;
122         init(nCreateAnimator(property, finalValue));
123     }
124 
125     @UnsupportedAppUsage
RenderNodeAnimator(CanvasProperty<Float> property, float finalValue)126     public RenderNodeAnimator(CanvasProperty<Float> property, float finalValue) {
127         init(nCreateCanvasPropertyFloatAnimator(
128                 property.getNativeContainer(), finalValue));
129         mUiThreadHandlesDelay = false;
130     }
131 
132     /**
133      * Creates a new render node animator for a field on a Paint property.
134      *
135      * @param property The paint property to target
136      * @param paintField Paint field to animate, one of {@link #PAINT_ALPHA} or
137      *            {@link #PAINT_STROKE_WIDTH}
138      * @param finalValue The target value for the property
139      */
140     @UnsupportedAppUsage
RenderNodeAnimator(CanvasProperty<Paint> property, int paintField, float finalValue)141     public RenderNodeAnimator(CanvasProperty<Paint> property, int paintField, float finalValue) {
142         init(nCreateCanvasPropertyPaintAnimator(
143                 property.getNativeContainer(), paintField, finalValue));
144         mUiThreadHandlesDelay = false;
145     }
146 
RenderNodeAnimator(int x, int y, float startRadius, float endRadius)147     public RenderNodeAnimator(int x, int y, float startRadius, float endRadius) {
148         init(nCreateRevealAnimator(x, y, startRadius, endRadius));
149         mUiThreadHandlesDelay = true;
150     }
151 
init(long ptr)152     private void init(long ptr) {
153         mNativePtr = new VirtualRefBasePtr(ptr);
154     }
155 
checkMutable()156     private void checkMutable() {
157         if (mState != STATE_PREPARE) {
158             throw new IllegalStateException("Animator has already started, cannot change it now!");
159         }
160         if (mNativePtr == null) {
161             throw new IllegalStateException("Animator's target has been destroyed "
162                     + "(trying to modify an animation after activity destroy?)");
163         }
164     }
165 
isNativeInterpolator(TimeInterpolator interpolator)166     static boolean isNativeInterpolator(TimeInterpolator interpolator) {
167         return interpolator.getClass().isAnnotationPresent(HasNativeInterpolator.class);
168     }
169 
applyInterpolator()170     private void applyInterpolator() {
171         if (mInterpolator == null || mNativePtr == null) return;
172 
173         long ni;
174         if (isNativeInterpolator(mInterpolator)) {
175             ni = ((NativeInterpolatorFactory)mInterpolator).createNativeInterpolator();
176         } else {
177             long duration = nGetDuration(mNativePtr.get());
178             ni = FallbackLUTInterpolator.createNativeInterpolator(mInterpolator, duration);
179         }
180         nSetInterpolator(mNativePtr.get(), ni);
181     }
182 
183     @Override
start()184     public void start() {
185         if (mTarget == null) {
186             throw new IllegalStateException("Missing target!");
187         }
188 
189         if (mState != STATE_PREPARE) {
190             throw new IllegalStateException("Already started!");
191         }
192 
193         mState = STATE_DELAYED;
194         if (mHandler == null) {
195             mHandler = new Handler(true);
196         }
197         applyInterpolator();
198 
199         if (mNativePtr == null) {
200             // It's dead, immediately cancel
201             cancel();
202         } else if (mStartDelay <= 0 || !mUiThreadHandlesDelay) {
203             nSetStartDelay(mNativePtr.get(), mStartDelay);
204             doStart();
205         } else {
206             getHelper().addDelayedAnimation(this);
207         }
208     }
209 
doStart()210     private void doStart() {
211         // Alpha is a special snowflake that has the canonical value stored
212         // in mTransformationInfo instead of in RenderNode, so we need to update
213         // it with the final value here.
214         if (mRenderProperty == RenderNodeAnimator.ALPHA) {
215             mViewTarget.ensureTransformationInfo();
216             mViewTarget.setAlphaInternal(mFinalValue);
217         }
218 
219         moveToRunningState();
220 
221         if (mViewTarget != null) {
222             // Kick off a frame to start the process
223             mViewTarget.invalidateViewProperty(true, false);
224         }
225     }
226 
moveToRunningState()227     private void moveToRunningState() {
228         mState = STATE_RUNNING;
229         if (mNativePtr != null) {
230             nStart(mNativePtr.get());
231         }
232         notifyStartListeners();
233     }
234 
notifyStartListeners()235     private void notifyStartListeners() {
236         final ArrayList<AnimatorListener> listeners = cloneListeners();
237         final int numListeners = listeners == null ? 0 : listeners.size();
238         for (int i = 0; i < numListeners; i++) {
239             listeners.get(i).onAnimationStart(this);
240         }
241     }
242 
243     @Override
cancel()244     public void cancel() {
245         if (mState != STATE_PREPARE && mState != STATE_FINISHED) {
246             if (mState == STATE_DELAYED) {
247                 getHelper().removeDelayedAnimation(this);
248                 moveToRunningState();
249             }
250 
251             final ArrayList<AnimatorListener> listeners = cloneListeners();
252             final int numListeners = listeners == null ? 0 : listeners.size();
253             for (int i = 0; i < numListeners; i++) {
254                 listeners.get(i).onAnimationCancel(this);
255             }
256 
257             end();
258         }
259     }
260 
261     @Override
end()262     public void end() {
263         if (mState != STATE_FINISHED) {
264             if (mState < STATE_RUNNING) {
265                 getHelper().removeDelayedAnimation(this);
266                 doStart();
267             }
268             if (mNativePtr != null) {
269                 nEnd(mNativePtr.get());
270                 if (mViewTarget != null) {
271                     // Kick off a frame to flush the state change
272                     mViewTarget.invalidateViewProperty(true, false);
273                 }
274             } else {
275                 // It's already dead, jump to onFinish
276                 onFinished();
277             }
278         }
279     }
280 
281     @Override
pause()282     public void pause() {
283         throw new UnsupportedOperationException();
284     }
285 
286     @Override
resume()287     public void resume() {
288         throw new UnsupportedOperationException();
289     }
290 
291     /** @hide */
292     @UnsupportedAppUsage
setTarget(View view)293     public void setTarget(View view) {
294         mViewTarget = view;
295         setTarget(mViewTarget.mRenderNode);
296     }
297 
298     /** Sets the animation target to the owning view of the RecordingCanvas */
setTarget(RecordingCanvas canvas)299     public void setTarget(RecordingCanvas canvas) {
300         setTarget(canvas.mNode);
301     }
302 
303     /** @hide */
304     @UnsupportedAppUsage
setTarget(DisplayListCanvas canvas)305     public void setTarget(DisplayListCanvas canvas) {
306         setTarget((RecordingCanvas) canvas);
307     }
308 
setTarget(RenderNode node)309     private void setTarget(RenderNode node) {
310         checkMutable();
311         if (mTarget != null) {
312             throw new IllegalStateException("Target already set!");
313         }
314         nSetListener(mNativePtr.get(), this);
315         mTarget = node;
316         mTarget.addAnimator(this);
317     }
318 
319     @UnsupportedAppUsage
setStartValue(float startValue)320     public void setStartValue(float startValue) {
321         checkMutable();
322         nSetStartValue(mNativePtr.get(), startValue);
323     }
324 
325     @Override
setStartDelay(long startDelay)326     public void setStartDelay(long startDelay) {
327         checkMutable();
328         if (startDelay < 0) {
329             throw new IllegalArgumentException("startDelay must be positive; " + startDelay);
330         }
331         mUnscaledStartDelay = startDelay;
332         mStartDelay = (long) (ValueAnimator.getDurationScale() * startDelay);
333     }
334 
335     @Override
getStartDelay()336     public long getStartDelay() {
337         return mUnscaledStartDelay;
338     }
339 
340     @UnsupportedAppUsage
341     @Override
setDuration(long duration)342     public RenderNodeAnimator setDuration(long duration) {
343         checkMutable();
344         if (duration < 0) {
345             throw new IllegalArgumentException("duration must be positive; " + duration);
346         }
347         mUnscaledDuration = duration;
348         nSetDuration(mNativePtr.get(), (long) (duration * ValueAnimator.getDurationScale()));
349         return this;
350     }
351 
352     @Override
getDuration()353     public long getDuration() {
354         return mUnscaledDuration;
355     }
356 
357     @Override
getTotalDuration()358     public long getTotalDuration() {
359         return mUnscaledDuration + mUnscaledStartDelay;
360     }
361 
362     @Override
isRunning()363     public boolean isRunning() {
364         return mState == STATE_DELAYED || mState == STATE_RUNNING;
365     }
366 
367     @Override
isStarted()368     public boolean isStarted() {
369         return mState != STATE_PREPARE;
370     }
371 
372     @Override
setInterpolator(TimeInterpolator interpolator)373     public void setInterpolator(TimeInterpolator interpolator) {
374         checkMutable();
375         mInterpolator = interpolator;
376     }
377 
378     @Override
getInterpolator()379     public TimeInterpolator getInterpolator() {
380         return mInterpolator;
381     }
382 
onFinished()383     protected void onFinished() {
384         if (mState == STATE_PREPARE) {
385             // Unlikely but possible, the native side has been destroyed
386             // before we have started.
387             releaseNativePtr();
388             return;
389         }
390         if (mState == STATE_DELAYED) {
391             getHelper().removeDelayedAnimation(this);
392             notifyStartListeners();
393         }
394         mState = STATE_FINISHED;
395 
396         final ArrayList<AnimatorListener> listeners = cloneListeners();
397         final int numListeners = listeners == null ? 0 : listeners.size();
398         for (int i = 0; i < numListeners; i++) {
399             listeners.get(i).onAnimationEnd(this);
400         }
401 
402         // Release the native object, as it has a global reference to us. This
403         // breaks the cyclic reference chain, and allows this object to be
404         // GC'd
405         releaseNativePtr();
406     }
407 
releaseNativePtr()408     private void releaseNativePtr() {
409         if (mNativePtr != null) {
410             mNativePtr.release();
411             mNativePtr = null;
412         }
413     }
414 
415     @SuppressWarnings("unchecked")
cloneListeners()416     private ArrayList<AnimatorListener> cloneListeners() {
417         ArrayList<AnimatorListener> listeners = getListeners();
418         if (listeners != null) {
419             listeners = (ArrayList<AnimatorListener>) listeners.clone();
420         }
421         return listeners;
422     }
423 
getNativeAnimator()424     public long getNativeAnimator() {
425         return mNativePtr.get();
426     }
427 
428     /**
429      * @return true if the animator was started, false if still delayed
430      */
processDelayed(long frameTimeMs)431     private boolean processDelayed(long frameTimeMs) {
432         if (mStartTime == 0) {
433             mStartTime = frameTimeMs;
434         } else if ((frameTimeMs - mStartTime) >= mStartDelay) {
435             doStart();
436             return true;
437         }
438         return false;
439     }
440 
getHelper()441     private static DelayedAnimationHelper getHelper() {
442         DelayedAnimationHelper helper = sAnimationHelper.get();
443         if (helper == null) {
444             helper = new DelayedAnimationHelper();
445             sAnimationHelper.set(helper);
446         }
447         return helper;
448     }
449 
450     private static ThreadLocal<DelayedAnimationHelper> sAnimationHelper =
451             new ThreadLocal<DelayedAnimationHelper>();
452 
453     private static class DelayedAnimationHelper implements Runnable {
454 
455         private ArrayList<RenderNodeAnimator> mDelayedAnims = new ArrayList<RenderNodeAnimator>();
456         private final Choreographer mChoreographer;
457         private boolean mCallbackScheduled;
458 
DelayedAnimationHelper()459         public DelayedAnimationHelper() {
460             mChoreographer = Choreographer.getInstance();
461         }
462 
addDelayedAnimation(RenderNodeAnimator animator)463         public void addDelayedAnimation(RenderNodeAnimator animator) {
464             mDelayedAnims.add(animator);
465             scheduleCallback();
466         }
467 
removeDelayedAnimation(RenderNodeAnimator animator)468         public void removeDelayedAnimation(RenderNodeAnimator animator) {
469             mDelayedAnims.remove(animator);
470         }
471 
scheduleCallback()472         private void scheduleCallback() {
473             if (!mCallbackScheduled) {
474                 mCallbackScheduled = true;
475                 mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null);
476             }
477         }
478 
479         @Override
run()480         public void run() {
481             long frameTimeMs = mChoreographer.getFrameTime();
482             mCallbackScheduled = false;
483 
484             int end = 0;
485             for (int i = 0; i < mDelayedAnims.size(); i++) {
486                 RenderNodeAnimator animator = mDelayedAnims.get(i);
487                 if (!animator.processDelayed(frameTimeMs)) {
488                     if (end != i) {
489                         mDelayedAnims.set(end, animator);
490                     }
491                     end++;
492                 }
493             }
494             while (mDelayedAnims.size() > end) {
495                 mDelayedAnims.remove(mDelayedAnims.size() - 1);
496             }
497 
498             if (mDelayedAnims.size() > 0) {
499                 scheduleCallback();
500             }
501         }
502     }
503 
504     // Called by native
505     @UnsupportedAppUsage
callOnFinished(RenderNodeAnimator animator)506     private static void callOnFinished(RenderNodeAnimator animator) {
507         if (animator.mHandler != null) {
508             animator.mHandler.post(animator::onFinished);
509         } else {
510             new Handler(Looper.getMainLooper(), null, true).post(animator::onFinished);
511         }
512     }
513 
514     @Override
clone()515     public Animator clone() {
516         throw new IllegalStateException("Cannot clone this animator");
517     }
518 
519     @Override
setAllowRunningAsynchronously(boolean mayRunAsync)520     public void setAllowRunningAsynchronously(boolean mayRunAsync) {
521         checkMutable();
522         nSetAllowRunningAsync(mNativePtr.get(), mayRunAsync);
523     }
524 
nCreateAnimator(int property, float finalValue)525     private static native long nCreateAnimator(int property, float finalValue);
nCreateCanvasPropertyFloatAnimator( long canvasProperty, float finalValue)526     private static native long nCreateCanvasPropertyFloatAnimator(
527             long canvasProperty, float finalValue);
nCreateCanvasPropertyPaintAnimator( long canvasProperty, int paintField, float finalValue)528     private static native long nCreateCanvasPropertyPaintAnimator(
529             long canvasProperty, int paintField, float finalValue);
nCreateRevealAnimator( int x, int y, float startRadius, float endRadius)530     private static native long nCreateRevealAnimator(
531             int x, int y, float startRadius, float endRadius);
532 
nSetStartValue(long nativePtr, float startValue)533     private static native void nSetStartValue(long nativePtr, float startValue);
nSetDuration(long nativePtr, long duration)534     private static native void nSetDuration(long nativePtr, long duration);
nGetDuration(long nativePtr)535     private static native long nGetDuration(long nativePtr);
nSetStartDelay(long nativePtr, long startDelay)536     private static native void nSetStartDelay(long nativePtr, long startDelay);
nSetInterpolator(long animPtr, long interpolatorPtr)537     private static native void nSetInterpolator(long animPtr, long interpolatorPtr);
nSetAllowRunningAsync(long animPtr, boolean mayRunAsync)538     private static native void nSetAllowRunningAsync(long animPtr, boolean mayRunAsync);
nSetListener(long animPtr, RenderNodeAnimator listener)539     private static native void nSetListener(long animPtr, RenderNodeAnimator listener);
540 
nStart(long animPtr)541     private static native void nStart(long animPtr);
nEnd(long animPtr)542     private static native void nEnd(long animPtr);
543 }
544