VectorDrawable.java revision ef062ebd20032efe697741d6c3dfd1faec54f590
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
928        // The native object will be created in the constructor and will be destroyed in native
929        // when the neither java nor native has ref to the tree. This pointer should be valid
930        // throughout this VGroup Java object's life.
931        private final long mNativePtr;
932        public VGroup(VGroup copy, ArrayMap<String, Object> targetsMap) {
933
934            mIsStateful = copy.mIsStateful;
935            mThemeAttrs = copy.mThemeAttrs;
936            mGroupName = copy.mGroupName;
937            mChangingConfigurations = copy.mChangingConfigurations;
938            if (mGroupName != null) {
939                targetsMap.put(mGroupName, this);
940            }
941            mNativePtr = nCreateGroup(copy.mNativePtr);
942
943            final ArrayList<VObject> children = copy.mChildren;
944            for (int i = 0; i < children.size(); i++) {
945                final VObject copyChild = children.get(i);
946                if (copyChild instanceof VGroup) {
947                    final VGroup copyGroup = (VGroup) copyChild;
948                    addChild(new VGroup(copyGroup, targetsMap));
949                } else {
950                    final VPath newPath;
951                    if (copyChild instanceof VFullPath) {
952                        newPath = new VFullPath((VFullPath) copyChild);
953                    } else if (copyChild instanceof VClipPath) {
954                        newPath = new VClipPath((VClipPath) copyChild);
955                    } else {
956                        throw new IllegalStateException("Unknown object in the tree!");
957                    }
958                    addChild(newPath);
959                    if (newPath.mPathName != null) {
960                        targetsMap.put(newPath.mPathName, newPath);
961                    }
962                }
963            }
964        }
965
966        public VGroup() {
967            mNativePtr = nCreateGroup();
968        }
969
970        public String getGroupName() {
971            return mGroupName;
972        }
973
974        public void addChild(VObject child) {
975            nAddChild(mNativePtr, child.getNativePtr());
976            mChildren.add(child);
977
978            mIsStateful |= child.isStateful();
979        }
980
981        @Override
982        public long getNativePtr() {
983            return mNativePtr;
984        }
985
986        @Override
987        public void inflate(Resources res, AttributeSet attrs, Theme theme) {
988            final TypedArray a = obtainAttributes(res, theme, attrs,
989                    R.styleable.VectorDrawableGroup);
990            updateStateFromTypedArray(a);
991            a.recycle();
992        }
993
994        void updateStateFromTypedArray(TypedArray a) {
995            // Account for any configuration changes.
996            mChangingConfigurations |= a.getChangingConfigurations();
997
998            // Extract the theme attributes, if any.
999            mThemeAttrs = a.extractThemeAttrs();
1000            if (mTransform == null) {
1001                // Lazy initialization: If the group is created through copy constructor, this may
1002                // never get called.
1003                mTransform = new float[TRANSFORM_PROPERTY_COUNT];
1004            }
1005            boolean success = nGetGroupProperties(mNativePtr, mTransform, TRANSFORM_PROPERTY_COUNT);
1006            if (!success) {
1007                throw new RuntimeException("Error: inconsistent property count");
1008            }
1009            float rotate = a.getFloat(R.styleable.VectorDrawableGroup_rotation,
1010                    mTransform[ROTATE_INDEX]);
1011            float pivotX = a.getFloat(R.styleable.VectorDrawableGroup_pivotX,
1012                    mTransform[PIVOT_X_INDEX]);
1013            float pivotY = a.getFloat(R.styleable.VectorDrawableGroup_pivotY,
1014                    mTransform[PIVOT_Y_INDEX]);
1015            float scaleX = a.getFloat(R.styleable.VectorDrawableGroup_scaleX,
1016                    mTransform[SCALE_X_INDEX]);
1017            float scaleY = a.getFloat(R.styleable.VectorDrawableGroup_scaleY,
1018                    mTransform[SCALE_Y_INDEX]);
1019            float translateX = a.getFloat(R.styleable.VectorDrawableGroup_translateX,
1020                    mTransform[TRANSLATE_X_INDEX]);
1021            float translateY = a.getFloat(R.styleable.VectorDrawableGroup_translateY,
1022                    mTransform[TRANSLATE_Y_INDEX]);
1023
1024            final String groupName = a.getString(R.styleable.VectorDrawableGroup_name);
1025            if (groupName != null) {
1026                mGroupName = groupName;
1027                nSetName(mNativePtr, mGroupName);
1028            }
1029             nUpdateGroupProperties(mNativePtr, rotate, pivotX, pivotY, scaleX, scaleY,
1030                     translateX, translateY);
1031        }
1032
1033        @Override
1034        public boolean onStateChange(int[] stateSet) {
1035            boolean changed = false;
1036
1037            final ArrayList<VObject> children = mChildren;
1038            for (int i = 0, count = children.size(); i < count; i++) {
1039                final VObject child = children.get(i);
1040                if (child.isStateful()) {
1041                    changed |= child.onStateChange(stateSet);
1042                }
1043            }
1044
1045            return changed;
1046        }
1047
1048        @Override
1049        public boolean isStateful() {
1050            return mIsStateful;
1051        }
1052
1053        @Override
1054        public boolean canApplyTheme() {
1055            if (mThemeAttrs != null) {
1056                return true;
1057            }
1058
1059            final ArrayList<VObject> children = mChildren;
1060            for (int i = 0, count = children.size(); i < count; i++) {
1061                final VObject child = children.get(i);
1062                if (child.canApplyTheme()) {
1063                    return true;
1064                }
1065            }
1066
1067            return false;
1068        }
1069
1070        @Override
1071        public void applyTheme(Theme t) {
1072            if (mThemeAttrs != null) {
1073                final TypedArray a = t.resolveAttributes(mThemeAttrs,
1074                        R.styleable.VectorDrawableGroup);
1075                updateStateFromTypedArray(a);
1076                a.recycle();
1077            }
1078
1079            final ArrayList<VObject> children = mChildren;
1080            for (int i = 0, count = children.size(); i < count; i++) {
1081                final VObject child = children.get(i);
1082                if (child.canApplyTheme()) {
1083                    child.applyTheme(t);
1084
1085                    // Applying a theme may have made the child stateful.
1086                    mIsStateful |= child.isStateful();
1087                }
1088            }
1089        }
1090
1091        /* Setters and Getters, used by animator from AnimatedVectorDrawable. */
1092        @SuppressWarnings("unused")
1093        public float getRotation() {
1094            return nGetRotation(mNativePtr);
1095        }
1096
1097        @SuppressWarnings("unused")
1098        public void setRotation(float rotation) {
1099            nSetRotation(mNativePtr, rotation);
1100        }
1101
1102        @SuppressWarnings("unused")
1103        public float getPivotX() {
1104            return nGetPivotX(mNativePtr);
1105        }
1106
1107        @SuppressWarnings("unused")
1108        public void setPivotX(float pivotX) {
1109            nSetPivotX(mNativePtr, pivotX);
1110        }
1111
1112        @SuppressWarnings("unused")
1113        public float getPivotY() {
1114            return nGetPivotY(mNativePtr);
1115        }
1116
1117        @SuppressWarnings("unused")
1118        public void setPivotY(float pivotY) {
1119            nSetPivotY(mNativePtr, pivotY);
1120        }
1121
1122        @SuppressWarnings("unused")
1123        public float getScaleX() {
1124            return nGetScaleX(mNativePtr);
1125        }
1126
1127        @SuppressWarnings("unused")
1128        public void setScaleX(float scaleX) {
1129            nSetScaleX(mNativePtr, scaleX);
1130        }
1131
1132        @SuppressWarnings("unused")
1133        public float getScaleY() {
1134            return nGetScaleY(mNativePtr);
1135        }
1136
1137        @SuppressWarnings("unused")
1138        public void setScaleY(float scaleY) {
1139            nSetScaleY(mNativePtr, scaleY);
1140        }
1141
1142        @SuppressWarnings("unused")
1143        public float getTranslateX() {
1144            return nGetTranslateX(mNativePtr);
1145        }
1146
1147        @SuppressWarnings("unused")
1148        public void setTranslateX(float translateX) {
1149            nSetTranslateX(mNativePtr, translateX);
1150        }
1151
1152        @SuppressWarnings("unused")
1153        public float getTranslateY() {
1154            return nGetTranslateY(mNativePtr);
1155        }
1156
1157        @SuppressWarnings("unused")
1158        public void setTranslateY(float translateY) {
1159            nSetTranslateY(mNativePtr, translateY);
1160        }
1161    }
1162
1163    /**
1164     * Common Path information for clip path and normal path.
1165     */
1166    static abstract class VPath implements VObject {
1167        protected PathParser.PathData mPathData = null;
1168
1169        String mPathName;
1170        int mChangingConfigurations;
1171
1172        public VPath() {
1173            // Empty constructor.
1174        }
1175
1176        public VPath(VPath copy) {
1177            mPathName = copy.mPathName;
1178            mChangingConfigurations = copy.mChangingConfigurations;
1179            mPathData = copy.mPathData == null ? null : new PathParser.PathData(copy.mPathData);
1180        }
1181
1182        public String getPathName() {
1183            return mPathName;
1184        }
1185
1186        /* Setters and Getters, used by animator from AnimatedVectorDrawable. */
1187        @SuppressWarnings("unused")
1188        public PathParser.PathData getPathData() {
1189            return mPathData;
1190        }
1191
1192        // TODO: Move the PathEvaluator and this setter and the getter above into native.
1193        @SuppressWarnings("unused")
1194        public void setPathData(PathParser.PathData pathData) {
1195            mPathData.setPathData(pathData);
1196            nSetPathData(getNativePtr(), mPathData.getNativePtr());
1197        }
1198    }
1199
1200    /**
1201     * Clip path, which only has name and pathData.
1202     */
1203    private static class VClipPath extends VPath {
1204        private final long mNativePtr;
1205
1206        public VClipPath() {
1207            mNativePtr = nCreateClipPath();
1208        }
1209
1210        public VClipPath(VClipPath copy) {
1211            super(copy);
1212            mNativePtr = nCreateClipPath(copy.mNativePtr);
1213        }
1214
1215        @Override
1216        public long getNativePtr() {
1217            return mNativePtr;
1218        }
1219
1220        @Override
1221        public void inflate(Resources r, AttributeSet attrs, Theme theme) {
1222            final TypedArray a = obtainAttributes(r, theme, attrs,
1223                    R.styleable.VectorDrawableClipPath);
1224            updateStateFromTypedArray(a);
1225            a.recycle();
1226        }
1227
1228        @Override
1229        public boolean canApplyTheme() {
1230            return false;
1231        }
1232
1233        @Override
1234        public void applyTheme(Theme theme) {
1235            // No-op.
1236        }
1237
1238        @Override
1239        public boolean onStateChange(int[] stateSet) {
1240            return false;
1241        }
1242
1243        @Override
1244        public boolean isStateful() {
1245            return false;
1246        }
1247
1248        private void updateStateFromTypedArray(TypedArray a) {
1249            // Account for any configuration changes.
1250            mChangingConfigurations |= a.getChangingConfigurations();
1251
1252            final String pathName = a.getString(R.styleable.VectorDrawableClipPath_name);
1253            if (pathName != null) {
1254                mPathName = pathName;
1255                nSetName(mNativePtr, mPathName);
1256            }
1257
1258            final String pathDataString = a.getString(R.styleable.VectorDrawableClipPath_pathData);
1259            if (pathDataString != null) {
1260                mPathData = new PathParser.PathData(pathDataString);
1261                nSetPathString(mNativePtr, pathDataString, pathDataString.length());
1262            }
1263        }
1264    }
1265
1266    /**
1267     * Normal path, which contains all the fill / paint information.
1268     */
1269    static class VFullPath extends VPath {
1270        private static final int STROKE_WIDTH_INDEX = 0;
1271        private static final int STROKE_COLOR_INDEX = 1;
1272        private static final int STROKE_ALPHA_INDEX = 2;
1273        private static final int FILL_COLOR_INDEX = 3;
1274        private static final int FILL_ALPHA_INDEX = 4;
1275        private static final int TRIM_PATH_START_INDEX = 5;
1276        private static final int TRIM_PATH_END_INDEX = 6;
1277        private static final int TRIM_PATH_OFFSET_INDEX = 7;
1278        private static final int STROKE_LINE_CAP_INDEX = 8;
1279        private static final int STROKE_LINE_JOIN_INDEX = 9;
1280        private static final int STROKE_MITER_LIMIT_INDEX = 10;
1281        private static final int TOTAL_PROPERTY_COUNT = 11;
1282
1283        private final static HashMap<String, Integer> sPropertyMap
1284                = new HashMap<String, Integer> () {
1285            {
1286                put("strokeWidth", STROKE_WIDTH_INDEX);
1287                put("strokeColor", STROKE_COLOR_INDEX);
1288                put("strokeAlpha", STROKE_ALPHA_INDEX);
1289                put("fillColor", FILL_COLOR_INDEX);
1290                put("fillAlpha", FILL_ALPHA_INDEX);
1291                put("trimPathStart", TRIM_PATH_START_INDEX);
1292                put("trimPathEnd", TRIM_PATH_END_INDEX);
1293                put("trimPathOffset", TRIM_PATH_OFFSET_INDEX);
1294            }
1295        };
1296
1297        // Temp array to store property data obtained from native getter.
1298        private byte[] mPropertyData;
1299        /////////////////////////////////////////////////////
1300        // Variables below need to be copied (deep copy if applicable) for mutation.
1301        private int[] mThemeAttrs;
1302
1303        ComplexColor mStrokeColors = null;
1304        ComplexColor mFillColors = null;
1305        private final long mNativePtr;
1306
1307        public VFullPath() {
1308            mNativePtr = nCreateFullPath();
1309        }
1310
1311        public VFullPath(VFullPath copy) {
1312            super(copy);
1313            mNativePtr = nCreateFullPath(copy.mNativePtr);
1314            mThemeAttrs = copy.mThemeAttrs;
1315            mStrokeColors = copy.mStrokeColors;
1316            mFillColors = copy.mFillColors;
1317        }
1318
1319        int getPropertyIndex(String propertyName) {
1320            if (!sPropertyMap.containsKey(propertyName)) {
1321                return -1;
1322            } else {
1323                return sPropertyMap.get(propertyName);
1324            }
1325        }
1326
1327        @Override
1328        public boolean onStateChange(int[] stateSet) {
1329            boolean changed = false;
1330
1331            if (mStrokeColors != null && mStrokeColors instanceof ColorStateList) {
1332                final int oldStrokeColor = getStrokeColor();
1333                final int newStrokeColor =
1334                        ((ColorStateList) mStrokeColors).getColorForState(stateSet, oldStrokeColor);
1335                changed |= oldStrokeColor != newStrokeColor;
1336                if (oldStrokeColor != newStrokeColor) {
1337                    nSetStrokeColor(mNativePtr, newStrokeColor);
1338                }
1339            }
1340
1341            if (mFillColors != null && mFillColors instanceof ColorStateList) {
1342                final int oldFillColor = getFillColor();
1343                final int newFillColor = ((ColorStateList) mFillColors).getColorForState(stateSet, oldFillColor);
1344                changed |= oldFillColor != newFillColor;
1345                if (oldFillColor != newFillColor) {
1346                    nSetFillColor(mNativePtr, newFillColor);
1347                }
1348            }
1349
1350            return changed;
1351        }
1352
1353        @Override
1354        public boolean isStateful() {
1355            return mStrokeColors != null || mFillColors != null;
1356        }
1357
1358        @Override
1359        public long getNativePtr() {
1360            return mNativePtr;
1361        }
1362
1363        @Override
1364        public void inflate(Resources r, AttributeSet attrs, Theme theme) {
1365            final TypedArray a = obtainAttributes(r, theme, attrs,
1366                    R.styleable.VectorDrawablePath);
1367            updateStateFromTypedArray(a);
1368            a.recycle();
1369        }
1370
1371        private void updateStateFromTypedArray(TypedArray a) {
1372            int byteCount = TOTAL_PROPERTY_COUNT * 4;
1373            if (mPropertyData == null) {
1374                // Lazy initialization: If the path is created through copy constructor, this may
1375                // never get called.
1376                mPropertyData = new byte[byteCount];
1377            }
1378            // The bulk getters/setters of property data (e.g. stroke width, color, etc) allows us
1379            // to pull current values from native and store modifications with only two methods,
1380            // minimizing JNI overhead.
1381            boolean success = nGetFullPathProperties(mNativePtr, mPropertyData, byteCount);
1382            if (!success) {
1383                throw new RuntimeException("Error: inconsistent property count");
1384            }
1385
1386            ByteBuffer properties = ByteBuffer.wrap(mPropertyData);
1387            properties.order(ByteOrder.nativeOrder());
1388            float strokeWidth = properties.getFloat(STROKE_WIDTH_INDEX * 4);
1389            int strokeColor = properties.getInt(STROKE_COLOR_INDEX * 4);
1390            float strokeAlpha = properties.getFloat(STROKE_ALPHA_INDEX * 4);
1391            int fillColor =  properties.getInt(FILL_COLOR_INDEX * 4);
1392            float fillAlpha = properties.getFloat(FILL_ALPHA_INDEX * 4);
1393            float trimPathStart = properties.getFloat(TRIM_PATH_START_INDEX * 4);
1394            float trimPathEnd = properties.getFloat(TRIM_PATH_END_INDEX * 4);
1395            float trimPathOffset = properties.getFloat(TRIM_PATH_OFFSET_INDEX * 4);
1396            int strokeLineCap =  properties.getInt(STROKE_LINE_CAP_INDEX * 4);
1397            int strokeLineJoin = properties.getInt(STROKE_LINE_JOIN_INDEX * 4);
1398            float strokeMiterLimit = properties.getFloat(STROKE_MITER_LIMIT_INDEX * 4);
1399            Shader fillGradient = null;
1400            Shader strokeGradient = null;
1401            // Account for any configuration changes.
1402            mChangingConfigurations |= a.getChangingConfigurations();
1403
1404            // Extract the theme attributes, if any.
1405            mThemeAttrs = a.extractThemeAttrs();
1406
1407            final String pathName = a.getString(R.styleable.VectorDrawablePath_name);
1408            if (pathName != null) {
1409                mPathName = pathName;
1410                nSetName(mNativePtr, mPathName);
1411            }
1412
1413            final String pathString = a.getString(R.styleable.VectorDrawablePath_pathData);
1414            if (pathString != null) {
1415                mPathData = new PathParser.PathData(pathString);
1416                nSetPathString(mNativePtr, pathString, pathString.length());
1417            }
1418
1419            final ComplexColor fillColors = a.getComplexColor(
1420                    R.styleable.VectorDrawablePath_fillColor);
1421            if (fillColors != null) {
1422                // If the colors is a gradient color, or the color state list is stateful, keep the
1423                // colors information. Otherwise, discard the colors and keep the default color.
1424                if (fillColors instanceof  GradientColor) {
1425                    mFillColors = fillColors;
1426                    fillGradient = ((GradientColor) fillColors).getShader();
1427                } else if (fillColors.isStateful()) {
1428                    mFillColors = fillColors;
1429                } else {
1430                    mFillColors = null;
1431                }
1432                fillColor = fillColors.getDefaultColor();
1433            }
1434
1435            final ComplexColor strokeColors = a.getComplexColor(
1436                    R.styleable.VectorDrawablePath_strokeColor);
1437            if (strokeColors != null) {
1438                // If the colors is a gradient color, or the color state list is stateful, keep the
1439                // colors information. Otherwise, discard the colors and keep the default color.
1440                if (strokeColors instanceof GradientColor) {
1441                    mStrokeColors = strokeColors;
1442                    strokeGradient = ((GradientColor) strokeColors).getShader();
1443                } else if (strokeColors.isStateful()) {
1444                    mStrokeColors = strokeColors;
1445                } else {
1446                    mStrokeColors = null;
1447                }
1448                strokeColor = strokeColors.getDefaultColor();
1449            }
1450            // Update the gradient info, even if the gradiet is null.
1451            nUpdateFullPathFillGradient(mNativePtr,
1452                    fillGradient != null ? fillGradient.getNativeInstance() : 0);
1453            nUpdateFullPathStrokeGradient(mNativePtr,
1454                    strokeGradient != null ? strokeGradient.getNativeInstance() : 0);
1455
1456            fillAlpha = a.getFloat(R.styleable.VectorDrawablePath_fillAlpha, fillAlpha);
1457
1458            strokeLineCap = a.getInt(
1459                    R.styleable.VectorDrawablePath_strokeLineCap, strokeLineCap);
1460            strokeLineJoin = a.getInt(
1461                    R.styleable.VectorDrawablePath_strokeLineJoin, strokeLineJoin);
1462            strokeMiterLimit = a.getFloat(
1463                    R.styleable.VectorDrawablePath_strokeMiterLimit, strokeMiterLimit);
1464            strokeAlpha = a.getFloat(R.styleable.VectorDrawablePath_strokeAlpha,
1465                    strokeAlpha);
1466            strokeWidth = a.getFloat(R.styleable.VectorDrawablePath_strokeWidth,
1467                    strokeWidth);
1468            trimPathEnd = a.getFloat(R.styleable.VectorDrawablePath_trimPathEnd,
1469                    trimPathEnd);
1470            trimPathOffset = a.getFloat(
1471                    R.styleable.VectorDrawablePath_trimPathOffset, trimPathOffset);
1472            trimPathStart = a.getFloat(
1473                    R.styleable.VectorDrawablePath_trimPathStart, trimPathStart);
1474
1475            nUpdateFullPathProperties(mNativePtr, strokeWidth, strokeColor, strokeAlpha,
1476                    fillColor, fillAlpha, trimPathStart, trimPathEnd, trimPathOffset,
1477                    strokeMiterLimit, strokeLineCap, strokeLineJoin);
1478        }
1479
1480        @Override
1481        public boolean canApplyTheme() {
1482            if (mThemeAttrs != null) {
1483                return true;
1484            }
1485            boolean fillCanApplyTheme = canGradientApplyTheme(mFillColors);
1486            boolean strokeCanApplyTheme = canGradientApplyTheme(mStrokeColors);
1487            if (fillCanApplyTheme || strokeCanApplyTheme) {
1488                return true;
1489            }
1490            return false;
1491
1492        }
1493
1494        @Override
1495        public void applyTheme(Theme t) {
1496            if (mThemeAttrs != null) {
1497                final TypedArray a = t.resolveAttributes(mThemeAttrs, R.styleable.VectorDrawablePath);
1498                updateStateFromTypedArray(a);
1499                a.recycle();
1500            }
1501
1502            boolean fillCanApplyTheme = canGradientApplyTheme(mFillColors);
1503            boolean strokeCanApplyTheme = canGradientApplyTheme(mStrokeColors);
1504            if (fillCanApplyTheme) {
1505                mFillColors = mFillColors.obtainForTheme(t);
1506                nUpdateFullPathFillGradient(mNativePtr,
1507                        ((GradientColor)mFillColors).getShader().getNativeInstance());
1508            }
1509
1510            if (strokeCanApplyTheme) {
1511                mStrokeColors = mStrokeColors.obtainForTheme(t);
1512                nUpdateFullPathStrokeGradient(mNativePtr,
1513                        ((GradientColor)mStrokeColors).getShader().getNativeInstance());
1514            }
1515        }
1516
1517        private boolean canGradientApplyTheme(ComplexColor complexColor) {
1518            return complexColor != null && complexColor.canApplyTheme()
1519                    && complexColor instanceof GradientColor;
1520        }
1521
1522        /* Setters and Getters, used by animator from AnimatedVectorDrawable. */
1523        @SuppressWarnings("unused")
1524        int getStrokeColor() {
1525            return nGetStrokeColor(mNativePtr);
1526        }
1527
1528        @SuppressWarnings("unused")
1529        void setStrokeColor(int strokeColor) {
1530            mStrokeColors = null;
1531            nSetStrokeColor(mNativePtr, strokeColor);
1532        }
1533
1534        @SuppressWarnings("unused")
1535        float getStrokeWidth() {
1536            return nGetStrokeWidth(mNativePtr);
1537        }
1538
1539        @SuppressWarnings("unused")
1540        void setStrokeWidth(float strokeWidth) {
1541            nSetStrokeWidth(mNativePtr, strokeWidth);
1542        }
1543
1544        @SuppressWarnings("unused")
1545        float getStrokeAlpha() {
1546            return nGetStrokeAlpha(mNativePtr);
1547        }
1548
1549        @SuppressWarnings("unused")
1550        void setStrokeAlpha(float strokeAlpha) {
1551            nSetStrokeAlpha(mNativePtr, strokeAlpha);
1552        }
1553
1554        @SuppressWarnings("unused")
1555        int getFillColor() {
1556            return nGetFillColor(mNativePtr);
1557        }
1558
1559        @SuppressWarnings("unused")
1560        void setFillColor(int fillColor) {
1561            mFillColors = null;
1562            nSetFillColor(mNativePtr, fillColor);
1563        }
1564
1565        @SuppressWarnings("unused")
1566        float getFillAlpha() {
1567            return nGetFillAlpha(mNativePtr);
1568        }
1569
1570        @SuppressWarnings("unused")
1571        void setFillAlpha(float fillAlpha) {
1572            nSetFillAlpha(mNativePtr, fillAlpha);
1573        }
1574
1575        @SuppressWarnings("unused")
1576        float getTrimPathStart() {
1577            return nGetTrimPathStart(mNativePtr);
1578        }
1579
1580        @SuppressWarnings("unused")
1581        void setTrimPathStart(float trimPathStart) {
1582            nSetTrimPathStart(mNativePtr, trimPathStart);
1583        }
1584
1585        @SuppressWarnings("unused")
1586        float getTrimPathEnd() {
1587            return nGetTrimPathEnd(mNativePtr);
1588        }
1589
1590        @SuppressWarnings("unused")
1591        void setTrimPathEnd(float trimPathEnd) {
1592            nSetTrimPathEnd(mNativePtr, trimPathEnd);
1593        }
1594
1595        @SuppressWarnings("unused")
1596        float getTrimPathOffset() {
1597            return nGetTrimPathOffset(mNativePtr);
1598        }
1599
1600        @SuppressWarnings("unused")
1601        void setTrimPathOffset(float trimPathOffset) {
1602            nSetTrimPathOffset(mNativePtr, trimPathOffset);
1603        }
1604    }
1605
1606    interface VObject {
1607        long getNativePtr();
1608        void inflate(Resources r, AttributeSet attrs, Theme theme);
1609        boolean canApplyTheme();
1610        void applyTheme(Theme t);
1611        boolean onStateChange(int[] state);
1612        boolean isStateful();
1613    }
1614
1615    private static native long nCreateRenderer(long rootGroupPtr);
1616    private static native void nSetRendererViewportSize(long rendererPtr, float viewportWidth,
1617            float viewportHeight);
1618    private static native boolean nSetRootAlpha(long rendererPtr, float alpha);
1619    private static native float nGetRootAlpha(long rendererPtr);
1620    private static native void nSetAllowCaching(long rendererPtr, boolean allowCaching);
1621
1622    private static native void nDraw(long rendererPtr, long canvasWrapperPtr,
1623            long colorFilterPtr, Rect bounds, boolean needsMirroring, boolean canReuseCache);
1624    private static native long nCreateFullPath();
1625    private static native long nCreateFullPath(long nativeFullPathPtr);
1626    private static native boolean nGetFullPathProperties(long pathPtr, byte[] properties,
1627            int length);
1628
1629    private static native void nUpdateFullPathProperties(long pathPtr, float strokeWidth,
1630            int strokeColor, float strokeAlpha, int fillColor, float fillAlpha, float trimPathStart,
1631            float trimPathEnd, float trimPathOffset, float strokeMiterLimit, int strokeLineCap,
1632            int strokeLineJoin);
1633    private static native void nUpdateFullPathFillGradient(long pathPtr, long fillGradientPtr);
1634    private static native void nUpdateFullPathStrokeGradient(long pathPtr, long strokeGradientPtr);
1635
1636    private static native long nCreateClipPath();
1637    private static native long nCreateClipPath(long clipPathPtr);
1638
1639    private static native long nCreateGroup();
1640    private static native long nCreateGroup(long groupPtr);
1641    private static native void nSetName(long nodePtr, String name);
1642    private static native boolean nGetGroupProperties(long groupPtr, float[] properties,
1643            int length);
1644    private static native void nUpdateGroupProperties(long groupPtr, float rotate, float pivotX,
1645            float pivotY, float scaleX, float scaleY, float translateX, float translateY);
1646
1647    private static native void nAddChild(long groupPtr, long nodePtr);
1648    private static native void nSetPathString(long pathPtr, String pathString, int length);
1649
1650    /**
1651     * The setters and getters below for paths and groups are here temporarily, and will be
1652     * removed once the animation in AVD is replaced with RenderNodeAnimator, in which case the
1653     * animation will modify these properties in native. By then no JNI hopping would be necessary
1654     * for VD during animation, and these setters and getters will be obsolete.
1655     */
1656    // Setters and getters during animation.
1657    private static native float nGetRotation(long groupPtr);
1658    private static native void nSetRotation(long groupPtr, float rotation);
1659    private static native float nGetPivotX(long groupPtr);
1660    private static native void nSetPivotX(long groupPtr, float pivotX);
1661    private static native float nGetPivotY(long groupPtr);
1662    private static native void nSetPivotY(long groupPtr, float pivotY);
1663    private static native float nGetScaleX(long groupPtr);
1664    private static native void nSetScaleX(long groupPtr, float scaleX);
1665    private static native float nGetScaleY(long groupPtr);
1666    private static native void nSetScaleY(long groupPtr, float scaleY);
1667    private static native float nGetTranslateX(long groupPtr);
1668    private static native void nSetTranslateX(long groupPtr, float translateX);
1669    private static native float nGetTranslateY(long groupPtr);
1670    private static native void nSetTranslateY(long groupPtr, float translateY);
1671
1672    // Setters and getters for VPath during animation.
1673    private static native void nSetPathData(long pathPtr, long pathDataPtr);
1674    private static native float nGetStrokeWidth(long pathPtr);
1675    private static native void nSetStrokeWidth(long pathPtr, float width);
1676    private static native int nGetStrokeColor(long pathPtr);
1677    private static native void nSetStrokeColor(long pathPtr, int strokeColor);
1678    private static native float nGetStrokeAlpha(long pathPtr);
1679    private static native void nSetStrokeAlpha(long pathPtr, float alpha);
1680    private static native int nGetFillColor(long pathPtr);
1681    private static native void nSetFillColor(long pathPtr, int fillColor);
1682    private static native float nGetFillAlpha(long pathPtr);
1683    private static native void nSetFillAlpha(long pathPtr, float fillAlpha);
1684    private static native float nGetTrimPathStart(long pathPtr);
1685    private static native void nSetTrimPathStart(long pathPtr, float trimPathStart);
1686    private static native float nGetTrimPathEnd(long pathPtr);
1687    private static native void nSetTrimPathEnd(long pathPtr, float trimPathEnd);
1688    private static native float nGetTrimPathOffset(long pathPtr);
1689    private static native void nSetTrimPathOffset(long pathPtr, float trimPathOffset);
1690}
1691