VectorDrawable.java revision f10d08a3d4968d3dfb4324959e33ebb691899eda
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        // We can't tell whether the drawable is fully opaque unless we examine all the pixels,
368        // but we could tell it is transparent if the root alpha is 0.
369        return getAlpha() == 0 ? PixelFormat.TRANSPARENT : PixelFormat.TRANSLUCENT;
370    }
371
372    @Override
373    public int getIntrinsicWidth() {
374        if (mDpiScaledDirty) {
375            computeVectorSize();
376        }
377        return mDpiScaledWidth;
378    }
379
380    @Override
381    public int getIntrinsicHeight() {
382        if (mDpiScaledDirty) {
383            computeVectorSize();
384        }
385        return mDpiScaledHeight;
386    }
387
388    /** @hide */
389    @Override
390    public Insets getOpticalInsets() {
391        if (mDpiScaledDirty) {
392            computeVectorSize();
393        }
394        return mDpiScaledInsets;
395    }
396
397    /*
398     * Update local dimensions to adjust for a target density that may differ
399     * from the source density against which the constant state was loaded.
400     */
401    void computeVectorSize() {
402        final Insets opticalInsets = mVectorState.mOpticalInsets;
403
404        final int sourceDensity = mVectorState.mDensity;
405        final int targetDensity = mTargetDensity;
406        if (targetDensity != sourceDensity) {
407            mDpiScaledWidth = Drawable.scaleFromDensity(
408                    (int) mVectorState.mBaseWidth, sourceDensity, targetDensity, true);
409            mDpiScaledHeight = Drawable.scaleFromDensity(
410                    (int) mVectorState.mBaseHeight,sourceDensity, targetDensity, true);
411            final int left = Drawable.scaleFromDensity(
412                    opticalInsets.left, sourceDensity, targetDensity, false);
413            final int right = Drawable.scaleFromDensity(
414                    opticalInsets.right, sourceDensity, targetDensity, false);
415            final int top = Drawable.scaleFromDensity(
416                    opticalInsets.top, sourceDensity, targetDensity, false);
417            final int bottom = Drawable.scaleFromDensity(
418                    opticalInsets.bottom, sourceDensity, targetDensity, false);
419            mDpiScaledInsets = Insets.of(left, top, right, bottom);
420        } else {
421            mDpiScaledWidth = (int) mVectorState.mBaseWidth;
422            mDpiScaledHeight = (int) mVectorState.mBaseHeight;
423            mDpiScaledInsets = opticalInsets;
424        }
425
426        mDpiScaledDirty = false;
427    }
428
429    @Override
430    public boolean canApplyTheme() {
431        return (mVectorState != null && mVectorState.canApplyTheme()) || super.canApplyTheme();
432    }
433
434    @Override
435    public void applyTheme(Theme t) {
436        super.applyTheme(t);
437
438        final VectorDrawableState state = mVectorState;
439        if (state == null) {
440            return;
441        }
442
443        final boolean changedDensity = mVectorState.setDensity(
444                Drawable.resolveDensity(t.getResources(), 0));
445        mDpiScaledDirty |= changedDensity;
446
447        if (state.mThemeAttrs != null) {
448            final TypedArray a = t.resolveAttributes(
449                    state.mThemeAttrs, R.styleable.VectorDrawable);
450            try {
451                state.mCacheDirty = true;
452                updateStateFromTypedArray(a);
453            } catch (XmlPullParserException e) {
454                throw new RuntimeException(e);
455            } finally {
456                a.recycle();
457            }
458
459            // May have changed size.
460            mDpiScaledDirty = true;
461        }
462
463        // Apply theme to contained color state list.
464        if (state.mTint != null && state.mTint.canApplyTheme()) {
465            state.mTint = state.mTint.obtainForTheme(t);
466        }
467
468        if (mVectorState != null && mVectorState.canApplyTheme()) {
469            mVectorState.applyTheme(t);
470        }
471
472        // Update local properties.
473        updateLocalState(t.getResources());
474    }
475
476    /**
477     * The size of a pixel when scaled from the intrinsic dimension to the viewport dimension.
478     * This is used to calculate the path animation accuracy.
479     *
480     * @hide
481     */
482    public float getPixelSize() {
483        if (mVectorState == null ||
484                mVectorState.mBaseWidth == 0 ||
485                mVectorState.mBaseHeight == 0 ||
486                mVectorState.mViewportHeight == 0 ||
487                mVectorState.mViewportWidth == 0) {
488            return 1; // fall back to 1:1 pixel mapping.
489        }
490        float intrinsicWidth = mVectorState.mBaseWidth;
491        float intrinsicHeight = mVectorState.mBaseHeight;
492        float viewportWidth = mVectorState.mViewportWidth;
493        float viewportHeight = mVectorState.mViewportHeight;
494        float scaleX = viewportWidth / intrinsicWidth;
495        float scaleY = viewportHeight / intrinsicHeight;
496        return Math.min(scaleX, scaleY);
497    }
498
499    /** @hide */
500    public static VectorDrawable create(Resources resources, int rid) {
501        try {
502            final XmlPullParser parser = resources.getXml(rid);
503            final AttributeSet attrs = Xml.asAttributeSet(parser);
504            int type;
505            while ((type=parser.next()) != XmlPullParser.START_TAG &&
506                    type != XmlPullParser.END_DOCUMENT) {
507                // Empty loop
508            }
509            if (type != XmlPullParser.START_TAG) {
510                throw new XmlPullParserException("No start tag found");
511            }
512
513            final VectorDrawable drawable = new VectorDrawable();
514            drawable.inflate(resources, parser, attrs);
515
516            return drawable;
517        } catch (XmlPullParserException e) {
518            Log.e(LOGTAG, "parser error", e);
519        } catch (IOException e) {
520            Log.e(LOGTAG, "parser error", e);
521        }
522        return null;
523    }
524
525    @Override
526    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
527            @NonNull AttributeSet attrs, @Nullable Theme theme)
528            throws XmlPullParserException, IOException {
529        if (mVectorState.mRootGroup != null || mVectorState.mNativeRendererRefBase != null) {
530            // This VD has been used to display other VD resource content, clean up.
531            mVectorState.mRootGroup = new VGroup();
532            if (mVectorState.mNativeRendererRefBase != null) {
533                mVectorState.mNativeRendererRefBase.release();
534            }
535            mVectorState.createNativeRenderer(mVectorState.mRootGroup.mNativePtr);
536        }
537        final VectorDrawableState state = mVectorState;
538        state.setDensity(Drawable.resolveDensity(r, 0));
539
540        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.VectorDrawable);
541        updateStateFromTypedArray(a);
542        a.recycle();
543
544        mDpiScaledDirty = true;
545
546        state.mCacheDirty = true;
547        inflateChildElements(r, parser, attrs, theme);
548
549        // Update local properties.
550        updateLocalState(r);
551    }
552
553    private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException {
554        final VectorDrawableState state = mVectorState;
555
556        // Account for any configuration changes.
557        state.mChangingConfigurations |= a.getChangingConfigurations();
558
559        // Extract the theme attributes, if any.
560        state.mThemeAttrs = a.extractThemeAttrs();
561
562        final int tintMode = a.getInt(R.styleable.VectorDrawable_tintMode, -1);
563        if (tintMode != -1) {
564            state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN);
565        }
566
567        final ColorStateList tint = a.getColorStateList(R.styleable.VectorDrawable_tint);
568        if (tint != null) {
569            state.mTint = tint;
570        }
571
572        state.mAutoMirrored = a.getBoolean(
573                R.styleable.VectorDrawable_autoMirrored, state.mAutoMirrored);
574
575        float viewportWidth = a.getFloat(
576                R.styleable.VectorDrawable_viewportWidth, state.mViewportWidth);
577        float viewportHeight = a.getFloat(
578                R.styleable.VectorDrawable_viewportHeight, state.mViewportHeight);
579        state.setViewportSize(viewportWidth, viewportHeight);
580
581        if (state.mViewportWidth <= 0) {
582            throw new XmlPullParserException(a.getPositionDescription() +
583                    "<vector> tag requires viewportWidth > 0");
584        } else if (state.mViewportHeight <= 0) {
585            throw new XmlPullParserException(a.getPositionDescription() +
586                    "<vector> tag requires viewportHeight > 0");
587        }
588
589        state.mBaseWidth = a.getDimension(
590                R.styleable.VectorDrawable_width, state.mBaseWidth);
591        state.mBaseHeight = a.getDimension(
592                R.styleable.VectorDrawable_height, state.mBaseHeight);
593
594        if (state.mBaseWidth <= 0) {
595            throw new XmlPullParserException(a.getPositionDescription() +
596                    "<vector> tag requires width > 0");
597        } else if (state.mBaseHeight <= 0) {
598            throw new XmlPullParserException(a.getPositionDescription() +
599                    "<vector> tag requires height > 0");
600        }
601
602        final int insetLeft = a.getDimensionPixelOffset(
603                R.styleable.VectorDrawable_opticalInsetLeft, state.mOpticalInsets.left);
604        final int insetTop = a.getDimensionPixelOffset(
605                R.styleable.VectorDrawable_opticalInsetTop, state.mOpticalInsets.top);
606        final int insetRight = a.getDimensionPixelOffset(
607                R.styleable.VectorDrawable_opticalInsetRight, state.mOpticalInsets.right);
608        final int insetBottom = a.getDimensionPixelOffset(
609                R.styleable.VectorDrawable_opticalInsetBottom, state.mOpticalInsets.bottom);
610        state.mOpticalInsets = Insets.of(insetLeft, insetTop, insetRight, insetBottom);
611
612        final float alphaInFloat = a.getFloat(
613                R.styleable.VectorDrawable_alpha, state.getAlpha());
614        state.setAlpha(alphaInFloat);
615
616        final String name = a.getString(R.styleable.VectorDrawable_name);
617        if (name != null) {
618            state.mRootName = name;
619            state.mVGTargetsMap.put(name, state);
620        }
621    }
622
623    private void inflateChildElements(Resources res, XmlPullParser parser, AttributeSet attrs,
624            Theme theme) throws XmlPullParserException, IOException {
625        final VectorDrawableState state = mVectorState;
626        boolean noPathTag = true;
627
628        // Use a stack to help to build the group tree.
629        // The top of the stack is always the current group.
630        final Stack<VGroup> groupStack = new Stack<VGroup>();
631        groupStack.push(state.mRootGroup);
632
633        int eventType = parser.getEventType();
634        while (eventType != XmlPullParser.END_DOCUMENT) {
635            if (eventType == XmlPullParser.START_TAG) {
636                final String tagName = parser.getName();
637                final VGroup currentGroup = groupStack.peek();
638
639                if (SHAPE_PATH.equals(tagName)) {
640                    final VFullPath path = new VFullPath();
641                    path.inflate(res, attrs, theme);
642                    currentGroup.addChild(path);
643                    if (path.getPathName() != null) {
644                        state.mVGTargetsMap.put(path.getPathName(), path);
645                    }
646                    noPathTag = false;
647                    state.mChangingConfigurations |= path.mChangingConfigurations;
648                } else if (SHAPE_CLIP_PATH.equals(tagName)) {
649                    final VClipPath path = new VClipPath();
650                    path.inflate(res, attrs, theme);
651                    currentGroup.addChild(path);
652                    if (path.getPathName() != null) {
653                        state.mVGTargetsMap.put(path.getPathName(), path);
654                    }
655                    state.mChangingConfigurations |= path.mChangingConfigurations;
656                } else if (SHAPE_GROUP.equals(tagName)) {
657                    VGroup newChildGroup = new VGroup();
658                    newChildGroup.inflate(res, attrs, theme);
659                    currentGroup.addChild(newChildGroup);
660                    groupStack.push(newChildGroup);
661                    if (newChildGroup.getGroupName() != null) {
662                        state.mVGTargetsMap.put(newChildGroup.getGroupName(),
663                                newChildGroup);
664                    }
665                    state.mChangingConfigurations |= newChildGroup.mChangingConfigurations;
666                }
667            } else if (eventType == XmlPullParser.END_TAG) {
668                final String tagName = parser.getName();
669                if (SHAPE_GROUP.equals(tagName)) {
670                    groupStack.pop();
671                }
672            }
673            eventType = parser.next();
674        }
675
676        if (noPathTag) {
677            final StringBuffer tag = new StringBuffer();
678
679            if (tag.length() > 0) {
680                tag.append(" or ");
681            }
682            tag.append(SHAPE_PATH);
683
684            throw new XmlPullParserException("no " + tag + " defined");
685        }
686    }
687
688    @Override
689    public int getChangingConfigurations() {
690        return super.getChangingConfigurations() | mVectorState.getChangingConfigurations();
691    }
692
693    void setAllowCaching(boolean allowCaching) {
694        nSetAllowCaching(mVectorState.getNativeRenderer(), allowCaching);
695    }
696
697    private boolean needMirroring() {
698        return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL;
699    }
700
701    @Override
702    public void setAutoMirrored(boolean mirrored) {
703        if (mVectorState.mAutoMirrored != mirrored) {
704            mVectorState.mAutoMirrored = mirrored;
705            invalidateSelf();
706        }
707    }
708
709    @Override
710    public boolean isAutoMirrored() {
711        return mVectorState.mAutoMirrored;
712    }
713
714    static class VectorDrawableState extends ConstantState {
715        // Variables below need to be copied (deep copy if applicable) for mutation.
716        int[] mThemeAttrs;
717        int mChangingConfigurations;
718        ColorStateList mTint = null;
719        Mode mTintMode = DEFAULT_TINT_MODE;
720        boolean mAutoMirrored;
721
722        float mBaseWidth = 0;
723        float mBaseHeight = 0;
724        float mViewportWidth = 0;
725        float mViewportHeight = 0;
726        Insets mOpticalInsets = Insets.NONE;
727        String mRootName = null;
728        VGroup mRootGroup;
729        VirtualRefBasePtr mNativeRendererRefBase = null;
730
731        int mDensity = DisplayMetrics.DENSITY_DEFAULT;
732        final ArrayMap<String, Object> mVGTargetsMap = new ArrayMap<>();
733
734        // Fields for cache
735        int[] mCachedThemeAttrs;
736        ColorStateList mCachedTint;
737        Mode mCachedTintMode;
738        boolean mCachedAutoMirrored;
739        boolean mCacheDirty;
740
741        // Deep copy for mutate() or implicitly mutate.
742        public VectorDrawableState(VectorDrawableState copy) {
743            if (copy != null) {
744                mThemeAttrs = copy.mThemeAttrs;
745                mChangingConfigurations = copy.mChangingConfigurations;
746                mTint = copy.mTint;
747                mTintMode = copy.mTintMode;
748                mAutoMirrored = copy.mAutoMirrored;
749                mRootGroup = new VGroup(copy.mRootGroup, mVGTargetsMap);
750                createNativeRenderer(mRootGroup.mNativePtr);
751
752                mBaseWidth = copy.mBaseWidth;
753                mBaseHeight = copy.mBaseHeight;
754                setViewportSize(copy.mViewportWidth, copy.mViewportHeight);
755                mOpticalInsets = copy.mOpticalInsets;
756
757                mRootName = copy.mRootName;
758                mDensity = copy.mDensity;
759                if (copy.mRootName != null) {
760                    mVGTargetsMap.put(copy.mRootName, this);
761                }
762            }
763        }
764
765        private void createNativeRenderer(long rootGroupPtr) {
766            mNativeRendererRefBase = new VirtualRefBasePtr(nCreateRenderer(rootGroupPtr));
767        }
768
769        long getNativeRenderer() {
770            if (mNativeRendererRefBase == null) {
771                return 0;
772            }
773            return mNativeRendererRefBase.get();
774        }
775
776        public boolean canReuseCache() {
777            if (!mCacheDirty
778                    && mCachedThemeAttrs == mThemeAttrs
779                    && mCachedTint == mTint
780                    && mCachedTintMode == mTintMode
781                    && mCachedAutoMirrored == mAutoMirrored) {
782                return true;
783            }
784            updateCacheStates();
785            return false;
786        }
787
788        public void updateCacheStates() {
789            // Use shallow copy here and shallow comparison in canReuseCache(),
790            // likely hit cache miss more, but practically not much difference.
791            mCachedThemeAttrs = mThemeAttrs;
792            mCachedTint = mTint;
793            mCachedTintMode = mTintMode;
794            mCachedAutoMirrored = mAutoMirrored;
795            mCacheDirty = false;
796        }
797
798        public void applyTheme(Theme t) {
799            mRootGroup.applyTheme(t);
800        }
801
802        @Override
803        public boolean canApplyTheme() {
804            return mThemeAttrs != null
805                    || (mRootGroup != null && mRootGroup.canApplyTheme())
806                    || (mTint != null && mTint.canApplyTheme())
807                    || super.canApplyTheme();
808        }
809
810        public VectorDrawableState() {
811            mRootGroup = new VGroup();
812            createNativeRenderer(mRootGroup.mNativePtr);
813        }
814
815        @Override
816        public Drawable newDrawable() {
817            return new VectorDrawable(this, null);
818        }
819
820        @Override
821        public Drawable newDrawable(Resources res) {
822            return new VectorDrawable(this, res);
823        }
824
825        @Override
826        public int getChangingConfigurations() {
827            return mChangingConfigurations
828                    | (mTint != null ? mTint.getChangingConfigurations() : 0);
829        }
830
831        public boolean isStateful() {
832            return (mTint != null && mTint.isStateful())
833                    || (mRootGroup != null && mRootGroup.isStateful());
834        }
835
836        void setViewportSize(float viewportWidth, float viewportHeight) {
837            mViewportWidth = viewportWidth;
838            mViewportHeight = viewportHeight;
839            nSetRendererViewportSize(getNativeRenderer(), viewportWidth, viewportHeight);
840        }
841
842        public final boolean setDensity(int targetDensity) {
843            if (mDensity != targetDensity) {
844                final int sourceDensity = mDensity;
845                mDensity = targetDensity;
846                applyDensityScaling(sourceDensity, targetDensity);
847                return true;
848            }
849            return false;
850        }
851
852        private void applyDensityScaling(int sourceDensity, int targetDensity) {
853            mBaseWidth = Drawable.scaleFromDensity(mBaseWidth, sourceDensity, targetDensity);
854            mBaseHeight = Drawable.scaleFromDensity(mBaseHeight, sourceDensity, targetDensity);
855
856            final int insetLeft = Drawable.scaleFromDensity(
857                    mOpticalInsets.left, sourceDensity, targetDensity, false);
858            final int insetTop = Drawable.scaleFromDensity(
859                    mOpticalInsets.top, sourceDensity, targetDensity, false);
860            final int insetRight = Drawable.scaleFromDensity(
861                    mOpticalInsets.right, sourceDensity, targetDensity, false);
862            final int insetBottom = Drawable.scaleFromDensity(
863                    mOpticalInsets.bottom, sourceDensity, targetDensity, false);
864            mOpticalInsets = Insets.of(insetLeft, insetTop, insetRight, insetBottom);
865        }
866
867        public boolean onStateChange(int[] stateSet) {
868            return mRootGroup.onStateChange(stateSet);
869        }
870
871        /**
872         * setAlpha() and getAlpha() are used mostly for animation purpose. Return true if alpha
873         * has changed.
874         */
875        public boolean setAlpha(float alpha) {
876            return nSetRootAlpha(mNativeRendererRefBase.get(), alpha);
877        }
878
879        @SuppressWarnings("unused")
880        public float getAlpha() {
881            return nGetRootAlpha(mNativeRendererRefBase.get());
882        }
883    }
884
885    static class VGroup implements VObject {
886        private static final int ROTATE_INDEX = 0;
887        private static final int PIVOT_X_INDEX = 1;
888        private static final int PIVOT_Y_INDEX = 2;
889        private static final int SCALE_X_INDEX = 3;
890        private static final int SCALE_Y_INDEX = 4;
891        private static final int TRANSLATE_X_INDEX = 5;
892        private static final int TRANSLATE_Y_INDEX = 6;
893        private static final int TRANSFORM_PROPERTY_COUNT = 7;
894
895        private static final HashMap<String, Integer> sPropertyMap =
896                new HashMap<String, Integer>() {
897                    {
898                        put("translateX", TRANSLATE_X_INDEX);
899                        put("translateY", TRANSLATE_Y_INDEX);
900                        put("scaleX", SCALE_X_INDEX);
901                        put("scaleY", SCALE_Y_INDEX);
902                        put("pivotX", PIVOT_X_INDEX);
903                        put("pivotY", PIVOT_Y_INDEX);
904                        put("rotation", ROTATE_INDEX);
905                    }
906                };
907
908        static int getPropertyIndex(String propertyName) {
909            if (sPropertyMap.containsKey(propertyName)) {
910                return sPropertyMap.get(propertyName);
911            } else {
912                // property not found
913                return -1;
914            }
915        }
916
917        // Temp array to store transform values obtained from native.
918        private float[] mTransform;
919        /////////////////////////////////////////////////////
920        // Variables below need to be copied (deep copy if applicable) for mutation.
921        private final ArrayList<VObject> mChildren = new ArrayList<>();
922        private boolean mIsStateful;
923
924        // mLocalMatrix is updated based on the update of transformation information,
925        // either parsed from the XML or by animation.
926        private int mChangingConfigurations;
927        private int[] mThemeAttrs;
928        private String mGroupName = null;
929
930        // The native object will be created in the constructor and will be destroyed in native
931        // when the neither java nor native has ref to the tree. This pointer should be valid
932        // throughout this VGroup Java object's life.
933        private final long mNativePtr;
934        public VGroup(VGroup copy, ArrayMap<String, Object> targetsMap) {
935
936            mIsStateful = copy.mIsStateful;
937            mThemeAttrs = copy.mThemeAttrs;
938            mGroupName = copy.mGroupName;
939            mChangingConfigurations = copy.mChangingConfigurations;
940            if (mGroupName != null) {
941                targetsMap.put(mGroupName, this);
942            }
943            mNativePtr = nCreateGroup(copy.mNativePtr);
944
945            final ArrayList<VObject> children = copy.mChildren;
946            for (int i = 0; i < children.size(); i++) {
947                final VObject copyChild = children.get(i);
948                if (copyChild instanceof VGroup) {
949                    final VGroup copyGroup = (VGroup) copyChild;
950                    addChild(new VGroup(copyGroup, targetsMap));
951                } else {
952                    final VPath newPath;
953                    if (copyChild instanceof VFullPath) {
954                        newPath = new VFullPath((VFullPath) copyChild);
955                    } else if (copyChild instanceof VClipPath) {
956                        newPath = new VClipPath((VClipPath) copyChild);
957                    } else {
958                        throw new IllegalStateException("Unknown object in the tree!");
959                    }
960                    addChild(newPath);
961                    if (newPath.mPathName != null) {
962                        targetsMap.put(newPath.mPathName, newPath);
963                    }
964                }
965            }
966        }
967
968        public VGroup() {
969            mNativePtr = nCreateGroup();
970        }
971
972        public String getGroupName() {
973            return mGroupName;
974        }
975
976        public void addChild(VObject child) {
977            nAddChild(mNativePtr, child.getNativePtr());
978            mChildren.add(child);
979
980            mIsStateful |= child.isStateful();
981        }
982
983        @Override
984        public long getNativePtr() {
985            return mNativePtr;
986        }
987
988        @Override
989        public void inflate(Resources res, AttributeSet attrs, Theme theme) {
990            final TypedArray a = obtainAttributes(res, theme, attrs,
991                    R.styleable.VectorDrawableGroup);
992            updateStateFromTypedArray(a);
993            a.recycle();
994        }
995
996        void updateStateFromTypedArray(TypedArray a) {
997            // Account for any configuration changes.
998            mChangingConfigurations |= a.getChangingConfigurations();
999
1000            // Extract the theme attributes, if any.
1001            mThemeAttrs = a.extractThemeAttrs();
1002            if (mTransform == null) {
1003                // Lazy initialization: If the group is created through copy constructor, this may
1004                // never get called.
1005                mTransform = new float[TRANSFORM_PROPERTY_COUNT];
1006            }
1007            boolean success = nGetGroupProperties(mNativePtr, mTransform, TRANSFORM_PROPERTY_COUNT);
1008            if (!success) {
1009                throw new RuntimeException("Error: inconsistent property count");
1010            }
1011            float rotate = a.getFloat(R.styleable.VectorDrawableGroup_rotation,
1012                    mTransform[ROTATE_INDEX]);
1013            float pivotX = a.getFloat(R.styleable.VectorDrawableGroup_pivotX,
1014                    mTransform[PIVOT_X_INDEX]);
1015            float pivotY = a.getFloat(R.styleable.VectorDrawableGroup_pivotY,
1016                    mTransform[PIVOT_Y_INDEX]);
1017            float scaleX = a.getFloat(R.styleable.VectorDrawableGroup_scaleX,
1018                    mTransform[SCALE_X_INDEX]);
1019            float scaleY = a.getFloat(R.styleable.VectorDrawableGroup_scaleY,
1020                    mTransform[SCALE_Y_INDEX]);
1021            float translateX = a.getFloat(R.styleable.VectorDrawableGroup_translateX,
1022                    mTransform[TRANSLATE_X_INDEX]);
1023            float translateY = a.getFloat(R.styleable.VectorDrawableGroup_translateY,
1024                    mTransform[TRANSLATE_Y_INDEX]);
1025
1026            final String groupName = a.getString(R.styleable.VectorDrawableGroup_name);
1027            if (groupName != null) {
1028                mGroupName = groupName;
1029                nSetName(mNativePtr, mGroupName);
1030            }
1031             nUpdateGroupProperties(mNativePtr, rotate, pivotX, pivotY, scaleX, scaleY,
1032                     translateX, translateY);
1033        }
1034
1035        @Override
1036        public boolean onStateChange(int[] stateSet) {
1037            boolean changed = false;
1038
1039            final ArrayList<VObject> children = mChildren;
1040            for (int i = 0, count = children.size(); i < count; i++) {
1041                final VObject child = children.get(i);
1042                if (child.isStateful()) {
1043                    changed |= child.onStateChange(stateSet);
1044                }
1045            }
1046
1047            return changed;
1048        }
1049
1050        @Override
1051        public boolean isStateful() {
1052            return mIsStateful;
1053        }
1054
1055        @Override
1056        public boolean canApplyTheme() {
1057            if (mThemeAttrs != null) {
1058                return true;
1059            }
1060
1061            final ArrayList<VObject> children = mChildren;
1062            for (int i = 0, count = children.size(); i < count; i++) {
1063                final VObject child = children.get(i);
1064                if (child.canApplyTheme()) {
1065                    return true;
1066                }
1067            }
1068
1069            return false;
1070        }
1071
1072        @Override
1073        public void applyTheme(Theme t) {
1074            if (mThemeAttrs != null) {
1075                final TypedArray a = t.resolveAttributes(mThemeAttrs,
1076                        R.styleable.VectorDrawableGroup);
1077                updateStateFromTypedArray(a);
1078                a.recycle();
1079            }
1080
1081            final ArrayList<VObject> children = mChildren;
1082            for (int i = 0, count = children.size(); i < count; i++) {
1083                final VObject child = children.get(i);
1084                if (child.canApplyTheme()) {
1085                    child.applyTheme(t);
1086
1087                    // Applying a theme may have made the child stateful.
1088                    mIsStateful |= child.isStateful();
1089                }
1090            }
1091        }
1092
1093        /* Setters and Getters, used by animator from AnimatedVectorDrawable. */
1094        @SuppressWarnings("unused")
1095        public float getRotation() {
1096            return nGetRotation(mNativePtr);
1097        }
1098
1099        @SuppressWarnings("unused")
1100        public void setRotation(float rotation) {
1101            nSetRotation(mNativePtr, rotation);
1102        }
1103
1104        @SuppressWarnings("unused")
1105        public float getPivotX() {
1106            return nGetPivotX(mNativePtr);
1107        }
1108
1109        @SuppressWarnings("unused")
1110        public void setPivotX(float pivotX) {
1111            nSetPivotX(mNativePtr, pivotX);
1112        }
1113
1114        @SuppressWarnings("unused")
1115        public float getPivotY() {
1116            return nGetPivotY(mNativePtr);
1117        }
1118
1119        @SuppressWarnings("unused")
1120        public void setPivotY(float pivotY) {
1121            nSetPivotY(mNativePtr, pivotY);
1122        }
1123
1124        @SuppressWarnings("unused")
1125        public float getScaleX() {
1126            return nGetScaleX(mNativePtr);
1127        }
1128
1129        @SuppressWarnings("unused")
1130        public void setScaleX(float scaleX) {
1131            nSetScaleX(mNativePtr, scaleX);
1132        }
1133
1134        @SuppressWarnings("unused")
1135        public float getScaleY() {
1136            return nGetScaleY(mNativePtr);
1137        }
1138
1139        @SuppressWarnings("unused")
1140        public void setScaleY(float scaleY) {
1141            nSetScaleY(mNativePtr, scaleY);
1142        }
1143
1144        @SuppressWarnings("unused")
1145        public float getTranslateX() {
1146            return nGetTranslateX(mNativePtr);
1147        }
1148
1149        @SuppressWarnings("unused")
1150        public void setTranslateX(float translateX) {
1151            nSetTranslateX(mNativePtr, translateX);
1152        }
1153
1154        @SuppressWarnings("unused")
1155        public float getTranslateY() {
1156            return nGetTranslateY(mNativePtr);
1157        }
1158
1159        @SuppressWarnings("unused")
1160        public void setTranslateY(float translateY) {
1161            nSetTranslateY(mNativePtr, translateY);
1162        }
1163    }
1164
1165    /**
1166     * Common Path information for clip path and normal path.
1167     */
1168    static abstract class VPath implements VObject {
1169        protected PathParser.PathData mPathData = null;
1170
1171        String mPathName;
1172        int mChangingConfigurations;
1173
1174        public VPath() {
1175            // Empty constructor.
1176        }
1177
1178        public VPath(VPath copy) {
1179            mPathName = copy.mPathName;
1180            mChangingConfigurations = copy.mChangingConfigurations;
1181            mPathData = copy.mPathData == null ? null : new PathParser.PathData(copy.mPathData);
1182        }
1183
1184        public String getPathName() {
1185            return mPathName;
1186        }
1187
1188        /* Setters and Getters, used by animator from AnimatedVectorDrawable. */
1189        @SuppressWarnings("unused")
1190        public PathParser.PathData getPathData() {
1191            return mPathData;
1192        }
1193
1194        // TODO: Move the PathEvaluator and this setter and the getter above into native.
1195        @SuppressWarnings("unused")
1196        public void setPathData(PathParser.PathData pathData) {
1197            mPathData.setPathData(pathData);
1198            nSetPathData(getNativePtr(), mPathData.getNativePtr());
1199        }
1200    }
1201
1202    /**
1203     * Clip path, which only has name and pathData.
1204     */
1205    private static class VClipPath extends VPath {
1206        private final long mNativePtr;
1207
1208        public VClipPath() {
1209            mNativePtr = nCreateClipPath();
1210        }
1211
1212        public VClipPath(VClipPath copy) {
1213            super(copy);
1214            mNativePtr = nCreateClipPath(copy.mNativePtr);
1215        }
1216
1217        @Override
1218        public long getNativePtr() {
1219            return mNativePtr;
1220        }
1221
1222        @Override
1223        public void inflate(Resources r, AttributeSet attrs, Theme theme) {
1224            final TypedArray a = obtainAttributes(r, theme, attrs,
1225                    R.styleable.VectorDrawableClipPath);
1226            updateStateFromTypedArray(a);
1227            a.recycle();
1228        }
1229
1230        @Override
1231        public boolean canApplyTheme() {
1232            return false;
1233        }
1234
1235        @Override
1236        public void applyTheme(Theme theme) {
1237            // No-op.
1238        }
1239
1240        @Override
1241        public boolean onStateChange(int[] stateSet) {
1242            return false;
1243        }
1244
1245        @Override
1246        public boolean isStateful() {
1247            return false;
1248        }
1249
1250        private void updateStateFromTypedArray(TypedArray a) {
1251            // Account for any configuration changes.
1252            mChangingConfigurations |= a.getChangingConfigurations();
1253
1254            final String pathName = a.getString(R.styleable.VectorDrawableClipPath_name);
1255            if (pathName != null) {
1256                mPathName = pathName;
1257                nSetName(mNativePtr, mPathName);
1258            }
1259
1260            final String pathDataString = a.getString(R.styleable.VectorDrawableClipPath_pathData);
1261            if (pathDataString != null) {
1262                mPathData = new PathParser.PathData(pathDataString);
1263                nSetPathString(mNativePtr, pathDataString, pathDataString.length());
1264            }
1265        }
1266    }
1267
1268    /**
1269     * Normal path, which contains all the fill / paint information.
1270     */
1271    static class VFullPath extends VPath {
1272        private static final int STROKE_WIDTH_INDEX = 0;
1273        private static final int STROKE_COLOR_INDEX = 1;
1274        private static final int STROKE_ALPHA_INDEX = 2;
1275        private static final int FILL_COLOR_INDEX = 3;
1276        private static final int FILL_ALPHA_INDEX = 4;
1277        private static final int TRIM_PATH_START_INDEX = 5;
1278        private static final int TRIM_PATH_END_INDEX = 6;
1279        private static final int TRIM_PATH_OFFSET_INDEX = 7;
1280        private static final int STROKE_LINE_CAP_INDEX = 8;
1281        private static final int STROKE_LINE_JOIN_INDEX = 9;
1282        private static final int STROKE_MITER_LIMIT_INDEX = 10;
1283        private static final int TOTAL_PROPERTY_COUNT = 11;
1284
1285        private final static HashMap<String, Integer> sPropertyMap
1286                = new HashMap<String, Integer> () {
1287            {
1288                put("strokeWidth", STROKE_WIDTH_INDEX);
1289                put("strokeColor", STROKE_COLOR_INDEX);
1290                put("strokeAlpha", STROKE_ALPHA_INDEX);
1291                put("fillColor", FILL_COLOR_INDEX);
1292                put("fillAlpha", FILL_ALPHA_INDEX);
1293                put("trimPathStart", TRIM_PATH_START_INDEX);
1294                put("trimPathEnd", TRIM_PATH_END_INDEX);
1295                put("trimPathOffset", TRIM_PATH_OFFSET_INDEX);
1296            }
1297        };
1298
1299        // Temp array to store property data obtained from native getter.
1300        private byte[] mPropertyData;
1301        /////////////////////////////////////////////////////
1302        // Variables below need to be copied (deep copy if applicable) for mutation.
1303        private int[] mThemeAttrs;
1304
1305        ComplexColor mStrokeColors = null;
1306        ComplexColor mFillColors = null;
1307        private final long mNativePtr;
1308
1309        public VFullPath() {
1310            mNativePtr = nCreateFullPath();
1311        }
1312
1313        public VFullPath(VFullPath copy) {
1314            super(copy);
1315            mNativePtr = nCreateFullPath(copy.mNativePtr);
1316            mThemeAttrs = copy.mThemeAttrs;
1317            mStrokeColors = copy.mStrokeColors;
1318            mFillColors = copy.mFillColors;
1319        }
1320
1321        int getPropertyIndex(String propertyName) {
1322            if (!sPropertyMap.containsKey(propertyName)) {
1323                return -1;
1324            } else {
1325                return sPropertyMap.get(propertyName);
1326            }
1327        }
1328
1329        @Override
1330        public boolean onStateChange(int[] stateSet) {
1331            boolean changed = false;
1332
1333            if (mStrokeColors != null && mStrokeColors instanceof ColorStateList) {
1334                final int oldStrokeColor = getStrokeColor();
1335                final int newStrokeColor =
1336                        ((ColorStateList) mStrokeColors).getColorForState(stateSet, oldStrokeColor);
1337                changed |= oldStrokeColor != newStrokeColor;
1338                if (oldStrokeColor != newStrokeColor) {
1339                    nSetStrokeColor(mNativePtr, newStrokeColor);
1340                }
1341            }
1342
1343            if (mFillColors != null && mFillColors instanceof ColorStateList) {
1344                final int oldFillColor = getFillColor();
1345                final int newFillColor = ((ColorStateList) mFillColors).getColorForState(stateSet, oldFillColor);
1346                changed |= oldFillColor != newFillColor;
1347                if (oldFillColor != newFillColor) {
1348                    nSetFillColor(mNativePtr, newFillColor);
1349                }
1350            }
1351
1352            return changed;
1353        }
1354
1355        @Override
1356        public boolean isStateful() {
1357            return mStrokeColors != null || mFillColors != null;
1358        }
1359
1360        @Override
1361        public long getNativePtr() {
1362            return mNativePtr;
1363        }
1364
1365        @Override
1366        public void inflate(Resources r, AttributeSet attrs, Theme theme) {
1367            final TypedArray a = obtainAttributes(r, theme, attrs,
1368                    R.styleable.VectorDrawablePath);
1369            updateStateFromTypedArray(a);
1370            a.recycle();
1371        }
1372
1373        private void updateStateFromTypedArray(TypedArray a) {
1374            int byteCount = TOTAL_PROPERTY_COUNT * 4;
1375            if (mPropertyData == null) {
1376                // Lazy initialization: If the path is created through copy constructor, this may
1377                // never get called.
1378                mPropertyData = new byte[byteCount];
1379            }
1380            // The bulk getters/setters of property data (e.g. stroke width, color, etc) allows us
1381            // to pull current values from native and store modifications with only two methods,
1382            // minimizing JNI overhead.
1383            boolean success = nGetFullPathProperties(mNativePtr, mPropertyData, byteCount);
1384            if (!success) {
1385                throw new RuntimeException("Error: inconsistent property count");
1386            }
1387
1388            ByteBuffer properties = ByteBuffer.wrap(mPropertyData);
1389            properties.order(ByteOrder.nativeOrder());
1390            float strokeWidth = properties.getFloat(STROKE_WIDTH_INDEX * 4);
1391            int strokeColor = properties.getInt(STROKE_COLOR_INDEX * 4);
1392            float strokeAlpha = properties.getFloat(STROKE_ALPHA_INDEX * 4);
1393            int fillColor =  properties.getInt(FILL_COLOR_INDEX * 4);
1394            float fillAlpha = properties.getFloat(FILL_ALPHA_INDEX * 4);
1395            float trimPathStart = properties.getFloat(TRIM_PATH_START_INDEX * 4);
1396            float trimPathEnd = properties.getFloat(TRIM_PATH_END_INDEX * 4);
1397            float trimPathOffset = properties.getFloat(TRIM_PATH_OFFSET_INDEX * 4);
1398            int strokeLineCap =  properties.getInt(STROKE_LINE_CAP_INDEX * 4);
1399            int strokeLineJoin = properties.getInt(STROKE_LINE_JOIN_INDEX * 4);
1400            float strokeMiterLimit = properties.getFloat(STROKE_MITER_LIMIT_INDEX * 4);
1401            Shader fillGradient = null;
1402            Shader strokeGradient = null;
1403            // Account for any configuration changes.
1404            mChangingConfigurations |= a.getChangingConfigurations();
1405
1406            // Extract the theme attributes, if any.
1407            mThemeAttrs = a.extractThemeAttrs();
1408
1409            final String pathName = a.getString(R.styleable.VectorDrawablePath_name);
1410            if (pathName != null) {
1411                mPathName = pathName;
1412                nSetName(mNativePtr, mPathName);
1413            }
1414
1415            final String pathString = a.getString(R.styleable.VectorDrawablePath_pathData);
1416            if (pathString != null) {
1417                mPathData = new PathParser.PathData(pathString);
1418                nSetPathString(mNativePtr, pathString, pathString.length());
1419            }
1420
1421            final ComplexColor fillColors = a.getComplexColor(
1422                    R.styleable.VectorDrawablePath_fillColor);
1423            if (fillColors != null) {
1424                // If the colors is a gradient color, or the color state list is stateful, keep the
1425                // colors information. Otherwise, discard the colors and keep the default color.
1426                if (fillColors instanceof  GradientColor) {
1427                    mFillColors = fillColors;
1428                    fillGradient = ((GradientColor) fillColors).getShader();
1429                } else if (fillColors.isStateful()) {
1430                    mFillColors = fillColors;
1431                } else {
1432                    mFillColors = null;
1433                }
1434                fillColor = fillColors.getDefaultColor();
1435            }
1436
1437            final ComplexColor strokeColors = a.getComplexColor(
1438                    R.styleable.VectorDrawablePath_strokeColor);
1439            if (strokeColors != null) {
1440                // If the colors is a gradient color, or the color state list is stateful, keep the
1441                // colors information. Otherwise, discard the colors and keep the default color.
1442                if (strokeColors instanceof GradientColor) {
1443                    mStrokeColors = strokeColors;
1444                    strokeGradient = ((GradientColor) strokeColors).getShader();
1445                } else if (strokeColors.isStateful()) {
1446                    mStrokeColors = strokeColors;
1447                } else {
1448                    mStrokeColors = null;
1449                }
1450                strokeColor = strokeColors.getDefaultColor();
1451            }
1452            // Update the gradient info, even if the gradiet is null.
1453            nUpdateFullPathFillGradient(mNativePtr,
1454                    fillGradient != null ? fillGradient.getNativeInstance() : 0);
1455            nUpdateFullPathStrokeGradient(mNativePtr,
1456                    strokeGradient != null ? strokeGradient.getNativeInstance() : 0);
1457
1458            fillAlpha = a.getFloat(R.styleable.VectorDrawablePath_fillAlpha, fillAlpha);
1459
1460            strokeLineCap = a.getInt(
1461                    R.styleable.VectorDrawablePath_strokeLineCap, strokeLineCap);
1462            strokeLineJoin = a.getInt(
1463                    R.styleable.VectorDrawablePath_strokeLineJoin, strokeLineJoin);
1464            strokeMiterLimit = a.getFloat(
1465                    R.styleable.VectorDrawablePath_strokeMiterLimit, strokeMiterLimit);
1466            strokeAlpha = a.getFloat(R.styleable.VectorDrawablePath_strokeAlpha,
1467                    strokeAlpha);
1468            strokeWidth = a.getFloat(R.styleable.VectorDrawablePath_strokeWidth,
1469                    strokeWidth);
1470            trimPathEnd = a.getFloat(R.styleable.VectorDrawablePath_trimPathEnd,
1471                    trimPathEnd);
1472            trimPathOffset = a.getFloat(
1473                    R.styleable.VectorDrawablePath_trimPathOffset, trimPathOffset);
1474            trimPathStart = a.getFloat(
1475                    R.styleable.VectorDrawablePath_trimPathStart, trimPathStart);
1476
1477            nUpdateFullPathProperties(mNativePtr, strokeWidth, strokeColor, strokeAlpha,
1478                    fillColor, fillAlpha, trimPathStart, trimPathEnd, trimPathOffset,
1479                    strokeMiterLimit, strokeLineCap, strokeLineJoin);
1480        }
1481
1482        @Override
1483        public boolean canApplyTheme() {
1484            if (mThemeAttrs != null) {
1485                return true;
1486            }
1487
1488            boolean fillCanApplyTheme = canComplexColorApplyTheme(mFillColors);
1489            boolean strokeCanApplyTheme = canComplexColorApplyTheme(mStrokeColors);
1490            if (fillCanApplyTheme || strokeCanApplyTheme) {
1491                return true;
1492            }
1493            return false;
1494
1495        }
1496
1497        @Override
1498        public void applyTheme(Theme t) {
1499            // Resolve the theme attributes directly referred by the VectorDrawable.
1500            if (mThemeAttrs != null) {
1501                final TypedArray a = t.resolveAttributes(mThemeAttrs, R.styleable.VectorDrawablePath);
1502                updateStateFromTypedArray(a);
1503                a.recycle();
1504            }
1505
1506            // Resolve the theme attributes in-directly referred by the VectorDrawable, for example,
1507            // fillColor can refer to a color state list which itself needs to apply theme.
1508            // And this is the reason we still want to keep partial update for the path's properties.
1509            boolean fillCanApplyTheme = canComplexColorApplyTheme(mFillColors);
1510            boolean strokeCanApplyTheme = canComplexColorApplyTheme(mStrokeColors);
1511
1512            if (fillCanApplyTheme) {
1513                mFillColors = mFillColors.obtainForTheme(t);
1514                if (mFillColors instanceof GradientColor) {
1515                    nUpdateFullPathFillGradient(mNativePtr,
1516                            ((GradientColor) mFillColors).getShader().getNativeInstance());
1517                } else if (mFillColors instanceof ColorStateList) {
1518                    nSetFillColor(mNativePtr, mFillColors.getDefaultColor());
1519                }
1520            }
1521
1522            if (strokeCanApplyTheme) {
1523                mStrokeColors = mStrokeColors.obtainForTheme(t);
1524                if (mStrokeColors instanceof GradientColor) {
1525                    nUpdateFullPathStrokeGradient(mNativePtr,
1526                            ((GradientColor) mStrokeColors).getShader().getNativeInstance());
1527                } else if (mStrokeColors instanceof ColorStateList) {
1528                    nSetStrokeColor(mNativePtr, mStrokeColors.getDefaultColor());
1529                }
1530            }
1531        }
1532
1533        private boolean canComplexColorApplyTheme(ComplexColor complexColor) {
1534            return complexColor != null && complexColor.canApplyTheme();
1535        }
1536
1537        /* Setters and Getters, used by animator from AnimatedVectorDrawable. */
1538        @SuppressWarnings("unused")
1539        int getStrokeColor() {
1540            return nGetStrokeColor(mNativePtr);
1541        }
1542
1543        @SuppressWarnings("unused")
1544        void setStrokeColor(int strokeColor) {
1545            mStrokeColors = null;
1546            nSetStrokeColor(mNativePtr, strokeColor);
1547        }
1548
1549        @SuppressWarnings("unused")
1550        float getStrokeWidth() {
1551            return nGetStrokeWidth(mNativePtr);
1552        }
1553
1554        @SuppressWarnings("unused")
1555        void setStrokeWidth(float strokeWidth) {
1556            nSetStrokeWidth(mNativePtr, strokeWidth);
1557        }
1558
1559        @SuppressWarnings("unused")
1560        float getStrokeAlpha() {
1561            return nGetStrokeAlpha(mNativePtr);
1562        }
1563
1564        @SuppressWarnings("unused")
1565        void setStrokeAlpha(float strokeAlpha) {
1566            nSetStrokeAlpha(mNativePtr, strokeAlpha);
1567        }
1568
1569        @SuppressWarnings("unused")
1570        int getFillColor() {
1571            return nGetFillColor(mNativePtr);
1572        }
1573
1574        @SuppressWarnings("unused")
1575        void setFillColor(int fillColor) {
1576            mFillColors = null;
1577            nSetFillColor(mNativePtr, fillColor);
1578        }
1579
1580        @SuppressWarnings("unused")
1581        float getFillAlpha() {
1582            return nGetFillAlpha(mNativePtr);
1583        }
1584
1585        @SuppressWarnings("unused")
1586        void setFillAlpha(float fillAlpha) {
1587            nSetFillAlpha(mNativePtr, fillAlpha);
1588        }
1589
1590        @SuppressWarnings("unused")
1591        float getTrimPathStart() {
1592            return nGetTrimPathStart(mNativePtr);
1593        }
1594
1595        @SuppressWarnings("unused")
1596        void setTrimPathStart(float trimPathStart) {
1597            nSetTrimPathStart(mNativePtr, trimPathStart);
1598        }
1599
1600        @SuppressWarnings("unused")
1601        float getTrimPathEnd() {
1602            return nGetTrimPathEnd(mNativePtr);
1603        }
1604
1605        @SuppressWarnings("unused")
1606        void setTrimPathEnd(float trimPathEnd) {
1607            nSetTrimPathEnd(mNativePtr, trimPathEnd);
1608        }
1609
1610        @SuppressWarnings("unused")
1611        float getTrimPathOffset() {
1612            return nGetTrimPathOffset(mNativePtr);
1613        }
1614
1615        @SuppressWarnings("unused")
1616        void setTrimPathOffset(float trimPathOffset) {
1617            nSetTrimPathOffset(mNativePtr, trimPathOffset);
1618        }
1619    }
1620
1621    interface VObject {
1622        long getNativePtr();
1623        void inflate(Resources r, AttributeSet attrs, Theme theme);
1624        boolean canApplyTheme();
1625        void applyTheme(Theme t);
1626        boolean onStateChange(int[] state);
1627        boolean isStateful();
1628    }
1629
1630    private static native long nCreateRenderer(long rootGroupPtr);
1631    private static native void nSetRendererViewportSize(long rendererPtr, float viewportWidth,
1632            float viewportHeight);
1633    private static native boolean nSetRootAlpha(long rendererPtr, float alpha);
1634    private static native float nGetRootAlpha(long rendererPtr);
1635    private static native void nSetAllowCaching(long rendererPtr, boolean allowCaching);
1636
1637    private static native void nDraw(long rendererPtr, long canvasWrapperPtr,
1638            long colorFilterPtr, Rect bounds, boolean needsMirroring, boolean canReuseCache);
1639    private static native long nCreateFullPath();
1640    private static native long nCreateFullPath(long nativeFullPathPtr);
1641    private static native boolean nGetFullPathProperties(long pathPtr, byte[] properties,
1642            int length);
1643
1644    private static native void nUpdateFullPathProperties(long pathPtr, float strokeWidth,
1645            int strokeColor, float strokeAlpha, int fillColor, float fillAlpha, float trimPathStart,
1646            float trimPathEnd, float trimPathOffset, float strokeMiterLimit, int strokeLineCap,
1647            int strokeLineJoin);
1648    private static native void nUpdateFullPathFillGradient(long pathPtr, long fillGradientPtr);
1649    private static native void nUpdateFullPathStrokeGradient(long pathPtr, long strokeGradientPtr);
1650
1651    private static native long nCreateClipPath();
1652    private static native long nCreateClipPath(long clipPathPtr);
1653
1654    private static native long nCreateGroup();
1655    private static native long nCreateGroup(long groupPtr);
1656    private static native void nSetName(long nodePtr, String name);
1657    private static native boolean nGetGroupProperties(long groupPtr, float[] properties,
1658            int length);
1659    private static native void nUpdateGroupProperties(long groupPtr, float rotate, float pivotX,
1660            float pivotY, float scaleX, float scaleY, float translateX, float translateY);
1661
1662    private static native void nAddChild(long groupPtr, long nodePtr);
1663    private static native void nSetPathString(long pathPtr, String pathString, int length);
1664
1665    /**
1666     * The setters and getters below for paths and groups are here temporarily, and will be
1667     * removed once the animation in AVD is replaced with RenderNodeAnimator, in which case the
1668     * animation will modify these properties in native. By then no JNI hopping would be necessary
1669     * for VD during animation, and these setters and getters will be obsolete.
1670     */
1671    // Setters and getters during animation.
1672    private static native float nGetRotation(long groupPtr);
1673    private static native void nSetRotation(long groupPtr, float rotation);
1674    private static native float nGetPivotX(long groupPtr);
1675    private static native void nSetPivotX(long groupPtr, float pivotX);
1676    private static native float nGetPivotY(long groupPtr);
1677    private static native void nSetPivotY(long groupPtr, float pivotY);
1678    private static native float nGetScaleX(long groupPtr);
1679    private static native void nSetScaleX(long groupPtr, float scaleX);
1680    private static native float nGetScaleY(long groupPtr);
1681    private static native void nSetScaleY(long groupPtr, float scaleY);
1682    private static native float nGetTranslateX(long groupPtr);
1683    private static native void nSetTranslateX(long groupPtr, float translateX);
1684    private static native float nGetTranslateY(long groupPtr);
1685    private static native void nSetTranslateY(long groupPtr, float translateY);
1686
1687    // Setters and getters for VPath during animation.
1688    private static native void nSetPathData(long pathPtr, long pathDataPtr);
1689    private static native float nGetStrokeWidth(long pathPtr);
1690    private static native void nSetStrokeWidth(long pathPtr, float width);
1691    private static native int nGetStrokeColor(long pathPtr);
1692    private static native void nSetStrokeColor(long pathPtr, int strokeColor);
1693    private static native float nGetStrokeAlpha(long pathPtr);
1694    private static native void nSetStrokeAlpha(long pathPtr, float alpha);
1695    private static native int nGetFillColor(long pathPtr);
1696    private static native void nSetFillColor(long pathPtr, int fillColor);
1697    private static native float nGetFillAlpha(long pathPtr);
1698    private static native void nSetFillAlpha(long pathPtr, float fillAlpha);
1699    private static native float nGetTrimPathStart(long pathPtr);
1700    private static native void nSetTrimPathStart(long pathPtr, float trimPathStart);
1701    private static native float nGetTrimPathEnd(long pathPtr);
1702    private static native void nSetTrimPathEnd(long pathPtr, float trimPathEnd);
1703    private static native float nGetTrimPathOffset(long pathPtr);
1704    private static native void nSetTrimPathOffset(long pathPtr, float trimPathOffset);
1705}
1706