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