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