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