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