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