VectorDrawable.java revision 46591f4a2dbd785bcae2b82bb490e78208605ec8
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.annotation.NonNull;
18import android.annotation.Nullable;
19import android.content.pm.ActivityInfo.Config;
20import android.content.res.ColorStateList;
21import android.content.res.ComplexColor;
22import android.content.res.GradientColor;
23import android.content.res.Resources;
24import android.content.res.Resources.Theme;
25import android.content.res.TypedArray;
26import android.graphics.Canvas;
27import android.graphics.ColorFilter;
28import android.graphics.Insets;
29import android.graphics.PixelFormat;
30import android.graphics.PorterDuffColorFilter;
31import android.graphics.Rect;
32import android.graphics.PorterDuff.Mode;
33import android.graphics.Shader;
34import android.util.ArrayMap;
35import android.util.AttributeSet;
36import android.util.DisplayMetrics;
37import android.util.LayoutDirection;
38import android.util.Log;
39import android.util.PathParser;
40import android.util.Xml;
41
42import com.android.internal.R;
43import com.android.internal.util.VirtualRefBasePtr;
44
45import org.xmlpull.v1.XmlPullParser;
46import org.xmlpull.v1.XmlPullParserException;
47
48import java.io.IOException;
49import java.nio.ByteBuffer;
50import java.nio.ByteOrder;
51import java.util.ArrayList;
52import java.util.HashMap;
53import java.util.Stack;
54
55/**
56 * This lets you create a drawable based on an XML vector graphic. It can be
57 * defined in an XML file with the <code>&lt;vector></code> element.
58 * <p/>
59 * The vector drawable has the following elements:
60 * <p/>
61 * <dt><code>&lt;vector></code></dt>
62 * <dl>
63 * <dd>Used to define a vector drawable
64 * <dl>
65 * <dt><code>android:name</code></dt>
66 * <dd>Defines the name of this vector drawable.</dd>
67 * <dt><code>android:width</code></dt>
68 * <dd>Used to define the intrinsic width of the drawable.
69 * This support all the dimension units, normally specified with dp.</dd>
70 * <dt><code>android:height</code></dt>
71 * <dd>Used to define the intrinsic height the drawable.
72 * This support all the dimension units, normally specified with dp.</dd>
73 * <dt><code>android:viewportWidth</code></dt>
74 * <dd>Used to define the width of the viewport space. Viewport is basically
75 * the virtual canvas where the paths are drawn on.</dd>
76 * <dt><code>android:viewportHeight</code></dt>
77 * <dd>Used to define the height of the viewport space. Viewport is basically
78 * the virtual canvas where the paths are drawn on.</dd>
79 * <dt><code>android:tint</code></dt>
80 * <dd>The color to apply to the drawable as a tint. By default, no tint is applied.</dd>
81 * <dt><code>android:tintMode</code></dt>
82 * <dd>The Porter-Duff blending mode for the tint color. The default value is src_in.</dd>
83 * <dt><code>android:autoMirrored</code></dt>
84 * <dd>Indicates if the drawable needs to be mirrored when its layout direction is
85 * RTL (right-to-left).</dd>
86 * <dt><code>android:alpha</code></dt>
87 * <dd>The opacity of this drawable.</dd>
88 * </dl></dd>
89 * </dl>
90 *
91 * <dl>
92 * <dt><code>&lt;group></code></dt>
93 * <dd>Defines a group of paths or subgroups, plus transformation information.
94 * The transformations are defined in the same coordinates as the viewport.
95 * And the transformations are applied in the order of scale, rotate then translate.
96 * <dl>
97 * <dt><code>android:name</code></dt>
98 * <dd>Defines the name of the group.</dd>
99 * <dt><code>android:rotation</code></dt>
100 * <dd>The degrees of rotation of the group.</dd>
101 * <dt><code>android:pivotX</code></dt>
102 * <dd>The X coordinate of the pivot for the scale and rotation of the group.
103 * This is defined in the viewport space.</dd>
104 * <dt><code>android:pivotY</code></dt>
105 * <dd>The Y coordinate of the pivot for the scale and rotation of the group.
106 * This is defined in the viewport space.</dd>
107 * <dt><code>android:scaleX</code></dt>
108 * <dd>The amount of scale on the X Coordinate.</dd>
109 * <dt><code>android:scaleY</code></dt>
110 * <dd>The amount of scale on the Y coordinate.</dd>
111 * <dt><code>android:translateX</code></dt>
112 * <dd>The amount of translation on the X coordinate.
113 * This is defined in the viewport space.</dd>
114 * <dt><code>android:translateY</code></dt>
115 * <dd>The amount of translation on the Y coordinate.
116 * This is defined in the viewport space.</dd>
117 * </dl></dd>
118 * </dl>
119 *
120 * <dl>
121 * <dt><code>&lt;path></code></dt>
122 * <dd>Defines paths to be drawn.
123 * <dl>
124 * <dt><code>android:name</code></dt>
125 * <dd>Defines the name of the path.</dd>
126 * <dt><code>android:pathData</code></dt>
127 * <dd>Defines path data using exactly same format as "d" attribute
128 * in the SVG's path data. This is defined in the viewport space.</dd>
129 * <dt><code>android:fillColor</code></dt>
130 * <dd>Specifies the color used to fill the path. May be a color, also may be a color state list or
131 * a gradient color for SDK 24+. If this property is animated, any value set by the animation will
132 * override the original value. No path fill is drawn if this property is not specified.</dd>
133 * <dt><code>android:strokeColor</code></dt>
134 * <dd>Specifies the color used to draw the path outline. May be a color or (SDK 24+ only) a color
135 * state list. If this property is animated, any value set by the animation will override the
136 * original value. No path outline is drawn if this property is not specified.</dd>
137 * <dt><code>android:strokeWidth</code></dt>
138 * <dd>The width a path stroke.</dd>
139 * <dt><code>android:strokeAlpha</code></dt>
140 * <dd>The opacity of a path stroke.</dd>
141 * <dt><code>android:fillAlpha</code></dt>
142 * <dd>The opacity to fill the path with.</dd>
143 * <dt><code>android:trimPathStart</code></dt>
144 * <dd>The fraction of the path to trim from the start, in the range from 0 to 1.</dd>
145 * <dt><code>android:trimPathEnd</code></dt>
146 * <dd>The fraction of the path to trim from the end, in the range from 0 to 1.</dd>
147 * <dt><code>android:trimPathOffset</code></dt>
148 * <dd>Shift trim region (allows showed region to include the start and end), in the range
149 * from 0 to 1.</dd>
150 * <dt><code>android:strokeLineCap</code></dt>
151 * <dd>Sets the linecap for a stroked path: butt, round, square.</dd>
152 * <dt><code>android:strokeLineJoin</code></dt>
153 * <dd>Sets the lineJoin for a stroked path: miter,round,bevel.</dd>
154 * <dt><code>android:strokeMiterLimit</code></dt>
155 * <dd>Sets the Miter limit for a stroked path.</dd>
156 * </dl></dd>
157 * </dl>
158 *
159 * <dl>
160 * <dt><code>&lt;clip-path></code></dt>
161 * <dd>Defines path to be the current clip. Note that the clip path only apply to
162 * the current group and its children.
163 * <dl>
164 * <dt><code>android:name</code></dt>
165 * <dd>Defines the name of the clip path.</dd>
166 * <dt><code>android:pathData</code></dt>
167 * <dd>Defines clip path using the same format as "d" attribute
168 * in the SVG's path data.</dd>
169 * </dl></dd>
170 * </dl>
171 * <li>Here is a simple VectorDrawable in this vectordrawable.xml file.
172 * <pre>
173 * &lt;vector xmlns:android=&quot;http://schemas.android.com/apk/res/android";
174 *     android:height=&quot;64dp&quot;
175 *     android:width=&quot;64dp&quot;
176 *     android:viewportHeight=&quot;600&quot;
177 *     android:viewportWidth=&quot;600&quot; &gt;
178 *     &lt;group
179 *         android:name=&quot;rotationGroup&quot;
180 *         android:pivotX=&quot;300.0&quot;
181 *         android:pivotY=&quot;300.0&quot;
182 *         android:rotation=&quot;45.0&quot; &gt;
183 *         &lt;path
184 *             android:name=&quot;v&quot;
185 *             android:fillColor=&quot;#000000&quot;
186 *             android:pathData=&quot;M300,70 l 0,-70 70,70 0,0 -70,70z&quot; /&gt;
187 *     &lt;/group&gt;
188 * &lt;/vector&gt;
189 * </pre></li>
190 */
191
192public class VectorDrawable extends Drawable {
193    private static final String LOGTAG = VectorDrawable.class.getSimpleName();
194
195    private static final String SHAPE_CLIP_PATH = "clip-path";
196    private static final String SHAPE_GROUP = "group";
197    private static final String SHAPE_PATH = "path";
198    private static final String SHAPE_VECTOR = "vector";
199
200    private VectorDrawableState mVectorState;
201
202    private PorterDuffColorFilter mTintFilter;
203    private ColorFilter mColorFilter;
204
205    private boolean mMutated;
206
207    /** The density of the display on which this drawable will be rendered. */
208    private int mTargetDensity;
209
210    // Given the virtual display setup, the dpi can be different than the inflation's dpi.
211    // Therefore, we need to scale the values we got from the getDimension*().
212    private int mDpiScaledWidth = 0;
213    private int mDpiScaledHeight = 0;
214    private Insets mDpiScaledInsets = Insets.NONE;
215
216    /** Whether DPI-scaled width, height, and insets need to be updated. */
217    private boolean mDpiScaledDirty = true;
218
219    // Temp variable, only for saving "new" operation at the draw() time.
220    private final Rect mTmpBounds = new Rect();
221
222    public VectorDrawable() {
223        this(new VectorDrawableState(), null);
224    }
225
226    /**
227     * The one constructor to rule them all. This is called by all public
228     * constructors to set the state and initialize local properties.
229     */
230    private VectorDrawable(@NonNull VectorDrawableState state, @Nullable Resources res) {
231        mVectorState = state;
232        updateLocalState(res);
233    }
234
235    /**
236     * Initializes local dynamic properties from state. This should be called
237     * after significant state changes, e.g. from the One True Constructor and
238     * after inflating or applying a theme.
239     *
240     * @param res resources of the context in which the drawable will be
241     *            displayed, or {@code null} to use the constant state defaults
242     */
243    private void updateLocalState(Resources res) {
244        final int density = Drawable.resolveDensity(res, mVectorState.mDensity);
245        if (mTargetDensity != density) {
246            mTargetDensity = density;
247            mDpiScaledDirty = true;
248        }
249
250        mTintFilter = updateTintFilter(mTintFilter, mVectorState.mTint, mVectorState.mTintMode);
251    }
252
253    @Override
254    public Drawable mutate() {
255        if (!mMutated && super.mutate() == this) {
256            mVectorState = new VectorDrawableState(mVectorState);
257            mMutated = true;
258        }
259        return this;
260    }
261
262    /**
263     * @hide
264     */
265    public void clearMutated() {
266        super.clearMutated();
267        mMutated = false;
268    }
269
270    Object getTargetByName(String name) {
271        return mVectorState.mVGTargetsMap.get(name);
272    }
273
274    @Override
275    public ConstantState getConstantState() {
276        mVectorState.mChangingConfigurations = getChangingConfigurations();
277        return mVectorState;
278    }
279
280    @Override
281    public void draw(Canvas canvas) {
282        // We will offset the bounds for drawBitmap, so copyBounds() here instead
283        // of getBounds().
284        copyBounds(mTmpBounds);
285        if (mTmpBounds.width() <= 0 || mTmpBounds.height() <= 0) {
286            // Nothing to draw
287            return;
288        }
289
290        // Color filters always override tint filters.
291        final ColorFilter colorFilter = (mColorFilter == null ? mTintFilter : mColorFilter);
292        final long colorFilterNativeInstance = colorFilter == null ? 0 :
293                colorFilter.native_instance;
294        boolean canReuseCache = mVectorState.canReuseCache();
295        nDraw(mVectorState.getNativeRenderer(), canvas.getNativeCanvasWrapper(),
296                colorFilterNativeInstance, mTmpBounds, needMirroring(),
297                canReuseCache);
298    }
299
300
301    @Override
302    public int getAlpha() {
303        return (int) (mVectorState.getAlpha() * 255);
304    }
305
306    @Override
307    public void setAlpha(int alpha) {
308        if (mVectorState.setAlpha(alpha / 255f)) {
309            invalidateSelf();
310        }
311    }
312
313    @Override
314    public void setColorFilter(ColorFilter colorFilter) {
315        mColorFilter = colorFilter;
316        invalidateSelf();
317    }
318
319    @Override
320    public ColorFilter getColorFilter() {
321        return mColorFilter;
322    }
323
324    @Override
325    public void setTintList(ColorStateList tint) {
326        final VectorDrawableState state = mVectorState;
327        if (state.mTint != tint) {
328            state.mTint = tint;
329            mTintFilter = updateTintFilter(mTintFilter, tint, state.mTintMode);
330            invalidateSelf();
331        }
332    }
333
334    @Override
335    public void setTintMode(Mode tintMode) {
336        final VectorDrawableState state = mVectorState;
337        if (state.mTintMode != tintMode) {
338            state.mTintMode = tintMode;
339            mTintFilter = updateTintFilter(mTintFilter, state.mTint, tintMode);
340            invalidateSelf();
341        }
342    }
343
344    @Override
345    public boolean isStateful() {
346        return super.isStateful() || (mVectorState != null && mVectorState.isStateful());
347    }
348
349    @Override
350    protected boolean onStateChange(int[] stateSet) {
351        boolean changed = false;
352
353        final VectorDrawableState state = mVectorState;
354        if (state.onStateChange(stateSet)) {
355            changed = true;
356            state.mCacheDirty = true;
357        }
358        if (state.mTint != null && state.mTintMode != null) {
359            mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
360            changed = true;
361        }
362
363        return changed;
364    }
365
366    @Override
367    public int getOpacity() {
368        // We can't tell whether the drawable is fully opaque unless we examine all the pixels,
369        // but we could tell it is transparent if the root alpha is 0.
370        return getAlpha() == 0 ? PixelFormat.TRANSPARENT : PixelFormat.TRANSLUCENT;
371    }
372
373    @Override
374    public int getIntrinsicWidth() {
375        if (mDpiScaledDirty) {
376            computeVectorSize();
377        }
378        return mDpiScaledWidth;
379    }
380
381    @Override
382    public int getIntrinsicHeight() {
383        if (mDpiScaledDirty) {
384            computeVectorSize();
385        }
386        return mDpiScaledHeight;
387    }
388
389    /** @hide */
390    @Override
391    public Insets getOpticalInsets() {
392        if (mDpiScaledDirty) {
393            computeVectorSize();
394        }
395        return mDpiScaledInsets;
396    }
397
398    /*
399     * Update local dimensions to adjust for a target density that may differ
400     * from the source density against which the constant state was loaded.
401     */
402    void computeVectorSize() {
403        final Insets opticalInsets = mVectorState.mOpticalInsets;
404
405        final int sourceDensity = mVectorState.mDensity;
406        final int targetDensity = mTargetDensity;
407        if (targetDensity != sourceDensity) {
408            mDpiScaledWidth = Drawable.scaleFromDensity(
409                    (int) mVectorState.mBaseWidth, sourceDensity, targetDensity, true);
410            mDpiScaledHeight = Drawable.scaleFromDensity(
411                    (int) mVectorState.mBaseHeight,sourceDensity, targetDensity, true);
412            final int left = Drawable.scaleFromDensity(
413                    opticalInsets.left, sourceDensity, targetDensity, false);
414            final int right = Drawable.scaleFromDensity(
415                    opticalInsets.right, sourceDensity, targetDensity, false);
416            final int top = Drawable.scaleFromDensity(
417                    opticalInsets.top, sourceDensity, targetDensity, false);
418            final int bottom = Drawable.scaleFromDensity(
419                    opticalInsets.bottom, sourceDensity, targetDensity, false);
420            mDpiScaledInsets = Insets.of(left, top, right, bottom);
421        } else {
422            mDpiScaledWidth = (int) mVectorState.mBaseWidth;
423            mDpiScaledHeight = (int) mVectorState.mBaseHeight;
424            mDpiScaledInsets = opticalInsets;
425        }
426
427        mDpiScaledDirty = false;
428    }
429
430    @Override
431    public boolean canApplyTheme() {
432        return (mVectorState != null && mVectorState.canApplyTheme()) || super.canApplyTheme();
433    }
434
435    @Override
436    public void applyTheme(Theme t) {
437        super.applyTheme(t);
438
439        final VectorDrawableState state = mVectorState;
440        if (state == null) {
441            return;
442        }
443
444        final boolean changedDensity = mVectorState.setDensity(
445                Drawable.resolveDensity(t.getResources(), 0));
446        mDpiScaledDirty |= changedDensity;
447
448        if (state.mThemeAttrs != null) {
449            final TypedArray a = t.resolveAttributes(
450                    state.mThemeAttrs, R.styleable.VectorDrawable);
451            try {
452                state.mCacheDirty = true;
453                updateStateFromTypedArray(a);
454            } catch (XmlPullParserException e) {
455                throw new RuntimeException(e);
456            } finally {
457                a.recycle();
458            }
459
460            // May have changed size.
461            mDpiScaledDirty = true;
462        }
463
464        // Apply theme to contained color state list.
465        if (state.mTint != null && state.mTint.canApplyTheme()) {
466            state.mTint = state.mTint.obtainForTheme(t);
467        }
468
469        if (mVectorState != null && mVectorState.canApplyTheme()) {
470            mVectorState.applyTheme(t);
471        }
472
473        // Update local properties.
474        updateLocalState(t.getResources());
475    }
476
477    /**
478     * The size of a pixel when scaled from the intrinsic dimension to the viewport dimension.
479     * This is used to calculate the path animation accuracy.
480     *
481     * @hide
482     */
483    public float getPixelSize() {
484        if (mVectorState == null ||
485                mVectorState.mBaseWidth == 0 ||
486                mVectorState.mBaseHeight == 0 ||
487                mVectorState.mViewportHeight == 0 ||
488                mVectorState.mViewportWidth == 0) {
489            return 1; // fall back to 1:1 pixel mapping.
490        }
491        float intrinsicWidth = mVectorState.mBaseWidth;
492        float intrinsicHeight = mVectorState.mBaseHeight;
493        float viewportWidth = mVectorState.mViewportWidth;
494        float viewportHeight = mVectorState.mViewportHeight;
495        float scaleX = viewportWidth / intrinsicWidth;
496        float scaleY = viewportHeight / intrinsicHeight;
497        return Math.min(scaleX, scaleY);
498    }
499
500    /** @hide */
501    public static VectorDrawable create(Resources resources, int rid) {
502        try {
503            final XmlPullParser parser = resources.getXml(rid);
504            final AttributeSet attrs = Xml.asAttributeSet(parser);
505            int type;
506            while ((type=parser.next()) != XmlPullParser.START_TAG &&
507                    type != XmlPullParser.END_DOCUMENT) {
508                // Empty loop
509            }
510            if (type != XmlPullParser.START_TAG) {
511                throw new XmlPullParserException("No start tag found");
512            }
513
514            final VectorDrawable drawable = new VectorDrawable();
515            drawable.inflate(resources, parser, attrs);
516
517            return drawable;
518        } catch (XmlPullParserException e) {
519            Log.e(LOGTAG, "parser error", e);
520        } catch (IOException e) {
521            Log.e(LOGTAG, "parser error", e);
522        }
523        return null;
524    }
525
526    @Override
527    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
528            @NonNull AttributeSet attrs, @Nullable Theme theme)
529            throws XmlPullParserException, IOException {
530        if (mVectorState.mRootGroup != null || mVectorState.mNativeRendererRefBase != null) {
531            // This VD has been used to display other VD resource content, clean up.
532            mVectorState.mRootGroup = new VGroup();
533            if (mVectorState.mNativeRendererRefBase != null) {
534                mVectorState.mNativeRendererRefBase.release();
535            }
536            mVectorState.createNativeRenderer(mVectorState.mRootGroup.mNativePtr);
537        }
538        final VectorDrawableState state = mVectorState;
539        state.setDensity(Drawable.resolveDensity(r, 0));
540
541        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.VectorDrawable);
542        updateStateFromTypedArray(a);
543        a.recycle();
544
545        mDpiScaledDirty = true;
546
547        state.mCacheDirty = true;
548        inflateChildElements(r, parser, attrs, theme);
549
550        // Update local properties.
551        updateLocalState(r);
552    }
553
554    private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException {
555        final VectorDrawableState state = mVectorState;
556
557        // Account for any configuration changes.
558        state.mChangingConfigurations |= a.getChangingConfigurations();
559
560        // Extract the theme attributes, if any.
561        state.mThemeAttrs = a.extractThemeAttrs();
562
563        final int tintMode = a.getInt(R.styleable.VectorDrawable_tintMode, -1);
564        if (tintMode != -1) {
565            state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN);
566        }
567
568        final ColorStateList tint = a.getColorStateList(R.styleable.VectorDrawable_tint);
569        if (tint != null) {
570            state.mTint = tint;
571        }
572
573        state.mAutoMirrored = a.getBoolean(
574                R.styleable.VectorDrawable_autoMirrored, state.mAutoMirrored);
575
576        float viewportWidth = a.getFloat(
577                R.styleable.VectorDrawable_viewportWidth, state.mViewportWidth);
578        float viewportHeight = a.getFloat(
579                R.styleable.VectorDrawable_viewportHeight, state.mViewportHeight);
580        state.setViewportSize(viewportWidth, viewportHeight);
581
582        if (state.mViewportWidth <= 0) {
583            throw new XmlPullParserException(a.getPositionDescription() +
584                    "<vector> tag requires viewportWidth > 0");
585        } else if (state.mViewportHeight <= 0) {
586            throw new XmlPullParserException(a.getPositionDescription() +
587                    "<vector> tag requires viewportHeight > 0");
588        }
589
590        state.mBaseWidth = a.getDimension(
591                R.styleable.VectorDrawable_width, state.mBaseWidth);
592        state.mBaseHeight = a.getDimension(
593                R.styleable.VectorDrawable_height, state.mBaseHeight);
594
595        if (state.mBaseWidth <= 0) {
596            throw new XmlPullParserException(a.getPositionDescription() +
597                    "<vector> tag requires width > 0");
598        } else if (state.mBaseHeight <= 0) {
599            throw new XmlPullParserException(a.getPositionDescription() +
600                    "<vector> tag requires height > 0");
601        }
602
603        final int insetLeft = a.getDimensionPixelOffset(
604                R.styleable.VectorDrawable_opticalInsetLeft, state.mOpticalInsets.left);
605        final int insetTop = a.getDimensionPixelOffset(
606                R.styleable.VectorDrawable_opticalInsetTop, state.mOpticalInsets.top);
607        final int insetRight = a.getDimensionPixelOffset(
608                R.styleable.VectorDrawable_opticalInsetRight, state.mOpticalInsets.right);
609        final int insetBottom = a.getDimensionPixelOffset(
610                R.styleable.VectorDrawable_opticalInsetBottom, state.mOpticalInsets.bottom);
611        state.mOpticalInsets = Insets.of(insetLeft, insetTop, insetRight, insetBottom);
612
613        final float alphaInFloat = a.getFloat(
614                R.styleable.VectorDrawable_alpha, state.getAlpha());
615        state.setAlpha(alphaInFloat);
616
617        final String name = a.getString(R.styleable.VectorDrawable_name);
618        if (name != null) {
619            state.mRootName = name;
620            state.mVGTargetsMap.put(name, state);
621        }
622    }
623
624    private void inflateChildElements(Resources res, XmlPullParser parser, AttributeSet attrs,
625            Theme theme) throws XmlPullParserException, IOException {
626        final VectorDrawableState state = mVectorState;
627        boolean noPathTag = true;
628
629        // Use a stack to help to build the group tree.
630        // The top of the stack is always the current group.
631        final Stack<VGroup> groupStack = new Stack<VGroup>();
632        groupStack.push(state.mRootGroup);
633
634        int eventType = parser.getEventType();
635        while (eventType != XmlPullParser.END_DOCUMENT) {
636            if (eventType == XmlPullParser.START_TAG) {
637                final String tagName = parser.getName();
638                final VGroup currentGroup = groupStack.peek();
639
640                if (SHAPE_PATH.equals(tagName)) {
641                    final VFullPath path = new VFullPath();
642                    path.inflate(res, attrs, theme);
643                    currentGroup.addChild(path);
644                    if (path.getPathName() != null) {
645                        state.mVGTargetsMap.put(path.getPathName(), path);
646                    }
647                    noPathTag = false;
648                    state.mChangingConfigurations |= path.mChangingConfigurations;
649                } else if (SHAPE_CLIP_PATH.equals(tagName)) {
650                    final VClipPath path = new VClipPath();
651                    path.inflate(res, attrs, theme);
652                    currentGroup.addChild(path);
653                    if (path.getPathName() != null) {
654                        state.mVGTargetsMap.put(path.getPathName(), path);
655                    }
656                    state.mChangingConfigurations |= path.mChangingConfigurations;
657                } else if (SHAPE_GROUP.equals(tagName)) {
658                    VGroup newChildGroup = new VGroup();
659                    newChildGroup.inflate(res, attrs, theme);
660                    currentGroup.addChild(newChildGroup);
661                    groupStack.push(newChildGroup);
662                    if (newChildGroup.getGroupName() != null) {
663                        state.mVGTargetsMap.put(newChildGroup.getGroupName(),
664                                newChildGroup);
665                    }
666                    state.mChangingConfigurations |= newChildGroup.mChangingConfigurations;
667                }
668            } else if (eventType == XmlPullParser.END_TAG) {
669                final String tagName = parser.getName();
670                if (SHAPE_GROUP.equals(tagName)) {
671                    groupStack.pop();
672                }
673            }
674            eventType = parser.next();
675        }
676
677        if (noPathTag) {
678            final StringBuffer tag = new StringBuffer();
679
680            if (tag.length() > 0) {
681                tag.append(" or ");
682            }
683            tag.append(SHAPE_PATH);
684
685            throw new XmlPullParserException("no " + tag + " defined");
686        }
687    }
688
689    @Override
690    public @Config int getChangingConfigurations() {
691        return super.getChangingConfigurations() | mVectorState.getChangingConfigurations();
692    }
693
694    void setAllowCaching(boolean allowCaching) {
695        nSetAllowCaching(mVectorState.getNativeRenderer(), allowCaching);
696    }
697
698    private boolean needMirroring() {
699        return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL;
700    }
701
702    @Override
703    public void setAutoMirrored(boolean mirrored) {
704        if (mVectorState.mAutoMirrored != mirrored) {
705            mVectorState.mAutoMirrored = mirrored;
706            invalidateSelf();
707        }
708    }
709
710    @Override
711    public boolean isAutoMirrored() {
712        return mVectorState.mAutoMirrored;
713    }
714
715    static class VectorDrawableState extends ConstantState {
716        // Variables below need to be copied (deep copy if applicable) for mutation.
717        int[] mThemeAttrs;
718        @Config int mChangingConfigurations;
719        ColorStateList mTint = null;
720        Mode mTintMode = DEFAULT_TINT_MODE;
721        boolean mAutoMirrored;
722
723        float mBaseWidth = 0;
724        float mBaseHeight = 0;
725        float mViewportWidth = 0;
726        float mViewportHeight = 0;
727        Insets mOpticalInsets = Insets.NONE;
728        String mRootName = null;
729        VGroup mRootGroup;
730        VirtualRefBasePtr mNativeRendererRefBase = null;
731
732        int mDensity = DisplayMetrics.DENSITY_DEFAULT;
733        final ArrayMap<String, Object> mVGTargetsMap = new ArrayMap<>();
734
735        // Fields for cache
736        int[] mCachedThemeAttrs;
737        ColorStateList mCachedTint;
738        Mode mCachedTintMode;
739        boolean mCachedAutoMirrored;
740        boolean mCacheDirty;
741
742        // Deep copy for mutate() or implicitly mutate.
743        public VectorDrawableState(VectorDrawableState copy) {
744            if (copy != null) {
745                mThemeAttrs = copy.mThemeAttrs;
746                mChangingConfigurations = copy.mChangingConfigurations;
747                mTint = copy.mTint;
748                mTintMode = copy.mTintMode;
749                mAutoMirrored = copy.mAutoMirrored;
750                mRootGroup = new VGroup(copy.mRootGroup, mVGTargetsMap);
751                createNativeRenderer(mRootGroup.mNativePtr);
752
753                mBaseWidth = copy.mBaseWidth;
754                mBaseHeight = copy.mBaseHeight;
755                setViewportSize(copy.mViewportWidth, copy.mViewportHeight);
756                mOpticalInsets = copy.mOpticalInsets;
757
758                mRootName = copy.mRootName;
759                mDensity = copy.mDensity;
760                if (copy.mRootName != null) {
761                    mVGTargetsMap.put(copy.mRootName, this);
762                }
763            }
764        }
765
766        private void createNativeRenderer(long rootGroupPtr) {
767            mNativeRendererRefBase = new VirtualRefBasePtr(nCreateRenderer(rootGroupPtr));
768        }
769
770        long getNativeRenderer() {
771            if (mNativeRendererRefBase == null) {
772                return 0;
773            }
774            return mNativeRendererRefBase.get();
775        }
776
777        public boolean canReuseCache() {
778            if (!mCacheDirty
779                    && mCachedThemeAttrs == mThemeAttrs
780                    && mCachedTint == mTint
781                    && mCachedTintMode == mTintMode
782                    && mCachedAutoMirrored == mAutoMirrored) {
783                return true;
784            }
785            updateCacheStates();
786            return false;
787        }
788
789        public void updateCacheStates() {
790            // Use shallow copy here and shallow comparison in canReuseCache(),
791            // likely hit cache miss more, but practically not much difference.
792            mCachedThemeAttrs = mThemeAttrs;
793            mCachedTint = mTint;
794            mCachedTintMode = mTintMode;
795            mCachedAutoMirrored = mAutoMirrored;
796            mCacheDirty = false;
797        }
798
799        public void applyTheme(Theme t) {
800            mRootGroup.applyTheme(t);
801        }
802
803        @Override
804        public boolean canApplyTheme() {
805            return mThemeAttrs != null
806                    || (mRootGroup != null && mRootGroup.canApplyTheme())
807                    || (mTint != null && mTint.canApplyTheme())
808                    || super.canApplyTheme();
809        }
810
811        public VectorDrawableState() {
812            mRootGroup = new VGroup();
813            createNativeRenderer(mRootGroup.mNativePtr);
814        }
815
816        @Override
817        public Drawable newDrawable() {
818            return new VectorDrawable(this, null);
819        }
820
821        @Override
822        public Drawable newDrawable(Resources res) {
823            return new VectorDrawable(this, res);
824        }
825
826        @Override
827        public @Config int getChangingConfigurations() {
828            return mChangingConfigurations
829                    | (mTint != null ? mTint.getChangingConfigurations() : 0);
830        }
831
832        public boolean isStateful() {
833            return (mTint != null && mTint.isStateful())
834                    || (mRootGroup != null && mRootGroup.isStateful());
835        }
836
837        void setViewportSize(float viewportWidth, float viewportHeight) {
838            mViewportWidth = viewportWidth;
839            mViewportHeight = viewportHeight;
840            nSetRendererViewportSize(getNativeRenderer(), viewportWidth, viewportHeight);
841        }
842
843        public final boolean setDensity(int targetDensity) {
844            if (mDensity != targetDensity) {
845                final int sourceDensity = mDensity;
846                mDensity = targetDensity;
847                applyDensityScaling(sourceDensity, targetDensity);
848                return true;
849            }
850            return false;
851        }
852
853        private void applyDensityScaling(int sourceDensity, int targetDensity) {
854            mBaseWidth = Drawable.scaleFromDensity(mBaseWidth, sourceDensity, targetDensity);
855            mBaseHeight = Drawable.scaleFromDensity(mBaseHeight, sourceDensity, targetDensity);
856
857            final int insetLeft = Drawable.scaleFromDensity(
858                    mOpticalInsets.left, sourceDensity, targetDensity, false);
859            final int insetTop = Drawable.scaleFromDensity(
860                    mOpticalInsets.top, sourceDensity, targetDensity, false);
861            final int insetRight = Drawable.scaleFromDensity(
862                    mOpticalInsets.right, sourceDensity, targetDensity, false);
863            final int insetBottom = Drawable.scaleFromDensity(
864                    mOpticalInsets.bottom, sourceDensity, targetDensity, false);
865            mOpticalInsets = Insets.of(insetLeft, insetTop, insetRight, insetBottom);
866        }
867
868        public boolean onStateChange(int[] stateSet) {
869            return mRootGroup.onStateChange(stateSet);
870        }
871
872        /**
873         * setAlpha() and getAlpha() are used mostly for animation purpose. Return true if alpha
874         * has changed.
875         */
876        public boolean setAlpha(float alpha) {
877            return nSetRootAlpha(mNativeRendererRefBase.get(), alpha);
878        }
879
880        @SuppressWarnings("unused")
881        public float getAlpha() {
882            return nGetRootAlpha(mNativeRendererRefBase.get());
883        }
884    }
885
886    static class VGroup implements VObject {
887        private static final int ROTATE_INDEX = 0;
888        private static final int PIVOT_X_INDEX = 1;
889        private static final int PIVOT_Y_INDEX = 2;
890        private static final int SCALE_X_INDEX = 3;
891        private static final int SCALE_Y_INDEX = 4;
892        private static final int TRANSLATE_X_INDEX = 5;
893        private static final int TRANSLATE_Y_INDEX = 6;
894        private static final int TRANSFORM_PROPERTY_COUNT = 7;
895
896        private static final HashMap<String, Integer> sPropertyMap =
897                new HashMap<String, Integer>() {
898                    {
899                        put("translateX", TRANSLATE_X_INDEX);
900                        put("translateY", TRANSLATE_Y_INDEX);
901                        put("scaleX", SCALE_X_INDEX);
902                        put("scaleY", SCALE_Y_INDEX);
903                        put("pivotX", PIVOT_X_INDEX);
904                        put("pivotY", PIVOT_Y_INDEX);
905                        put("rotation", ROTATE_INDEX);
906                    }
907                };
908
909        static int getPropertyIndex(String propertyName) {
910            if (sPropertyMap.containsKey(propertyName)) {
911                return sPropertyMap.get(propertyName);
912            } else {
913                // property not found
914                return -1;
915            }
916        }
917
918        // Temp array to store transform values obtained from native.
919        private float[] mTransform;
920        /////////////////////////////////////////////////////
921        // Variables below need to be copied (deep copy if applicable) for mutation.
922        private final ArrayList<VObject> mChildren = new ArrayList<>();
923        private boolean mIsStateful;
924
925        // mLocalMatrix is updated based on the update of transformation information,
926        // either parsed from the XML or by animation.
927        private @Config int mChangingConfigurations;
928        private int[] mThemeAttrs;
929        private String mGroupName = null;
930
931        // The native object will be created in the constructor and will be destroyed in native
932        // when the neither java nor native has ref to the tree. This pointer should be valid
933        // throughout this VGroup Java object's life.
934        private final long mNativePtr;
935        public VGroup(VGroup copy, ArrayMap<String, Object> targetsMap) {
936
937            mIsStateful = copy.mIsStateful;
938            mThemeAttrs = copy.mThemeAttrs;
939            mGroupName = copy.mGroupName;
940            mChangingConfigurations = copy.mChangingConfigurations;
941            if (mGroupName != null) {
942                targetsMap.put(mGroupName, this);
943            }
944            mNativePtr = nCreateGroup(copy.mNativePtr);
945
946            final ArrayList<VObject> children = copy.mChildren;
947            for (int i = 0; i < children.size(); i++) {
948                final VObject copyChild = children.get(i);
949                if (copyChild instanceof VGroup) {
950                    final VGroup copyGroup = (VGroup) copyChild;
951                    addChild(new VGroup(copyGroup, targetsMap));
952                } else {
953                    final VPath newPath;
954                    if (copyChild instanceof VFullPath) {
955                        newPath = new VFullPath((VFullPath) copyChild);
956                    } else if (copyChild instanceof VClipPath) {
957                        newPath = new VClipPath((VClipPath) copyChild);
958                    } else {
959                        throw new IllegalStateException("Unknown object in the tree!");
960                    }
961                    addChild(newPath);
962                    if (newPath.mPathName != null) {
963                        targetsMap.put(newPath.mPathName, newPath);
964                    }
965                }
966            }
967        }
968
969        public VGroup() {
970            mNativePtr = nCreateGroup();
971        }
972
973        public String getGroupName() {
974            return mGroupName;
975        }
976
977        public void addChild(VObject child) {
978            nAddChild(mNativePtr, child.getNativePtr());
979            mChildren.add(child);
980
981            mIsStateful |= child.isStateful();
982        }
983
984        @Override
985        public long getNativePtr() {
986            return mNativePtr;
987        }
988
989        @Override
990        public void inflate(Resources res, AttributeSet attrs, Theme theme) {
991            final TypedArray a = obtainAttributes(res, theme, attrs,
992                    R.styleable.VectorDrawableGroup);
993            updateStateFromTypedArray(a);
994            a.recycle();
995        }
996
997        void updateStateFromTypedArray(TypedArray a) {
998            // Account for any configuration changes.
999            mChangingConfigurations |= a.getChangingConfigurations();
1000
1001            // Extract the theme attributes, if any.
1002            mThemeAttrs = a.extractThemeAttrs();
1003            if (mTransform == null) {
1004                // Lazy initialization: If the group is created through copy constructor, this may
1005                // never get called.
1006                mTransform = new float[TRANSFORM_PROPERTY_COUNT];
1007            }
1008            boolean success = nGetGroupProperties(mNativePtr, mTransform, TRANSFORM_PROPERTY_COUNT);
1009            if (!success) {
1010                throw new RuntimeException("Error: inconsistent property count");
1011            }
1012            float rotate = a.getFloat(R.styleable.VectorDrawableGroup_rotation,
1013                    mTransform[ROTATE_INDEX]);
1014            float pivotX = a.getFloat(R.styleable.VectorDrawableGroup_pivotX,
1015                    mTransform[PIVOT_X_INDEX]);
1016            float pivotY = a.getFloat(R.styleable.VectorDrawableGroup_pivotY,
1017                    mTransform[PIVOT_Y_INDEX]);
1018            float scaleX = a.getFloat(R.styleable.VectorDrawableGroup_scaleX,
1019                    mTransform[SCALE_X_INDEX]);
1020            float scaleY = a.getFloat(R.styleable.VectorDrawableGroup_scaleY,
1021                    mTransform[SCALE_Y_INDEX]);
1022            float translateX = a.getFloat(R.styleable.VectorDrawableGroup_translateX,
1023                    mTransform[TRANSLATE_X_INDEX]);
1024            float translateY = a.getFloat(R.styleable.VectorDrawableGroup_translateY,
1025                    mTransform[TRANSLATE_Y_INDEX]);
1026
1027            final String groupName = a.getString(R.styleable.VectorDrawableGroup_name);
1028            if (groupName != null) {
1029                mGroupName = groupName;
1030                nSetName(mNativePtr, mGroupName);
1031            }
1032             nUpdateGroupProperties(mNativePtr, rotate, pivotX, pivotY, scaleX, scaleY,
1033                     translateX, translateY);
1034        }
1035
1036        @Override
1037        public boolean onStateChange(int[] stateSet) {
1038            boolean changed = false;
1039
1040            final ArrayList<VObject> children = mChildren;
1041            for (int i = 0, count = children.size(); i < count; i++) {
1042                final VObject child = children.get(i);
1043                if (child.isStateful()) {
1044                    changed |= child.onStateChange(stateSet);
1045                }
1046            }
1047
1048            return changed;
1049        }
1050
1051        @Override
1052        public boolean isStateful() {
1053            return mIsStateful;
1054        }
1055
1056        @Override
1057        public boolean canApplyTheme() {
1058            if (mThemeAttrs != null) {
1059                return true;
1060            }
1061
1062            final ArrayList<VObject> children = mChildren;
1063            for (int i = 0, count = children.size(); i < count; i++) {
1064                final VObject child = children.get(i);
1065                if (child.canApplyTheme()) {
1066                    return true;
1067                }
1068            }
1069
1070            return false;
1071        }
1072
1073        @Override
1074        public void applyTheme(Theme t) {
1075            if (mThemeAttrs != null) {
1076                final TypedArray a = t.resolveAttributes(mThemeAttrs,
1077                        R.styleable.VectorDrawableGroup);
1078                updateStateFromTypedArray(a);
1079                a.recycle();
1080            }
1081
1082            final ArrayList<VObject> children = mChildren;
1083            for (int i = 0, count = children.size(); i < count; i++) {
1084                final VObject child = children.get(i);
1085                if (child.canApplyTheme()) {
1086                    child.applyTheme(t);
1087
1088                    // Applying a theme may have made the child stateful.
1089                    mIsStateful |= child.isStateful();
1090                }
1091            }
1092        }
1093
1094        /* Setters and Getters, used by animator from AnimatedVectorDrawable. */
1095        @SuppressWarnings("unused")
1096        public float getRotation() {
1097            return nGetRotation(mNativePtr);
1098        }
1099
1100        @SuppressWarnings("unused")
1101        public void setRotation(float rotation) {
1102            nSetRotation(mNativePtr, rotation);
1103        }
1104
1105        @SuppressWarnings("unused")
1106        public float getPivotX() {
1107            return nGetPivotX(mNativePtr);
1108        }
1109
1110        @SuppressWarnings("unused")
1111        public void setPivotX(float pivotX) {
1112            nSetPivotX(mNativePtr, pivotX);
1113        }
1114
1115        @SuppressWarnings("unused")
1116        public float getPivotY() {
1117            return nGetPivotY(mNativePtr);
1118        }
1119
1120        @SuppressWarnings("unused")
1121        public void setPivotY(float pivotY) {
1122            nSetPivotY(mNativePtr, pivotY);
1123        }
1124
1125        @SuppressWarnings("unused")
1126        public float getScaleX() {
1127            return nGetScaleX(mNativePtr);
1128        }
1129
1130        @SuppressWarnings("unused")
1131        public void setScaleX(float scaleX) {
1132            nSetScaleX(mNativePtr, scaleX);
1133        }
1134
1135        @SuppressWarnings("unused")
1136        public float getScaleY() {
1137            return nGetScaleY(mNativePtr);
1138        }
1139
1140        @SuppressWarnings("unused")
1141        public void setScaleY(float scaleY) {
1142            nSetScaleY(mNativePtr, scaleY);
1143        }
1144
1145        @SuppressWarnings("unused")
1146        public float getTranslateX() {
1147            return nGetTranslateX(mNativePtr);
1148        }
1149
1150        @SuppressWarnings("unused")
1151        public void setTranslateX(float translateX) {
1152            nSetTranslateX(mNativePtr, translateX);
1153        }
1154
1155        @SuppressWarnings("unused")
1156        public float getTranslateY() {
1157            return nGetTranslateY(mNativePtr);
1158        }
1159
1160        @SuppressWarnings("unused")
1161        public void setTranslateY(float translateY) {
1162            nSetTranslateY(mNativePtr, translateY);
1163        }
1164    }
1165
1166    /**
1167     * Common Path information for clip path and normal path.
1168     */
1169    static abstract class VPath implements VObject {
1170        protected PathParser.PathData mPathData = null;
1171
1172        String mPathName;
1173        @Config int mChangingConfigurations;
1174
1175        public VPath() {
1176            // Empty constructor.
1177        }
1178
1179        public VPath(VPath copy) {
1180            mPathName = copy.mPathName;
1181            mChangingConfigurations = copy.mChangingConfigurations;
1182            mPathData = copy.mPathData == null ? null : new PathParser.PathData(copy.mPathData);
1183        }
1184
1185        public String getPathName() {
1186            return mPathName;
1187        }
1188
1189        /* Setters and Getters, used by animator from AnimatedVectorDrawable. */
1190        @SuppressWarnings("unused")
1191        public PathParser.PathData getPathData() {
1192            return mPathData;
1193        }
1194
1195        // TODO: Move the PathEvaluator and this setter and the getter above into native.
1196        @SuppressWarnings("unused")
1197        public void setPathData(PathParser.PathData pathData) {
1198            mPathData.setPathData(pathData);
1199            nSetPathData(getNativePtr(), mPathData.getNativePtr());
1200        }
1201    }
1202
1203    /**
1204     * Clip path, which only has name and pathData.
1205     */
1206    private static class VClipPath extends VPath {
1207        private final long mNativePtr;
1208
1209        public VClipPath() {
1210            mNativePtr = nCreateClipPath();
1211        }
1212
1213        public VClipPath(VClipPath copy) {
1214            super(copy);
1215            mNativePtr = nCreateClipPath(copy.mNativePtr);
1216        }
1217
1218        @Override
1219        public long getNativePtr() {
1220            return mNativePtr;
1221        }
1222
1223        @Override
1224        public void inflate(Resources r, AttributeSet attrs, Theme theme) {
1225            final TypedArray a = obtainAttributes(r, theme, attrs,
1226                    R.styleable.VectorDrawableClipPath);
1227            updateStateFromTypedArray(a);
1228            a.recycle();
1229        }
1230
1231        @Override
1232        public boolean canApplyTheme() {
1233            return false;
1234        }
1235
1236        @Override
1237        public void applyTheme(Theme theme) {
1238            // No-op.
1239        }
1240
1241        @Override
1242        public boolean onStateChange(int[] stateSet) {
1243            return false;
1244        }
1245
1246        @Override
1247        public boolean isStateful() {
1248            return false;
1249        }
1250
1251        private void updateStateFromTypedArray(TypedArray a) {
1252            // Account for any configuration changes.
1253            mChangingConfigurations |= a.getChangingConfigurations();
1254
1255            final String pathName = a.getString(R.styleable.VectorDrawableClipPath_name);
1256            if (pathName != null) {
1257                mPathName = pathName;
1258                nSetName(mNativePtr, mPathName);
1259            }
1260
1261            final String pathDataString = a.getString(R.styleable.VectorDrawableClipPath_pathData);
1262            if (pathDataString != null) {
1263                mPathData = new PathParser.PathData(pathDataString);
1264                nSetPathString(mNativePtr, pathDataString, pathDataString.length());
1265            }
1266        }
1267    }
1268
1269    /**
1270     * Normal path, which contains all the fill / paint information.
1271     */
1272    static class VFullPath extends VPath {
1273        private static final int STROKE_WIDTH_INDEX = 0;
1274        private static final int STROKE_COLOR_INDEX = 1;
1275        private static final int STROKE_ALPHA_INDEX = 2;
1276        private static final int FILL_COLOR_INDEX = 3;
1277        private static final int FILL_ALPHA_INDEX = 4;
1278        private static final int TRIM_PATH_START_INDEX = 5;
1279        private static final int TRIM_PATH_END_INDEX = 6;
1280        private static final int TRIM_PATH_OFFSET_INDEX = 7;
1281        private static final int STROKE_LINE_CAP_INDEX = 8;
1282        private static final int STROKE_LINE_JOIN_INDEX = 9;
1283        private static final int STROKE_MITER_LIMIT_INDEX = 10;
1284        private static final int FILL_TYPE_INDEX = 11;
1285        private static final int TOTAL_PROPERTY_COUNT = 12;
1286
1287        // Property map for animatable attributes.
1288        private final static HashMap<String, Integer> sPropertyMap
1289                = new HashMap<String, Integer> () {
1290            {
1291                put("strokeWidth", STROKE_WIDTH_INDEX);
1292                put("strokeColor", STROKE_COLOR_INDEX);
1293                put("strokeAlpha", STROKE_ALPHA_INDEX);
1294                put("fillColor", FILL_COLOR_INDEX);
1295                put("fillAlpha", FILL_ALPHA_INDEX);
1296                put("trimPathStart", TRIM_PATH_START_INDEX);
1297                put("trimPathEnd", TRIM_PATH_END_INDEX);
1298                put("trimPathOffset", TRIM_PATH_OFFSET_INDEX);
1299            }
1300        };
1301
1302        // Temp array to store property data obtained from native getter.
1303        private byte[] mPropertyData;
1304        /////////////////////////////////////////////////////
1305        // Variables below need to be copied (deep copy if applicable) for mutation.
1306        private int[] mThemeAttrs;
1307
1308        ComplexColor mStrokeColors = null;
1309        ComplexColor mFillColors = null;
1310        private final long mNativePtr;
1311
1312        public VFullPath() {
1313            mNativePtr = nCreateFullPath();
1314        }
1315
1316        public VFullPath(VFullPath copy) {
1317            super(copy);
1318            mNativePtr = nCreateFullPath(copy.mNativePtr);
1319            mThemeAttrs = copy.mThemeAttrs;
1320            mStrokeColors = copy.mStrokeColors;
1321            mFillColors = copy.mFillColors;
1322        }
1323
1324        int getPropertyIndex(String propertyName) {
1325            if (!sPropertyMap.containsKey(propertyName)) {
1326                return -1;
1327            } else {
1328                return sPropertyMap.get(propertyName);
1329            }
1330        }
1331
1332        @Override
1333        public boolean onStateChange(int[] stateSet) {
1334            boolean changed = false;
1335
1336            if (mStrokeColors != null && mStrokeColors instanceof ColorStateList) {
1337                final int oldStrokeColor = getStrokeColor();
1338                final int newStrokeColor =
1339                        ((ColorStateList) mStrokeColors).getColorForState(stateSet, oldStrokeColor);
1340                changed |= oldStrokeColor != newStrokeColor;
1341                if (oldStrokeColor != newStrokeColor) {
1342                    nSetStrokeColor(mNativePtr, newStrokeColor);
1343                }
1344            }
1345
1346            if (mFillColors != null && mFillColors instanceof ColorStateList) {
1347                final int oldFillColor = getFillColor();
1348                final int newFillColor = ((ColorStateList) mFillColors).getColorForState(stateSet, oldFillColor);
1349                changed |= oldFillColor != newFillColor;
1350                if (oldFillColor != newFillColor) {
1351                    nSetFillColor(mNativePtr, newFillColor);
1352                }
1353            }
1354
1355            return changed;
1356        }
1357
1358        @Override
1359        public boolean isStateful() {
1360            return mStrokeColors != null || mFillColors != null;
1361        }
1362
1363        @Override
1364        public long getNativePtr() {
1365            return mNativePtr;
1366        }
1367
1368        @Override
1369        public void inflate(Resources r, AttributeSet attrs, Theme theme) {
1370            final TypedArray a = obtainAttributes(r, theme, attrs,
1371                    R.styleable.VectorDrawablePath);
1372            updateStateFromTypedArray(a);
1373            a.recycle();
1374        }
1375
1376        private void updateStateFromTypedArray(TypedArray a) {
1377            int byteCount = TOTAL_PROPERTY_COUNT * 4;
1378            if (mPropertyData == null) {
1379                // Lazy initialization: If the path is created through copy constructor, this may
1380                // never get called.
1381                mPropertyData = new byte[byteCount];
1382            }
1383            // The bulk getters/setters of property data (e.g. stroke width, color, etc) allows us
1384            // to pull current values from native and store modifications with only two methods,
1385            // minimizing JNI overhead.
1386            boolean success = nGetFullPathProperties(mNativePtr, mPropertyData, byteCount);
1387            if (!success) {
1388                throw new RuntimeException("Error: inconsistent property count");
1389            }
1390
1391            ByteBuffer properties = ByteBuffer.wrap(mPropertyData);
1392            properties.order(ByteOrder.nativeOrder());
1393            float strokeWidth = properties.getFloat(STROKE_WIDTH_INDEX * 4);
1394            int strokeColor = properties.getInt(STROKE_COLOR_INDEX * 4);
1395            float strokeAlpha = properties.getFloat(STROKE_ALPHA_INDEX * 4);
1396            int fillColor =  properties.getInt(FILL_COLOR_INDEX * 4);
1397            float fillAlpha = properties.getFloat(FILL_ALPHA_INDEX * 4);
1398            float trimPathStart = properties.getFloat(TRIM_PATH_START_INDEX * 4);
1399            float trimPathEnd = properties.getFloat(TRIM_PATH_END_INDEX * 4);
1400            float trimPathOffset = properties.getFloat(TRIM_PATH_OFFSET_INDEX * 4);
1401            int strokeLineCap =  properties.getInt(STROKE_LINE_CAP_INDEX * 4);
1402            int strokeLineJoin = properties.getInt(STROKE_LINE_JOIN_INDEX * 4);
1403            float strokeMiterLimit = properties.getFloat(STROKE_MITER_LIMIT_INDEX * 4);
1404            int fillType = properties.getInt(FILL_TYPE_INDEX * 4);
1405            Shader fillGradient = null;
1406            Shader strokeGradient = null;
1407            // Account for any configuration changes.
1408            mChangingConfigurations |= a.getChangingConfigurations();
1409
1410            // Extract the theme attributes, if any.
1411            mThemeAttrs = a.extractThemeAttrs();
1412
1413            final String pathName = a.getString(R.styleable.VectorDrawablePath_name);
1414            if (pathName != null) {
1415                mPathName = pathName;
1416                nSetName(mNativePtr, mPathName);
1417            }
1418
1419            final String pathString = a.getString(R.styleable.VectorDrawablePath_pathData);
1420            if (pathString != null) {
1421                mPathData = new PathParser.PathData(pathString);
1422                nSetPathString(mNativePtr, pathString, pathString.length());
1423            }
1424
1425            final ComplexColor fillColors = a.getComplexColor(
1426                    R.styleable.VectorDrawablePath_fillColor);
1427            if (fillColors != null) {
1428                // If the colors is a gradient color, or the color state list is stateful, keep the
1429                // colors information. Otherwise, discard the colors and keep the default color.
1430                if (fillColors instanceof  GradientColor) {
1431                    mFillColors = fillColors;
1432                    fillGradient = ((GradientColor) fillColors).getShader();
1433                } else if (fillColors.isStateful()) {
1434                    mFillColors = fillColors;
1435                } else {
1436                    mFillColors = null;
1437                }
1438                fillColor = fillColors.getDefaultColor();
1439            }
1440
1441            final ComplexColor strokeColors = a.getComplexColor(
1442                    R.styleable.VectorDrawablePath_strokeColor);
1443            if (strokeColors != null) {
1444                // If the colors is a gradient color, or the color state list is stateful, keep the
1445                // colors information. Otherwise, discard the colors and keep the default color.
1446                if (strokeColors instanceof GradientColor) {
1447                    mStrokeColors = strokeColors;
1448                    strokeGradient = ((GradientColor) strokeColors).getShader();
1449                } else if (strokeColors.isStateful()) {
1450                    mStrokeColors = strokeColors;
1451                } else {
1452                    mStrokeColors = null;
1453                }
1454                strokeColor = strokeColors.getDefaultColor();
1455            }
1456            // Update the gradient info, even if the gradiet is null.
1457            nUpdateFullPathFillGradient(mNativePtr,
1458                    fillGradient != null ? fillGradient.getNativeInstance() : 0);
1459            nUpdateFullPathStrokeGradient(mNativePtr,
1460                    strokeGradient != null ? strokeGradient.getNativeInstance() : 0);
1461
1462            fillAlpha = a.getFloat(R.styleable.VectorDrawablePath_fillAlpha, fillAlpha);
1463
1464            strokeLineCap = a.getInt(
1465                    R.styleable.VectorDrawablePath_strokeLineCap, strokeLineCap);
1466            strokeLineJoin = a.getInt(
1467                    R.styleable.VectorDrawablePath_strokeLineJoin, strokeLineJoin);
1468            strokeMiterLimit = a.getFloat(
1469                    R.styleable.VectorDrawablePath_strokeMiterLimit, strokeMiterLimit);
1470            strokeAlpha = a.getFloat(R.styleable.VectorDrawablePath_strokeAlpha,
1471                    strokeAlpha);
1472            strokeWidth = a.getFloat(R.styleable.VectorDrawablePath_strokeWidth,
1473                    strokeWidth);
1474            trimPathEnd = a.getFloat(R.styleable.VectorDrawablePath_trimPathEnd,
1475                    trimPathEnd);
1476            trimPathOffset = a.getFloat(
1477                    R.styleable.VectorDrawablePath_trimPathOffset, trimPathOffset);
1478            trimPathStart = a.getFloat(
1479                    R.styleable.VectorDrawablePath_trimPathStart, trimPathStart);
1480            fillType = a.getInt(R.styleable.VectorDrawablePath_fillType, fillType);
1481
1482            nUpdateFullPathProperties(mNativePtr, strokeWidth, strokeColor, strokeAlpha,
1483                    fillColor, fillAlpha, trimPathStart, trimPathEnd, trimPathOffset,
1484                    strokeMiterLimit, strokeLineCap, strokeLineJoin, fillType);
1485        }
1486
1487        @Override
1488        public boolean canApplyTheme() {
1489            if (mThemeAttrs != null) {
1490                return true;
1491            }
1492
1493            boolean fillCanApplyTheme = canComplexColorApplyTheme(mFillColors);
1494            boolean strokeCanApplyTheme = canComplexColorApplyTheme(mStrokeColors);
1495            if (fillCanApplyTheme || strokeCanApplyTheme) {
1496                return true;
1497            }
1498            return false;
1499
1500        }
1501
1502        @Override
1503        public void applyTheme(Theme t) {
1504            // Resolve the theme attributes directly referred by the VectorDrawable.
1505            if (mThemeAttrs != null) {
1506                final TypedArray a = t.resolveAttributes(mThemeAttrs, R.styleable.VectorDrawablePath);
1507                updateStateFromTypedArray(a);
1508                a.recycle();
1509            }
1510
1511            // Resolve the theme attributes in-directly referred by the VectorDrawable, for example,
1512            // fillColor can refer to a color state list which itself needs to apply theme.
1513            // And this is the reason we still want to keep partial update for the path's properties.
1514            boolean fillCanApplyTheme = canComplexColorApplyTheme(mFillColors);
1515            boolean strokeCanApplyTheme = canComplexColorApplyTheme(mStrokeColors);
1516
1517            if (fillCanApplyTheme) {
1518                mFillColors = mFillColors.obtainForTheme(t);
1519                if (mFillColors instanceof GradientColor) {
1520                    nUpdateFullPathFillGradient(mNativePtr,
1521                            ((GradientColor) mFillColors).getShader().getNativeInstance());
1522                } else if (mFillColors instanceof ColorStateList) {
1523                    nSetFillColor(mNativePtr, mFillColors.getDefaultColor());
1524                }
1525            }
1526
1527            if (strokeCanApplyTheme) {
1528                mStrokeColors = mStrokeColors.obtainForTheme(t);
1529                if (mStrokeColors instanceof GradientColor) {
1530                    nUpdateFullPathStrokeGradient(mNativePtr,
1531                            ((GradientColor) mStrokeColors).getShader().getNativeInstance());
1532                } else if (mStrokeColors instanceof ColorStateList) {
1533                    nSetStrokeColor(mNativePtr, mStrokeColors.getDefaultColor());
1534                }
1535            }
1536        }
1537
1538        private boolean canComplexColorApplyTheme(ComplexColor complexColor) {
1539            return complexColor != null && complexColor.canApplyTheme();
1540        }
1541
1542        /* Setters and Getters, used by animator from AnimatedVectorDrawable. */
1543        @SuppressWarnings("unused")
1544        int getStrokeColor() {
1545            return nGetStrokeColor(mNativePtr);
1546        }
1547
1548        @SuppressWarnings("unused")
1549        void setStrokeColor(int strokeColor) {
1550            mStrokeColors = null;
1551            nSetStrokeColor(mNativePtr, strokeColor);
1552        }
1553
1554        @SuppressWarnings("unused")
1555        float getStrokeWidth() {
1556            return nGetStrokeWidth(mNativePtr);
1557        }
1558
1559        @SuppressWarnings("unused")
1560        void setStrokeWidth(float strokeWidth) {
1561            nSetStrokeWidth(mNativePtr, strokeWidth);
1562        }
1563
1564        @SuppressWarnings("unused")
1565        float getStrokeAlpha() {
1566            return nGetStrokeAlpha(mNativePtr);
1567        }
1568
1569        @SuppressWarnings("unused")
1570        void setStrokeAlpha(float strokeAlpha) {
1571            nSetStrokeAlpha(mNativePtr, strokeAlpha);
1572        }
1573
1574        @SuppressWarnings("unused")
1575        int getFillColor() {
1576            return nGetFillColor(mNativePtr);
1577        }
1578
1579        @SuppressWarnings("unused")
1580        void setFillColor(int fillColor) {
1581            mFillColors = null;
1582            nSetFillColor(mNativePtr, fillColor);
1583        }
1584
1585        @SuppressWarnings("unused")
1586        float getFillAlpha() {
1587            return nGetFillAlpha(mNativePtr);
1588        }
1589
1590        @SuppressWarnings("unused")
1591        void setFillAlpha(float fillAlpha) {
1592            nSetFillAlpha(mNativePtr, fillAlpha);
1593        }
1594
1595        @SuppressWarnings("unused")
1596        float getTrimPathStart() {
1597            return nGetTrimPathStart(mNativePtr);
1598        }
1599
1600        @SuppressWarnings("unused")
1601        void setTrimPathStart(float trimPathStart) {
1602            nSetTrimPathStart(mNativePtr, trimPathStart);
1603        }
1604
1605        @SuppressWarnings("unused")
1606        float getTrimPathEnd() {
1607            return nGetTrimPathEnd(mNativePtr);
1608        }
1609
1610        @SuppressWarnings("unused")
1611        void setTrimPathEnd(float trimPathEnd) {
1612            nSetTrimPathEnd(mNativePtr, trimPathEnd);
1613        }
1614
1615        @SuppressWarnings("unused")
1616        float getTrimPathOffset() {
1617            return nGetTrimPathOffset(mNativePtr);
1618        }
1619
1620        @SuppressWarnings("unused")
1621        void setTrimPathOffset(float trimPathOffset) {
1622            nSetTrimPathOffset(mNativePtr, trimPathOffset);
1623        }
1624    }
1625
1626    interface VObject {
1627        long getNativePtr();
1628        void inflate(Resources r, AttributeSet attrs, Theme theme);
1629        boolean canApplyTheme();
1630        void applyTheme(Theme t);
1631        boolean onStateChange(int[] state);
1632        boolean isStateful();
1633    }
1634
1635    private static native long nCreateRenderer(long rootGroupPtr);
1636    private static native void nSetRendererViewportSize(long rendererPtr, float viewportWidth,
1637            float viewportHeight);
1638    private static native boolean nSetRootAlpha(long rendererPtr, float alpha);
1639    private static native float nGetRootAlpha(long rendererPtr);
1640    private static native void nSetAllowCaching(long rendererPtr, boolean allowCaching);
1641
1642    private static native void nDraw(long rendererPtr, long canvasWrapperPtr,
1643            long colorFilterPtr, Rect bounds, boolean needsMirroring, boolean canReuseCache);
1644    private static native long nCreateFullPath();
1645    private static native long nCreateFullPath(long nativeFullPathPtr);
1646    private static native boolean nGetFullPathProperties(long pathPtr, byte[] properties,
1647            int length);
1648
1649    private static native void nUpdateFullPathProperties(long pathPtr, float strokeWidth,
1650            int strokeColor, float strokeAlpha, int fillColor, float fillAlpha, float trimPathStart,
1651            float trimPathEnd, float trimPathOffset, float strokeMiterLimit, int strokeLineCap,
1652            int strokeLineJoin, int fillType);
1653    private static native void nUpdateFullPathFillGradient(long pathPtr, long fillGradientPtr);
1654    private static native void nUpdateFullPathStrokeGradient(long pathPtr, long strokeGradientPtr);
1655
1656    private static native long nCreateClipPath();
1657    private static native long nCreateClipPath(long clipPathPtr);
1658
1659    private static native long nCreateGroup();
1660    private static native long nCreateGroup(long groupPtr);
1661    private static native void nSetName(long nodePtr, String name);
1662    private static native boolean nGetGroupProperties(long groupPtr, float[] properties,
1663            int length);
1664    private static native void nUpdateGroupProperties(long groupPtr, float rotate, float pivotX,
1665            float pivotY, float scaleX, float scaleY, float translateX, float translateY);
1666
1667    private static native void nAddChild(long groupPtr, long nodePtr);
1668    private static native void nSetPathString(long pathPtr, String pathString, int length);
1669
1670    /**
1671     * The setters and getters below for paths and groups are here temporarily, and will be
1672     * removed once the animation in AVD is replaced with RenderNodeAnimator, in which case the
1673     * animation will modify these properties in native. By then no JNI hopping would be necessary
1674     * for VD during animation, and these setters and getters will be obsolete.
1675     */
1676    // Setters and getters during animation.
1677    private static native float nGetRotation(long groupPtr);
1678    private static native void nSetRotation(long groupPtr, float rotation);
1679    private static native float nGetPivotX(long groupPtr);
1680    private static native void nSetPivotX(long groupPtr, float pivotX);
1681    private static native float nGetPivotY(long groupPtr);
1682    private static native void nSetPivotY(long groupPtr, float pivotY);
1683    private static native float nGetScaleX(long groupPtr);
1684    private static native void nSetScaleX(long groupPtr, float scaleX);
1685    private static native float nGetScaleY(long groupPtr);
1686    private static native void nSetScaleY(long groupPtr, float scaleY);
1687    private static native float nGetTranslateX(long groupPtr);
1688    private static native void nSetTranslateX(long groupPtr, float translateX);
1689    private static native float nGetTranslateY(long groupPtr);
1690    private static native void nSetTranslateY(long groupPtr, float translateY);
1691
1692    // Setters and getters for VPath during animation.
1693    private static native void nSetPathData(long pathPtr, long pathDataPtr);
1694    private static native float nGetStrokeWidth(long pathPtr);
1695    private static native void nSetStrokeWidth(long pathPtr, float width);
1696    private static native int nGetStrokeColor(long pathPtr);
1697    private static native void nSetStrokeColor(long pathPtr, int strokeColor);
1698    private static native float nGetStrokeAlpha(long pathPtr);
1699    private static native void nSetStrokeAlpha(long pathPtr, float alpha);
1700    private static native int nGetFillColor(long pathPtr);
1701    private static native void nSetFillColor(long pathPtr, int fillColor);
1702    private static native float nGetFillAlpha(long pathPtr);
1703    private static native void nSetFillAlpha(long pathPtr, float fillAlpha);
1704    private static native float nGetTrimPathStart(long pathPtr);
1705    private static native void nSetTrimPathStart(long pathPtr, float trimPathStart);
1706    private static native float nGetTrimPathEnd(long pathPtr);
1707    private static native void nSetTrimPathEnd(long pathPtr, float trimPathEnd);
1708    private static native float nGetTrimPathOffset(long pathPtr);
1709    private static native void nSetTrimPathOffset(long pathPtr, float trimPathOffset);
1710}
1711