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