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