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