VectorDrawable.java revision ac85f90466dd60d2af8ffc3942d503a0de606726
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 TOTAL_PROPERTY_COUNT = 11;
1285
1286        private final static HashMap<String, Integer> sPropertyMap
1287                = new HashMap<String, Integer> () {
1288            {
1289                put("strokeWidth", STROKE_WIDTH_INDEX);
1290                put("strokeColor", STROKE_COLOR_INDEX);
1291                put("strokeAlpha", STROKE_ALPHA_INDEX);
1292                put("fillColor", FILL_COLOR_INDEX);
1293                put("fillAlpha", FILL_ALPHA_INDEX);
1294                put("trimPathStart", TRIM_PATH_START_INDEX);
1295                put("trimPathEnd", TRIM_PATH_END_INDEX);
1296                put("trimPathOffset", TRIM_PATH_OFFSET_INDEX);
1297            }
1298        };
1299
1300        // Temp array to store property data obtained from native getter.
1301        private byte[] mPropertyData;
1302        /////////////////////////////////////////////////////
1303        // Variables below need to be copied (deep copy if applicable) for mutation.
1304        private int[] mThemeAttrs;
1305
1306        ComplexColor mStrokeColors = null;
1307        ComplexColor mFillColors = null;
1308        private final long mNativePtr;
1309
1310        public VFullPath() {
1311            mNativePtr = nCreateFullPath();
1312        }
1313
1314        public VFullPath(VFullPath copy) {
1315            super(copy);
1316            mNativePtr = nCreateFullPath(copy.mNativePtr);
1317            mThemeAttrs = copy.mThemeAttrs;
1318            mStrokeColors = copy.mStrokeColors;
1319            mFillColors = copy.mFillColors;
1320        }
1321
1322        int getPropertyIndex(String propertyName) {
1323            if (!sPropertyMap.containsKey(propertyName)) {
1324                return -1;
1325            } else {
1326                return sPropertyMap.get(propertyName);
1327            }
1328        }
1329
1330        @Override
1331        public boolean onStateChange(int[] stateSet) {
1332            boolean changed = false;
1333
1334            if (mStrokeColors != null && mStrokeColors instanceof ColorStateList) {
1335                final int oldStrokeColor = getStrokeColor();
1336                final int newStrokeColor =
1337                        ((ColorStateList) mStrokeColors).getColorForState(stateSet, oldStrokeColor);
1338                changed |= oldStrokeColor != newStrokeColor;
1339                if (oldStrokeColor != newStrokeColor) {
1340                    nSetStrokeColor(mNativePtr, newStrokeColor);
1341                }
1342            }
1343
1344            if (mFillColors != null && mFillColors instanceof ColorStateList) {
1345                final int oldFillColor = getFillColor();
1346                final int newFillColor = ((ColorStateList) mFillColors).getColorForState(stateSet, oldFillColor);
1347                changed |= oldFillColor != newFillColor;
1348                if (oldFillColor != newFillColor) {
1349                    nSetFillColor(mNativePtr, newFillColor);
1350                }
1351            }
1352
1353            return changed;
1354        }
1355
1356        @Override
1357        public boolean isStateful() {
1358            return mStrokeColors != null || mFillColors != null;
1359        }
1360
1361        @Override
1362        public long getNativePtr() {
1363            return mNativePtr;
1364        }
1365
1366        @Override
1367        public void inflate(Resources r, AttributeSet attrs, Theme theme) {
1368            final TypedArray a = obtainAttributes(r, theme, attrs,
1369                    R.styleable.VectorDrawablePath);
1370            updateStateFromTypedArray(a);
1371            a.recycle();
1372        }
1373
1374        private void updateStateFromTypedArray(TypedArray a) {
1375            int byteCount = TOTAL_PROPERTY_COUNT * 4;
1376            if (mPropertyData == null) {
1377                // Lazy initialization: If the path is created through copy constructor, this may
1378                // never get called.
1379                mPropertyData = new byte[byteCount];
1380            }
1381            // The bulk getters/setters of property data (e.g. stroke width, color, etc) allows us
1382            // to pull current values from native and store modifications with only two methods,
1383            // minimizing JNI overhead.
1384            boolean success = nGetFullPathProperties(mNativePtr, mPropertyData, byteCount);
1385            if (!success) {
1386                throw new RuntimeException("Error: inconsistent property count");
1387            }
1388
1389            ByteBuffer properties = ByteBuffer.wrap(mPropertyData);
1390            properties.order(ByteOrder.nativeOrder());
1391            float strokeWidth = properties.getFloat(STROKE_WIDTH_INDEX * 4);
1392            int strokeColor = properties.getInt(STROKE_COLOR_INDEX * 4);
1393            float strokeAlpha = properties.getFloat(STROKE_ALPHA_INDEX * 4);
1394            int fillColor =  properties.getInt(FILL_COLOR_INDEX * 4);
1395            float fillAlpha = properties.getFloat(FILL_ALPHA_INDEX * 4);
1396            float trimPathStart = properties.getFloat(TRIM_PATH_START_INDEX * 4);
1397            float trimPathEnd = properties.getFloat(TRIM_PATH_END_INDEX * 4);
1398            float trimPathOffset = properties.getFloat(TRIM_PATH_OFFSET_INDEX * 4);
1399            int strokeLineCap =  properties.getInt(STROKE_LINE_CAP_INDEX * 4);
1400            int strokeLineJoin = properties.getInt(STROKE_LINE_JOIN_INDEX * 4);
1401            float strokeMiterLimit = properties.getFloat(STROKE_MITER_LIMIT_INDEX * 4);
1402            Shader fillGradient = null;
1403            Shader strokeGradient = null;
1404            // Account for any configuration changes.
1405            mChangingConfigurations |= a.getChangingConfigurations();
1406
1407            // Extract the theme attributes, if any.
1408            mThemeAttrs = a.extractThemeAttrs();
1409
1410            final String pathName = a.getString(R.styleable.VectorDrawablePath_name);
1411            if (pathName != null) {
1412                mPathName = pathName;
1413                nSetName(mNativePtr, mPathName);
1414            }
1415
1416            final String pathString = a.getString(R.styleable.VectorDrawablePath_pathData);
1417            if (pathString != null) {
1418                mPathData = new PathParser.PathData(pathString);
1419                nSetPathString(mNativePtr, pathString, pathString.length());
1420            }
1421
1422            final ComplexColor fillColors = a.getComplexColor(
1423                    R.styleable.VectorDrawablePath_fillColor);
1424            if (fillColors != null) {
1425                // If the colors is a gradient color, or the color state list is stateful, keep the
1426                // colors information. Otherwise, discard the colors and keep the default color.
1427                if (fillColors instanceof  GradientColor) {
1428                    mFillColors = fillColors;
1429                    fillGradient = ((GradientColor) fillColors).getShader();
1430                } else if (fillColors.isStateful()) {
1431                    mFillColors = fillColors;
1432                } else {
1433                    mFillColors = null;
1434                }
1435                fillColor = fillColors.getDefaultColor();
1436            }
1437
1438            final ComplexColor strokeColors = a.getComplexColor(
1439                    R.styleable.VectorDrawablePath_strokeColor);
1440            if (strokeColors != null) {
1441                // If the colors is a gradient color, or the color state list is stateful, keep the
1442                // colors information. Otherwise, discard the colors and keep the default color.
1443                if (strokeColors instanceof GradientColor) {
1444                    mStrokeColors = strokeColors;
1445                    strokeGradient = ((GradientColor) strokeColors).getShader();
1446                } else if (strokeColors.isStateful()) {
1447                    mStrokeColors = strokeColors;
1448                } else {
1449                    mStrokeColors = null;
1450                }
1451                strokeColor = strokeColors.getDefaultColor();
1452            }
1453            // Update the gradient info, even if the gradiet is null.
1454            nUpdateFullPathFillGradient(mNativePtr,
1455                    fillGradient != null ? fillGradient.getNativeInstance() : 0);
1456            nUpdateFullPathStrokeGradient(mNativePtr,
1457                    strokeGradient != null ? strokeGradient.getNativeInstance() : 0);
1458
1459            fillAlpha = a.getFloat(R.styleable.VectorDrawablePath_fillAlpha, fillAlpha);
1460
1461            strokeLineCap = a.getInt(
1462                    R.styleable.VectorDrawablePath_strokeLineCap, strokeLineCap);
1463            strokeLineJoin = a.getInt(
1464                    R.styleable.VectorDrawablePath_strokeLineJoin, strokeLineJoin);
1465            strokeMiterLimit = a.getFloat(
1466                    R.styleable.VectorDrawablePath_strokeMiterLimit, strokeMiterLimit);
1467            strokeAlpha = a.getFloat(R.styleable.VectorDrawablePath_strokeAlpha,
1468                    strokeAlpha);
1469            strokeWidth = a.getFloat(R.styleable.VectorDrawablePath_strokeWidth,
1470                    strokeWidth);
1471            trimPathEnd = a.getFloat(R.styleable.VectorDrawablePath_trimPathEnd,
1472                    trimPathEnd);
1473            trimPathOffset = a.getFloat(
1474                    R.styleable.VectorDrawablePath_trimPathOffset, trimPathOffset);
1475            trimPathStart = a.getFloat(
1476                    R.styleable.VectorDrawablePath_trimPathStart, trimPathStart);
1477
1478            nUpdateFullPathProperties(mNativePtr, strokeWidth, strokeColor, strokeAlpha,
1479                    fillColor, fillAlpha, trimPathStart, trimPathEnd, trimPathOffset,
1480                    strokeMiterLimit, strokeLineCap, strokeLineJoin);
1481        }
1482
1483        @Override
1484        public boolean canApplyTheme() {
1485            if (mThemeAttrs != null) {
1486                return true;
1487            }
1488
1489            boolean fillCanApplyTheme = canComplexColorApplyTheme(mFillColors);
1490            boolean strokeCanApplyTheme = canComplexColorApplyTheme(mStrokeColors);
1491            if (fillCanApplyTheme || strokeCanApplyTheme) {
1492                return true;
1493            }
1494            return false;
1495
1496        }
1497
1498        @Override
1499        public void applyTheme(Theme t) {
1500            // Resolve the theme attributes directly referred by the VectorDrawable.
1501            if (mThemeAttrs != null) {
1502                final TypedArray a = t.resolveAttributes(mThemeAttrs, R.styleable.VectorDrawablePath);
1503                updateStateFromTypedArray(a);
1504                a.recycle();
1505            }
1506
1507            // Resolve the theme attributes in-directly referred by the VectorDrawable, for example,
1508            // fillColor can refer to a color state list which itself needs to apply theme.
1509            // And this is the reason we still want to keep partial update for the path's properties.
1510            boolean fillCanApplyTheme = canComplexColorApplyTheme(mFillColors);
1511            boolean strokeCanApplyTheme = canComplexColorApplyTheme(mStrokeColors);
1512
1513            if (fillCanApplyTheme) {
1514                mFillColors = mFillColors.obtainForTheme(t);
1515                if (mFillColors instanceof GradientColor) {
1516                    nUpdateFullPathFillGradient(mNativePtr,
1517                            ((GradientColor) mFillColors).getShader().getNativeInstance());
1518                } else if (mFillColors instanceof ColorStateList) {
1519                    nSetFillColor(mNativePtr, mFillColors.getDefaultColor());
1520                }
1521            }
1522
1523            if (strokeCanApplyTheme) {
1524                mStrokeColors = mStrokeColors.obtainForTheme(t);
1525                if (mStrokeColors instanceof GradientColor) {
1526                    nUpdateFullPathStrokeGradient(mNativePtr,
1527                            ((GradientColor) mStrokeColors).getShader().getNativeInstance());
1528                } else if (mStrokeColors instanceof ColorStateList) {
1529                    nSetStrokeColor(mNativePtr, mStrokeColors.getDefaultColor());
1530                }
1531            }
1532        }
1533
1534        private boolean canComplexColorApplyTheme(ComplexColor complexColor) {
1535            return complexColor != null && complexColor.canApplyTheme();
1536        }
1537
1538        /* Setters and Getters, used by animator from AnimatedVectorDrawable. */
1539        @SuppressWarnings("unused")
1540        int getStrokeColor() {
1541            return nGetStrokeColor(mNativePtr);
1542        }
1543
1544        @SuppressWarnings("unused")
1545        void setStrokeColor(int strokeColor) {
1546            mStrokeColors = null;
1547            nSetStrokeColor(mNativePtr, strokeColor);
1548        }
1549
1550        @SuppressWarnings("unused")
1551        float getStrokeWidth() {
1552            return nGetStrokeWidth(mNativePtr);
1553        }
1554
1555        @SuppressWarnings("unused")
1556        void setStrokeWidth(float strokeWidth) {
1557            nSetStrokeWidth(mNativePtr, strokeWidth);
1558        }
1559
1560        @SuppressWarnings("unused")
1561        float getStrokeAlpha() {
1562            return nGetStrokeAlpha(mNativePtr);
1563        }
1564
1565        @SuppressWarnings("unused")
1566        void setStrokeAlpha(float strokeAlpha) {
1567            nSetStrokeAlpha(mNativePtr, strokeAlpha);
1568        }
1569
1570        @SuppressWarnings("unused")
1571        int getFillColor() {
1572            return nGetFillColor(mNativePtr);
1573        }
1574
1575        @SuppressWarnings("unused")
1576        void setFillColor(int fillColor) {
1577            mFillColors = null;
1578            nSetFillColor(mNativePtr, fillColor);
1579        }
1580
1581        @SuppressWarnings("unused")
1582        float getFillAlpha() {
1583            return nGetFillAlpha(mNativePtr);
1584        }
1585
1586        @SuppressWarnings("unused")
1587        void setFillAlpha(float fillAlpha) {
1588            nSetFillAlpha(mNativePtr, fillAlpha);
1589        }
1590
1591        @SuppressWarnings("unused")
1592        float getTrimPathStart() {
1593            return nGetTrimPathStart(mNativePtr);
1594        }
1595
1596        @SuppressWarnings("unused")
1597        void setTrimPathStart(float trimPathStart) {
1598            nSetTrimPathStart(mNativePtr, trimPathStart);
1599        }
1600
1601        @SuppressWarnings("unused")
1602        float getTrimPathEnd() {
1603            return nGetTrimPathEnd(mNativePtr);
1604        }
1605
1606        @SuppressWarnings("unused")
1607        void setTrimPathEnd(float trimPathEnd) {
1608            nSetTrimPathEnd(mNativePtr, trimPathEnd);
1609        }
1610
1611        @SuppressWarnings("unused")
1612        float getTrimPathOffset() {
1613            return nGetTrimPathOffset(mNativePtr);
1614        }
1615
1616        @SuppressWarnings("unused")
1617        void setTrimPathOffset(float trimPathOffset) {
1618            nSetTrimPathOffset(mNativePtr, trimPathOffset);
1619        }
1620    }
1621
1622    interface VObject {
1623        long getNativePtr();
1624        void inflate(Resources r, AttributeSet attrs, Theme theme);
1625        boolean canApplyTheme();
1626        void applyTheme(Theme t);
1627        boolean onStateChange(int[] state);
1628        boolean isStateful();
1629    }
1630
1631    private static native long nCreateRenderer(long rootGroupPtr);
1632    private static native void nSetRendererViewportSize(long rendererPtr, float viewportWidth,
1633            float viewportHeight);
1634    private static native boolean nSetRootAlpha(long rendererPtr, float alpha);
1635    private static native float nGetRootAlpha(long rendererPtr);
1636    private static native void nSetAllowCaching(long rendererPtr, boolean allowCaching);
1637
1638    private static native void nDraw(long rendererPtr, long canvasWrapperPtr,
1639            long colorFilterPtr, Rect bounds, boolean needsMirroring, boolean canReuseCache);
1640    private static native long nCreateFullPath();
1641    private static native long nCreateFullPath(long nativeFullPathPtr);
1642    private static native boolean nGetFullPathProperties(long pathPtr, byte[] properties,
1643            int length);
1644
1645    private static native void nUpdateFullPathProperties(long pathPtr, float strokeWidth,
1646            int strokeColor, float strokeAlpha, int fillColor, float fillAlpha, float trimPathStart,
1647            float trimPathEnd, float trimPathOffset, float strokeMiterLimit, int strokeLineCap,
1648            int strokeLineJoin);
1649    private static native void nUpdateFullPathFillGradient(long pathPtr, long fillGradientPtr);
1650    private static native void nUpdateFullPathStrokeGradient(long pathPtr, long strokeGradientPtr);
1651
1652    private static native long nCreateClipPath();
1653    private static native long nCreateClipPath(long clipPathPtr);
1654
1655    private static native long nCreateGroup();
1656    private static native long nCreateGroup(long groupPtr);
1657    private static native void nSetName(long nodePtr, String name);
1658    private static native boolean nGetGroupProperties(long groupPtr, float[] properties,
1659            int length);
1660    private static native void nUpdateGroupProperties(long groupPtr, float rotate, float pivotX,
1661            float pivotY, float scaleX, float scaleY, float translateX, float translateY);
1662
1663    private static native void nAddChild(long groupPtr, long nodePtr);
1664    private static native void nSetPathString(long pathPtr, String pathString, int length);
1665
1666    /**
1667     * The setters and getters below for paths and groups are here temporarily, and will be
1668     * removed once the animation in AVD is replaced with RenderNodeAnimator, in which case the
1669     * animation will modify these properties in native. By then no JNI hopping would be necessary
1670     * for VD during animation, and these setters and getters will be obsolete.
1671     */
1672    // Setters and getters during animation.
1673    private static native float nGetRotation(long groupPtr);
1674    private static native void nSetRotation(long groupPtr, float rotation);
1675    private static native float nGetPivotX(long groupPtr);
1676    private static native void nSetPivotX(long groupPtr, float pivotX);
1677    private static native float nGetPivotY(long groupPtr);
1678    private static native void nSetPivotY(long groupPtr, float pivotY);
1679    private static native float nGetScaleX(long groupPtr);
1680    private static native void nSetScaleX(long groupPtr, float scaleX);
1681    private static native float nGetScaleY(long groupPtr);
1682    private static native void nSetScaleY(long groupPtr, float scaleY);
1683    private static native float nGetTranslateX(long groupPtr);
1684    private static native void nSetTranslateX(long groupPtr, float translateX);
1685    private static native float nGetTranslateY(long groupPtr);
1686    private static native void nSetTranslateY(long groupPtr, float translateY);
1687
1688    // Setters and getters for VPath during animation.
1689    private static native void nSetPathData(long pathPtr, long pathDataPtr);
1690    private static native float nGetStrokeWidth(long pathPtr);
1691    private static native void nSetStrokeWidth(long pathPtr, float width);
1692    private static native int nGetStrokeColor(long pathPtr);
1693    private static native void nSetStrokeColor(long pathPtr, int strokeColor);
1694    private static native float nGetStrokeAlpha(long pathPtr);
1695    private static native void nSetStrokeAlpha(long pathPtr, float alpha);
1696    private static native int nGetFillColor(long pathPtr);
1697    private static native void nSetFillColor(long pathPtr, int fillColor);
1698    private static native float nGetFillAlpha(long pathPtr);
1699    private static native void nSetFillAlpha(long pathPtr, float fillAlpha);
1700    private static native float nGetTrimPathStart(long pathPtr);
1701    private static native void nSetTrimPathStart(long pathPtr, float trimPathStart);
1702    private static native float nGetTrimPathEnd(long pathPtr);
1703    private static native void nSetTrimPathEnd(long pathPtr, float trimPathEnd);
1704    private static native float nGetTrimPathOffset(long pathPtr);
1705    private static native void nSetTrimPathOffset(long pathPtr, float trimPathOffset);
1706}
1707