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