VectorDrawable.java revision 1f859716a8e4b8c12d69ca8b3db8344de577d5c7
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.PorterDuff.Mode;
31import android.graphics.PorterDuffColorFilter;
32import android.graphics.Rect;
33import android.graphics.Shader;
34import android.util.ArrayMap;
35import android.util.AttributeSet;
36import android.util.DisplayMetrics;
37import android.util.FloatProperty;
38import android.util.IntProperty;
39import android.util.LayoutDirection;
40import android.util.Log;
41import android.util.PathParser;
42import android.util.Property;
43import android.util.Xml;
44
45import com.android.internal.R;
46import com.android.internal.util.VirtualRefBasePtr;
47
48import org.xmlpull.v1.XmlPullParser;
49import org.xmlpull.v1.XmlPullParserException;
50
51import java.io.IOException;
52import java.nio.ByteBuffer;
53import java.nio.ByteOrder;
54import java.util.ArrayList;
55import java.util.HashMap;
56import java.util.Stack;
57
58import dalvik.system.VMRuntime;
59
60/**
61 * This lets you create a drawable based on an XML vector graphic.
62 * <p/>
63 * <strong>Note:</strong> To optimize for the re-drawing performance, one bitmap cache is created
64 * for each VectorDrawable. Therefore, referring to the same VectorDrawable means sharing the same
65 * bitmap cache. If these references don't agree upon on the same size, the bitmap will be recreated
66 * and redrawn every time size is changed. In other words, if a VectorDrawable is used for
67 * different sizes, it is more efficient to create multiple VectorDrawables, one for each size.
68 * <p/>
69 * VectorDrawable can be defined in an XML file with the <code>&lt;vector></code> element.
70 * <p/>
71 * The vector drawable has the following elements:
72 * <p/>
73 * <dt><code>&lt;vector></code></dt>
74 * <dl>
75 * <dd>Used to define a vector drawable
76 * <dl>
77 * <dt><code>android:name</code></dt>
78 * <dd>Defines the name of this vector drawable.</dd>
79 * <dt><code>android:width</code></dt>
80 * <dd>Used to define the intrinsic width of the drawable.
81 * This support all the dimension units, normally specified with dp.</dd>
82 * <dt><code>android:height</code></dt>
83 * <dd>Used to define the intrinsic height the drawable.
84 * This support all the dimension units, normally specified with dp.</dd>
85 * <dt><code>android:viewportWidth</code></dt>
86 * <dd>Used to define the width of the viewport space. Viewport is basically
87 * the virtual canvas where the paths are drawn on.</dd>
88 * <dt><code>android:viewportHeight</code></dt>
89 * <dd>Used to define the height of the viewport space. Viewport is basically
90 * the virtual canvas where the paths are drawn on.</dd>
91 * <dt><code>android:tint</code></dt>
92 * <dd>The color to apply to the drawable as a tint. By default, no tint is applied.</dd>
93 * <dt><code>android:tintMode</code></dt>
94 * <dd>The Porter-Duff blending mode for the tint color. The default value is src_in.</dd>
95 * <dt><code>android:autoMirrored</code></dt>
96 * <dd>Indicates if the drawable needs to be mirrored when its layout direction is
97 * RTL (right-to-left).</dd>
98 * <dt><code>android:alpha</code></dt>
99 * <dd>The opacity of this drawable.</dd>
100 * </dl></dd>
101 * </dl>
102 *
103 * <dl>
104 * <dt><code>&lt;group></code></dt>
105 * <dd>Defines a group of paths or subgroups, plus transformation information.
106 * The transformations are defined in the same coordinates as the viewport.
107 * And the transformations are applied in the order of scale, rotate then translate.
108 * <dl>
109 * <dt><code>android:name</code></dt>
110 * <dd>Defines the name of the group.</dd>
111 * <dt><code>android:rotation</code></dt>
112 * <dd>The degrees of rotation of the group.</dd>
113 * <dt><code>android:pivotX</code></dt>
114 * <dd>The X coordinate of the pivot for the scale and rotation of the group.
115 * This is defined in the viewport space.</dd>
116 * <dt><code>android:pivotY</code></dt>
117 * <dd>The Y coordinate of the pivot for the scale and rotation of the group.
118 * This is defined in the viewport space.</dd>
119 * <dt><code>android:scaleX</code></dt>
120 * <dd>The amount of scale on the X Coordinate.</dd>
121 * <dt><code>android:scaleY</code></dt>
122 * <dd>The amount of scale on the Y coordinate.</dd>
123 * <dt><code>android:translateX</code></dt>
124 * <dd>The amount of translation on the X coordinate.
125 * This is defined in the viewport space.</dd>
126 * <dt><code>android:translateY</code></dt>
127 * <dd>The amount of translation on the Y coordinate.
128 * This is defined in the viewport space.</dd>
129 * </dl></dd>
130 * </dl>
131 *
132 * <dl>
133 * <dt><code>&lt;path></code></dt>
134 * <dd>Defines paths to be drawn.
135 * <dl>
136 * <dt><code>android:name</code></dt>
137 * <dd>Defines the name of the path.</dd>
138 * <dt><code>android:pathData</code></dt>
139 * <dd>Defines path data using exactly same format as "d" attribute
140 * in the SVG's path data. This is defined in the viewport space.</dd>
141 * <dt><code>android:fillColor</code></dt>
142 * <dd>Specifies the color used to fill the path. May be a color or, for SDK 24+, a color state list
143 * or a gradient color (See {@link android.R.styleable#GradientColor}
144 * and {@link android.R.styleable#GradientColorItem}).
145 * If this property is animated, any value set by the animation will override the original value.
146 * No path fill is drawn if this property is not specified.</dd>
147 * <dt><code>android:strokeColor</code></dt>
148 * <dd>Specifies the color used to draw the path outline. May be a color or, for SDK 24+, a color
149 * state list or a gradient color (See {@link android.R.styleable#GradientColor}
150 * and {@link android.R.styleable#GradientColorItem}).
151 * If this property is animated, any value set by the animation will override the original value.
152 * No path outline is drawn if this property is not specified.</dd>
153 * <dt><code>android:strokeWidth</code></dt>
154 * <dd>The width a path stroke.</dd>
155 * <dt><code>android:strokeAlpha</code></dt>
156 * <dd>The opacity of a path stroke.</dd>
157 * <dt><code>android:fillAlpha</code></dt>
158 * <dd>The opacity to fill the path with.</dd>
159 * <dt><code>android:trimPathStart</code></dt>
160 * <dd>The fraction of the path to trim from the start, in the range from 0 to 1.</dd>
161 * <dt><code>android:trimPathEnd</code></dt>
162 * <dd>The fraction of the path to trim from the end, in the range from 0 to 1.</dd>
163 * <dt><code>android:trimPathOffset</code></dt>
164 * <dd>Shift trim region (allows showed region to include the start and end), in the range
165 * from 0 to 1.</dd>
166 * <dt><code>android:strokeLineCap</code></dt>
167 * <dd>Sets the linecap for a stroked path: butt, round, square.</dd>
168 * <dt><code>android:strokeLineJoin</code></dt>
169 * <dd>Sets the lineJoin for a stroked path: miter,round,bevel.</dd>
170 * <dt><code>android:strokeMiterLimit</code></dt>
171 * <dd>Sets the Miter limit for a stroked path.</dd>
172 * <dt><code>android:fillType</code></dt>
173 * <dd>Sets the fillType for a path. The types can be either "evenOdd" or "nonZero". They behave the
174 * same as SVG's "fill-rule" properties. For more details, see
175 * <a href="https://www.w3.org/TR/SVG/painting.html#FillRuleProperty">FillRuleProperty</a></dd>
176 * </dl></dd>
177 * </dl>
178 *
179 * <dl>
180 * <dt><code>&lt;clip-path></code></dt>
181 * <dd>Defines path to be the current clip. Note that the clip path only apply to
182 * the current group and its children.
183 * <dl>
184 * <dt><code>android:name</code></dt>
185 * <dd>Defines the name of the clip path.</dd>
186 * <dt><code>android:pathData</code></dt>
187 * <dd>Defines clip path using the same format as "d" attribute
188 * in the SVG's path data.</dd>
189 * </dl></dd>
190 * </dl>
191 * <li>Here is a simple VectorDrawable in this vectordrawable.xml file.
192 * <pre>
193 * &lt;vector xmlns:android=&quot;http://schemas.android.com/apk/res/android";
194 *     android:height=&quot;64dp&quot;
195 *     android:width=&quot;64dp&quot;
196 *     android:viewportHeight=&quot;600&quot;
197 *     android:viewportWidth=&quot;600&quot; &gt;
198 *     &lt;group
199 *         android:name=&quot;rotationGroup&quot;
200 *         android:pivotX=&quot;300.0&quot;
201 *         android:pivotY=&quot;300.0&quot;
202 *         android:rotation=&quot;45.0&quot; &gt;
203 *         &lt;path
204 *             android:name=&quot;v&quot;
205 *             android:fillColor=&quot;#000000&quot;
206 *             android:pathData=&quot;M300,70 l 0,-70 70,70 0,0 -70,70z&quot; /&gt;
207 *     &lt;/group&gt;
208 * &lt;/vector&gt;
209 * </pre>
210 * </li>
211 * <li>And here is an example of linear gradient color, which is supported in SDK 24+.
212 * See more details in {@link android.R.styleable#GradientColor} and
213 * {@link android.R.styleable#GradientColorItem}.
214 * <pre>
215 * &lt;gradient xmlns:android="http://schemas.android.com/apk/res/android"
216 *     android:angle="90"
217 *     android:startColor="?android:attr/colorPrimary"
218 *     android:endColor="?android:attr/colorControlActivated"
219 *     android:centerColor="#f00"
220 *     android:startX="0"
221 *     android:startY="0"
222 *     android:endX="100"
223 *     android:endY="100"
224 *     android:type="linear"&gt;
225 * &lt;/gradient&gt;
226 * </pre>
227 * </li>
228 *
229 */
230
231public class VectorDrawable extends Drawable {
232    private static final String LOGTAG = VectorDrawable.class.getSimpleName();
233
234    private static final String SHAPE_CLIP_PATH = "clip-path";
235    private static final String SHAPE_GROUP = "group";
236    private static final String SHAPE_PATH = "path";
237    private static final String SHAPE_VECTOR = "vector";
238
239    private VectorDrawableState mVectorState;
240
241    private PorterDuffColorFilter mTintFilter;
242    private ColorFilter mColorFilter;
243
244    private boolean mMutated;
245
246    /** The density of the display on which this drawable will be rendered. */
247    private int mTargetDensity;
248
249    // Given the virtual display setup, the dpi can be different than the inflation's dpi.
250    // Therefore, we need to scale the values we got from the getDimension*().
251    private int mDpiScaledWidth = 0;
252    private int mDpiScaledHeight = 0;
253    private Insets mDpiScaledInsets = Insets.NONE;
254
255    /** Whether DPI-scaled width, height, and insets need to be updated. */
256    private boolean mDpiScaledDirty = true;
257
258    // Temp variable, only for saving "new" operation at the draw() time.
259    private final Rect mTmpBounds = new Rect();
260
261    public VectorDrawable() {
262        this(new VectorDrawableState(null), null);
263    }
264
265    /**
266     * The one constructor to rule them all. This is called by all public
267     * constructors to set the state and initialize local properties.
268     */
269    private VectorDrawable(@NonNull VectorDrawableState state, @Nullable Resources res) {
270        mVectorState = state;
271        updateLocalState(res);
272    }
273
274    /**
275     * Initializes local dynamic properties from state. This should be called
276     * after significant state changes, e.g. from the One True Constructor and
277     * after inflating or applying a theme.
278     *
279     * @param res resources of the context in which the drawable will be
280     *            displayed, or {@code null} to use the constant state defaults
281     */
282    private void updateLocalState(Resources res) {
283        final int density = Drawable.resolveDensity(res, mVectorState.mDensity);
284        if (mTargetDensity != density) {
285            mTargetDensity = density;
286            mDpiScaledDirty = true;
287        }
288
289        mTintFilter = updateTintFilter(mTintFilter, mVectorState.mTint, mVectorState.mTintMode);
290    }
291
292    @Override
293    public Drawable mutate() {
294        if (!mMutated && super.mutate() == this) {
295            mVectorState = new VectorDrawableState(mVectorState);
296            mMutated = true;
297        }
298        return this;
299    }
300
301    /**
302     * @hide
303     */
304    public void clearMutated() {
305        super.clearMutated();
306        mMutated = false;
307    }
308
309    Object getTargetByName(String name) {
310        return mVectorState.mVGTargetsMap.get(name);
311    }
312
313    @Override
314    public ConstantState getConstantState() {
315        mVectorState.mChangingConfigurations = getChangingConfigurations();
316        return mVectorState;
317    }
318
319    @Override
320    public void draw(Canvas canvas) {
321        // We will offset the bounds for drawBitmap, so copyBounds() here instead
322        // of getBounds().
323        copyBounds(mTmpBounds);
324        if (mTmpBounds.width() <= 0 || mTmpBounds.height() <= 0) {
325            // Nothing to draw
326            return;
327        }
328
329        // Color filters always override tint filters.
330        final ColorFilter colorFilter = (mColorFilter == null ? mTintFilter : mColorFilter);
331        final long colorFilterNativeInstance = colorFilter == null ? 0 :
332                colorFilter.native_instance;
333        boolean canReuseCache = mVectorState.canReuseCache();
334        int pixelCount = nDraw(mVectorState.getNativeRenderer(), canvas.getNativeCanvasWrapper(),
335                colorFilterNativeInstance, mTmpBounds, needMirroring(),
336                canReuseCache);
337        if (pixelCount == 0) {
338            // Invalid canvas matrix or drawable bounds. This would not affect existing bitmap
339            // cache, if any.
340            return;
341        }
342
343        int deltaInBytes;
344        // Track different bitmap cache based whether the canvas is hw accelerated. By doing so,
345        // we don't over count bitmap cache allocation: if the input canvas is always of the same
346        // type, only one bitmap cache is allocated.
347        if (canvas.isHardwareAccelerated()) {
348            // Each pixel takes 4 bytes.
349            deltaInBytes = (pixelCount - mVectorState.mLastHWCachePixelCount) * 4;
350            mVectorState.mLastHWCachePixelCount = pixelCount;
351        } else {
352            // Each pixel takes 4 bytes.
353            deltaInBytes = (pixelCount - mVectorState.mLastSWCachePixelCount) * 4;
354            mVectorState.mLastSWCachePixelCount = pixelCount;
355        }
356        if (deltaInBytes > 0) {
357            VMRuntime.getRuntime().registerNativeAllocation(deltaInBytes);
358        } else if (deltaInBytes < 0) {
359            VMRuntime.getRuntime().registerNativeFree(-deltaInBytes);
360        }
361    }
362
363
364    @Override
365    public int getAlpha() {
366        return (int) (mVectorState.getAlpha() * 255);
367    }
368
369    @Override
370    public void setAlpha(int alpha) {
371        if (mVectorState.setAlpha(alpha / 255f)) {
372            invalidateSelf();
373        }
374    }
375
376    @Override
377    public void setColorFilter(ColorFilter colorFilter) {
378        mColorFilter = colorFilter;
379        invalidateSelf();
380    }
381
382    @Override
383    public ColorFilter getColorFilter() {
384        return mColorFilter;
385    }
386
387    @Override
388    public void setTintList(ColorStateList tint) {
389        final VectorDrawableState state = mVectorState;
390        if (state.mTint != tint) {
391            state.mTint = tint;
392            mTintFilter = updateTintFilter(mTintFilter, tint, state.mTintMode);
393            invalidateSelf();
394        }
395    }
396
397    @Override
398    public void setTintMode(Mode tintMode) {
399        final VectorDrawableState state = mVectorState;
400        if (state.mTintMode != tintMode) {
401            state.mTintMode = tintMode;
402            mTintFilter = updateTintFilter(mTintFilter, state.mTint, tintMode);
403            invalidateSelf();
404        }
405    }
406
407    @Override
408    public boolean isStateful() {
409        return super.isStateful() || (mVectorState != null && mVectorState.isStateful());
410    }
411
412    @Override
413    protected boolean onStateChange(int[] stateSet) {
414        boolean changed = false;
415
416        // When the VD is stateful, we need to mutate the drawable such that we don't share the
417        // cache bitmap with others. Such that the state change only affect this new cached bitmap.
418        if (isStateful()) {
419            mutate();
420        }
421        final VectorDrawableState state = mVectorState;
422        if (state.onStateChange(stateSet)) {
423            changed = true;
424            state.mCacheDirty = true;
425        }
426        if (state.mTint != null && state.mTintMode != null) {
427            mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
428            changed = true;
429        }
430
431        return changed;
432    }
433
434    @Override
435    public int getOpacity() {
436        // We can't tell whether the drawable is fully opaque unless we examine all the pixels,
437        // but we could tell it is transparent if the root alpha is 0.
438        return getAlpha() == 0 ? PixelFormat.TRANSPARENT : PixelFormat.TRANSLUCENT;
439    }
440
441    @Override
442    public int getIntrinsicWidth() {
443        if (mDpiScaledDirty) {
444            computeVectorSize();
445        }
446        return mDpiScaledWidth;
447    }
448
449    @Override
450    public int getIntrinsicHeight() {
451        if (mDpiScaledDirty) {
452            computeVectorSize();
453        }
454        return mDpiScaledHeight;
455    }
456
457    /** @hide */
458    @Override
459    public Insets getOpticalInsets() {
460        if (mDpiScaledDirty) {
461            computeVectorSize();
462        }
463        return mDpiScaledInsets;
464    }
465
466    /*
467     * Update local dimensions to adjust for a target density that may differ
468     * from the source density against which the constant state was loaded.
469     */
470    void computeVectorSize() {
471        final Insets opticalInsets = mVectorState.mOpticalInsets;
472
473        final int sourceDensity = mVectorState.mDensity;
474        final int targetDensity = mTargetDensity;
475        if (targetDensity != sourceDensity) {
476            mDpiScaledWidth = Drawable.scaleFromDensity(
477                    (int) mVectorState.mBaseWidth, sourceDensity, targetDensity, true);
478            mDpiScaledHeight = Drawable.scaleFromDensity(
479                    (int) mVectorState.mBaseHeight,sourceDensity, targetDensity, true);
480            final int left = Drawable.scaleFromDensity(
481                    opticalInsets.left, sourceDensity, targetDensity, false);
482            final int right = Drawable.scaleFromDensity(
483                    opticalInsets.right, sourceDensity, targetDensity, false);
484            final int top = Drawable.scaleFromDensity(
485                    opticalInsets.top, sourceDensity, targetDensity, false);
486            final int bottom = Drawable.scaleFromDensity(
487                    opticalInsets.bottom, sourceDensity, targetDensity, false);
488            mDpiScaledInsets = Insets.of(left, top, right, bottom);
489        } else {
490            mDpiScaledWidth = (int) mVectorState.mBaseWidth;
491            mDpiScaledHeight = (int) mVectorState.mBaseHeight;
492            mDpiScaledInsets = opticalInsets;
493        }
494
495        mDpiScaledDirty = false;
496    }
497
498    @Override
499    public boolean canApplyTheme() {
500        return (mVectorState != null && mVectorState.canApplyTheme()) || super.canApplyTheme();
501    }
502
503    @Override
504    public void applyTheme(Theme t) {
505        super.applyTheme(t);
506
507        final VectorDrawableState state = mVectorState;
508        if (state == null) {
509            return;
510        }
511
512        final boolean changedDensity = mVectorState.setDensity(
513                Drawable.resolveDensity(t.getResources(), 0));
514        mDpiScaledDirty |= changedDensity;
515
516        if (state.mThemeAttrs != null) {
517            final TypedArray a = t.resolveAttributes(
518                    state.mThemeAttrs, R.styleable.VectorDrawable);
519            try {
520                state.mCacheDirty = true;
521                updateStateFromTypedArray(a);
522            } catch (XmlPullParserException e) {
523                throw new RuntimeException(e);
524            } finally {
525                a.recycle();
526            }
527
528            // May have changed size.
529            mDpiScaledDirty = true;
530        }
531
532        // Apply theme to contained color state list.
533        if (state.mTint != null && state.mTint.canApplyTheme()) {
534            state.mTint = state.mTint.obtainForTheme(t);
535        }
536
537        if (mVectorState != null && mVectorState.canApplyTheme()) {
538            mVectorState.applyTheme(t);
539        }
540
541        // Update local properties.
542        updateLocalState(t.getResources());
543    }
544
545    /**
546     * The size of a pixel when scaled from the intrinsic dimension to the viewport dimension.
547     * This is used to calculate the path animation accuracy.
548     *
549     * @hide
550     */
551    public float getPixelSize() {
552        if (mVectorState == null ||
553                mVectorState.mBaseWidth == 0 ||
554                mVectorState.mBaseHeight == 0 ||
555                mVectorState.mViewportHeight == 0 ||
556                mVectorState.mViewportWidth == 0) {
557            return 1; // fall back to 1:1 pixel mapping.
558        }
559        float intrinsicWidth = mVectorState.mBaseWidth;
560        float intrinsicHeight = mVectorState.mBaseHeight;
561        float viewportWidth = mVectorState.mViewportWidth;
562        float viewportHeight = mVectorState.mViewportHeight;
563        float scaleX = viewportWidth / intrinsicWidth;
564        float scaleY = viewportHeight / intrinsicHeight;
565        return Math.min(scaleX, scaleY);
566    }
567
568    /** @hide */
569    public static VectorDrawable create(Resources resources, int rid) {
570        try {
571            final XmlPullParser parser = resources.getXml(rid);
572            final AttributeSet attrs = Xml.asAttributeSet(parser);
573            int type;
574            while ((type=parser.next()) != XmlPullParser.START_TAG &&
575                    type != XmlPullParser.END_DOCUMENT) {
576                // Empty loop
577            }
578            if (type != XmlPullParser.START_TAG) {
579                throw new XmlPullParserException("No start tag found");
580            }
581
582            final VectorDrawable drawable = new VectorDrawable();
583            drawable.inflate(resources, parser, attrs);
584
585            return drawable;
586        } catch (XmlPullParserException e) {
587            Log.e(LOGTAG, "parser error", e);
588        } catch (IOException e) {
589            Log.e(LOGTAG, "parser error", e);
590        }
591        return null;
592    }
593
594    @Override
595    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
596            @NonNull AttributeSet attrs, @Nullable Theme theme)
597            throws XmlPullParserException, IOException {
598        if (mVectorState.mRootGroup != null || mVectorState.mNativeTree != null) {
599            // This VD has been used to display other VD resource content, clean up.
600            if (mVectorState.mRootGroup != null) {
601                // Subtract the native allocation for all the nodes.
602                VMRuntime.getRuntime().registerNativeFree(mVectorState.mRootGroup.getNativeSize());
603                // Remove child nodes' reference to tree
604                mVectorState.mRootGroup.setTree(null);
605            }
606            mVectorState.mRootGroup = new VGroup();
607            if (mVectorState.mNativeTree != null) {
608                // Subtract the native allocation for the tree wrapper, which contains root node
609                // as well as rendering related data.
610                VMRuntime.getRuntime().registerNativeFree(mVectorState.NATIVE_ALLOCATION_SIZE);
611                mVectorState.mNativeTree.release();
612            }
613            mVectorState.createNativeTree(mVectorState.mRootGroup);
614        }
615        final VectorDrawableState state = mVectorState;
616        state.setDensity(Drawable.resolveDensity(r, 0));
617
618        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.VectorDrawable);
619        updateStateFromTypedArray(a);
620        a.recycle();
621
622        mDpiScaledDirty = true;
623
624        state.mCacheDirty = true;
625        inflateChildElements(r, parser, attrs, theme);
626
627        state.onTreeConstructionFinished();
628        // Update local properties.
629        updateLocalState(r);
630    }
631
632    private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException {
633        final VectorDrawableState state = mVectorState;
634
635        // Account for any configuration changes.
636        state.mChangingConfigurations |= a.getChangingConfigurations();
637
638        // Extract the theme attributes, if any.
639        state.mThemeAttrs = a.extractThemeAttrs();
640
641        final int tintMode = a.getInt(R.styleable.VectorDrawable_tintMode, -1);
642        if (tintMode != -1) {
643            state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN);
644        }
645
646        final ColorStateList tint = a.getColorStateList(R.styleable.VectorDrawable_tint);
647        if (tint != null) {
648            state.mTint = tint;
649        }
650
651        state.mAutoMirrored = a.getBoolean(
652                R.styleable.VectorDrawable_autoMirrored, state.mAutoMirrored);
653
654        float viewportWidth = a.getFloat(
655                R.styleable.VectorDrawable_viewportWidth, state.mViewportWidth);
656        float viewportHeight = a.getFloat(
657                R.styleable.VectorDrawable_viewportHeight, state.mViewportHeight);
658        state.setViewportSize(viewportWidth, viewportHeight);
659
660        if (state.mViewportWidth <= 0) {
661            throw new XmlPullParserException(a.getPositionDescription() +
662                    "<vector> tag requires viewportWidth > 0");
663        } else if (state.mViewportHeight <= 0) {
664            throw new XmlPullParserException(a.getPositionDescription() +
665                    "<vector> tag requires viewportHeight > 0");
666        }
667
668        state.mBaseWidth = a.getDimension(
669                R.styleable.VectorDrawable_width, state.mBaseWidth);
670        state.mBaseHeight = a.getDimension(
671                R.styleable.VectorDrawable_height, state.mBaseHeight);
672
673        if (state.mBaseWidth <= 0) {
674            throw new XmlPullParserException(a.getPositionDescription() +
675                    "<vector> tag requires width > 0");
676        } else if (state.mBaseHeight <= 0) {
677            throw new XmlPullParserException(a.getPositionDescription() +
678                    "<vector> tag requires height > 0");
679        }
680
681        final int insetLeft = a.getDimensionPixelOffset(
682                R.styleable.VectorDrawable_opticalInsetLeft, state.mOpticalInsets.left);
683        final int insetTop = a.getDimensionPixelOffset(
684                R.styleable.VectorDrawable_opticalInsetTop, state.mOpticalInsets.top);
685        final int insetRight = a.getDimensionPixelOffset(
686                R.styleable.VectorDrawable_opticalInsetRight, state.mOpticalInsets.right);
687        final int insetBottom = a.getDimensionPixelOffset(
688                R.styleable.VectorDrawable_opticalInsetBottom, state.mOpticalInsets.bottom);
689        state.mOpticalInsets = Insets.of(insetLeft, insetTop, insetRight, insetBottom);
690
691        final float alphaInFloat = a.getFloat(
692                R.styleable.VectorDrawable_alpha, state.getAlpha());
693        state.setAlpha(alphaInFloat);
694
695        final String name = a.getString(R.styleable.VectorDrawable_name);
696        if (name != null) {
697            state.mRootName = name;
698            state.mVGTargetsMap.put(name, state);
699        }
700    }
701
702    private void inflateChildElements(Resources res, XmlPullParser parser, AttributeSet attrs,
703            Theme theme) throws XmlPullParserException, IOException {
704        final VectorDrawableState state = mVectorState;
705        boolean noPathTag = true;
706
707        // Use a stack to help to build the group tree.
708        // The top of the stack is always the current group.
709        final Stack<VGroup> groupStack = new Stack<VGroup>();
710        groupStack.push(state.mRootGroup);
711
712        int eventType = parser.getEventType();
713        final int innerDepth = parser.getDepth() + 1;
714
715        // Parse everything until the end of the vector element.
716        while (eventType != XmlPullParser.END_DOCUMENT
717                && (parser.getDepth() >= innerDepth || eventType != XmlPullParser.END_TAG)) {
718            if (eventType == XmlPullParser.START_TAG) {
719                final String tagName = parser.getName();
720                final VGroup currentGroup = groupStack.peek();
721
722                if (SHAPE_PATH.equals(tagName)) {
723                    final VFullPath path = new VFullPath();
724                    path.inflate(res, attrs, theme);
725                    currentGroup.addChild(path);
726                    if (path.getPathName() != null) {
727                        state.mVGTargetsMap.put(path.getPathName(), path);
728                    }
729                    noPathTag = false;
730                    state.mChangingConfigurations |= path.mChangingConfigurations;
731                } else if (SHAPE_CLIP_PATH.equals(tagName)) {
732                    final VClipPath path = new VClipPath();
733                    path.inflate(res, attrs, theme);
734                    currentGroup.addChild(path);
735                    if (path.getPathName() != null) {
736                        state.mVGTargetsMap.put(path.getPathName(), path);
737                    }
738                    state.mChangingConfigurations |= path.mChangingConfigurations;
739                } else if (SHAPE_GROUP.equals(tagName)) {
740                    VGroup newChildGroup = new VGroup();
741                    newChildGroup.inflate(res, attrs, theme);
742                    currentGroup.addChild(newChildGroup);
743                    groupStack.push(newChildGroup);
744                    if (newChildGroup.getGroupName() != null) {
745                        state.mVGTargetsMap.put(newChildGroup.getGroupName(),
746                                newChildGroup);
747                    }
748                    state.mChangingConfigurations |= newChildGroup.mChangingConfigurations;
749                }
750            } else if (eventType == XmlPullParser.END_TAG) {
751                final String tagName = parser.getName();
752                if (SHAPE_GROUP.equals(tagName)) {
753                    groupStack.pop();
754                }
755            }
756            eventType = parser.next();
757        }
758
759        if (noPathTag) {
760            final StringBuffer tag = new StringBuffer();
761
762            if (tag.length() > 0) {
763                tag.append(" or ");
764            }
765            tag.append(SHAPE_PATH);
766
767            throw new XmlPullParserException("no " + tag + " defined");
768        }
769    }
770
771    @Override
772    public @Config int getChangingConfigurations() {
773        return super.getChangingConfigurations() | mVectorState.getChangingConfigurations();
774    }
775
776    void setAllowCaching(boolean allowCaching) {
777        nSetAllowCaching(mVectorState.getNativeRenderer(), allowCaching);
778    }
779
780    private boolean needMirroring() {
781        return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL;
782    }
783
784    @Override
785    public void setAutoMirrored(boolean mirrored) {
786        if (mVectorState.mAutoMirrored != mirrored) {
787            mVectorState.mAutoMirrored = mirrored;
788            invalidateSelf();
789        }
790    }
791
792    @Override
793    public boolean isAutoMirrored() {
794        return mVectorState.mAutoMirrored;
795    }
796
797    /**
798     * @hide
799     */
800    public long getNativeTree() {
801        return mVectorState.getNativeRenderer();
802    }
803
804    static class VectorDrawableState extends ConstantState {
805        // Variables below need to be copied (deep copy if applicable) for mutation.
806        int[] mThemeAttrs;
807        @Config int mChangingConfigurations;
808        ColorStateList mTint = null;
809        Mode mTintMode = DEFAULT_TINT_MODE;
810        boolean mAutoMirrored;
811
812        float mBaseWidth = 0;
813        float mBaseHeight = 0;
814        float mViewportWidth = 0;
815        float mViewportHeight = 0;
816        Insets mOpticalInsets = Insets.NONE;
817        String mRootName = null;
818        VGroup mRootGroup;
819        VirtualRefBasePtr mNativeTree = null;
820
821        int mDensity = DisplayMetrics.DENSITY_DEFAULT;
822        final ArrayMap<String, Object> mVGTargetsMap = new ArrayMap<>();
823
824        // Fields for cache
825        int[] mCachedThemeAttrs;
826        ColorStateList mCachedTint;
827        Mode mCachedTintMode;
828        boolean mCachedAutoMirrored;
829        boolean mCacheDirty;
830
831        // Since sw canvas and hw canvas uses different bitmap caches, we track the allocation of
832        // these bitmaps separately.
833        int mLastSWCachePixelCount = 0;
834        int mLastHWCachePixelCount = 0;
835
836        final static Property<VectorDrawableState, Float> ALPHA =
837                new FloatProperty<VectorDrawableState>("alpha") {
838                    @Override
839                    public void setValue(VectorDrawableState state, float value) {
840                        state.setAlpha(value);
841                    }
842
843                    @Override
844                    public Float get(VectorDrawableState state) {
845                        return state.getAlpha();
846                    }
847                };
848
849        Property getProperty(String propertyName) {
850            if (ALPHA.getName().equals(propertyName)) {
851                return ALPHA;
852            }
853            return null;
854        }
855
856        // This tracks the total native allocation for all the nodes.
857        private int mAllocationOfAllNodes = 0;
858
859        private static final int NATIVE_ALLOCATION_SIZE = 316;
860
861        // If copy is not null, deep copy the given VectorDrawableState. Otherwise, create a
862        // native vector drawable tree with an empty root group.
863        public VectorDrawableState(VectorDrawableState copy) {
864            if (copy != null) {
865                mThemeAttrs = copy.mThemeAttrs;
866                mChangingConfigurations = copy.mChangingConfigurations;
867                mTint = copy.mTint;
868                mTintMode = copy.mTintMode;
869                mAutoMirrored = copy.mAutoMirrored;
870                mRootGroup = new VGroup(copy.mRootGroup, mVGTargetsMap);
871                createNativeTreeFromCopy(copy, mRootGroup);
872
873                mBaseWidth = copy.mBaseWidth;
874                mBaseHeight = copy.mBaseHeight;
875                setViewportSize(copy.mViewportWidth, copy.mViewportHeight);
876                mOpticalInsets = copy.mOpticalInsets;
877
878                mRootName = copy.mRootName;
879                mDensity = copy.mDensity;
880                if (copy.mRootName != null) {
881                    mVGTargetsMap.put(copy.mRootName, this);
882                }
883            } else {
884                mRootGroup = new VGroup();
885                createNativeTree(mRootGroup);
886            }
887            onTreeConstructionFinished();
888        }
889
890        private void createNativeTree(VGroup rootGroup) {
891            mNativeTree = new VirtualRefBasePtr(nCreateTree(rootGroup.mNativePtr));
892            // Register tree size
893            VMRuntime.getRuntime().registerNativeAllocation(NATIVE_ALLOCATION_SIZE);
894        }
895
896        // Create a new native tree with the given root group, and copy the properties from the
897        // given VectorDrawableState's native tree.
898        private void createNativeTreeFromCopy(VectorDrawableState copy, VGroup rootGroup) {
899            mNativeTree = new VirtualRefBasePtr(nCreateTreeFromCopy(
900                    copy.mNativeTree.get(), rootGroup.mNativePtr));
901            // Register tree size
902            VMRuntime.getRuntime().registerNativeAllocation(NATIVE_ALLOCATION_SIZE);
903        }
904
905        // This should be called every time after a new RootGroup and all its subtrees are created
906        // (i.e. in constructors of VectorDrawableState and in inflate).
907        void onTreeConstructionFinished() {
908            mRootGroup.setTree(mNativeTree);
909            mAllocationOfAllNodes = mRootGroup.getNativeSize();
910            VMRuntime.getRuntime().registerNativeAllocation(mAllocationOfAllNodes);
911        }
912
913        long getNativeRenderer() {
914            if (mNativeTree == null) {
915                return 0;
916            }
917            return mNativeTree.get();
918        }
919
920        public boolean canReuseCache() {
921            if (!mCacheDirty
922                    && mCachedThemeAttrs == mThemeAttrs
923                    && mCachedTint == mTint
924                    && mCachedTintMode == mTintMode
925                    && mCachedAutoMirrored == mAutoMirrored) {
926                return true;
927            }
928            updateCacheStates();
929            return false;
930        }
931
932        public void updateCacheStates() {
933            // Use shallow copy here and shallow comparison in canReuseCache(),
934            // likely hit cache miss more, but practically not much difference.
935            mCachedThemeAttrs = mThemeAttrs;
936            mCachedTint = mTint;
937            mCachedTintMode = mTintMode;
938            mCachedAutoMirrored = mAutoMirrored;
939            mCacheDirty = false;
940        }
941
942        public void applyTheme(Theme t) {
943            mRootGroup.applyTheme(t);
944        }
945
946        @Override
947        public boolean canApplyTheme() {
948            return mThemeAttrs != null
949                    || (mRootGroup != null && mRootGroup.canApplyTheme())
950                    || (mTint != null && mTint.canApplyTheme())
951                    || super.canApplyTheme();
952        }
953
954        @Override
955        public Drawable newDrawable() {
956            return new VectorDrawable(this, null);
957        }
958
959        @Override
960        public Drawable newDrawable(Resources res) {
961            return new VectorDrawable(this, res);
962        }
963
964        @Override
965        public @Config int getChangingConfigurations() {
966            return mChangingConfigurations
967                    | (mTint != null ? mTint.getChangingConfigurations() : 0);
968        }
969
970        public boolean isStateful() {
971            return (mTint != null && mTint.isStateful())
972                    || (mRootGroup != null && mRootGroup.isStateful());
973        }
974
975        void setViewportSize(float viewportWidth, float viewportHeight) {
976            mViewportWidth = viewportWidth;
977            mViewportHeight = viewportHeight;
978            nSetRendererViewportSize(getNativeRenderer(), viewportWidth, viewportHeight);
979        }
980
981        public final boolean setDensity(int targetDensity) {
982            if (mDensity != targetDensity) {
983                final int sourceDensity = mDensity;
984                mDensity = targetDensity;
985                applyDensityScaling(sourceDensity, targetDensity);
986                return true;
987            }
988            return false;
989        }
990
991        private void applyDensityScaling(int sourceDensity, int targetDensity) {
992            mBaseWidth = Drawable.scaleFromDensity(mBaseWidth, sourceDensity, targetDensity);
993            mBaseHeight = Drawable.scaleFromDensity(mBaseHeight, sourceDensity, targetDensity);
994
995            final int insetLeft = Drawable.scaleFromDensity(
996                    mOpticalInsets.left, sourceDensity, targetDensity, false);
997            final int insetTop = Drawable.scaleFromDensity(
998                    mOpticalInsets.top, sourceDensity, targetDensity, false);
999            final int insetRight = Drawable.scaleFromDensity(
1000                    mOpticalInsets.right, sourceDensity, targetDensity, false);
1001            final int insetBottom = Drawable.scaleFromDensity(
1002                    mOpticalInsets.bottom, sourceDensity, targetDensity, false);
1003            mOpticalInsets = Insets.of(insetLeft, insetTop, insetRight, insetBottom);
1004        }
1005
1006        public boolean onStateChange(int[] stateSet) {
1007            return mRootGroup.onStateChange(stateSet);
1008        }
1009
1010        @Override
1011        public void finalize() throws Throwable {
1012            super.finalize();
1013            int bitmapCacheSize = mLastHWCachePixelCount * 4 + mLastSWCachePixelCount * 4;
1014            VMRuntime.getRuntime().registerNativeFree(NATIVE_ALLOCATION_SIZE
1015                    + mAllocationOfAllNodes + bitmapCacheSize);
1016        }
1017
1018        /**
1019         * setAlpha() and getAlpha() are used mostly for animation purpose. Return true if alpha
1020         * has changed.
1021         */
1022        public boolean setAlpha(float alpha) {
1023            return nSetRootAlpha(mNativeTree.get(), alpha);
1024        }
1025
1026        @SuppressWarnings("unused")
1027        public float getAlpha() {
1028            return nGetRootAlpha(mNativeTree.get());
1029        }
1030    }
1031
1032    static class VGroup extends VObject {
1033        private static final int ROTATION_INDEX = 0;
1034        private static final int PIVOT_X_INDEX = 1;
1035        private static final int PIVOT_Y_INDEX = 2;
1036        private static final int SCALE_X_INDEX = 3;
1037        private static final int SCALE_Y_INDEX = 4;
1038        private static final int TRANSLATE_X_INDEX = 5;
1039        private static final int TRANSLATE_Y_INDEX = 6;
1040        private static final int TRANSFORM_PROPERTY_COUNT = 7;
1041
1042        private static final int NATIVE_ALLOCATION_SIZE = 100;
1043
1044        private static final HashMap<String, Integer> sPropertyIndexMap =
1045                new HashMap<String, Integer>() {
1046                    {
1047                        put("translateX", TRANSLATE_X_INDEX);
1048                        put("translateY", TRANSLATE_Y_INDEX);
1049                        put("scaleX", SCALE_X_INDEX);
1050                        put("scaleY", SCALE_Y_INDEX);
1051                        put("pivotX", PIVOT_X_INDEX);
1052                        put("pivotY", PIVOT_Y_INDEX);
1053                        put("rotation", ROTATION_INDEX);
1054                    }
1055                };
1056
1057        static int getPropertyIndex(String propertyName) {
1058            if (sPropertyIndexMap.containsKey(propertyName)) {
1059                return sPropertyIndexMap.get(propertyName);
1060            } else {
1061                // property not found
1062                return -1;
1063            }
1064        }
1065
1066        // Below are the Properties that wrap the setters to avoid reflection overhead in animations
1067        private static final Property<VGroup, Float> TRANSLATE_X =
1068                new FloatProperty<VGroup> ("translateX") {
1069                    @Override
1070                    public void setValue(VGroup object, float value) {
1071                        object.setTranslateX(value);
1072                    }
1073
1074                    @Override
1075                    public Float get(VGroup object) {
1076                        return object.getTranslateX();
1077                    }
1078                };
1079
1080        private static final Property<VGroup, Float> TRANSLATE_Y =
1081                new FloatProperty<VGroup> ("translateY") {
1082                    @Override
1083                    public void setValue(VGroup object, float value) {
1084                        object.setTranslateY(value);
1085                    }
1086
1087                    @Override
1088                    public Float get(VGroup object) {
1089                        return object.getTranslateY();
1090                    }
1091        };
1092
1093        private static final Property<VGroup, Float> SCALE_X =
1094                new FloatProperty<VGroup> ("scaleX") {
1095                    @Override
1096                    public void setValue(VGroup object, float value) {
1097                        object.setScaleX(value);
1098                    }
1099
1100                    @Override
1101                    public Float get(VGroup object) {
1102                        return object.getScaleX();
1103                    }
1104                };
1105
1106        private static final Property<VGroup, Float> SCALE_Y =
1107                new FloatProperty<VGroup> ("scaleY") {
1108                    @Override
1109                    public void setValue(VGroup object, float value) {
1110                        object.setScaleY(value);
1111                    }
1112
1113                    @Override
1114                    public Float get(VGroup object) {
1115                        return object.getScaleY();
1116                    }
1117                };
1118
1119        private static final Property<VGroup, Float> PIVOT_X =
1120                new FloatProperty<VGroup> ("pivotX") {
1121                    @Override
1122                    public void setValue(VGroup object, float value) {
1123                        object.setPivotX(value);
1124                    }
1125
1126                    @Override
1127                    public Float get(VGroup object) {
1128                        return object.getPivotX();
1129                    }
1130                };
1131
1132        private static final Property<VGroup, Float> PIVOT_Y =
1133                new FloatProperty<VGroup> ("pivotY") {
1134                    @Override
1135                    public void setValue(VGroup object, float value) {
1136                        object.setPivotY(value);
1137                    }
1138
1139                    @Override
1140                    public Float get(VGroup object) {
1141                        return object.getPivotY();
1142                    }
1143                };
1144
1145        private static final Property<VGroup, Float> ROTATION =
1146                new FloatProperty<VGroup> ("rotation") {
1147                    @Override
1148                    public void setValue(VGroup object, float value) {
1149                        object.setRotation(value);
1150                    }
1151
1152                    @Override
1153                    public Float get(VGroup object) {
1154                        return object.getRotation();
1155                    }
1156                };
1157
1158        private static final HashMap<String, Property> sPropertyMap =
1159                new HashMap<String, Property>() {
1160                    {
1161                        put("translateX", TRANSLATE_X);
1162                        put("translateY", TRANSLATE_Y);
1163                        put("scaleX", SCALE_X);
1164                        put("scaleY", SCALE_Y);
1165                        put("pivotX", PIVOT_X);
1166                        put("pivotY", PIVOT_Y);
1167                        put("rotation", ROTATION);
1168                    }
1169                };
1170        // Temp array to store transform values obtained from native.
1171        private float[] mTransform;
1172        /////////////////////////////////////////////////////
1173        // Variables below need to be copied (deep copy if applicable) for mutation.
1174        private final ArrayList<VObject> mChildren = new ArrayList<>();
1175        private boolean mIsStateful;
1176
1177        // mLocalMatrix is updated based on the update of transformation information,
1178        // either parsed from the XML or by animation.
1179        private @Config int mChangingConfigurations;
1180        private int[] mThemeAttrs;
1181        private String mGroupName = null;
1182
1183        // The native object will be created in the constructor and will be destroyed in native
1184        // when the neither java nor native has ref to the tree. This pointer should be valid
1185        // throughout this VGroup Java object's life.
1186        private final long mNativePtr;
1187        public VGroup(VGroup copy, ArrayMap<String, Object> targetsMap) {
1188
1189            mIsStateful = copy.mIsStateful;
1190            mThemeAttrs = copy.mThemeAttrs;
1191            mGroupName = copy.mGroupName;
1192            mChangingConfigurations = copy.mChangingConfigurations;
1193            if (mGroupName != null) {
1194                targetsMap.put(mGroupName, this);
1195            }
1196            mNativePtr = nCreateGroup(copy.mNativePtr);
1197
1198            final ArrayList<VObject> children = copy.mChildren;
1199            for (int i = 0; i < children.size(); i++) {
1200                final VObject copyChild = children.get(i);
1201                if (copyChild instanceof VGroup) {
1202                    final VGroup copyGroup = (VGroup) copyChild;
1203                    addChild(new VGroup(copyGroup, targetsMap));
1204                } else {
1205                    final VPath newPath;
1206                    if (copyChild instanceof VFullPath) {
1207                        newPath = new VFullPath((VFullPath) copyChild);
1208                    } else if (copyChild instanceof VClipPath) {
1209                        newPath = new VClipPath((VClipPath) copyChild);
1210                    } else {
1211                        throw new IllegalStateException("Unknown object in the tree!");
1212                    }
1213                    addChild(newPath);
1214                    if (newPath.mPathName != null) {
1215                        targetsMap.put(newPath.mPathName, newPath);
1216                    }
1217                }
1218            }
1219        }
1220
1221        public VGroup() {
1222            mNativePtr = nCreateGroup();
1223        }
1224
1225        Property getProperty(String propertyName) {
1226            if (sPropertyMap.containsKey(propertyName)) {
1227                return sPropertyMap.get(propertyName);
1228            } else {
1229                // property not found
1230                return null;
1231            }
1232        }
1233
1234        public String getGroupName() {
1235            return mGroupName;
1236        }
1237
1238        public void addChild(VObject child) {
1239            nAddChild(mNativePtr, child.getNativePtr());
1240            mChildren.add(child);
1241            mIsStateful |= child.isStateful();
1242        }
1243
1244        @Override
1245        public void setTree(VirtualRefBasePtr treeRoot) {
1246            super.setTree(treeRoot);
1247            for (int i = 0; i < mChildren.size(); i++) {
1248                mChildren.get(i).setTree(treeRoot);
1249            }
1250        }
1251
1252        @Override
1253        public long getNativePtr() {
1254            return mNativePtr;
1255        }
1256
1257        @Override
1258        public void inflate(Resources res, AttributeSet attrs, Theme theme) {
1259            final TypedArray a = obtainAttributes(res, theme, attrs,
1260                    R.styleable.VectorDrawableGroup);
1261            updateStateFromTypedArray(a);
1262            a.recycle();
1263        }
1264
1265        void updateStateFromTypedArray(TypedArray a) {
1266            // Account for any configuration changes.
1267            mChangingConfigurations |= a.getChangingConfigurations();
1268
1269            // Extract the theme attributes, if any.
1270            mThemeAttrs = a.extractThemeAttrs();
1271            if (mTransform == null) {
1272                // Lazy initialization: If the group is created through copy constructor, this may
1273                // never get called.
1274                mTransform = new float[TRANSFORM_PROPERTY_COUNT];
1275            }
1276            boolean success = nGetGroupProperties(mNativePtr, mTransform, TRANSFORM_PROPERTY_COUNT);
1277            if (!success) {
1278                throw new RuntimeException("Error: inconsistent property count");
1279            }
1280            float rotate = a.getFloat(R.styleable.VectorDrawableGroup_rotation,
1281                    mTransform[ROTATION_INDEX]);
1282            float pivotX = a.getFloat(R.styleable.VectorDrawableGroup_pivotX,
1283                    mTransform[PIVOT_X_INDEX]);
1284            float pivotY = a.getFloat(R.styleable.VectorDrawableGroup_pivotY,
1285                    mTransform[PIVOT_Y_INDEX]);
1286            float scaleX = a.getFloat(R.styleable.VectorDrawableGroup_scaleX,
1287                    mTransform[SCALE_X_INDEX]);
1288            float scaleY = a.getFloat(R.styleable.VectorDrawableGroup_scaleY,
1289                    mTransform[SCALE_Y_INDEX]);
1290            float translateX = a.getFloat(R.styleable.VectorDrawableGroup_translateX,
1291                    mTransform[TRANSLATE_X_INDEX]);
1292            float translateY = a.getFloat(R.styleable.VectorDrawableGroup_translateY,
1293                    mTransform[TRANSLATE_Y_INDEX]);
1294
1295            final String groupName = a.getString(R.styleable.VectorDrawableGroup_name);
1296            if (groupName != null) {
1297                mGroupName = groupName;
1298                nSetName(mNativePtr, mGroupName);
1299            }
1300             nUpdateGroupProperties(mNativePtr, rotate, pivotX, pivotY, scaleX, scaleY,
1301                     translateX, translateY);
1302        }
1303
1304        @Override
1305        public boolean onStateChange(int[] stateSet) {
1306            boolean changed = false;
1307
1308            final ArrayList<VObject> children = mChildren;
1309            for (int i = 0, count = children.size(); i < count; i++) {
1310                final VObject child = children.get(i);
1311                if (child.isStateful()) {
1312                    changed |= child.onStateChange(stateSet);
1313                }
1314            }
1315
1316            return changed;
1317        }
1318
1319        @Override
1320        public boolean isStateful() {
1321            return mIsStateful;
1322        }
1323
1324        @Override
1325        int getNativeSize() {
1326            // Return the native allocation needed for the subtree.
1327            int size = NATIVE_ALLOCATION_SIZE;
1328            for (int i = 0; i < mChildren.size(); i++) {
1329                size += mChildren.get(i).getNativeSize();
1330            }
1331            return size;
1332        }
1333
1334        @Override
1335        public boolean canApplyTheme() {
1336            if (mThemeAttrs != null) {
1337                return true;
1338            }
1339
1340            final ArrayList<VObject> children = mChildren;
1341            for (int i = 0, count = children.size(); i < count; i++) {
1342                final VObject child = children.get(i);
1343                if (child.canApplyTheme()) {
1344                    return true;
1345                }
1346            }
1347
1348            return false;
1349        }
1350
1351        @Override
1352        public void applyTheme(Theme t) {
1353            if (mThemeAttrs != null) {
1354                final TypedArray a = t.resolveAttributes(mThemeAttrs,
1355                        R.styleable.VectorDrawableGroup);
1356                updateStateFromTypedArray(a);
1357                a.recycle();
1358            }
1359
1360            final ArrayList<VObject> children = mChildren;
1361            for (int i = 0, count = children.size(); i < count; i++) {
1362                final VObject child = children.get(i);
1363                if (child.canApplyTheme()) {
1364                    child.applyTheme(t);
1365
1366                    // Applying a theme may have made the child stateful.
1367                    mIsStateful |= child.isStateful();
1368                }
1369            }
1370        }
1371
1372        /* Setters and Getters, used by animator from AnimatedVectorDrawable. */
1373        @SuppressWarnings("unused")
1374        public float getRotation() {
1375            return isTreeValid() ? nGetRotation(mNativePtr) : 0;
1376        }
1377
1378        @SuppressWarnings("unused")
1379        public void setRotation(float rotation) {
1380            if (isTreeValid()) {
1381                nSetRotation(mNativePtr, rotation);
1382            }
1383        }
1384
1385        @SuppressWarnings("unused")
1386        public float getPivotX() {
1387            return isTreeValid() ? nGetPivotX(mNativePtr) : 0;
1388        }
1389
1390        @SuppressWarnings("unused")
1391        public void setPivotX(float pivotX) {
1392            if (isTreeValid()) {
1393                nSetPivotX(mNativePtr, pivotX);
1394            }
1395        }
1396
1397        @SuppressWarnings("unused")
1398        public float getPivotY() {
1399            return isTreeValid() ? nGetPivotY(mNativePtr) : 0;
1400        }
1401
1402        @SuppressWarnings("unused")
1403        public void setPivotY(float pivotY) {
1404            if (isTreeValid()) {
1405                nSetPivotY(mNativePtr, pivotY);
1406            }
1407        }
1408
1409        @SuppressWarnings("unused")
1410        public float getScaleX() {
1411            return isTreeValid() ? nGetScaleX(mNativePtr) : 0;
1412        }
1413
1414        @SuppressWarnings("unused")
1415        public void setScaleX(float scaleX) {
1416            if (isTreeValid()) {
1417                nSetScaleX(mNativePtr, scaleX);
1418            }
1419        }
1420
1421        @SuppressWarnings("unused")
1422        public float getScaleY() {
1423            return isTreeValid() ? nGetScaleY(mNativePtr) : 0;
1424        }
1425
1426        @SuppressWarnings("unused")
1427        public void setScaleY(float scaleY) {
1428            if (isTreeValid()) {
1429                nSetScaleY(mNativePtr, scaleY);
1430            }
1431        }
1432
1433        @SuppressWarnings("unused")
1434        public float getTranslateX() {
1435            return isTreeValid() ? nGetTranslateX(mNativePtr) : 0;
1436        }
1437
1438        @SuppressWarnings("unused")
1439        public void setTranslateX(float translateX) {
1440            if (isTreeValid()) {
1441                nSetTranslateX(mNativePtr, translateX);
1442            }
1443        }
1444
1445        @SuppressWarnings("unused")
1446        public float getTranslateY() {
1447            return isTreeValid() ? nGetTranslateY(mNativePtr) : 0;
1448        }
1449
1450        @SuppressWarnings("unused")
1451        public void setTranslateY(float translateY) {
1452            if (isTreeValid()) {
1453                nSetTranslateY(mNativePtr, translateY);
1454            }
1455        }
1456    }
1457
1458    /**
1459     * Common Path information for clip path and normal path.
1460     */
1461    static abstract class VPath extends VObject {
1462        protected PathParser.PathData mPathData = null;
1463
1464        String mPathName;
1465        @Config int mChangingConfigurations;
1466
1467        private static final Property<VPath, PathParser.PathData> PATH_DATA =
1468                new Property<VPath, PathParser.PathData>(PathParser.PathData.class, "pathData") {
1469                    @Override
1470                    public void set(VPath object, PathParser.PathData data) {
1471                        object.setPathData(data);
1472                    }
1473
1474                    @Override
1475                    public PathParser.PathData get(VPath object) {
1476                        return object.getPathData();
1477                    }
1478                };
1479
1480        Property getProperty(String propertyName) {
1481            if (PATH_DATA.getName().equals(propertyName)) {
1482                return PATH_DATA;
1483            }
1484            // property not found
1485            return null;
1486        }
1487
1488        public VPath() {
1489            // Empty constructor.
1490        }
1491
1492        public VPath(VPath copy) {
1493            mPathName = copy.mPathName;
1494            mChangingConfigurations = copy.mChangingConfigurations;
1495            mPathData = copy.mPathData == null ? null : new PathParser.PathData(copy.mPathData);
1496        }
1497
1498        public String getPathName() {
1499            return mPathName;
1500        }
1501
1502        /* Setters and Getters, used by animator from AnimatedVectorDrawable. */
1503        @SuppressWarnings("unused")
1504        public PathParser.PathData getPathData() {
1505            return mPathData;
1506        }
1507
1508        // TODO: Move the PathEvaluator and this setter and the getter above into native.
1509        @SuppressWarnings("unused")
1510        public void setPathData(PathParser.PathData pathData) {
1511            mPathData.setPathData(pathData);
1512            if (isTreeValid()) {
1513                nSetPathData(getNativePtr(), mPathData.getNativePtr());
1514            }
1515        }
1516    }
1517
1518    /**
1519     * Clip path, which only has name and pathData.
1520     */
1521    private static class VClipPath extends VPath {
1522        private final long mNativePtr;
1523        private static final int NATIVE_ALLOCATION_SIZE = 120;
1524
1525        public VClipPath() {
1526            mNativePtr = nCreateClipPath();
1527        }
1528
1529        public VClipPath(VClipPath copy) {
1530            super(copy);
1531            mNativePtr = nCreateClipPath(copy.mNativePtr);
1532        }
1533
1534        @Override
1535        public long getNativePtr() {
1536            return mNativePtr;
1537        }
1538
1539        @Override
1540        public void inflate(Resources r, AttributeSet attrs, Theme theme) {
1541            final TypedArray a = obtainAttributes(r, theme, attrs,
1542                    R.styleable.VectorDrawableClipPath);
1543            updateStateFromTypedArray(a);
1544            a.recycle();
1545        }
1546
1547        @Override
1548        public boolean canApplyTheme() {
1549            return false;
1550        }
1551
1552        @Override
1553        public void applyTheme(Theme theme) {
1554            // No-op.
1555        }
1556
1557        @Override
1558        public boolean onStateChange(int[] stateSet) {
1559            return false;
1560        }
1561
1562        @Override
1563        public boolean isStateful() {
1564            return false;
1565        }
1566
1567        @Override
1568        int getNativeSize() {
1569            return NATIVE_ALLOCATION_SIZE;
1570        }
1571
1572        private void updateStateFromTypedArray(TypedArray a) {
1573            // Account for any configuration changes.
1574            mChangingConfigurations |= a.getChangingConfigurations();
1575
1576            final String pathName = a.getString(R.styleable.VectorDrawableClipPath_name);
1577            if (pathName != null) {
1578                mPathName = pathName;
1579                nSetName(mNativePtr, mPathName);
1580            }
1581
1582            final String pathDataString = a.getString(R.styleable.VectorDrawableClipPath_pathData);
1583            if (pathDataString != null) {
1584                mPathData = new PathParser.PathData(pathDataString);
1585                nSetPathString(mNativePtr, pathDataString, pathDataString.length());
1586            }
1587        }
1588    }
1589
1590    /**
1591     * Normal path, which contains all the fill / paint information.
1592     */
1593    static class VFullPath extends VPath {
1594        private static final int STROKE_WIDTH_INDEX = 0;
1595        private static final int STROKE_COLOR_INDEX = 1;
1596        private static final int STROKE_ALPHA_INDEX = 2;
1597        private static final int FILL_COLOR_INDEX = 3;
1598        private static final int FILL_ALPHA_INDEX = 4;
1599        private static final int TRIM_PATH_START_INDEX = 5;
1600        private static final int TRIM_PATH_END_INDEX = 6;
1601        private static final int TRIM_PATH_OFFSET_INDEX = 7;
1602        private static final int STROKE_LINE_CAP_INDEX = 8;
1603        private static final int STROKE_LINE_JOIN_INDEX = 9;
1604        private static final int STROKE_MITER_LIMIT_INDEX = 10;
1605        private static final int FILL_TYPE_INDEX = 11;
1606        private static final int TOTAL_PROPERTY_COUNT = 12;
1607
1608        private static final int NATIVE_ALLOCATION_SIZE = 264;
1609        // Property map for animatable attributes.
1610        private final static HashMap<String, Integer> sPropertyIndexMap
1611                = new HashMap<String, Integer> () {
1612            {
1613                put("strokeWidth", STROKE_WIDTH_INDEX);
1614                put("strokeColor", STROKE_COLOR_INDEX);
1615                put("strokeAlpha", STROKE_ALPHA_INDEX);
1616                put("fillColor", FILL_COLOR_INDEX);
1617                put("fillAlpha", FILL_ALPHA_INDEX);
1618                put("trimPathStart", TRIM_PATH_START_INDEX);
1619                put("trimPathEnd", TRIM_PATH_END_INDEX);
1620                put("trimPathOffset", TRIM_PATH_OFFSET_INDEX);
1621            }
1622        };
1623
1624        // Below are the Properties that wrap the setters to avoid reflection overhead in animations
1625        private static final Property<VFullPath, Float> STROKE_WIDTH =
1626                new FloatProperty<VFullPath> ("strokeWidth") {
1627                    @Override
1628                    public void setValue(VFullPath object, float value) {
1629                        object.setStrokeWidth(value);
1630                    }
1631
1632                    @Override
1633                    public Float get(VFullPath object) {
1634                        return object.getStrokeWidth();
1635                    }
1636                };
1637
1638        private static final Property<VFullPath, Integer> STROKE_COLOR =
1639                new IntProperty<VFullPath> ("strokeColor") {
1640                    @Override
1641                    public void setValue(VFullPath object, int value) {
1642                        object.setStrokeColor(value);
1643                    }
1644
1645                    @Override
1646                    public Integer get(VFullPath object) {
1647                        return object.getStrokeColor();
1648                    }
1649                };
1650
1651        private static final Property<VFullPath, Float> STROKE_ALPHA =
1652                new FloatProperty<VFullPath> ("strokeAlpha") {
1653                    @Override
1654                    public void setValue(VFullPath object, float value) {
1655                        object.setStrokeAlpha(value);
1656                    }
1657
1658                    @Override
1659                    public Float get(VFullPath object) {
1660                        return object.getStrokeAlpha();
1661                    }
1662                };
1663
1664        private static final Property<VFullPath, Integer> FILL_COLOR =
1665                new IntProperty<VFullPath>("fillColor") {
1666                    @Override
1667                    public void setValue(VFullPath object, int value) {
1668                        object.setFillColor(value);
1669                    }
1670
1671                    @Override
1672                    public Integer get(VFullPath object) {
1673                        return object.getFillColor();
1674                    }
1675                };
1676
1677        private static final Property<VFullPath, Float> FILL_ALPHA =
1678                new FloatProperty<VFullPath> ("fillAlpha") {
1679                    @Override
1680                    public void setValue(VFullPath object, float value) {
1681                        object.setFillAlpha(value);
1682                    }
1683
1684                    @Override
1685                    public Float get(VFullPath object) {
1686                        return object.getFillAlpha();
1687                    }
1688                };
1689
1690        private static final Property<VFullPath, Float> TRIM_PATH_START =
1691                new FloatProperty<VFullPath> ("trimPathStart") {
1692                    @Override
1693                    public void setValue(VFullPath object, float value) {
1694                        object.setTrimPathStart(value);
1695                    }
1696
1697                    @Override
1698                    public Float get(VFullPath object) {
1699                        return object.getTrimPathStart();
1700                    }
1701                };
1702
1703        private static final Property<VFullPath, Float> TRIM_PATH_END =
1704                new FloatProperty<VFullPath> ("trimPathEnd") {
1705                    @Override
1706                    public void setValue(VFullPath object, float value) {
1707                        object.setTrimPathEnd(value);
1708                    }
1709
1710                    @Override
1711                    public Float get(VFullPath object) {
1712                        return object.getTrimPathEnd();
1713                    }
1714                };
1715
1716        private static final Property<VFullPath, Float> TRIM_PATH_OFFSET =
1717                new FloatProperty<VFullPath> ("trimPathOffset") {
1718                    @Override
1719                    public void setValue(VFullPath object, float value) {
1720                        object.setTrimPathOffset(value);
1721                    }
1722
1723                    @Override
1724                    public Float get(VFullPath object) {
1725                        return object.getTrimPathOffset();
1726                    }
1727                };
1728
1729        private final static HashMap<String, Property> sPropertyMap
1730                = new HashMap<String, Property> () {
1731            {
1732                put("strokeWidth", STROKE_WIDTH);
1733                put("strokeColor", STROKE_COLOR);
1734                put("strokeAlpha", STROKE_ALPHA);
1735                put("fillColor", FILL_COLOR);
1736                put("fillAlpha", FILL_ALPHA);
1737                put("trimPathStart", TRIM_PATH_START);
1738                put("trimPathEnd", TRIM_PATH_END);
1739                put("trimPathOffset", TRIM_PATH_OFFSET);
1740            }
1741        };
1742
1743        // Temp array to store property data obtained from native getter.
1744        private byte[] mPropertyData;
1745        /////////////////////////////////////////////////////
1746        // Variables below need to be copied (deep copy if applicable) for mutation.
1747        private int[] mThemeAttrs;
1748
1749        ComplexColor mStrokeColors = null;
1750        ComplexColor mFillColors = null;
1751        private final long mNativePtr;
1752
1753        public VFullPath() {
1754            mNativePtr = nCreateFullPath();
1755        }
1756
1757        public VFullPath(VFullPath copy) {
1758            super(copy);
1759            mNativePtr = nCreateFullPath(copy.mNativePtr);
1760            mThemeAttrs = copy.mThemeAttrs;
1761            mStrokeColors = copy.mStrokeColors;
1762            mFillColors = copy.mFillColors;
1763        }
1764
1765        Property getProperty(String propertyName) {
1766            Property p = super.getProperty(propertyName);
1767            if (p != null) {
1768                return p;
1769            }
1770            if (sPropertyMap.containsKey(propertyName)) {
1771                return sPropertyMap.get(propertyName);
1772            } else {
1773                // property not found
1774                return null;
1775            }
1776        }
1777
1778        int getPropertyIndex(String propertyName) {
1779            if (!sPropertyIndexMap.containsKey(propertyName)) {
1780                return -1;
1781            } else {
1782                return sPropertyIndexMap.get(propertyName);
1783            }
1784        }
1785
1786        @Override
1787        public boolean onStateChange(int[] stateSet) {
1788            boolean changed = false;
1789
1790            if (mStrokeColors != null && mStrokeColors instanceof ColorStateList) {
1791                final int oldStrokeColor = getStrokeColor();
1792                final int newStrokeColor =
1793                        ((ColorStateList) mStrokeColors).getColorForState(stateSet, oldStrokeColor);
1794                changed |= oldStrokeColor != newStrokeColor;
1795                if (oldStrokeColor != newStrokeColor) {
1796                    nSetStrokeColor(mNativePtr, newStrokeColor);
1797                }
1798            }
1799
1800            if (mFillColors != null && mFillColors instanceof ColorStateList) {
1801                final int oldFillColor = getFillColor();
1802                final int newFillColor = ((ColorStateList) mFillColors).getColorForState(stateSet, oldFillColor);
1803                changed |= oldFillColor != newFillColor;
1804                if (oldFillColor != newFillColor) {
1805                    nSetFillColor(mNativePtr, newFillColor);
1806                }
1807            }
1808
1809            return changed;
1810        }
1811
1812        @Override
1813        public boolean isStateful() {
1814            return mStrokeColors != null || mFillColors != null;
1815        }
1816
1817        @Override
1818        int getNativeSize() {
1819            return NATIVE_ALLOCATION_SIZE;
1820        }
1821
1822        @Override
1823        public long getNativePtr() {
1824            return mNativePtr;
1825        }
1826
1827        @Override
1828        public void inflate(Resources r, AttributeSet attrs, Theme theme) {
1829            final TypedArray a = obtainAttributes(r, theme, attrs,
1830                    R.styleable.VectorDrawablePath);
1831            updateStateFromTypedArray(a);
1832            a.recycle();
1833        }
1834
1835        private void updateStateFromTypedArray(TypedArray a) {
1836            int byteCount = TOTAL_PROPERTY_COUNT * 4;
1837            if (mPropertyData == null) {
1838                // Lazy initialization: If the path is created through copy constructor, this may
1839                // never get called.
1840                mPropertyData = new byte[byteCount];
1841            }
1842            // The bulk getters/setters of property data (e.g. stroke width, color, etc) allows us
1843            // to pull current values from native and store modifications with only two methods,
1844            // minimizing JNI overhead.
1845            boolean success = nGetFullPathProperties(mNativePtr, mPropertyData, byteCount);
1846            if (!success) {
1847                throw new RuntimeException("Error: inconsistent property count");
1848            }
1849
1850            ByteBuffer properties = ByteBuffer.wrap(mPropertyData);
1851            properties.order(ByteOrder.nativeOrder());
1852            float strokeWidth = properties.getFloat(STROKE_WIDTH_INDEX * 4);
1853            int strokeColor = properties.getInt(STROKE_COLOR_INDEX * 4);
1854            float strokeAlpha = properties.getFloat(STROKE_ALPHA_INDEX * 4);
1855            int fillColor =  properties.getInt(FILL_COLOR_INDEX * 4);
1856            float fillAlpha = properties.getFloat(FILL_ALPHA_INDEX * 4);
1857            float trimPathStart = properties.getFloat(TRIM_PATH_START_INDEX * 4);
1858            float trimPathEnd = properties.getFloat(TRIM_PATH_END_INDEX * 4);
1859            float trimPathOffset = properties.getFloat(TRIM_PATH_OFFSET_INDEX * 4);
1860            int strokeLineCap =  properties.getInt(STROKE_LINE_CAP_INDEX * 4);
1861            int strokeLineJoin = properties.getInt(STROKE_LINE_JOIN_INDEX * 4);
1862            float strokeMiterLimit = properties.getFloat(STROKE_MITER_LIMIT_INDEX * 4);
1863            int fillType = properties.getInt(FILL_TYPE_INDEX * 4);
1864            Shader fillGradient = null;
1865            Shader strokeGradient = null;
1866            // Account for any configuration changes.
1867            mChangingConfigurations |= a.getChangingConfigurations();
1868
1869            // Extract the theme attributes, if any.
1870            mThemeAttrs = a.extractThemeAttrs();
1871
1872            final String pathName = a.getString(R.styleable.VectorDrawablePath_name);
1873            if (pathName != null) {
1874                mPathName = pathName;
1875                nSetName(mNativePtr, mPathName);
1876            }
1877
1878            final String pathString = a.getString(R.styleable.VectorDrawablePath_pathData);
1879            if (pathString != null) {
1880                mPathData = new PathParser.PathData(pathString);
1881                nSetPathString(mNativePtr, pathString, pathString.length());
1882            }
1883
1884            final ComplexColor fillColors = a.getComplexColor(
1885                    R.styleable.VectorDrawablePath_fillColor);
1886            if (fillColors != null) {
1887                // If the colors is a gradient color, or the color state list is stateful, keep the
1888                // colors information. Otherwise, discard the colors and keep the default color.
1889                if (fillColors instanceof  GradientColor) {
1890                    mFillColors = fillColors;
1891                    fillGradient = ((GradientColor) fillColors).getShader();
1892                } else if (fillColors.isStateful()) {
1893                    mFillColors = fillColors;
1894                } else {
1895                    mFillColors = null;
1896                }
1897                fillColor = fillColors.getDefaultColor();
1898            }
1899
1900            final ComplexColor strokeColors = a.getComplexColor(
1901                    R.styleable.VectorDrawablePath_strokeColor);
1902            if (strokeColors != null) {
1903                // If the colors is a gradient color, or the color state list is stateful, keep the
1904                // colors information. Otherwise, discard the colors and keep the default color.
1905                if (strokeColors instanceof GradientColor) {
1906                    mStrokeColors = strokeColors;
1907                    strokeGradient = ((GradientColor) strokeColors).getShader();
1908                } else if (strokeColors.isStateful()) {
1909                    mStrokeColors = strokeColors;
1910                } else {
1911                    mStrokeColors = null;
1912                }
1913                strokeColor = strokeColors.getDefaultColor();
1914            }
1915            // Update the gradient info, even if the gradiet is null.
1916            nUpdateFullPathFillGradient(mNativePtr,
1917                    fillGradient != null ? fillGradient.getNativeInstance() : 0);
1918            nUpdateFullPathStrokeGradient(mNativePtr,
1919                    strokeGradient != null ? strokeGradient.getNativeInstance() : 0);
1920
1921            fillAlpha = a.getFloat(R.styleable.VectorDrawablePath_fillAlpha, fillAlpha);
1922
1923            strokeLineCap = a.getInt(
1924                    R.styleable.VectorDrawablePath_strokeLineCap, strokeLineCap);
1925            strokeLineJoin = a.getInt(
1926                    R.styleable.VectorDrawablePath_strokeLineJoin, strokeLineJoin);
1927            strokeMiterLimit = a.getFloat(
1928                    R.styleable.VectorDrawablePath_strokeMiterLimit, strokeMiterLimit);
1929            strokeAlpha = a.getFloat(R.styleable.VectorDrawablePath_strokeAlpha,
1930                    strokeAlpha);
1931            strokeWidth = a.getFloat(R.styleable.VectorDrawablePath_strokeWidth,
1932                    strokeWidth);
1933            trimPathEnd = a.getFloat(R.styleable.VectorDrawablePath_trimPathEnd,
1934                    trimPathEnd);
1935            trimPathOffset = a.getFloat(
1936                    R.styleable.VectorDrawablePath_trimPathOffset, trimPathOffset);
1937            trimPathStart = a.getFloat(
1938                    R.styleable.VectorDrawablePath_trimPathStart, trimPathStart);
1939            fillType = a.getInt(R.styleable.VectorDrawablePath_fillType, fillType);
1940
1941            nUpdateFullPathProperties(mNativePtr, strokeWidth, strokeColor, strokeAlpha,
1942                    fillColor, fillAlpha, trimPathStart, trimPathEnd, trimPathOffset,
1943                    strokeMiterLimit, strokeLineCap, strokeLineJoin, fillType);
1944        }
1945
1946        @Override
1947        public boolean canApplyTheme() {
1948            if (mThemeAttrs != null) {
1949                return true;
1950            }
1951
1952            boolean fillCanApplyTheme = canComplexColorApplyTheme(mFillColors);
1953            boolean strokeCanApplyTheme = canComplexColorApplyTheme(mStrokeColors);
1954            if (fillCanApplyTheme || strokeCanApplyTheme) {
1955                return true;
1956            }
1957            return false;
1958
1959        }
1960
1961        @Override
1962        public void applyTheme(Theme t) {
1963            // Resolve the theme attributes directly referred by the VectorDrawable.
1964            if (mThemeAttrs != null) {
1965                final TypedArray a = t.resolveAttributes(mThemeAttrs, R.styleable.VectorDrawablePath);
1966                updateStateFromTypedArray(a);
1967                a.recycle();
1968            }
1969
1970            // Resolve the theme attributes in-directly referred by the VectorDrawable, for example,
1971            // fillColor can refer to a color state list which itself needs to apply theme.
1972            // And this is the reason we still want to keep partial update for the path's properties.
1973            boolean fillCanApplyTheme = canComplexColorApplyTheme(mFillColors);
1974            boolean strokeCanApplyTheme = canComplexColorApplyTheme(mStrokeColors);
1975
1976            if (fillCanApplyTheme) {
1977                mFillColors = mFillColors.obtainForTheme(t);
1978                if (mFillColors instanceof GradientColor) {
1979                    nUpdateFullPathFillGradient(mNativePtr,
1980                            ((GradientColor) mFillColors).getShader().getNativeInstance());
1981                } else if (mFillColors instanceof ColorStateList) {
1982                    nSetFillColor(mNativePtr, mFillColors.getDefaultColor());
1983                }
1984            }
1985
1986            if (strokeCanApplyTheme) {
1987                mStrokeColors = mStrokeColors.obtainForTheme(t);
1988                if (mStrokeColors instanceof GradientColor) {
1989                    nUpdateFullPathStrokeGradient(mNativePtr,
1990                            ((GradientColor) mStrokeColors).getShader().getNativeInstance());
1991                } else if (mStrokeColors instanceof ColorStateList) {
1992                    nSetStrokeColor(mNativePtr, mStrokeColors.getDefaultColor());
1993                }
1994            }
1995        }
1996
1997        private boolean canComplexColorApplyTheme(ComplexColor complexColor) {
1998            return complexColor != null && complexColor.canApplyTheme();
1999        }
2000
2001        /* Setters and Getters, used by animator from AnimatedVectorDrawable. */
2002        @SuppressWarnings("unused")
2003        int getStrokeColor() {
2004            return isTreeValid() ? nGetStrokeColor(mNativePtr) : 0;
2005        }
2006
2007        @SuppressWarnings("unused")
2008        void setStrokeColor(int strokeColor) {
2009            mStrokeColors = null;
2010            if (isTreeValid()) {
2011                nSetStrokeColor(mNativePtr, strokeColor);
2012            }
2013        }
2014
2015        @SuppressWarnings("unused")
2016        float getStrokeWidth() {
2017            return isTreeValid() ? nGetStrokeWidth(mNativePtr) : 0;
2018        }
2019
2020        @SuppressWarnings("unused")
2021        void setStrokeWidth(float strokeWidth) {
2022            if (isTreeValid()) {
2023                nSetStrokeWidth(mNativePtr, strokeWidth);
2024            }
2025        }
2026
2027        @SuppressWarnings("unused")
2028        float getStrokeAlpha() {
2029            return isTreeValid() ? nGetStrokeAlpha(mNativePtr) : 0;
2030        }
2031
2032        @SuppressWarnings("unused")
2033        void setStrokeAlpha(float strokeAlpha) {
2034            if (isTreeValid()) {
2035                nSetStrokeAlpha(mNativePtr, strokeAlpha);
2036            }
2037        }
2038
2039        @SuppressWarnings("unused")
2040        int getFillColor() {
2041            return isTreeValid() ? nGetFillColor(mNativePtr) : 0;
2042        }
2043
2044        @SuppressWarnings("unused")
2045        void setFillColor(int fillColor) {
2046            mFillColors = null;
2047            if (isTreeValid()) {
2048                nSetFillColor(mNativePtr, fillColor);
2049            }
2050        }
2051
2052        @SuppressWarnings("unused")
2053        float getFillAlpha() {
2054            return isTreeValid() ? nGetFillAlpha(mNativePtr) : 0;
2055        }
2056
2057        @SuppressWarnings("unused")
2058        void setFillAlpha(float fillAlpha) {
2059            if (isTreeValid()) {
2060                nSetFillAlpha(mNativePtr, fillAlpha);
2061            }
2062        }
2063
2064        @SuppressWarnings("unused")
2065        float getTrimPathStart() {
2066            return isTreeValid() ? nGetTrimPathStart(mNativePtr) : 0;
2067        }
2068
2069        @SuppressWarnings("unused")
2070        void setTrimPathStart(float trimPathStart) {
2071            if (isTreeValid()) {
2072                nSetTrimPathStart(mNativePtr, trimPathStart);
2073            }
2074        }
2075
2076        @SuppressWarnings("unused")
2077        float getTrimPathEnd() {
2078            return isTreeValid() ? nGetTrimPathEnd(mNativePtr) : 0;
2079        }
2080
2081        @SuppressWarnings("unused")
2082        void setTrimPathEnd(float trimPathEnd) {
2083            if (isTreeValid()) {
2084                nSetTrimPathEnd(mNativePtr, trimPathEnd);
2085            }
2086        }
2087
2088        @SuppressWarnings("unused")
2089        float getTrimPathOffset() {
2090            return isTreeValid() ? nGetTrimPathOffset(mNativePtr) : 0;
2091        }
2092
2093        @SuppressWarnings("unused")
2094        void setTrimPathOffset(float trimPathOffset) {
2095            if (isTreeValid()) {
2096                nSetTrimPathOffset(mNativePtr, trimPathOffset);
2097            }
2098        }
2099    }
2100
2101    abstract static class VObject {
2102        VirtualRefBasePtr mTreePtr = null;
2103        boolean isTreeValid() {
2104            return mTreePtr != null && mTreePtr.get() != 0;
2105        }
2106        void setTree(VirtualRefBasePtr ptr) {
2107            mTreePtr = ptr;
2108        }
2109        abstract long getNativePtr();
2110        abstract void inflate(Resources r, AttributeSet attrs, Theme theme);
2111        abstract boolean canApplyTheme();
2112        abstract void applyTheme(Theme t);
2113        abstract boolean onStateChange(int[] state);
2114        abstract boolean isStateful();
2115        abstract int getNativeSize();
2116        abstract Property getProperty(String propertyName);
2117    }
2118
2119    private static native long nCreateTree(long rootGroupPtr);
2120    private static native long nCreateTreeFromCopy(long treeToCopy, long rootGroupPtr);
2121    private static native void nSetRendererViewportSize(long rendererPtr, float viewportWidth,
2122            float viewportHeight);
2123    private static native boolean nSetRootAlpha(long rendererPtr, float alpha);
2124    private static native float nGetRootAlpha(long rendererPtr);
2125    private static native void nSetAllowCaching(long rendererPtr, boolean allowCaching);
2126
2127    private static native int nDraw(long rendererPtr, long canvasWrapperPtr,
2128            long colorFilterPtr, Rect bounds, boolean needsMirroring, boolean canReuseCache);
2129    private static native long nCreateFullPath();
2130    private static native long nCreateFullPath(long nativeFullPathPtr);
2131    private static native boolean nGetFullPathProperties(long pathPtr, byte[] properties,
2132            int length);
2133
2134    private static native void nUpdateFullPathProperties(long pathPtr, float strokeWidth,
2135            int strokeColor, float strokeAlpha, int fillColor, float fillAlpha, float trimPathStart,
2136            float trimPathEnd, float trimPathOffset, float strokeMiterLimit, int strokeLineCap,
2137            int strokeLineJoin, int fillType);
2138    private static native void nUpdateFullPathFillGradient(long pathPtr, long fillGradientPtr);
2139    private static native void nUpdateFullPathStrokeGradient(long pathPtr, long strokeGradientPtr);
2140
2141    private static native long nCreateClipPath();
2142    private static native long nCreateClipPath(long clipPathPtr);
2143
2144    private static native long nCreateGroup();
2145    private static native long nCreateGroup(long groupPtr);
2146    private static native void nSetName(long nodePtr, String name);
2147    private static native boolean nGetGroupProperties(long groupPtr, float[] properties,
2148            int length);
2149    private static native void nUpdateGroupProperties(long groupPtr, float rotate, float pivotX,
2150            float pivotY, float scaleX, float scaleY, float translateX, float translateY);
2151
2152    private static native void nAddChild(long groupPtr, long nodePtr);
2153    private static native void nSetPathString(long pathPtr, String pathString, int length);
2154
2155    /**
2156     * The setters and getters below for paths and groups are here temporarily, and will be
2157     * removed once the animation in AVD is replaced with RenderNodeAnimator, in which case the
2158     * animation will modify these properties in native. By then no JNI hopping would be necessary
2159     * for VD during animation, and these setters and getters will be obsolete.
2160     */
2161    // Setters and getters during animation.
2162    private static native float nGetRotation(long groupPtr);
2163    private static native void nSetRotation(long groupPtr, float rotation);
2164    private static native float nGetPivotX(long groupPtr);
2165    private static native void nSetPivotX(long groupPtr, float pivotX);
2166    private static native float nGetPivotY(long groupPtr);
2167    private static native void nSetPivotY(long groupPtr, float pivotY);
2168    private static native float nGetScaleX(long groupPtr);
2169    private static native void nSetScaleX(long groupPtr, float scaleX);
2170    private static native float nGetScaleY(long groupPtr);
2171    private static native void nSetScaleY(long groupPtr, float scaleY);
2172    private static native float nGetTranslateX(long groupPtr);
2173    private static native void nSetTranslateX(long groupPtr, float translateX);
2174    private static native float nGetTranslateY(long groupPtr);
2175    private static native void nSetTranslateY(long groupPtr, float translateY);
2176
2177    // Setters and getters for VPath during animation.
2178    private static native void nSetPathData(long pathPtr, long pathDataPtr);
2179    private static native float nGetStrokeWidth(long pathPtr);
2180    private static native void nSetStrokeWidth(long pathPtr, float width);
2181    private static native int nGetStrokeColor(long pathPtr);
2182    private static native void nSetStrokeColor(long pathPtr, int strokeColor);
2183    private static native float nGetStrokeAlpha(long pathPtr);
2184    private static native void nSetStrokeAlpha(long pathPtr, float alpha);
2185    private static native int nGetFillColor(long pathPtr);
2186    private static native void nSetFillColor(long pathPtr, int fillColor);
2187    private static native float nGetFillAlpha(long pathPtr);
2188    private static native void nSetFillAlpha(long pathPtr, float fillAlpha);
2189    private static native float nGetTrimPathStart(long pathPtr);
2190    private static native void nSetTrimPathStart(long pathPtr, float trimPathStart);
2191    private static native float nGetTrimPathEnd(long pathPtr);
2192    private static native void nSetTrimPathEnd(long pathPtr, float trimPathEnd);
2193    private static native float nGetTrimPathOffset(long pathPtr);
2194    private static native void nSetTrimPathOffset(long pathPtr, float trimPathOffset);
2195}
2196