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