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