AnimatedVectorDrawable.java revision f276acd98457bcaabc9e79a17a736b3b484f005e
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 * in compliance with the License. You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the License
10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 * or implied. See the License for the specific language governing permissions and limitations under
12 * the License.
13 */
14
15package android.graphics.drawable;
16
17import android.animation.Animator;
18import android.animation.AnimatorInflater;
19import android.animation.AnimatorListenerAdapter;
20import android.animation.AnimatorSet;
21import android.animation.Animator.AnimatorListener;
22import android.animation.PropertyValuesHolder;
23import android.animation.TimeInterpolator;
24import android.animation.ValueAnimator;
25import android.animation.ObjectAnimator;
26import android.annotation.NonNull;
27import android.annotation.Nullable;
28import android.content.res.ColorStateList;
29import android.content.res.Resources;
30import android.content.res.Resources.Theme;
31import android.content.res.TypedArray;
32import android.graphics.Canvas;
33import android.graphics.ColorFilter;
34import android.graphics.Insets;
35import android.graphics.Outline;
36import android.graphics.PorterDuff;
37import android.graphics.Rect;
38import android.util.ArrayMap;
39import android.util.AttributeSet;
40import android.util.Log;
41import android.util.LongArray;
42import android.util.PathParser;
43import android.util.TimeUtils;
44import android.view.Choreographer;
45import android.view.DisplayListCanvas;
46import android.view.RenderNode;
47import android.view.RenderNodeAnimatorSetHelper;
48import android.view.View;
49
50import com.android.internal.R;
51
52import com.android.internal.util.VirtualRefBasePtr;
53import org.xmlpull.v1.XmlPullParser;
54import org.xmlpull.v1.XmlPullParserException;
55
56import java.io.IOException;
57import java.lang.ref.WeakReference;
58import java.util.ArrayList;
59
60/**
61 * This class uses {@link android.animation.ObjectAnimator} and
62 * {@link android.animation.AnimatorSet} to animate the properties of a
63 * {@link android.graphics.drawable.VectorDrawable} to create an animated drawable.
64 * <p>
65 * AnimatedVectorDrawable are normally defined as 3 separate XML files.
66 * </p>
67 * <p>
68 * First is the XML file for {@link android.graphics.drawable.VectorDrawable}.
69 * Note that we allow the animation to happen on the group's attributes and path's
70 * attributes, which requires they are uniquely named in this XML file. Groups
71 * and paths without animations do not need names.
72 * </p>
73 * <li>Here is a simple VectorDrawable in this vectordrawable.xml file.
74 * <pre>
75 * &lt;vector xmlns:android=&quot;http://schemas.android.com/apk/res/android";
76 *     android:height=&quot;64dp&quot;
77 *     android:width=&quot;64dp&quot;
78 *     android:viewportHeight=&quot;600&quot;
79 *     android:viewportWidth=&quot;600&quot; &gt;
80 *     &lt;group
81 *         android:name=&quot;rotationGroup&quot;
82 *         android:pivotX=&quot;300.0&quot;
83 *         android:pivotY=&quot;300.0&quot;
84 *         android:rotation=&quot;45.0&quot; &gt;
85 *         &lt;path
86 *             android:name=&quot;v&quot;
87 *             android:fillColor=&quot;#000000&quot;
88 *             android:pathData=&quot;M300,70 l 0,-70 70,70 0,0 -70,70z&quot; /&gt;
89 *     &lt;/group&gt;
90 * &lt;/vector&gt;
91 * </pre></li>
92 * <p>
93 * Second is the AnimatedVectorDrawable's XML file, which defines the target
94 * VectorDrawable, the target paths and groups to animate, the properties of the
95 * path and group to animate and the animations defined as the ObjectAnimators
96 * or AnimatorSets.
97 * </p>
98 * <li>Here is a simple AnimatedVectorDrawable defined in this avd.xml file.
99 * Note how we use the names to refer to the groups and paths in the vectordrawable.xml.
100 * <pre>
101 * &lt;animated-vector xmlns:android=&quot;http://schemas.android.com/apk/res/android";
102 *   android:drawable=&quot;@drawable/vectordrawable&quot; &gt;
103 *     &lt;target
104 *         android:name=&quot;rotationGroup&quot;
105 *         android:animation=&quot;@anim/rotation&quot; /&gt;
106 *     &lt;target
107 *         android:name=&quot;v&quot;
108 *         android:animation=&quot;@anim/path_morph&quot; /&gt;
109 * &lt;/animated-vector&gt;
110 * </pre></li>
111 * <p>
112 * Last is the Animator XML file, which is the same as a normal ObjectAnimator
113 * or AnimatorSet.
114 * To complete this example, here are the 2 animator files used in avd.xml:
115 * rotation.xml and path_morph.xml.
116 * </p>
117 * <li>Here is the rotation.xml, which will rotate the target group for 360 degrees.
118 * <pre>
119 * &lt;objectAnimator
120 *     android:duration=&quot;6000&quot;
121 *     android:propertyName=&quot;rotation&quot;
122 *     android:valueFrom=&quot;0&quot;
123 *     android:valueTo=&quot;360&quot; /&gt;
124 * </pre></li>
125 * <li>Here is the path_morph.xml, which will morph the path from one shape to
126 * the other. Note that the paths must be compatible for morphing.
127 * In more details, the paths should have exact same length of commands , and
128 * exact same length of parameters for each commands.
129 * Note that the path strings are better stored in strings.xml for reusing.
130 * <pre>
131 * &lt;set xmlns:android=&quot;http://schemas.android.com/apk/res/android">;
132 *     &lt;objectAnimator
133 *         android:duration=&quot;3000&quot;
134 *         android:propertyName=&quot;pathData&quot;
135 *         android:valueFrom=&quot;M300,70 l 0,-70 70,70 0,0   -70,70z&quot;
136 *         android:valueTo=&quot;M300,70 l 0,-70 70,0  0,140 -70,0 z&quot;
137 *         android:valueType=&quot;pathType&quot;/&gt;
138 * &lt;/set&gt;
139 * </pre></li>
140 *
141 * @attr ref android.R.styleable#AnimatedVectorDrawable_drawable
142 * @attr ref android.R.styleable#AnimatedVectorDrawableTarget_name
143 * @attr ref android.R.styleable#AnimatedVectorDrawableTarget_animation
144 */
145public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
146    private static final String LOGTAG = "AnimatedVectorDrawable";
147
148    private static final String ANIMATED_VECTOR = "animated-vector";
149    private static final String TARGET = "target";
150
151    private static final boolean DBG_ANIMATION_VECTOR_DRAWABLE = false;
152
153    /** Local, mutable animator set. */
154    private final VectorDrawableAnimator mAnimatorSet = new VectorDrawableAnimator();
155
156    /**
157     * The resources against which this drawable was created. Used to attempt
158     * to inflate animators if applyTheme() doesn't get called.
159     */
160    private Resources mRes;
161
162    private AnimatedVectorDrawableState mAnimatedVectorState;
163
164    /** Whether the animator set has been prepared. */
165    private boolean mHasAnimatorSet;
166
167    private boolean mMutated;
168
169    /** Use a internal AnimatorListener to support callbacks during animation events. */
170    private ArrayList<Animatable2.AnimationCallback> mAnimationCallbacks = null;
171    private AnimatorListener mAnimatorListener = null;
172
173    public AnimatedVectorDrawable() {
174        this(null, null);
175    }
176
177    private AnimatedVectorDrawable(AnimatedVectorDrawableState state, Resources res) {
178        mAnimatedVectorState = new AnimatedVectorDrawableState(state, mCallback, res);
179        mRes = res;
180    }
181
182    @Override
183    public Drawable mutate() {
184        if (!mMutated && super.mutate() == this) {
185            mAnimatedVectorState = new AnimatedVectorDrawableState(
186                    mAnimatedVectorState, mCallback, mRes);
187            mMutated = true;
188        }
189        return this;
190    }
191
192    /**
193     * @hide
194     */
195    public void clearMutated() {
196        super.clearMutated();
197        if (mAnimatedVectorState.mVectorDrawable != null) {
198            mAnimatedVectorState.mVectorDrawable.clearMutated();
199        }
200        mMutated = false;
201    }
202
203    @Override
204    public ConstantState getConstantState() {
205        mAnimatedVectorState.mChangingConfigurations = getChangingConfigurations();
206        return mAnimatedVectorState;
207    }
208
209    @Override
210    public int getChangingConfigurations() {
211        return super.getChangingConfigurations() | mAnimatedVectorState.getChangingConfigurations();
212    }
213
214    @Override
215    public void draw(Canvas canvas) {
216        if (canvas.isHardwareAccelerated()) {
217            mAnimatorSet.recordLastSeenTarget((DisplayListCanvas) canvas);
218        }
219        mAnimatedVectorState.mVectorDrawable.draw(canvas);
220        if (isStarted()) {
221            invalidateSelf();
222        }
223    }
224
225    @Override
226    protected void onBoundsChange(Rect bounds) {
227        mAnimatedVectorState.mVectorDrawable.setBounds(bounds);
228    }
229
230    @Override
231    protected boolean onStateChange(int[] state) {
232        return mAnimatedVectorState.mVectorDrawable.setState(state);
233    }
234
235    @Override
236    protected boolean onLevelChange(int level) {
237        return mAnimatedVectorState.mVectorDrawable.setLevel(level);
238    }
239
240    @Override
241    public boolean onLayoutDirectionChanged(@View.ResolvedLayoutDir int layoutDirection) {
242        return mAnimatedVectorState.mVectorDrawable.setLayoutDirection(layoutDirection);
243    }
244
245    @Override
246    public int getAlpha() {
247        return mAnimatedVectorState.mVectorDrawable.getAlpha();
248    }
249
250    @Override
251    public void setAlpha(int alpha) {
252        mAnimatedVectorState.mVectorDrawable.setAlpha(alpha);
253    }
254
255    @Override
256    public void setColorFilter(ColorFilter colorFilter) {
257        mAnimatedVectorState.mVectorDrawable.setColorFilter(colorFilter);
258    }
259
260    @Override
261    public void setTintList(ColorStateList tint) {
262        mAnimatedVectorState.mVectorDrawable.setTintList(tint);
263    }
264
265    @Override
266    public void setHotspot(float x, float y) {
267        mAnimatedVectorState.mVectorDrawable.setHotspot(x, y);
268    }
269
270    @Override
271    public void setHotspotBounds(int left, int top, int right, int bottom) {
272        mAnimatedVectorState.mVectorDrawable.setHotspotBounds(left, top, right, bottom);
273    }
274
275    @Override
276    public void setTintMode(PorterDuff.Mode tintMode) {
277        mAnimatedVectorState.mVectorDrawable.setTintMode(tintMode);
278    }
279
280    @Override
281    public boolean setVisible(boolean visible, boolean restart) {
282        mAnimatedVectorState.mVectorDrawable.setVisible(visible, restart);
283        return super.setVisible(visible, restart);
284    }
285
286    @Override
287    public boolean isStateful() {
288        return mAnimatedVectorState.mVectorDrawable.isStateful();
289    }
290
291    @Override
292    public int getOpacity() {
293        return mAnimatedVectorState.mVectorDrawable.getOpacity();
294    }
295
296    @Override
297    public int getIntrinsicWidth() {
298        return mAnimatedVectorState.mVectorDrawable.getIntrinsicWidth();
299    }
300
301    @Override
302    public int getIntrinsicHeight() {
303        return mAnimatedVectorState.mVectorDrawable.getIntrinsicHeight();
304    }
305
306    @Override
307    public void getOutline(@NonNull Outline outline) {
308        mAnimatedVectorState.mVectorDrawable.getOutline(outline);
309    }
310
311    /** @hide */
312    @Override
313    public Insets getOpticalInsets() {
314        return mAnimatedVectorState.mVectorDrawable.getOpticalInsets();
315    }
316
317    @Override
318    public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)
319            throws XmlPullParserException, IOException {
320        final AnimatedVectorDrawableState state = mAnimatedVectorState;
321
322        int eventType = parser.getEventType();
323        float pathErrorScale = 1;
324        while (eventType != XmlPullParser.END_DOCUMENT) {
325            if (eventType == XmlPullParser.START_TAG) {
326                final String tagName = parser.getName();
327                if (ANIMATED_VECTOR.equals(tagName)) {
328                    final TypedArray a = obtainAttributes(res, theme, attrs,
329                            R.styleable.AnimatedVectorDrawable);
330                    int drawableRes = a.getResourceId(
331                            R.styleable.AnimatedVectorDrawable_drawable, 0);
332                    if (drawableRes != 0) {
333                        VectorDrawable vectorDrawable = (VectorDrawable) res.getDrawable(
334                                drawableRes, theme).mutate();
335                        vectorDrawable.setAllowCaching(false);
336                        vectorDrawable.setCallback(mCallback);
337                        pathErrorScale = vectorDrawable.getPixelSize();
338                        if (state.mVectorDrawable != null) {
339                            state.mVectorDrawable.setCallback(null);
340                        }
341                        state.mVectorDrawable = vectorDrawable;
342                    }
343                    a.recycle();
344                } else if (TARGET.equals(tagName)) {
345                    final TypedArray a = obtainAttributes(res, theme, attrs,
346                            R.styleable.AnimatedVectorDrawableTarget);
347                    final String target = a.getString(
348                            R.styleable.AnimatedVectorDrawableTarget_name);
349                    final int animResId = a.getResourceId(
350                            R.styleable.AnimatedVectorDrawableTarget_animation, 0);
351                    if (animResId != 0) {
352                        if (theme != null) {
353                            final Animator objectAnimator = AnimatorInflater.loadAnimator(
354                                    res, theme, animResId, pathErrorScale);
355                            state.addTargetAnimator(target, objectAnimator);
356                        } else {
357                            // The animation may be theme-dependent. As a
358                            // workaround until Animator has full support for
359                            // applyTheme(), postpone loading the animator
360                            // until we have a theme in applyTheme().
361                            state.addPendingAnimator(animResId, pathErrorScale, target);
362
363                        }
364                    }
365                    a.recycle();
366                }
367            }
368
369            eventType = parser.next();
370        }
371
372        // If we don't have any pending animations, we don't need to hold a
373        // reference to the resources.
374        mRes = state.mPendingAnims == null ? null : res;
375    }
376
377    @Override
378    public boolean canApplyTheme() {
379        return (mAnimatedVectorState != null && mAnimatedVectorState.canApplyTheme())
380                || super.canApplyTheme();
381    }
382
383    @Override
384    public void applyTheme(Theme t) {
385        super.applyTheme(t);
386
387        final VectorDrawable vectorDrawable = mAnimatedVectorState.mVectorDrawable;
388        if (vectorDrawable != null && vectorDrawable.canApplyTheme()) {
389            vectorDrawable.applyTheme(t);
390        }
391
392        if (t != null) {
393            mAnimatedVectorState.inflatePendingAnimators(t.getResources(), t);
394        }
395
396        // If we don't have any pending animations, we don't need to hold a
397        // reference to the resources.
398        if (mAnimatedVectorState.mPendingAnims == null) {
399            mRes = null;
400        }
401    }
402
403    private static class AnimatedVectorDrawableState extends ConstantState {
404        int mChangingConfigurations;
405        VectorDrawable mVectorDrawable;
406
407        /** Animators that require a theme before inflation. */
408        ArrayList<PendingAnimator> mPendingAnims;
409
410        /** Fully inflated animators awaiting cloning into an AnimatorSet. */
411        ArrayList<Animator> mAnimators;
412
413        /** Map of animators to their target object names */
414        ArrayMap<Animator, String> mTargetNameMap;
415
416        public AnimatedVectorDrawableState(AnimatedVectorDrawableState copy,
417                Callback owner, Resources res) {
418            if (copy != null) {
419                mChangingConfigurations = copy.mChangingConfigurations;
420
421                if (copy.mVectorDrawable != null) {
422                    final ConstantState cs = copy.mVectorDrawable.getConstantState();
423                    if (res != null) {
424                        mVectorDrawable = (VectorDrawable) cs.newDrawable(res);
425                    } else {
426                        mVectorDrawable = (VectorDrawable) cs.newDrawable();
427                    }
428                    mVectorDrawable = (VectorDrawable) mVectorDrawable.mutate();
429                    mVectorDrawable.setCallback(owner);
430                    mVectorDrawable.setLayoutDirection(copy.mVectorDrawable.getLayoutDirection());
431                    mVectorDrawable.setBounds(copy.mVectorDrawable.getBounds());
432                    mVectorDrawable.setAllowCaching(false);
433                }
434
435                if (copy.mAnimators != null) {
436                    mAnimators = new ArrayList<>(copy.mAnimators);
437                }
438
439                if (copy.mTargetNameMap != null) {
440                    mTargetNameMap = new ArrayMap<>(copy.mTargetNameMap);
441                }
442
443                if (copy.mPendingAnims != null) {
444                    mPendingAnims = new ArrayList<>(copy.mPendingAnims);
445                }
446            } else {
447                mVectorDrawable = new VectorDrawable();
448            }
449        }
450
451        @Override
452        public boolean canApplyTheme() {
453            return (mVectorDrawable != null && mVectorDrawable.canApplyTheme())
454                    || mPendingAnims != null || super.canApplyTheme();
455        }
456
457        @Override
458        public Drawable newDrawable() {
459            return new AnimatedVectorDrawable(this, null);
460        }
461
462        @Override
463        public Drawable newDrawable(Resources res) {
464            return new AnimatedVectorDrawable(this, res);
465        }
466
467        @Override
468        public int getChangingConfigurations() {
469            return mChangingConfigurations;
470        }
471
472        public void addPendingAnimator(int resId, float pathErrorScale, String target) {
473            if (mPendingAnims == null) {
474                mPendingAnims = new ArrayList<>(1);
475            }
476            mPendingAnims.add(new PendingAnimator(resId, pathErrorScale, target));
477        }
478
479        public void addTargetAnimator(String targetName, Animator animator) {
480            if (mAnimators == null) {
481                mAnimators = new ArrayList<>(1);
482                mTargetNameMap = new ArrayMap<>(1);
483            }
484            mAnimators.add(animator);
485            mTargetNameMap.put(animator, targetName);
486
487            if (DBG_ANIMATION_VECTOR_DRAWABLE) {
488                Log.v(LOGTAG, "add animator  for target " + targetName + " " + animator);
489            }
490        }
491
492        /**
493         * Prepares a local set of mutable animators based on the constant
494         * state.
495         * <p>
496         * If there are any pending uninflated animators, attempts to inflate
497         * them immediately against the provided resources object.
498         *
499         * @param animatorSet the animator set to which the animators should
500         *                    be added
501         * @param res the resources against which to inflate any pending
502         *            animators, or {@code null} if not available
503         */
504        public void prepareLocalAnimators(@NonNull AnimatorSet animatorSet,
505                @Nullable Resources res) {
506            // Check for uninflated animators. We can remove this after we add
507            // support for Animator.applyTheme(). See comments in inflate().
508            if (mPendingAnims != null) {
509                // Attempt to load animators without applying a theme.
510                if (res != null) {
511                    inflatePendingAnimators(res, null);
512                } else {
513                    Log.e(LOGTAG, "Failed to load animators. Either the AnimatedVectorDrawable"
514                            + " must be created using a Resources object or applyTheme() must be"
515                            + " called with a non-null Theme object.");
516                }
517
518                mPendingAnims = null;
519            }
520
521            // Perform a deep copy of the constant state's animators.
522            final int count = mAnimators == null ? 0 : mAnimators.size();
523            if (count > 0) {
524                final Animator firstAnim = prepareLocalAnimator(0);
525                final AnimatorSet.Builder builder = animatorSet.play(firstAnim);
526                for (int i = 1; i < count; ++i) {
527                    final Animator nextAnim = prepareLocalAnimator(i);
528                    builder.with(nextAnim);
529                }
530            }
531        }
532
533        /**
534         * Prepares a local animator for the given index within the constant
535         * state's list of animators.
536         *
537         * @param index the index of the animator within the constant state
538         */
539        private Animator prepareLocalAnimator(int index) {
540            final Animator animator = mAnimators.get(index);
541            final Animator localAnimator = animator.clone();
542            final String targetName = mTargetNameMap.get(animator);
543            final Object target = mVectorDrawable.getTargetByName(targetName);
544            localAnimator.setTarget(target);
545            return localAnimator;
546        }
547
548        /**
549         * Inflates pending animators, if any, against a theme. Clears the list of
550         * pending animators.
551         *
552         * @param t the theme against which to inflate the animators
553         */
554        public void inflatePendingAnimators(@NonNull Resources res, @Nullable Theme t) {
555            final ArrayList<PendingAnimator> pendingAnims = mPendingAnims;
556            if (pendingAnims != null) {
557                mPendingAnims = null;
558
559                for (int i = 0, count = pendingAnims.size(); i < count; i++) {
560                    final PendingAnimator pendingAnimator = pendingAnims.get(i);
561                    final Animator objectAnimator = pendingAnimator.newInstance(res, t);
562                    addTargetAnimator(pendingAnimator.target, objectAnimator);
563                }
564            }
565        }
566
567        /**
568         * Basically a constant state for Animators until we actually implement
569         * constant states for Animators.
570         */
571        private static class PendingAnimator {
572            public final int animResId;
573            public final float pathErrorScale;
574            public final String target;
575
576            public PendingAnimator(int animResId, float pathErrorScale, String target) {
577                this.animResId = animResId;
578                this.pathErrorScale = pathErrorScale;
579                this.target = target;
580            }
581
582            public Animator newInstance(Resources res, Theme theme) {
583                return AnimatorInflater.loadAnimator(res, theme, animResId, pathErrorScale);
584            }
585        }
586    }
587
588    @Override
589    public boolean isRunning() {
590        return mAnimatorSet.isRunning();
591    }
592
593    private boolean isStarted() {
594        return mAnimatorSet.isStarted();
595    }
596
597    /**
598     * Resets the AnimatedVectorDrawable to the start state as specified in the animators.
599     */
600    public void reset() {
601        mAnimatorSet.reset();
602        invalidateSelf();
603    }
604
605    @Override
606    public void start() {
607        ensureAnimatorSet();
608
609        // If any one of the animator has not ended, do nothing.
610        if (isStarted()) {
611            return;
612        }
613
614        mAnimatorSet.start();
615        invalidateSelf();
616    }
617
618    @NonNull
619    private void ensureAnimatorSet() {
620        if (!mHasAnimatorSet) {
621            // TODO: Skip the AnimatorSet creation and init the VectorDrawableAnimator directly
622            // with a list of LocalAnimators.
623            AnimatorSet set = new AnimatorSet();
624            mAnimatedVectorState.prepareLocalAnimators(set, mRes);
625            mHasAnimatorSet = true;
626            mAnimatorSet.initWithAnimatorSet(set);
627            mRes = null;
628        }
629    }
630
631    @Override
632    public void stop() {
633        mAnimatorSet.end();
634    }
635
636    /**
637     * Reverses ongoing animations or starts pending animations in reverse.
638     * <p>
639     * NOTE: Only works if all animations support reverse. Otherwise, this will
640     * do nothing.
641     * @hide
642     */
643    public void reverse() {
644        ensureAnimatorSet();
645
646        // Only reverse when all the animators can be reversed.
647        if (!canReverse()) {
648            Log.w(LOGTAG, "AnimatedVectorDrawable can't reverse()");
649            return;
650        }
651
652        mAnimatorSet.reverse();
653        invalidateSelf();
654    }
655
656    /**
657     * @hide
658     */
659    public boolean canReverse() {
660        return mAnimatorSet.canReverse();
661    }
662
663    private final Callback mCallback = new Callback() {
664        @Override
665        public void invalidateDrawable(Drawable who) {
666            invalidateSelf();
667        }
668
669        @Override
670        public void scheduleDrawable(Drawable who, Runnable what, long when) {
671            scheduleSelf(what, when);
672        }
673
674        @Override
675        public void unscheduleDrawable(Drawable who, Runnable what) {
676            unscheduleSelf(what);
677        }
678    };
679
680    @Override
681    public void registerAnimationCallback(@NonNull AnimationCallback callback) {
682        if (callback == null) {
683            return;
684        }
685
686        // Add listener accordingly.
687        if (mAnimationCallbacks == null) {
688            mAnimationCallbacks = new ArrayList<>();
689        }
690
691        mAnimationCallbacks.add(callback);
692
693        if (mAnimatorListener == null) {
694            // Create a animator listener and trigger the callback events when listener is
695            // triggered.
696            mAnimatorListener = new AnimatorListenerAdapter() {
697                @Override
698                public void onAnimationStart(Animator animation) {
699                    ArrayList<AnimationCallback> tmpCallbacks = new ArrayList<>(mAnimationCallbacks);
700                    int size = tmpCallbacks.size();
701                    for (int i = 0; i < size; i ++) {
702                        tmpCallbacks.get(i).onAnimationStart(AnimatedVectorDrawable.this);
703                    }
704                }
705
706                @Override
707                public void onAnimationEnd(Animator animation) {
708                    ArrayList<AnimationCallback> tmpCallbacks = new ArrayList<>(mAnimationCallbacks);
709                    int size = tmpCallbacks.size();
710                    for (int i = 0; i < size; i ++) {
711                        tmpCallbacks.get(i).onAnimationEnd(AnimatedVectorDrawable.this);
712                    }
713                }
714            };
715        }
716        mAnimatorSet.setListener(mAnimatorListener);
717    }
718
719    // A helper function to clean up the animator listener in the mAnimatorSet.
720    private void removeAnimatorSetListener() {
721        if (mAnimatorListener != null) {
722            mAnimatorSet.removeListener();
723            mAnimatorListener = null;
724        }
725    }
726
727    @Override
728    public boolean unregisterAnimationCallback(@NonNull AnimationCallback callback) {
729        if (mAnimationCallbacks == null || callback == null) {
730            // Nothing to be removed.
731            return false;
732        }
733        boolean removed = mAnimationCallbacks.remove(callback);
734
735        //  When the last call back unregistered, remove the listener accordingly.
736        if (mAnimationCallbacks.size() == 0) {
737            removeAnimatorSetListener();
738        }
739        return removed;
740    }
741
742    @Override
743    public void clearAnimationCallbacks() {
744        removeAnimatorSetListener();
745        if (mAnimationCallbacks == null) {
746            return;
747        }
748
749        mAnimationCallbacks.clear();
750    }
751
752    /**
753     * @hide
754     */
755    public static class VectorDrawableAnimator {
756        private AnimatorListener mListener = null;
757        private final LongArray mStartDelays = new LongArray();
758        private PropertyValuesHolder.PropertyValues mTmpValues =
759                new PropertyValuesHolder.PropertyValues();
760        private long mSetPtr = 0;
761        private boolean mContainsSequentialAnimators = false;
762        private boolean mStarted = false;
763        private boolean mInitialized = false;
764        private boolean mAnimationPending = false;
765        private boolean mIsReversible = false;
766        // TODO: Consider using NativeAllocationRegistery to track native allocation
767        private final VirtualRefBasePtr mSetRefBasePtr;
768        private WeakReference<RenderNode> mTarget = null;
769        private WeakReference<RenderNode> mLastSeenTarget = null;
770
771
772        VectorDrawableAnimator() {
773            mSetPtr = nCreateAnimatorSet();
774            // Increment ref count on native AnimatorSet, so it doesn't get released before Java
775            // side is done using it.
776            mSetRefBasePtr = new VirtualRefBasePtr(mSetPtr);
777        }
778
779        private void initWithAnimatorSet(AnimatorSet set) {
780            if (mInitialized) {
781                // Already initialized
782                throw new UnsupportedOperationException("VectorDrawableAnimator cannot be " +
783                        "re-initialized");
784            }
785            parseAnimatorSet(set, 0);
786            mInitialized = true;
787
788            // Check reversible.
789            if (mContainsSequentialAnimators) {
790                mIsReversible = false;
791            } else {
792                // Check if there's any start delay set on child
793                for (int i = 0; i < mStartDelays.size(); i++) {
794                    if (mStartDelays.get(i) > 0) {
795                        mIsReversible = false;
796                        return;
797                    }
798                }
799            }
800            mIsReversible = true;
801        }
802
803        private void parseAnimatorSet(AnimatorSet set, long startTime) {
804            ArrayList<Animator> animators = set.getChildAnimations();
805
806            boolean playTogether = set.shouldPlayTogether();
807            // Convert AnimatorSet to VectorDrawableAnimator
808            for (int i = 0; i < animators.size(); i++) {
809                Animator animator = animators.get(i);
810                // Here we only support ObjectAnimator
811                if (animator instanceof AnimatorSet) {
812                    parseAnimatorSet((AnimatorSet) animator, startTime);
813                } else if (animator instanceof ObjectAnimator) {
814                    createRTAnimator((ObjectAnimator) animator, startTime);
815                } // ignore ValueAnimators and others because they don't directly modify VD
816                  // therefore will be useless to AVD.
817
818                if (!playTogether) {
819                    // Assume not play together means play sequentially
820                    startTime += animator.getTotalDuration();
821                    mContainsSequentialAnimators = true;
822                }
823            }
824        }
825
826        // TODO: This method reads animation data from already parsed Animators. We need to move
827        // this step further up the chain in the parser to avoid the detour.
828        private void createRTAnimator(ObjectAnimator animator, long startTime) {
829            PropertyValuesHolder[] values = animator.getValues();
830            Object target = animator.getTarget();
831            if (target instanceof VectorDrawable.VGroup) {
832                createRTAnimatorForGroup(values, animator, (VectorDrawable.VGroup) target,
833                        startTime);
834            } else if (target instanceof VectorDrawable.VPath) {
835                for (int i = 0; i < values.length; i++) {
836                    values[i].getPropertyValues(mTmpValues);
837                    if (mTmpValues.endValue instanceof PathParser.PathData &&
838                            mTmpValues.propertyName.equals("pathData")) {
839                        createRTAnimatorForPath(animator, (VectorDrawable.VPath) target,
840                                startTime);
841                    }  else if (target instanceof VectorDrawable.VFullPath) {
842                        createRTAnimatorForFullPath(animator, (VectorDrawable.VFullPath) target,
843                                startTime);
844                    } else {
845                        throw new IllegalArgumentException("ClipPath only supports PathData " +
846                                "property");
847                    }
848
849                }
850            } else if (target instanceof VectorDrawable.VectorDrawableState) {
851                createRTAnimatorForRootGroup(values, animator,
852                        (VectorDrawable.VectorDrawableState) target, startTime);
853            } else {
854                // Should never get here
855                throw new UnsupportedOperationException("Target should be either VGroup, VPath, " +
856                        "or ConstantState, " + target.getClass() + " is not supported");
857            }
858        }
859
860        private void createRTAnimatorForGroup(PropertyValuesHolder[] values,
861                ObjectAnimator animator, VectorDrawable.VGroup target,
862                long startTime) {
863
864            long nativePtr = target.getNativePtr();
865            int propertyId;
866            for (int i = 0; i < values.length; i++) {
867                // TODO: We need to support the rare case in AVD where no start value is provided
868                values[i].getPropertyValues(mTmpValues);
869                propertyId = VectorDrawable.VGroup.getPropertyIndex(mTmpValues.propertyName);
870                if (mTmpValues.type != Float.class && mTmpValues.type != float.class) {
871                    if (DBG_ANIMATION_VECTOR_DRAWABLE) {
872                        Log.e(LOGTAG, "Unsupported type: " +
873                                mTmpValues.type + ". Only float value is supported for Groups.");
874                    }
875                    continue;
876                }
877                if (propertyId < 0) {
878                    if (DBG_ANIMATION_VECTOR_DRAWABLE) {
879                        Log.e(LOGTAG, "Unsupported property: " +
880                                mTmpValues.propertyName + " for Vector Drawable Group");
881                    }
882                    continue;
883                }
884                long propertyPtr = nCreateGroupPropertyHolder(nativePtr, propertyId,
885                        (Float) mTmpValues.startValue, (Float) mTmpValues.endValue);
886                if (mTmpValues.dataSource != null) {
887                    float[] dataPoints = createDataPoints(mTmpValues.dataSource, animator
888                            .getDuration());
889                    nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length);
890                }
891                createNativeChildAnimator(propertyPtr, startTime, animator);
892            }
893        }
894        private void createRTAnimatorForPath( ObjectAnimator animator, VectorDrawable.VPath target,
895                long startTime) {
896
897            long nativePtr = target.getNativePtr();
898            long startPathDataPtr = ((PathParser.PathData) mTmpValues.startValue)
899                    .getNativePtr();
900            long endPathDataPtr = ((PathParser.PathData) mTmpValues.endValue)
901                    .getNativePtr();
902            long propertyPtr = nCreatePathDataPropertyHolder(nativePtr, startPathDataPtr,
903                    endPathDataPtr);
904            createNativeChildAnimator(propertyPtr, startTime, animator);
905        }
906
907        private void createRTAnimatorForFullPath(ObjectAnimator animator,
908                VectorDrawable.VFullPath target, long startTime) {
909
910            int propertyId = target.getPropertyIndex(mTmpValues.propertyName);
911            long propertyPtr;
912            long nativePtr = target.getNativePtr();
913            if (mTmpValues.type == Float.class || mTmpValues.type == float.class) {
914                if (propertyId < 0) {
915                    throw new IllegalArgumentException("Property: " + mTmpValues
916                            .propertyName + " is not supported for FullPath");
917                }
918                propertyPtr = nCreatePathPropertyHolder(nativePtr, propertyId,
919                        (Float) mTmpValues.startValue, (Float) mTmpValues.endValue);
920
921            } else if (mTmpValues.type == Integer.class || mTmpValues.type == int.class) {
922                propertyPtr = nCreatePathColorPropertyHolder(nativePtr, propertyId,
923                        (Integer) mTmpValues.startValue, (Integer) mTmpValues.endValue);
924            } else {
925                throw new UnsupportedOperationException("Unsupported type: " +
926                        mTmpValues.type + ". Only float, int or PathData value is " +
927                        "supported for Paths.");
928            }
929            if (mTmpValues.dataSource != null) {
930                float[] dataPoints = createDataPoints(mTmpValues.dataSource, animator
931                        .getDuration());
932                nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length);
933            }
934            createNativeChildAnimator(propertyPtr, startTime, animator);
935        }
936
937        private void createRTAnimatorForRootGroup(PropertyValuesHolder[] values,
938                ObjectAnimator animator, VectorDrawable.VectorDrawableState target,
939                long startTime) {
940                long nativePtr = target.getNativeRenderer();
941                if (!animator.getPropertyName().equals("alpha")) {
942                    throw new UnsupportedOperationException("Only alpha is supported for root " +
943                            "group");
944                }
945                Float startValue = null;
946                Float endValue = null;
947                for (int i = 0; i < values.length; i++) {
948                    values[i].getPropertyValues(mTmpValues);
949                    if (mTmpValues.propertyName.equals("alpha")) {
950                        startValue = (Float) mTmpValues.startValue;
951                        endValue = (Float) mTmpValues.endValue;
952                        break;
953                    }
954                }
955                if (startValue == null && endValue == null) {
956                    throw new UnsupportedOperationException("No alpha values are specified");
957                }
958                long propertyPtr = nCreateRootAlphaPropertyHolder(nativePtr, startValue, endValue);
959                createNativeChildAnimator(propertyPtr, startTime, animator);
960        }
961
962        // These are the data points that define the value of the animating properties.
963        // e.g. translateX and translateY can animate along a Path, at any fraction in [0, 1]
964        // a point on the path corresponds to the values of translateX and translateY.
965        // TODO: (Optimization) We should pass the path down in native and chop it into segments
966        // in native.
967        private static float[] createDataPoints(
968                PropertyValuesHolder.PropertyValues.DataSource dataSource, long duration) {
969            long frameIntervalNanos = Choreographer.getInstance().getFrameIntervalNanos();
970            int animIntervalMs = (int) (frameIntervalNanos / TimeUtils.NANOS_PER_MS);
971            int numAnimFrames = (int) Math.ceil(((double) duration) / animIntervalMs);
972            float values[] = new float[numAnimFrames];
973            float lastFrame = numAnimFrames - 1;
974            for (int i = 0; i < numAnimFrames; i++) {
975                float fraction = i / lastFrame;
976                values[i] = (Float) dataSource.getValueAtFraction(fraction);
977            }
978            return values;
979        }
980
981        private void createNativeChildAnimator(long propertyPtr, long extraDelay,
982                                               ObjectAnimator animator) {
983            long duration = animator.getDuration();
984            int repeatCount = animator.getRepeatCount();
985            long startDelay = extraDelay + animator.getStartDelay();
986            TimeInterpolator interpolator = animator.getInterpolator();
987            long nativeInterpolator =
988                    RenderNodeAnimatorSetHelper.createNativeInterpolator(interpolator, duration);
989
990            startDelay *= ValueAnimator.getDurationScale();
991            duration *= ValueAnimator.getDurationScale();
992
993            mStartDelays.add(startDelay);
994            nAddAnimator(mSetPtr, propertyPtr, nativeInterpolator, startDelay, duration,
995                    repeatCount);
996        }
997
998        /**
999         * Holds a weak reference to the target that was last seen (through the DisplayListCanvas
1000         * in the last draw call), so that when animator set needs to start, we can add the animator
1001         * to the last seen RenderNode target and start right away.
1002         */
1003        protected void recordLastSeenTarget(DisplayListCanvas canvas) {
1004            if (mAnimationPending) {
1005                mLastSeenTarget = new WeakReference<RenderNode>(
1006                        RenderNodeAnimatorSetHelper.getTarget(canvas));
1007                if (DBG_ANIMATION_VECTOR_DRAWABLE) {
1008                    Log.d(LOGTAG, "Target is set in the next frame");
1009                }
1010                mAnimationPending = false;
1011                start();
1012            } else {
1013                mLastSeenTarget = new WeakReference<RenderNode>(
1014                        RenderNodeAnimatorSetHelper.getTarget(canvas));
1015            }
1016
1017        }
1018
1019        private boolean setTarget(RenderNode node) {
1020            if (mTarget != null && mTarget.get() != null) {
1021                // TODO: Maybe we want to support target change.
1022                throw new IllegalStateException("Target already set!");
1023            }
1024
1025            node.addAnimator(this);
1026            mTarget = new WeakReference<RenderNode>(node);
1027            return true;
1028        }
1029
1030        private boolean useLastSeenTarget() {
1031            if (mLastSeenTarget != null && mLastSeenTarget.get() != null) {
1032                setTarget(mLastSeenTarget.get());
1033                return true;
1034            }
1035            return false;
1036        }
1037
1038        public void start() {
1039            if (!mInitialized) {
1040                return;
1041            }
1042
1043            if (mStarted) {
1044                return;
1045            }
1046
1047            if (!useLastSeenTarget()) {
1048                mAnimationPending = true;
1049                return;
1050            }
1051
1052            if (DBG_ANIMATION_VECTOR_DRAWABLE) {
1053                Log.d(LOGTAG, "Target is set. Starting VDAnimatorSet from java");
1054            }
1055
1056           nStart(mSetPtr, this);
1057            if (mListener != null) {
1058                mListener.onAnimationStart(null);
1059            }
1060            mStarted = true;
1061        }
1062
1063        public void end() {
1064            if (mInitialized && mStarted) {
1065                nEnd(mSetPtr);
1066                onAnimationEnd();
1067            }
1068        }
1069
1070        void reset() {
1071            if (!mInitialized) {
1072                return;
1073            }
1074            // TODO: Need to implement reset.
1075            Log.w(LOGTAG, "Reset is yet to be implemented");
1076            nReset(mSetPtr);
1077        }
1078
1079        // Current (imperfect) Java AnimatorSet cannot be reversed when the set contains sequential
1080        // animators or when the animator set has a start delay
1081        void reverse() {
1082            if (!mIsReversible) {
1083                return;
1084            }
1085            // TODO: Need to support reverse (non-public API)
1086            Log.w(LOGTAG, "Reverse is yet to be implemented");
1087            nReverse(mSetPtr, this);
1088        }
1089
1090        public long getAnimatorNativePtr() {
1091            return mSetPtr;
1092        }
1093
1094        boolean canReverse() {
1095            return mIsReversible;
1096        }
1097
1098        boolean isStarted() {
1099            return mStarted;
1100        }
1101
1102        boolean isRunning() {
1103            if (!mInitialized) {
1104                return false;
1105            }
1106            return mStarted;
1107        }
1108
1109        void setListener(AnimatorListener listener) {
1110            mListener = listener;
1111        }
1112
1113        void removeListener() {
1114            mListener = null;
1115        }
1116
1117        private void onAnimationEnd() {
1118            mStarted = false;
1119            if (mListener != null) {
1120                mListener.onAnimationEnd(null);
1121            }
1122            mTarget = null;
1123        }
1124
1125        // onFinished: should be called from native
1126        private static void callOnFinished(VectorDrawableAnimator set) {
1127            if (DBG_ANIMATION_VECTOR_DRAWABLE) {
1128                Log.d(LOGTAG, "on finished called from native");
1129            }
1130            set.onAnimationEnd();
1131        }
1132    }
1133
1134    private static native long nCreateAnimatorSet();
1135    private static native void nAddAnimator(long setPtr, long propertyValuesHolder,
1136             long nativeInterpolator, long startDelay, long duration, int repeatCount);
1137
1138    private static native long nCreateGroupPropertyHolder(long nativePtr, int propertyId,
1139            float startValue, float endValue);
1140
1141    private static native long nCreatePathDataPropertyHolder(long nativePtr, long startValuePtr,
1142            long endValuePtr);
1143    private static native long nCreatePathColorPropertyHolder(long nativePtr, int propertyId,
1144            int startValue, int endValue);
1145    private static native long nCreatePathPropertyHolder(long nativePtr, int propertyId,
1146            float startValue, float endValue);
1147    private static native long nCreateRootAlphaPropertyHolder(long nativePtr, float startValue,
1148            float endValue);
1149    private static native void nSetPropertyHolderData(long nativePtr, float[] data, int length);
1150    private static native void nStart(long animatorSetPtr, VectorDrawableAnimator set);
1151    private static native void nReverse(long animatorSetPtr, VectorDrawableAnimator set);
1152    private static native void nEnd(long animatorSetPtr);
1153    private static native void nReset(long animatorSetPtr);
1154}
1155