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