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