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