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