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