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