1/*
2 * Copyright (C) 2015 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.support.graphics.drawable;
16
17import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
18
19import android.content.res.ColorStateList;
20import android.content.res.Resources;
21import android.content.res.Resources.Theme;
22import android.content.res.TypedArray;
23import android.graphics.Bitmap;
24import android.graphics.Canvas;
25import android.graphics.Color;
26import android.graphics.ColorFilter;
27import android.graphics.Matrix;
28import android.graphics.Paint;
29import android.graphics.Path;
30import android.graphics.PathMeasure;
31import android.graphics.PixelFormat;
32import android.graphics.PorterDuff;
33import android.graphics.PorterDuff.Mode;
34import android.graphics.PorterDuffColorFilter;
35import android.graphics.Rect;
36import android.graphics.drawable.Drawable;
37import android.graphics.drawable.VectorDrawable;
38import android.os.Build;
39import android.support.annotation.DrawableRes;
40import android.support.annotation.NonNull;
41import android.support.annotation.Nullable;
42import android.support.annotation.RequiresApi;
43import android.support.annotation.RestrictTo;
44import android.support.v4.content.res.ResourcesCompat;
45import android.support.v4.content.res.TypedArrayUtils;
46import android.support.v4.graphics.PathParser;
47import android.support.v4.graphics.drawable.DrawableCompat;
48import android.support.v4.util.ArrayMap;
49import android.util.AttributeSet;
50import android.util.LayoutDirection;
51import android.util.Log;
52import android.util.Xml;
53
54import org.xmlpull.v1.XmlPullParser;
55import org.xmlpull.v1.XmlPullParserException;
56
57import java.io.IOException;
58import java.util.ArrayList;
59import java.util.Stack;
60
61/**
62 * For API 24 and above, this class is delegating to the framework's {@link VectorDrawable}.
63 * For older API version, this class lets you create a drawable based on an XML vector graphic.
64 * <p/>
65 * You can always create a VectorDrawableCompat object and use it as a Drawable by the Java API.
66 * In order to refer to VectorDrawableCompat inside a XML file,  you can use app:srcCompat attribute
67 * in AppCompat library's ImageButton or ImageView.
68 * <p/>
69 * <strong>Note:</strong> To optimize for the re-drawing performance, one bitmap cache is created
70 * for each VectorDrawableCompat. Therefore, referring to the same VectorDrawableCompat means
71 * sharing the same bitmap cache. If these references don't agree upon on the same size, the bitmap
72 * will be recreated and redrawn every time size is changed. In other words, if a VectorDrawable is
73 * used for different sizes, it is more efficient to create multiple VectorDrawables, one for each
74 * size.
75 * <p/>
76 * VectorDrawableCompat can be defined in an XML file with the <code>&lt;vector></code> element.
77 * <p/>
78 * The VectorDrawableCompat has the following elements:
79 * <p/>
80 * <dt><code>&lt;vector></code></dt>
81 * <dl>
82 * <dd>Used to define a vector drawable
83 * <dl>
84 * <dt><code>android:name</code></dt>
85 * <dd>Defines the name of this vector drawable.</dd>
86 * <dt><code>android:width</code></dt>
87 * <dd>Used to define the intrinsic width of the drawable.
88 * This support all the dimension units, normally specified with dp.</dd>
89 * <dt><code>android:height</code></dt>
90 * <dd>Used to define the intrinsic height the drawable.
91 * This support all the dimension units, normally specified with dp.</dd>
92 * <dt><code>android:viewportWidth</code></dt>
93 * <dd>Used to define the width of the viewport space. Viewport is basically
94 * the virtual canvas where the paths are drawn on.</dd>
95 * <dt><code>android:viewportHeight</code></dt>
96 * <dd>Used to define the height of the viewport space. Viewport is basically
97 * the virtual canvas where the paths are drawn on.</dd>
98 * <dt><code>android:tint</code></dt>
99 * <dd>The color to apply to the drawable as a tint. By default, no tint is applied.</dd>
100 * <dt><code>android:tintMode</code></dt>
101 * <dd>The Porter-Duff blending mode for the tint color. Default is src_in.</dd>
102 * <dt><code>android:autoMirrored</code></dt>
103 * <dd>Indicates if the drawable needs to be mirrored when its layout direction is
104 * RTL (right-to-left). Default is false.</dd>
105 * <dt><code>android:alpha</code></dt>
106 * <dd>The opacity of this drawable. Default is 1.</dd>
107 * </dl></dd>
108 * </dl>
109 *
110 * <dl>
111 * <dt><code>&lt;group></code></dt>
112 * <dd>Defines a group of paths or subgroups, plus transformation information.
113 * The transformations are defined in the same coordinates as the viewport.
114 * And the transformations are applied in the order of scale, rotate then translate.
115 * <dl>
116 * <dt><code>android:name</code></dt>
117 * <dd>Defines the name of the group.</dd>
118 * <dt><code>android:rotation</code></dt>
119 * <dd>The degrees of rotation of the group. Default is 0.</dd>
120 * <dt><code>android:pivotX</code></dt>
121 * <dd>The X coordinate of the pivot for the scale and rotation of the group.
122 * This is defined in the viewport space. Default is 0.</dd>
123 * <dt><code>android:pivotY</code></dt>
124 * <dd>The Y coordinate of the pivot for the scale and rotation of the group.
125 * This is defined in the viewport space. Default is 0.</dd>
126 * <dt><code>android:scaleX</code></dt>
127 * <dd>The amount of scale on the X Coordinate. Default is 1.</dd>
128 * <dt><code>android:scaleY</code></dt>
129 * <dd>The amount of scale on the Y coordinate. Default is 1.</dd>
130 * <dt><code>android:translateX</code></dt>
131 * <dd>The amount of translation on the X coordinate.
132 * This is defined in the viewport space. Default is 0.</dd>
133 * <dt><code>android:translateY</code></dt>
134 * <dd>The amount of translation on the Y coordinate.
135 * This is defined in the viewport space. Default is 0.</dd>
136 * </dl></dd>
137 * </dl>
138 *
139 * <dl>
140 * <dt><code>&lt;path></code></dt>
141 * <dd>Defines paths to be drawn.
142 * <dl>
143 * <dt><code>android:name</code></dt>
144 * <dd>Defines the name of the path.</dd>
145 * <dt><code>android:pathData</code></dt>
146 * <dd>Defines path data using exactly same format as "d" attribute
147 * in the SVG's path data. This is defined in the viewport space.</dd>
148 * <dt><code>android:fillColor</code></dt>
149 * <dd>Specifies the color used to fill the path.
150 * If this property is animated, any value set by the animation will override the original value.
151 * No path fill is drawn if this property is not specified.</dd>
152 * <dt><code>android:strokeColor</code></dt>
153 * <dd>Specifies the color used to draw the path outline.
154 * If this property is animated, any value set by the animation will override the original value.
155 * No path outline is drawn if this property is not specified.</dd>
156 * <dt><code>android:strokeWidth</code></dt>
157 * <dd>The width a path stroke. Default is 0.</dd>
158 * <dt><code>android:strokeAlpha</code></dt>
159 * <dd>The opacity of a path stroke. Default is 1.</dd>
160 * <dt><code>android:fillAlpha</code></dt>
161 * <dd>The opacity to fill the path with. Default is 1.</dd>
162 * <dt><code>android:trimPathStart</code></dt>
163 * <dd>The fraction of the path to trim from the start, in the range from 0 to 1. Default is 0.</dd>
164 * <dt><code>android:trimPathEnd</code></dt>
165 * <dd>The fraction of the path to trim from the end, in the range from 0 to 1. Default is 1.</dd>
166 * <dt><code>android:trimPathOffset</code></dt>
167 * <dd>Shift trim region (allows showed region to include the start and end), in the range
168 * from 0 to 1. Default is 0.</dd>
169 * <dt><code>android:strokeLineCap</code></dt>
170 * <dd>Sets the linecap for a stroked path: butt, round, square. Default is butt.</dd>
171 * <dt><code>android:strokeLineJoin</code></dt>
172 * <dd>Sets the lineJoin for a stroked path: miter,round,bevel. Default is miter.</dd>
173 * <dt><code>android:strokeMiterLimit</code></dt>
174 * <dd>Sets the Miter limit for a stroked path. Default is 4.</dd>
175 * </dl></dd>
176 * </dl>
177 *
178 * <dl>
179 * <dt><code>&lt;clip-path></code></dt>
180 * <dd>Defines path to be the current clip. Note that the clip path only apply to
181 * the current group and its children.
182 * <dl>
183 * <dt><code>android:name</code></dt>
184 * <dd>Defines the name of the clip path.</dd>
185 * <dt><code>android:pathData</code></dt>
186 * <dd>Defines clip path using the same format as "d" attribute
187 * in the SVG's path data.</dd>
188 * </dl></dd>
189 * </dl>
190 * <p/>
191 * Note that theme attributes in XML file are supported through
192 * <code>{@link #inflate(Resources, XmlPullParser, AttributeSet, Theme)}</code>.
193 */
194public class VectorDrawableCompat extends VectorDrawableCommon {
195    static final String LOGTAG = "VectorDrawableCompat";
196
197    static final PorterDuff.Mode DEFAULT_TINT_MODE = PorterDuff.Mode.SRC_IN;
198
199    private static final String SHAPE_CLIP_PATH = "clip-path";
200    private static final String SHAPE_GROUP = "group";
201    private static final String SHAPE_PATH = "path";
202    private static final String SHAPE_VECTOR = "vector";
203
204    private static final int LINECAP_BUTT = 0;
205    private static final int LINECAP_ROUND = 1;
206    private static final int LINECAP_SQUARE = 2;
207
208    private static final int LINEJOIN_MITER = 0;
209    private static final int LINEJOIN_ROUND = 1;
210    private static final int LINEJOIN_BEVEL = 2;
211
212    // Cap the bitmap size, such that it won't hurt the performance too much
213    // and it won't crash due to a very large scale.
214    // The drawable will look blurry above this size.
215    private static final int MAX_CACHED_BITMAP_SIZE = 2048;
216
217    private static final boolean DBG_VECTOR_DRAWABLE = false;
218
219    private VectorDrawableCompatState mVectorState;
220
221    private PorterDuffColorFilter mTintFilter;
222    private ColorFilter mColorFilter;
223
224    private boolean mMutated;
225
226    // AnimatedVectorDrawable needs to turn off the cache all the time, otherwise,
227    // caching the bitmap by default is allowed.
228    private boolean mAllowCaching = true;
229
230    // The Constant state associated with the <code>mDelegateDrawable</code>.
231    private ConstantState mCachedConstantStateDelegate;
232
233    // Temp variable, only for saving "new" operation at the draw() time.
234    private final float[] mTmpFloats = new float[9];
235    private final Matrix mTmpMatrix = new Matrix();
236    private final Rect mTmpBounds = new Rect();
237
238    VectorDrawableCompat() {
239        mVectorState = new VectorDrawableCompatState();
240    }
241
242    VectorDrawableCompat(@NonNull VectorDrawableCompatState state) {
243        mVectorState = state;
244        mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
245    }
246
247    @Override
248    public Drawable mutate() {
249        if (mDelegateDrawable != null) {
250            mDelegateDrawable.mutate();
251            return this;
252        }
253
254        if (!mMutated && super.mutate() == this) {
255            mVectorState = new VectorDrawableCompatState(mVectorState);
256            mMutated = true;
257        }
258        return this;
259    }
260
261    Object getTargetByName(String name) {
262        return mVectorState.mVPathRenderer.mVGTargetsMap.get(name);
263    }
264
265    @Override
266    public ConstantState getConstantState() {
267        if (mDelegateDrawable != null && Build.VERSION.SDK_INT >= 24) {
268            // Such that the configuration can be refreshed.
269            return new VectorDrawableDelegateState(mDelegateDrawable.getConstantState());
270        }
271        mVectorState.mChangingConfigurations = getChangingConfigurations();
272        return mVectorState;
273    }
274
275    @Override
276    public void draw(Canvas canvas) {
277        if (mDelegateDrawable != null) {
278            mDelegateDrawable.draw(canvas);
279            return;
280        }
281        // We will offset the bounds for drawBitmap, so copyBounds() here instead
282        // of getBounds().
283        copyBounds(mTmpBounds);
284        if (mTmpBounds.width() <= 0 || mTmpBounds.height() <= 0) {
285            // Nothing to draw
286            return;
287        }
288
289        // Color filters always override tint filters.
290        final ColorFilter colorFilter = (mColorFilter == null ? mTintFilter : mColorFilter);
291
292        // The imageView can scale the canvas in different ways, in order to
293        // avoid blurry scaling, we have to draw into a bitmap with exact pixel
294        // size first. This bitmap size is determined by the bounds and the
295        // canvas scale.
296        canvas.getMatrix(mTmpMatrix);
297        mTmpMatrix.getValues(mTmpFloats);
298        float canvasScaleX = Math.abs(mTmpFloats[Matrix.MSCALE_X]);
299        float canvasScaleY = Math.abs(mTmpFloats[Matrix.MSCALE_Y]);
300
301        float canvasSkewX = Math.abs(mTmpFloats[Matrix.MSKEW_X]);
302        float canvasSkewY = Math.abs(mTmpFloats[Matrix.MSKEW_Y]);
303
304        // When there is any rotation / skew, then the scale value is not valid.
305        if (canvasSkewX != 0 || canvasSkewY != 0) {
306            canvasScaleX = 1.0f;
307            canvasScaleY = 1.0f;
308        }
309
310        int scaledWidth = (int) (mTmpBounds.width() * canvasScaleX);
311        int scaledHeight = (int) (mTmpBounds.height() * canvasScaleY);
312        scaledWidth = Math.min(MAX_CACHED_BITMAP_SIZE, scaledWidth);
313        scaledHeight = Math.min(MAX_CACHED_BITMAP_SIZE, scaledHeight);
314
315        if (scaledWidth <= 0 || scaledHeight <= 0) {
316            return;
317        }
318
319        final int saveCount = canvas.save();
320        canvas.translate(mTmpBounds.left, mTmpBounds.top);
321
322        // Handle RTL mirroring.
323        final boolean needMirroring = needMirroring();
324        if (needMirroring) {
325            canvas.translate(mTmpBounds.width(), 0);
326            canvas.scale(-1.0f, 1.0f);
327        }
328
329        // At this point, canvas has been translated to the right position.
330        // And we use this bound for the destination rect for the drawBitmap, so
331        // we offset to (0, 0);
332        mTmpBounds.offsetTo(0, 0);
333
334        mVectorState.createCachedBitmapIfNeeded(scaledWidth, scaledHeight);
335        if (!mAllowCaching) {
336            mVectorState.updateCachedBitmap(scaledWidth, scaledHeight);
337        } else {
338            if (!mVectorState.canReuseCache()) {
339                mVectorState.updateCachedBitmap(scaledWidth, scaledHeight);
340                mVectorState.updateCacheStates();
341            }
342        }
343        mVectorState.drawCachedBitmapWithRootAlpha(canvas, colorFilter, mTmpBounds);
344        canvas.restoreToCount(saveCount);
345    }
346
347    @Override
348    public int getAlpha() {
349        if (mDelegateDrawable != null) {
350            return DrawableCompat.getAlpha(mDelegateDrawable);
351        }
352
353        return mVectorState.mVPathRenderer.getRootAlpha();
354    }
355
356    @Override
357    public void setAlpha(int alpha) {
358        if (mDelegateDrawable != null) {
359            mDelegateDrawable.setAlpha(alpha);
360            return;
361        }
362
363        if (mVectorState.mVPathRenderer.getRootAlpha() != alpha) {
364            mVectorState.mVPathRenderer.setRootAlpha(alpha);
365            invalidateSelf();
366        }
367    }
368
369    @Override
370    public void setColorFilter(ColorFilter colorFilter) {
371        if (mDelegateDrawable != null) {
372            mDelegateDrawable.setColorFilter(colorFilter);
373            return;
374        }
375
376        mColorFilter = colorFilter;
377        invalidateSelf();
378    }
379
380    /**
381     * Ensures the tint filter is consistent with the current tint color and
382     * mode.
383     */
384    PorterDuffColorFilter updateTintFilter(PorterDuffColorFilter tintFilter, ColorStateList tint,
385                                           PorterDuff.Mode tintMode) {
386        if (tint == null || tintMode == null) {
387            return null;
388        }
389        // setMode, setColor of PorterDuffColorFilter are not public method in SDK v7.
390        // Therefore we create a new one all the time here. Don't expect this is called often.
391        final int color = tint.getColorForState(getState(), Color.TRANSPARENT);
392        return new PorterDuffColorFilter(color, tintMode);
393    }
394
395    @Override
396    public void setTint(int tint) {
397        if (mDelegateDrawable != null) {
398            DrawableCompat.setTint(mDelegateDrawable, tint);
399            return;
400        }
401
402        setTintList(ColorStateList.valueOf(tint));
403    }
404
405    @Override
406    public void setTintList(ColorStateList tint) {
407        if (mDelegateDrawable != null) {
408            DrawableCompat.setTintList(mDelegateDrawable, tint);
409            return;
410        }
411
412        final VectorDrawableCompatState state = mVectorState;
413        if (state.mTint != tint) {
414            state.mTint = tint;
415            mTintFilter = updateTintFilter(mTintFilter, tint, state.mTintMode);
416            invalidateSelf();
417        }
418    }
419
420    @Override
421    public void setTintMode(Mode tintMode) {
422        if (mDelegateDrawable != null) {
423            DrawableCompat.setTintMode(mDelegateDrawable, tintMode);
424            return;
425        }
426
427        final VectorDrawableCompatState state = mVectorState;
428        if (state.mTintMode != tintMode) {
429            state.mTintMode = tintMode;
430            mTintFilter = updateTintFilter(mTintFilter, state.mTint, tintMode);
431            invalidateSelf();
432        }
433    }
434
435    @Override
436    public boolean isStateful() {
437        if (mDelegateDrawable != null) {
438            return mDelegateDrawable.isStateful();
439        }
440
441        return super.isStateful() || (mVectorState != null && mVectorState.mTint != null
442                && mVectorState.mTint.isStateful());
443    }
444
445    @Override
446    protected boolean onStateChange(int[] stateSet) {
447        if (mDelegateDrawable != null) {
448            return mDelegateDrawable.setState(stateSet);
449        }
450
451        final VectorDrawableCompatState state = mVectorState;
452        if (state.mTint != null && state.mTintMode != null) {
453            mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
454            invalidateSelf();
455            return true;
456        }
457        return false;
458    }
459
460    @Override
461    public int getOpacity() {
462        if (mDelegateDrawable != null) {
463            return mDelegateDrawable.getOpacity();
464        }
465
466        return PixelFormat.TRANSLUCENT;
467    }
468
469    @Override
470    public int getIntrinsicWidth() {
471        if (mDelegateDrawable != null) {
472            return mDelegateDrawable.getIntrinsicWidth();
473        }
474
475        return (int) mVectorState.mVPathRenderer.mBaseWidth;
476    }
477
478    @Override
479    public int getIntrinsicHeight() {
480        if (mDelegateDrawable != null) {
481            return mDelegateDrawable.getIntrinsicHeight();
482        }
483
484        return (int) mVectorState.mVPathRenderer.mBaseHeight;
485    }
486
487    // Don't support re-applying themes. The initial theme loading is working.
488    @Override
489    public boolean canApplyTheme() {
490        if (mDelegateDrawable != null) {
491            DrawableCompat.canApplyTheme(mDelegateDrawable);
492        }
493
494        return false;
495    }
496
497    @Override
498    public boolean isAutoMirrored() {
499        if (mDelegateDrawable != null) {
500            return DrawableCompat.isAutoMirrored(mDelegateDrawable);
501        }
502        return mVectorState.mAutoMirrored;
503    }
504
505    @Override
506    public void setAutoMirrored(boolean mirrored) {
507        if (mDelegateDrawable != null) {
508            DrawableCompat.setAutoMirrored(mDelegateDrawable, mirrored);
509            return;
510        }
511        mVectorState.mAutoMirrored = mirrored;
512    }
513    /**
514     * The size of a pixel when scaled from the intrinsic dimension to the viewport dimension. This
515     * is used to calculate the path animation accuracy.
516     *
517     * @hide
518     */
519    @RestrictTo(LIBRARY_GROUP)
520    public float getPixelSize() {
521        if (mVectorState == null || mVectorState.mVPathRenderer == null
522                || mVectorState.mVPathRenderer.mBaseWidth == 0
523                || mVectorState.mVPathRenderer.mBaseHeight == 0
524                || mVectorState.mVPathRenderer.mViewportHeight == 0
525                || mVectorState.mVPathRenderer.mViewportWidth == 0) {
526            return 1; // fall back to 1:1 pixel mapping.
527        }
528        float intrinsicWidth = mVectorState.mVPathRenderer.mBaseWidth;
529        float intrinsicHeight = mVectorState.mVPathRenderer.mBaseHeight;
530        float viewportWidth = mVectorState.mVPathRenderer.mViewportWidth;
531        float viewportHeight = mVectorState.mVPathRenderer.mViewportHeight;
532        float scaleX = viewportWidth / intrinsicWidth;
533        float scaleY = viewportHeight / intrinsicHeight;
534        return Math.min(scaleX, scaleY);
535    }
536
537    /**
538     * Create a VectorDrawableCompat object.
539     *
540     * @param res   the resources.
541     * @param resId the resource ID for VectorDrawableCompat object.
542     * @param theme the theme of this vector drawable, it can be null.
543     * @return a new VectorDrawableCompat or null if parsing error is found.
544     */
545    @Nullable
546    public static VectorDrawableCompat create(@NonNull Resources res, @DrawableRes int resId,
547                                              @Nullable Theme theme) {
548        if (Build.VERSION.SDK_INT >= 24) {
549            final VectorDrawableCompat drawable = new VectorDrawableCompat();
550            drawable.mDelegateDrawable = ResourcesCompat.getDrawable(res, resId, theme);
551            drawable.mCachedConstantStateDelegate = new VectorDrawableDelegateState(
552                    drawable.mDelegateDrawable.getConstantState());
553            return drawable;
554        }
555
556        try {
557            final XmlPullParser parser = res.getXml(resId);
558            final AttributeSet attrs = Xml.asAttributeSet(parser);
559            int type;
560            while ((type = parser.next()) != XmlPullParser.START_TAG &&
561                    type != XmlPullParser.END_DOCUMENT) {
562                // Empty loop
563            }
564            if (type != XmlPullParser.START_TAG) {
565                throw new XmlPullParserException("No start tag found");
566            }
567            return createFromXmlInner(res, parser, attrs, theme);
568        } catch (XmlPullParserException e) {
569            Log.e(LOGTAG, "parser error", e);
570        } catch (IOException e) {
571            Log.e(LOGTAG, "parser error", e);
572        }
573        return null;
574    }
575
576    /**
577     * Create a VectorDrawableCompat from inside an XML document using an optional
578     * {@link Theme}. Called on a parser positioned at a tag in an XML
579     * document, tries to create a Drawable from that tag. Returns {@code null}
580     * if the tag is not a valid drawable.
581     */
582    public static VectorDrawableCompat createFromXmlInner(Resources r, XmlPullParser parser,
583            AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException {
584        final VectorDrawableCompat drawable = new VectorDrawableCompat();
585        drawable.inflate(r, parser, attrs, theme);
586        return drawable;
587    }
588
589    static int applyAlpha(int color, float alpha) {
590        int alphaBytes = Color.alpha(color);
591        color &= 0x00FFFFFF;
592        color |= ((int) (alphaBytes * alpha)) << 24;
593        return color;
594    }
595
596    @Override
597    public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs)
598            throws XmlPullParserException, IOException {
599        if (mDelegateDrawable != null) {
600            mDelegateDrawable.inflate(res, parser, attrs);
601            return;
602        }
603
604        inflate(res, parser, attrs, null);
605    }
606
607    @Override
608    public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)
609            throws XmlPullParserException, IOException {
610        if (mDelegateDrawable != null) {
611            DrawableCompat.inflate(mDelegateDrawable, res, parser, attrs, theme);
612            return;
613        }
614
615        final VectorDrawableCompatState state = mVectorState;
616        final VPathRenderer pathRenderer = new VPathRenderer();
617        state.mVPathRenderer = pathRenderer;
618
619        final TypedArray a = TypedArrayUtils.obtainAttributes(res, theme, attrs,
620                AndroidResources.STYLEABLE_VECTOR_DRAWABLE_TYPE_ARRAY);
621
622        updateStateFromTypedArray(a, parser);
623        a.recycle();
624        state.mChangingConfigurations = getChangingConfigurations();
625        state.mCacheDirty = true;
626        inflateInternal(res, parser, attrs, theme);
627
628        mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
629    }
630
631
632    /**
633     * Parses a {@link android.graphics.PorterDuff.Mode} from a tintMode
634     * attribute's enum value.
635     */
636    private static PorterDuff.Mode parseTintModeCompat(int value, Mode defaultMode) {
637        switch (value) {
638            case 3:
639                return Mode.SRC_OVER;
640            case 5:
641                return Mode.SRC_IN;
642            case 9:
643                return Mode.SRC_ATOP;
644            case 14:
645                return Mode.MULTIPLY;
646            case 15:
647                return Mode.SCREEN;
648            case 16:
649                if (Build.VERSION.SDK_INT >= 11) {
650                    return Mode.ADD;
651                } else {
652                    return defaultMode;
653                }
654            default:
655                return defaultMode;
656        }
657    }
658
659    private void updateStateFromTypedArray(TypedArray a, XmlPullParser parser)
660            throws XmlPullParserException {
661        final VectorDrawableCompatState state = mVectorState;
662        final VPathRenderer pathRenderer = state.mVPathRenderer;
663
664        // Account for any configuration changes.
665        // state.mChangingConfigurations |= Utils.getChangingConfigurations(a);
666
667        final int mode = TypedArrayUtils.getNamedInt(a, parser, "tintMode",
668                AndroidResources.STYLEABLE_VECTOR_DRAWABLE_TINT_MODE, -1);
669        state.mTintMode = parseTintModeCompat(mode, Mode.SRC_IN);
670
671        final ColorStateList tint =
672                a.getColorStateList(AndroidResources.STYLEABLE_VECTOR_DRAWABLE_TINT);
673        if (tint != null) {
674            state.mTint = tint;
675        }
676
677        state.mAutoMirrored = TypedArrayUtils.getNamedBoolean(a, parser, "autoMirrored",
678                AndroidResources.STYLEABLE_VECTOR_DRAWABLE_AUTO_MIRRORED, state.mAutoMirrored);
679
680        pathRenderer.mViewportWidth = TypedArrayUtils.getNamedFloat(a, parser, "viewportWidth",
681                AndroidResources.STYLEABLE_VECTOR_DRAWABLE_VIEWPORT_WIDTH,
682                pathRenderer.mViewportWidth);
683
684        pathRenderer.mViewportHeight = TypedArrayUtils.getNamedFloat(a, parser, "viewportHeight",
685                AndroidResources.STYLEABLE_VECTOR_DRAWABLE_VIEWPORT_HEIGHT,
686                pathRenderer.mViewportHeight);
687
688        if (pathRenderer.mViewportWidth <= 0) {
689            throw new XmlPullParserException(a.getPositionDescription() +
690                    "<vector> tag requires viewportWidth > 0");
691        } else if (pathRenderer.mViewportHeight <= 0) {
692            throw new XmlPullParserException(a.getPositionDescription() +
693                    "<vector> tag requires viewportHeight > 0");
694        }
695
696        pathRenderer.mBaseWidth = a.getDimension(
697                AndroidResources.STYLEABLE_VECTOR_DRAWABLE_WIDTH, pathRenderer.mBaseWidth);
698        pathRenderer.mBaseHeight = a.getDimension(
699                AndroidResources.STYLEABLE_VECTOR_DRAWABLE_HEIGHT, pathRenderer.mBaseHeight);
700        if (pathRenderer.mBaseWidth <= 0) {
701            throw new XmlPullParserException(a.getPositionDescription() +
702                    "<vector> tag requires width > 0");
703        } else if (pathRenderer.mBaseHeight <= 0) {
704            throw new XmlPullParserException(a.getPositionDescription() +
705                    "<vector> tag requires height > 0");
706        }
707
708        // shown up from API 11.
709        final float alphaInFloat = TypedArrayUtils.getNamedFloat(a, parser, "alpha",
710                AndroidResources.STYLEABLE_VECTOR_DRAWABLE_ALPHA, pathRenderer.getAlpha());
711        pathRenderer.setAlpha(alphaInFloat);
712
713        final String name = a.getString(AndroidResources.STYLEABLE_VECTOR_DRAWABLE_NAME);
714        if (name != null) {
715            pathRenderer.mRootName = name;
716            pathRenderer.mVGTargetsMap.put(name, pathRenderer);
717        }
718    }
719
720    private void inflateInternal(Resources res, XmlPullParser parser, AttributeSet attrs,
721                                 Theme theme) throws XmlPullParserException, IOException {
722        final VectorDrawableCompatState state = mVectorState;
723        final VPathRenderer pathRenderer = state.mVPathRenderer;
724        boolean noPathTag = true;
725
726        // Use a stack to help to build the group tree.
727        // The top of the stack is always the current group.
728        final Stack<VGroup> groupStack = new Stack<VGroup>();
729        groupStack.push(pathRenderer.mRootGroup);
730
731        int eventType = parser.getEventType();
732        final int innerDepth = parser.getDepth() + 1;
733
734        // Parse everything until the end of the vector element.
735        while (eventType != XmlPullParser.END_DOCUMENT
736                && (parser.getDepth() >= innerDepth || eventType != XmlPullParser.END_TAG)) {
737            if (eventType == XmlPullParser.START_TAG) {
738                final String tagName = parser.getName();
739                final VGroup currentGroup = groupStack.peek();
740                if (SHAPE_PATH.equals(tagName)) {
741                    final VFullPath path = new VFullPath();
742                    path.inflate(res, attrs, theme, parser);
743                    currentGroup.mChildren.add(path);
744                    if (path.getPathName() != null) {
745                        pathRenderer.mVGTargetsMap.put(path.getPathName(), path);
746                    }
747                    noPathTag = false;
748                    state.mChangingConfigurations |= path.mChangingConfigurations;
749                } else if (SHAPE_CLIP_PATH.equals(tagName)) {
750                    final VClipPath path = new VClipPath();
751                    path.inflate(res, attrs, theme, parser);
752                    currentGroup.mChildren.add(path);
753                    if (path.getPathName() != null) {
754                        pathRenderer.mVGTargetsMap.put(path.getPathName(), path);
755                    }
756                    state.mChangingConfigurations |= path.mChangingConfigurations;
757                } else if (SHAPE_GROUP.equals(tagName)) {
758                    VGroup newChildGroup = new VGroup();
759                    newChildGroup.inflate(res, attrs, theme, parser);
760                    currentGroup.mChildren.add(newChildGroup);
761                    groupStack.push(newChildGroup);
762                    if (newChildGroup.getGroupName() != null) {
763                        pathRenderer.mVGTargetsMap.put(newChildGroup.getGroupName(),
764                                newChildGroup);
765                    }
766                    state.mChangingConfigurations |= newChildGroup.mChangingConfigurations;
767                }
768            } else if (eventType == XmlPullParser.END_TAG) {
769                final String tagName = parser.getName();
770                if (SHAPE_GROUP.equals(tagName)) {
771                    groupStack.pop();
772                }
773            }
774            eventType = parser.next();
775        }
776
777        // Print the tree out for debug.
778        if (DBG_VECTOR_DRAWABLE) {
779            printGroupTree(pathRenderer.mRootGroup, 0);
780        }
781
782        if (noPathTag) {
783            final StringBuffer tag = new StringBuffer();
784
785            if (tag.length() > 0) {
786                tag.append(" or ");
787            }
788            tag.append(SHAPE_PATH);
789
790            throw new XmlPullParserException("no " + tag + " defined");
791        }
792    }
793
794    private void printGroupTree(VGroup currentGroup, int level) {
795        String indent = "";
796        for (int i = 0; i < level; i++) {
797            indent += "    ";
798        }
799        // Print the current node
800        Log.v(LOGTAG, indent + "current group is :" + currentGroup.getGroupName()
801                + " rotation is " + currentGroup.mRotate);
802        Log.v(LOGTAG, indent + "matrix is :" + currentGroup.getLocalMatrix().toString());
803        // Then print all the children groups
804        for (int i = 0; i < currentGroup.mChildren.size(); i++) {
805            Object child = currentGroup.mChildren.get(i);
806            if (child instanceof VGroup) {
807                printGroupTree((VGroup) child, level + 1);
808            } else {
809                ((VPath) child).printVPath(level + 1);
810            }
811        }
812    }
813
814    void setAllowCaching(boolean allowCaching) {
815        mAllowCaching = allowCaching;
816    }
817
818    // We don't support RTL auto mirroring since the getLayoutDirection() is for API 17+.
819    private boolean needMirroring() {
820        if (Build.VERSION.SDK_INT >= 17) {
821            return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL;
822        } else {
823            return false;
824        }
825    }
826
827    // Extra override functions for delegation for SDK >= 7.
828    @Override
829    protected void onBoundsChange(Rect bounds) {
830        if (mDelegateDrawable != null) {
831            mDelegateDrawable.setBounds(bounds);
832        }
833    }
834
835    @Override
836    public int getChangingConfigurations() {
837        if (mDelegateDrawable != null) {
838            return mDelegateDrawable.getChangingConfigurations();
839        }
840        return super.getChangingConfigurations() | mVectorState.getChangingConfigurations();
841    }
842
843    @Override
844    public void invalidateSelf() {
845        if (mDelegateDrawable != null) {
846            mDelegateDrawable.invalidateSelf();
847            return;
848        }
849        super.invalidateSelf();
850    }
851
852    @Override
853    public void scheduleSelf(Runnable what, long when) {
854        if (mDelegateDrawable != null) {
855            mDelegateDrawable.scheduleSelf(what, when);
856            return;
857        }
858        super.scheduleSelf(what, when);
859    }
860
861    @Override
862    public boolean setVisible(boolean visible, boolean restart) {
863        if (mDelegateDrawable != null) {
864            return mDelegateDrawable.setVisible(visible, restart);
865        }
866        return super.setVisible(visible, restart);
867    }
868
869    @Override
870    public void unscheduleSelf(Runnable what) {
871        if (mDelegateDrawable != null) {
872            mDelegateDrawable.unscheduleSelf(what);
873            return;
874        }
875        super.unscheduleSelf(what);
876    }
877
878    /**
879     * Constant state for delegating the creating drawable job for SDK >= 24.
880     * Instead of creating a VectorDrawable, create a VectorDrawableCompat instance which contains
881     * a delegated VectorDrawable instance.
882     */
883    @RequiresApi(24)
884    private static class VectorDrawableDelegateState extends ConstantState {
885        private final ConstantState mDelegateState;
886
887        public VectorDrawableDelegateState(ConstantState state) {
888            mDelegateState = state;
889        }
890
891        @Override
892        public Drawable newDrawable() {
893            VectorDrawableCompat drawableCompat = new VectorDrawableCompat();
894            drawableCompat.mDelegateDrawable = (VectorDrawable) mDelegateState.newDrawable();
895            return drawableCompat;
896        }
897
898        @Override
899        public Drawable newDrawable(Resources res) {
900            VectorDrawableCompat drawableCompat = new VectorDrawableCompat();
901            drawableCompat.mDelegateDrawable = (VectorDrawable) mDelegateState.newDrawable(res);
902            return drawableCompat;
903        }
904
905        @Override
906        public Drawable newDrawable(Resources res, Theme theme) {
907            VectorDrawableCompat drawableCompat = new VectorDrawableCompat();
908            drawableCompat.mDelegateDrawable =
909                    (VectorDrawable) mDelegateState.newDrawable(res, theme);
910            return drawableCompat;
911        }
912
913        @Override
914        public boolean canApplyTheme() {
915            return mDelegateState.canApplyTheme();
916        }
917
918        @Override
919        public int getChangingConfigurations() {
920            return mDelegateState.getChangingConfigurations();
921        }
922    }
923
924    private static class VectorDrawableCompatState extends ConstantState {
925        int mChangingConfigurations;
926        VPathRenderer mVPathRenderer;
927        ColorStateList mTint = null;
928        Mode mTintMode = DEFAULT_TINT_MODE;
929        boolean mAutoMirrored;
930
931        Bitmap mCachedBitmap;
932        int[] mCachedThemeAttrs;
933        ColorStateList mCachedTint;
934        Mode mCachedTintMode;
935        int mCachedRootAlpha;
936        boolean mCachedAutoMirrored;
937        boolean mCacheDirty;
938
939        /**
940         * Temporary paint object used to draw cached bitmaps.
941         */
942        Paint mTempPaint;
943
944        // Deep copy for mutate() or implicitly mutate.
945        public VectorDrawableCompatState(VectorDrawableCompatState copy) {
946            if (copy != null) {
947                mChangingConfigurations = copy.mChangingConfigurations;
948                mVPathRenderer = new VPathRenderer(copy.mVPathRenderer);
949                if (copy.mVPathRenderer.mFillPaint != null) {
950                    mVPathRenderer.mFillPaint = new Paint(copy.mVPathRenderer.mFillPaint);
951                }
952                if (copy.mVPathRenderer.mStrokePaint != null) {
953                    mVPathRenderer.mStrokePaint = new Paint(copy.mVPathRenderer.mStrokePaint);
954                }
955                mTint = copy.mTint;
956                mTintMode = copy.mTintMode;
957                mAutoMirrored = copy.mAutoMirrored;
958            }
959        }
960
961        public void drawCachedBitmapWithRootAlpha(Canvas canvas, ColorFilter filter,
962                                                  Rect originalBounds) {
963            // The bitmap's size is the same as the bounds.
964            final Paint p = getPaint(filter);
965            canvas.drawBitmap(mCachedBitmap, null, originalBounds, p);
966        }
967
968        public boolean hasTranslucentRoot() {
969            return mVPathRenderer.getRootAlpha() < 255;
970        }
971
972        /**
973         * @return null when there is no need for alpha paint.
974         */
975        public Paint getPaint(ColorFilter filter) {
976            if (!hasTranslucentRoot() && filter == null) {
977                return null;
978            }
979
980            if (mTempPaint == null) {
981                mTempPaint = new Paint();
982                mTempPaint.setFilterBitmap(true);
983            }
984            mTempPaint.setAlpha(mVPathRenderer.getRootAlpha());
985            mTempPaint.setColorFilter(filter);
986            return mTempPaint;
987        }
988
989        public void updateCachedBitmap(int width, int height) {
990            mCachedBitmap.eraseColor(Color.TRANSPARENT);
991            Canvas tmpCanvas = new Canvas(mCachedBitmap);
992            mVPathRenderer.draw(tmpCanvas, width, height, null);
993        }
994
995        public void createCachedBitmapIfNeeded(int width, int height) {
996            if (mCachedBitmap == null || !canReuseBitmap(width, height)) {
997                mCachedBitmap = Bitmap.createBitmap(width, height,
998                        Bitmap.Config.ARGB_8888);
999                mCacheDirty = true;
1000            }
1001
1002        }
1003
1004        public boolean canReuseBitmap(int width, int height) {
1005            if (width == mCachedBitmap.getWidth()
1006                    && height == mCachedBitmap.getHeight()) {
1007                return true;
1008            }
1009            return false;
1010        }
1011
1012        public boolean canReuseCache() {
1013            if (!mCacheDirty
1014                    && mCachedTint == mTint
1015                    && mCachedTintMode == mTintMode
1016                    && mCachedAutoMirrored == mAutoMirrored
1017                    && mCachedRootAlpha == mVPathRenderer.getRootAlpha()) {
1018                return true;
1019            }
1020            return false;
1021        }
1022
1023        public void updateCacheStates() {
1024            // Use shallow copy here and shallow comparison in canReuseCache(),
1025            // likely hit cache miss more, but practically not much difference.
1026            mCachedTint = mTint;
1027            mCachedTintMode = mTintMode;
1028            mCachedRootAlpha = mVPathRenderer.getRootAlpha();
1029            mCachedAutoMirrored = mAutoMirrored;
1030            mCacheDirty = false;
1031        }
1032
1033        public VectorDrawableCompatState() {
1034            mVPathRenderer = new VPathRenderer();
1035        }
1036
1037        @Override
1038        public Drawable newDrawable() {
1039            return new VectorDrawableCompat(this);
1040        }
1041
1042        @Override
1043        public Drawable newDrawable(Resources res) {
1044            return new VectorDrawableCompat(this);
1045        }
1046
1047        @Override
1048        public int getChangingConfigurations() {
1049            return mChangingConfigurations;
1050        }
1051    }
1052
1053    private static class VPathRenderer {
1054        /* Right now the internal data structure is organized as a tree.
1055         * Each node can be a group node, or a path.
1056         * A group node can have groups or paths as children, but a path node has
1057         * no children.
1058         * One example can be:
1059         *                 Root Group
1060         *                /    |     \
1061         *           Group    Path    Group
1062         *          /     \             |
1063         *         Path   Path         Path
1064         *
1065         */
1066        // Variables that only used temporarily inside the draw() call, so there
1067        // is no need for deep copying.
1068        private final Path mPath;
1069        private final Path mRenderPath;
1070        private static final Matrix IDENTITY_MATRIX = new Matrix();
1071        private final Matrix mFinalPathMatrix = new Matrix();
1072
1073        private Paint mStrokePaint;
1074        private Paint mFillPaint;
1075        private PathMeasure mPathMeasure;
1076
1077        /////////////////////////////////////////////////////
1078        // Variables below need to be copied (deep copy if applicable) for mutation.
1079        private int mChangingConfigurations;
1080        final VGroup mRootGroup;
1081        float mBaseWidth = 0;
1082        float mBaseHeight = 0;
1083        float mViewportWidth = 0;
1084        float mViewportHeight = 0;
1085        int mRootAlpha = 0xFF;
1086        String mRootName = null;
1087
1088        final ArrayMap<String, Object> mVGTargetsMap = new ArrayMap<String, Object>();
1089
1090        public VPathRenderer() {
1091            mRootGroup = new VGroup();
1092            mPath = new Path();
1093            mRenderPath = new Path();
1094        }
1095
1096        public void setRootAlpha(int alpha) {
1097            mRootAlpha = alpha;
1098        }
1099
1100        public int getRootAlpha() {
1101            return mRootAlpha;
1102        }
1103
1104        // setAlpha() and getAlpha() are used mostly for animation purpose, since
1105        // Animator like to use alpha from 0 to 1.
1106        public void setAlpha(float alpha) {
1107            setRootAlpha((int) (alpha * 255));
1108        }
1109
1110        @SuppressWarnings("unused")
1111        public float getAlpha() {
1112            return getRootAlpha() / 255.0f;
1113        }
1114
1115        public VPathRenderer(VPathRenderer copy) {
1116            mRootGroup = new VGroup(copy.mRootGroup, mVGTargetsMap);
1117            mPath = new Path(copy.mPath);
1118            mRenderPath = new Path(copy.mRenderPath);
1119            mBaseWidth = copy.mBaseWidth;
1120            mBaseHeight = copy.mBaseHeight;
1121            mViewportWidth = copy.mViewportWidth;
1122            mViewportHeight = copy.mViewportHeight;
1123            mChangingConfigurations = copy.mChangingConfigurations;
1124            mRootAlpha = copy.mRootAlpha;
1125            mRootName = copy.mRootName;
1126            if (copy.mRootName != null) {
1127                mVGTargetsMap.put(copy.mRootName, this);
1128            }
1129        }
1130
1131        private void drawGroupTree(VGroup currentGroup, Matrix currentMatrix,
1132                                   Canvas canvas, int w, int h, ColorFilter filter) {
1133            // Calculate current group's matrix by preConcat the parent's and
1134            // and the current one on the top of the stack.
1135            // Basically the Mfinal = Mviewport * M0 * M1 * M2;
1136            // Mi the local matrix at level i of the group tree.
1137            currentGroup.mStackedMatrix.set(currentMatrix);
1138
1139            currentGroup.mStackedMatrix.preConcat(currentGroup.mLocalMatrix);
1140
1141            // Save the current clip information, which is local to this group.
1142            canvas.save();
1143
1144            // Draw the group tree in the same order as the XML file.
1145            for (int i = 0; i < currentGroup.mChildren.size(); i++) {
1146                Object child = currentGroup.mChildren.get(i);
1147                if (child instanceof VGroup) {
1148                    VGroup childGroup = (VGroup) child;
1149                    drawGroupTree(childGroup, currentGroup.mStackedMatrix,
1150                            canvas, w, h, filter);
1151                } else if (child instanceof VPath) {
1152                    VPath childPath = (VPath) child;
1153                    drawPath(currentGroup, childPath, canvas, w, h, filter);
1154                }
1155            }
1156
1157            canvas.restore();
1158        }
1159
1160        public void draw(Canvas canvas, int w, int h, ColorFilter filter) {
1161            // Traverse the tree in pre-order to draw.
1162            drawGroupTree(mRootGroup, IDENTITY_MATRIX, canvas, w, h, filter);
1163        }
1164
1165        private void drawPath(VGroup vGroup, VPath vPath, Canvas canvas, int w, int h,
1166                              ColorFilter filter) {
1167            final float scaleX = w / mViewportWidth;
1168            final float scaleY = h / mViewportHeight;
1169            final float minScale = Math.min(scaleX, scaleY);
1170            final Matrix groupStackedMatrix = vGroup.mStackedMatrix;
1171
1172            mFinalPathMatrix.set(groupStackedMatrix);
1173            mFinalPathMatrix.postScale(scaleX, scaleY);
1174
1175
1176            final float matrixScale = getMatrixScale(groupStackedMatrix);
1177            if (matrixScale == 0) {
1178                // When either x or y is scaled to 0, we don't need to draw anything.
1179                return;
1180            }
1181            vPath.toPath(mPath);
1182            final Path path = mPath;
1183
1184            mRenderPath.reset();
1185
1186            if (vPath.isClipPath()) {
1187                mRenderPath.addPath(path, mFinalPathMatrix);
1188                canvas.clipPath(mRenderPath);
1189            } else {
1190                VFullPath fullPath = (VFullPath) vPath;
1191                if (fullPath.mTrimPathStart != 0.0f || fullPath.mTrimPathEnd != 1.0f) {
1192                    float start = (fullPath.mTrimPathStart + fullPath.mTrimPathOffset) % 1.0f;
1193                    float end = (fullPath.mTrimPathEnd + fullPath.mTrimPathOffset) % 1.0f;
1194
1195                    if (mPathMeasure == null) {
1196                        mPathMeasure = new PathMeasure();
1197                    }
1198                    mPathMeasure.setPath(mPath, false);
1199
1200                    float len = mPathMeasure.getLength();
1201                    start = start * len;
1202                    end = end * len;
1203                    path.reset();
1204                    if (start > end) {
1205                        mPathMeasure.getSegment(start, len, path, true);
1206                        mPathMeasure.getSegment(0f, end, path, true);
1207                    } else {
1208                        mPathMeasure.getSegment(start, end, path, true);
1209                    }
1210                    path.rLineTo(0, 0); // fix bug in measure
1211                }
1212                mRenderPath.addPath(path, mFinalPathMatrix);
1213
1214                if (fullPath.mFillColor != Color.TRANSPARENT) {
1215                    if (mFillPaint == null) {
1216                        mFillPaint = new Paint();
1217                        mFillPaint.setStyle(Paint.Style.FILL);
1218                        mFillPaint.setAntiAlias(true);
1219                    }
1220
1221                    final Paint fillPaint = mFillPaint;
1222                    fillPaint.setColor(applyAlpha(fullPath.mFillColor, fullPath.mFillAlpha));
1223                    fillPaint.setColorFilter(filter);
1224                    mRenderPath.setFillType(fullPath.mFillRule == 0 ? Path.FillType.WINDING
1225                            : Path.FillType.EVEN_ODD);
1226                    canvas.drawPath(mRenderPath, fillPaint);
1227                }
1228
1229                if (fullPath.mStrokeColor != Color.TRANSPARENT) {
1230                    if (mStrokePaint == null) {
1231                        mStrokePaint = new Paint();
1232                        mStrokePaint.setStyle(Paint.Style.STROKE);
1233                        mStrokePaint.setAntiAlias(true);
1234                    }
1235
1236                    final Paint strokePaint = mStrokePaint;
1237                    if (fullPath.mStrokeLineJoin != null) {
1238                        strokePaint.setStrokeJoin(fullPath.mStrokeLineJoin);
1239                    }
1240
1241                    if (fullPath.mStrokeLineCap != null) {
1242                        strokePaint.setStrokeCap(fullPath.mStrokeLineCap);
1243                    }
1244
1245                    strokePaint.setStrokeMiter(fullPath.mStrokeMiterlimit);
1246                    strokePaint.setColor(applyAlpha(fullPath.mStrokeColor, fullPath.mStrokeAlpha));
1247                    strokePaint.setColorFilter(filter);
1248                    final float finalStrokeScale = minScale * matrixScale;
1249                    strokePaint.setStrokeWidth(fullPath.mStrokeWidth * finalStrokeScale);
1250                    canvas.drawPath(mRenderPath, strokePaint);
1251                }
1252            }
1253        }
1254
1255        private static float cross(float v1x, float v1y, float v2x, float v2y) {
1256            return v1x * v2y - v1y * v2x;
1257        }
1258
1259        private float getMatrixScale(Matrix groupStackedMatrix) {
1260            // Given unit vectors A = (0, 1) and B = (1, 0).
1261            // After matrix mapping, we got A' and B'. Let theta = the angel b/t A' and B'.
1262            // Therefore, the final scale we want is min(|A'| * sin(theta), |B'| * sin(theta)),
1263            // which is (|A'| * |B'| * sin(theta)) / max (|A'|, |B'|);
1264            // If  max (|A'|, |B'|) = 0, that means either x or y has a scale of 0.
1265            //
1266            // For non-skew case, which is most of the cases, matrix scale is computing exactly the
1267            // scale on x and y axis, and take the minimal of these two.
1268            // For skew case, an unit square will mapped to a parallelogram. And this function will
1269            // return the minimal height of the 2 bases.
1270            float[] unitVectors = new float[]{0, 1, 1, 0};
1271            groupStackedMatrix.mapVectors(unitVectors);
1272            float scaleX = (float) Math.hypot(unitVectors[0], unitVectors[1]);
1273            float scaleY = (float) Math.hypot(unitVectors[2], unitVectors[3]);
1274            float crossProduct = cross(unitVectors[0], unitVectors[1], unitVectors[2],
1275                    unitVectors[3]);
1276            float maxScale = Math.max(scaleX, scaleY);
1277
1278            float matrixScale = 0;
1279            if (maxScale > 0) {
1280                matrixScale = Math.abs(crossProduct) / maxScale;
1281            }
1282            if (DBG_VECTOR_DRAWABLE) {
1283                Log.d(LOGTAG, "Scale x " + scaleX + " y " + scaleY + " final " + matrixScale);
1284            }
1285            return matrixScale;
1286        }
1287    }
1288
1289    private static class VGroup {
1290        // mStackedMatrix is only used temporarily when drawing, it combines all
1291        // the parents' local matrices with the current one.
1292        private final Matrix mStackedMatrix = new Matrix();
1293
1294        /////////////////////////////////////////////////////
1295        // Variables below need to be copied (deep copy if applicable) for mutation.
1296        final ArrayList<Object> mChildren = new ArrayList<Object>();
1297
1298        float mRotate = 0;
1299        private float mPivotX = 0;
1300        private float mPivotY = 0;
1301        private float mScaleX = 1;
1302        private float mScaleY = 1;
1303        private float mTranslateX = 0;
1304        private float mTranslateY = 0;
1305
1306        // mLocalMatrix is updated based on the update of transformation information,
1307        // either parsed from the XML or by animation.
1308        private final Matrix mLocalMatrix = new Matrix();
1309        int mChangingConfigurations;
1310        private int[] mThemeAttrs;
1311        private String mGroupName = null;
1312
1313        public VGroup(VGroup copy, ArrayMap<String, Object> targetsMap) {
1314            mRotate = copy.mRotate;
1315            mPivotX = copy.mPivotX;
1316            mPivotY = copy.mPivotY;
1317            mScaleX = copy.mScaleX;
1318            mScaleY = copy.mScaleY;
1319            mTranslateX = copy.mTranslateX;
1320            mTranslateY = copy.mTranslateY;
1321            mThemeAttrs = copy.mThemeAttrs;
1322            mGroupName = copy.mGroupName;
1323            mChangingConfigurations = copy.mChangingConfigurations;
1324            if (mGroupName != null) {
1325                targetsMap.put(mGroupName, this);
1326            }
1327
1328            mLocalMatrix.set(copy.mLocalMatrix);
1329
1330            final ArrayList<Object> children = copy.mChildren;
1331            for (int i = 0; i < children.size(); i++) {
1332                Object copyChild = children.get(i);
1333                if (copyChild instanceof VGroup) {
1334                    VGroup copyGroup = (VGroup) copyChild;
1335                    mChildren.add(new VGroup(copyGroup, targetsMap));
1336                } else {
1337                    VPath newPath = null;
1338                    if (copyChild instanceof VFullPath) {
1339                        newPath = new VFullPath((VFullPath) copyChild);
1340                    } else if (copyChild instanceof VClipPath) {
1341                        newPath = new VClipPath((VClipPath) copyChild);
1342                    } else {
1343                        throw new IllegalStateException("Unknown object in the tree!");
1344                    }
1345                    mChildren.add(newPath);
1346                    if (newPath.mPathName != null) {
1347                        targetsMap.put(newPath.mPathName, newPath);
1348                    }
1349                }
1350            }
1351        }
1352
1353        public VGroup() {
1354        }
1355
1356        public String getGroupName() {
1357            return mGroupName;
1358        }
1359
1360        public Matrix getLocalMatrix() {
1361            return mLocalMatrix;
1362        }
1363
1364        public void inflate(Resources res, AttributeSet attrs, Theme theme, XmlPullParser parser) {
1365            final TypedArray a = TypedArrayUtils.obtainAttributes(res, theme, attrs,
1366                    AndroidResources.STYLEABLE_VECTOR_DRAWABLE_GROUP);
1367            updateStateFromTypedArray(a, parser);
1368            a.recycle();
1369        }
1370
1371        private void updateStateFromTypedArray(TypedArray a, XmlPullParser parser) {
1372            // Account for any configuration changes.
1373            // mChangingConfigurations |= Utils.getChangingConfigurations(a);
1374
1375            // Extract the theme attributes, if any.
1376            mThemeAttrs = null; // TODO TINT THEME Not supported yet a.extractThemeAttrs();
1377
1378            // This is added in API 11
1379            mRotate = TypedArrayUtils.getNamedFloat(a, parser, "rotation",
1380                    AndroidResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_ROTATION, mRotate);
1381
1382            mPivotX = a.getFloat(AndroidResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_PIVOT_X, mPivotX);
1383            mPivotY = a.getFloat(AndroidResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_PIVOT_Y, mPivotY);
1384
1385            // This is added in API 11
1386            mScaleX = TypedArrayUtils.getNamedFloat(a, parser, "scaleX",
1387                    AndroidResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_SCALE_X, mScaleX);
1388
1389            // This is added in API 11
1390            mScaleY = TypedArrayUtils.getNamedFloat(a, parser, "scaleY",
1391                    AndroidResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_SCALE_Y, mScaleY);
1392
1393            mTranslateX = TypedArrayUtils.getNamedFloat(a, parser, "translateX",
1394                    AndroidResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_TRANSLATE_X, mTranslateX);
1395            mTranslateY = TypedArrayUtils.getNamedFloat(a, parser, "translateY",
1396                    AndroidResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_TRANSLATE_Y, mTranslateY);
1397
1398            final String groupName =
1399                    a.getString(AndroidResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_NAME);
1400            if (groupName != null) {
1401                mGroupName = groupName;
1402            }
1403
1404            updateLocalMatrix();
1405        }
1406
1407        private void updateLocalMatrix() {
1408            // The order we apply is the same as the
1409            // RenderNode.cpp::applyViewPropertyTransforms().
1410            mLocalMatrix.reset();
1411            mLocalMatrix.postTranslate(-mPivotX, -mPivotY);
1412            mLocalMatrix.postScale(mScaleX, mScaleY);
1413            mLocalMatrix.postRotate(mRotate, 0, 0);
1414            mLocalMatrix.postTranslate(mTranslateX + mPivotX, mTranslateY + mPivotY);
1415        }
1416
1417        /* Setters and Getters, used by animator from AnimatedVectorDrawable. */
1418        @SuppressWarnings("unused")
1419        public float getRotation() {
1420            return mRotate;
1421        }
1422
1423        @SuppressWarnings("unused")
1424        public void setRotation(float rotation) {
1425            if (rotation != mRotate) {
1426                mRotate = rotation;
1427                updateLocalMatrix();
1428            }
1429        }
1430
1431        @SuppressWarnings("unused")
1432        public float getPivotX() {
1433            return mPivotX;
1434        }
1435
1436        @SuppressWarnings("unused")
1437        public void setPivotX(float pivotX) {
1438            if (pivotX != mPivotX) {
1439                mPivotX = pivotX;
1440                updateLocalMatrix();
1441            }
1442        }
1443
1444        @SuppressWarnings("unused")
1445        public float getPivotY() {
1446            return mPivotY;
1447        }
1448
1449        @SuppressWarnings("unused")
1450        public void setPivotY(float pivotY) {
1451            if (pivotY != mPivotY) {
1452                mPivotY = pivotY;
1453                updateLocalMatrix();
1454            }
1455        }
1456
1457        @SuppressWarnings("unused")
1458        public float getScaleX() {
1459            return mScaleX;
1460        }
1461
1462        @SuppressWarnings("unused")
1463        public void setScaleX(float scaleX) {
1464            if (scaleX != mScaleX) {
1465                mScaleX = scaleX;
1466                updateLocalMatrix();
1467            }
1468        }
1469
1470        @SuppressWarnings("unused")
1471        public float getScaleY() {
1472            return mScaleY;
1473        }
1474
1475        @SuppressWarnings("unused")
1476        public void setScaleY(float scaleY) {
1477            if (scaleY != mScaleY) {
1478                mScaleY = scaleY;
1479                updateLocalMatrix();
1480            }
1481        }
1482
1483        @SuppressWarnings("unused")
1484        public float getTranslateX() {
1485            return mTranslateX;
1486        }
1487
1488        @SuppressWarnings("unused")
1489        public void setTranslateX(float translateX) {
1490            if (translateX != mTranslateX) {
1491                mTranslateX = translateX;
1492                updateLocalMatrix();
1493            }
1494        }
1495
1496        @SuppressWarnings("unused")
1497        public float getTranslateY() {
1498            return mTranslateY;
1499        }
1500
1501        @SuppressWarnings("unused")
1502        public void setTranslateY(float translateY) {
1503            if (translateY != mTranslateY) {
1504                mTranslateY = translateY;
1505                updateLocalMatrix();
1506            }
1507        }
1508    }
1509
1510    /**
1511     * Common Path information for clip path and normal path.
1512     */
1513    private static class VPath {
1514        protected PathParser.PathDataNode[] mNodes = null;
1515        String mPathName;
1516        int mChangingConfigurations;
1517
1518        public VPath() {
1519            // Empty constructor.
1520        }
1521
1522        public void printVPath(int level) {
1523            String indent = "";
1524            for (int i = 0; i < level; i++) {
1525                indent += "    ";
1526            }
1527            Log.v(LOGTAG, indent + "current path is :" + mPathName +
1528                    " pathData is " + nodesToString(mNodes));
1529
1530        }
1531
1532        public String nodesToString(PathParser.PathDataNode[] nodes) {
1533            String result = " ";
1534            for (int i = 0; i < nodes.length; i++) {
1535                result += nodes[i].mType + ":";
1536                float[] params = nodes[i].mParams;
1537                for (int j = 0; j < params.length; j++) {
1538                    result += params[j] + ",";
1539                }
1540            }
1541            return result;
1542        }
1543
1544        public VPath(VPath copy) {
1545            mPathName = copy.mPathName;
1546            mChangingConfigurations = copy.mChangingConfigurations;
1547            mNodes = PathParser.deepCopyNodes(copy.mNodes);
1548        }
1549
1550        public void toPath(Path path) {
1551            path.reset();
1552            if (mNodes != null) {
1553                PathParser.PathDataNode.nodesToPath(mNodes, path);
1554            }
1555        }
1556
1557        public String getPathName() {
1558            return mPathName;
1559        }
1560
1561        public boolean canApplyTheme() {
1562            return false;
1563        }
1564
1565        public void applyTheme(Theme t) {
1566        }
1567
1568        public boolean isClipPath() {
1569            return false;
1570        }
1571
1572        /* Setters and Getters, used by animator from AnimatedVectorDrawable. */
1573        @SuppressWarnings("unused")
1574        public PathParser.PathDataNode[] getPathData() {
1575            return mNodes;
1576        }
1577
1578        @SuppressWarnings("unused")
1579        public void setPathData(PathParser.PathDataNode[] nodes) {
1580            if (!PathParser.canMorph(mNodes, nodes)) {
1581                // This should not happen in the middle of animation.
1582                mNodes = PathParser.deepCopyNodes(nodes);
1583            } else {
1584                PathParser.updateNodes(mNodes, nodes);
1585            }
1586        }
1587    }
1588
1589    /**
1590     * Clip path, which only has name and pathData.
1591     */
1592    private static class VClipPath extends VPath {
1593        public VClipPath() {
1594            // Empty constructor.
1595        }
1596
1597        public VClipPath(VClipPath copy) {
1598            super(copy);
1599        }
1600
1601        public void inflate(Resources r, AttributeSet attrs, Theme theme, XmlPullParser parser) {
1602            // TODO TINT THEME Not supported yet
1603            final boolean hasPathData = TypedArrayUtils.hasAttribute(parser, "pathData");
1604            if (!hasPathData) {
1605                return;
1606            }
1607            final TypedArray a = TypedArrayUtils.obtainAttributes(r, theme, attrs,
1608                    AndroidResources.STYLEABLE_VECTOR_DRAWABLE_CLIP_PATH);
1609            updateStateFromTypedArray(a);
1610            a.recycle();
1611        }
1612
1613        private void updateStateFromTypedArray(TypedArray a) {
1614            // Account for any configuration changes.
1615            // mChangingConfigurations |= Utils.getChangingConfigurations(a);;
1616
1617            final String pathName =
1618                    a.getString(AndroidResources.STYLEABLE_VECTOR_DRAWABLE_CLIP_PATH_NAME);
1619            if (pathName != null) {
1620                mPathName = pathName;
1621            }
1622
1623            final String pathData =
1624                    a.getString(AndroidResources.STYLEABLE_VECTOR_DRAWABLE_CLIP_PATH_PATH_DATA);
1625            if (pathData != null) {
1626                mNodes = PathParser.createNodesFromPathData(pathData);
1627            }
1628        }
1629
1630        @Override
1631        public boolean isClipPath() {
1632            return true;
1633        }
1634    }
1635
1636    /**
1637     * Normal path, which contains all the fill / paint information.
1638     */
1639    private static class VFullPath extends VPath {
1640        /////////////////////////////////////////////////////
1641        // Variables below need to be copied (deep copy if applicable) for mutation.
1642        private int[] mThemeAttrs;
1643
1644        int mStrokeColor = Color.TRANSPARENT;
1645        float mStrokeWidth = 0;
1646
1647        int mFillColor = Color.TRANSPARENT;
1648        float mStrokeAlpha = 1.0f;
1649        int mFillRule = 0; // 0 is default value as "non-zero" fill type.
1650        float mFillAlpha = 1.0f;
1651        float mTrimPathStart = 0;
1652        float mTrimPathEnd = 1;
1653        float mTrimPathOffset = 0;
1654
1655        Paint.Cap mStrokeLineCap = Paint.Cap.BUTT;
1656        Paint.Join mStrokeLineJoin = Paint.Join.MITER;
1657        float mStrokeMiterlimit = 4;
1658
1659        public VFullPath() {
1660            // Empty constructor.
1661        }
1662
1663        public VFullPath(VFullPath copy) {
1664            super(copy);
1665            mThemeAttrs = copy.mThemeAttrs;
1666
1667            mStrokeColor = copy.mStrokeColor;
1668            mStrokeWidth = copy.mStrokeWidth;
1669            mStrokeAlpha = copy.mStrokeAlpha;
1670            mFillColor = copy.mFillColor;
1671            mFillRule = copy.mFillRule;
1672            mFillAlpha = copy.mFillAlpha;
1673            mTrimPathStart = copy.mTrimPathStart;
1674            mTrimPathEnd = copy.mTrimPathEnd;
1675            mTrimPathOffset = copy.mTrimPathOffset;
1676
1677            mStrokeLineCap = copy.mStrokeLineCap;
1678            mStrokeLineJoin = copy.mStrokeLineJoin;
1679            mStrokeMiterlimit = copy.mStrokeMiterlimit;
1680        }
1681
1682        private Paint.Cap getStrokeLineCap(int id, Paint.Cap defValue) {
1683            switch (id) {
1684                case LINECAP_BUTT:
1685                    return Paint.Cap.BUTT;
1686                case LINECAP_ROUND:
1687                    return Paint.Cap.ROUND;
1688                case LINECAP_SQUARE:
1689                    return Paint.Cap.SQUARE;
1690                default:
1691                    return defValue;
1692            }
1693        }
1694
1695        private Paint.Join getStrokeLineJoin(int id, Paint.Join defValue) {
1696            switch (id) {
1697                case LINEJOIN_MITER:
1698                    return Paint.Join.MITER;
1699                case LINEJOIN_ROUND:
1700                    return Paint.Join.ROUND;
1701                case LINEJOIN_BEVEL:
1702                    return Paint.Join.BEVEL;
1703                default:
1704                    return defValue;
1705            }
1706        }
1707
1708        @Override
1709        public boolean canApplyTheme() {
1710            return mThemeAttrs != null;
1711        }
1712
1713        public void inflate(Resources r, AttributeSet attrs, Theme theme, XmlPullParser parser) {
1714            final TypedArray a = TypedArrayUtils.obtainAttributes(r, theme, attrs,
1715                    AndroidResources.STYLEABLE_VECTOR_DRAWABLE_PATH);
1716            updateStateFromTypedArray(a, parser);
1717            a.recycle();
1718        }
1719
1720        private void updateStateFromTypedArray(TypedArray a, XmlPullParser parser) {
1721            // Account for any configuration changes.
1722            // mChangingConfigurations |= Utils.getChangingConfigurations(a);
1723
1724            // Extract the theme attributes, if any.
1725            mThemeAttrs = null; // TODO TINT THEME Not supported yet a.extractThemeAttrs();
1726
1727            // In order to work around the conflicting id issue, we need to double check the
1728            // existence of the attribute.
1729            // B/c if the attribute existed in the compiled XML, then calling TypedArray will be
1730            // safe since the framework will look up in the XML first.
1731            // Note that each getAttributeValue take roughly 0.03ms, it is a price we have to pay.
1732            final boolean hasPathData = TypedArrayUtils.hasAttribute(parser, "pathData");
1733            if (!hasPathData) {
1734                // If there is no pathData in the <path> tag, then this is an empty path,
1735                // nothing need to be drawn.
1736                return;
1737            }
1738
1739            final String pathName = a.getString(
1740                    AndroidResources.STYLEABLE_VECTOR_DRAWABLE_PATH_NAME);
1741            if (pathName != null) {
1742                mPathName = pathName;
1743            }
1744            final String pathData =
1745                    a.getString(AndroidResources.STYLEABLE_VECTOR_DRAWABLE_PATH_PATH_DATA);
1746            if (pathData != null) {
1747                mNodes = PathParser.createNodesFromPathData(pathData);
1748            }
1749
1750            mFillColor = TypedArrayUtils.getNamedColor(a, parser, "fillColor",
1751                    AndroidResources.STYLEABLE_VECTOR_DRAWABLE_PATH_FILL_COLOR, mFillColor);
1752            mFillAlpha = TypedArrayUtils.getNamedFloat(a, parser, "fillAlpha",
1753                    AndroidResources.STYLEABLE_VECTOR_DRAWABLE_PATH_FILL_ALPHA, mFillAlpha);
1754            final int lineCap = TypedArrayUtils.getNamedInt(a, parser, "strokeLineCap",
1755                    AndroidResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_LINE_CAP, -1);
1756            mStrokeLineCap = getStrokeLineCap(lineCap, mStrokeLineCap);
1757            final int lineJoin = TypedArrayUtils.getNamedInt(a, parser, "strokeLineJoin",
1758                    AndroidResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_LINE_JOIN, -1);
1759            mStrokeLineJoin = getStrokeLineJoin(lineJoin, mStrokeLineJoin);
1760            mStrokeMiterlimit = TypedArrayUtils.getNamedFloat(a, parser, "strokeMiterLimit",
1761                    AndroidResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_MITER_LIMIT,
1762                    mStrokeMiterlimit);
1763            mStrokeColor = TypedArrayUtils.getNamedColor(a, parser, "strokeColor",
1764                    AndroidResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_COLOR, mStrokeColor);
1765            mStrokeAlpha = TypedArrayUtils.getNamedFloat(a, parser, "strokeAlpha",
1766                    AndroidResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_ALPHA, mStrokeAlpha);
1767            mStrokeWidth = TypedArrayUtils.getNamedFloat(a, parser, "strokeWidth",
1768                    AndroidResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_WIDTH, mStrokeWidth);
1769            mTrimPathEnd = TypedArrayUtils.getNamedFloat(a, parser, "trimPathEnd",
1770                    AndroidResources.STYLEABLE_VECTOR_DRAWABLE_PATH_TRIM_PATH_END, mTrimPathEnd);
1771            mTrimPathOffset = TypedArrayUtils.getNamedFloat(a, parser, "trimPathOffset",
1772                    AndroidResources.STYLEABLE_VECTOR_DRAWABLE_PATH_TRIM_PATH_OFFSET,
1773                    mTrimPathOffset);
1774            mTrimPathStart = TypedArrayUtils.getNamedFloat(a, parser, "trimPathStart",
1775                    AndroidResources.STYLEABLE_VECTOR_DRAWABLE_PATH_TRIM_PATH_START,
1776                    mTrimPathStart);
1777            mFillRule = TypedArrayUtils.getNamedInt(a, parser, "fillType",
1778                    AndroidResources.STYLEABLE_VECTOR_DRAWABLE_PATH_TRIM_PATH_FILLTYPE,
1779                    mFillRule);
1780        }
1781
1782        @Override
1783        public void applyTheme(Theme t) {
1784            if (mThemeAttrs == null) {
1785                return;
1786            }
1787
1788            /*
1789             * TODO TINT THEME Not supported yet final TypedArray a =
1790             * t.resolveAttributes(mThemeAttrs, styleable_VectorDrawablePath);
1791             * updateStateFromTypedArray(a); a.recycle();
1792             */
1793        }
1794
1795        /* Setters and Getters, used by animator from AnimatedVectorDrawable. */
1796        @SuppressWarnings("unused")
1797        int getStrokeColor() {
1798            return mStrokeColor;
1799        }
1800
1801        @SuppressWarnings("unused")
1802        void setStrokeColor(int strokeColor) {
1803            mStrokeColor = strokeColor;
1804        }
1805
1806        @SuppressWarnings("unused")
1807        float getStrokeWidth() {
1808            return mStrokeWidth;
1809        }
1810
1811        @SuppressWarnings("unused")
1812        void setStrokeWidth(float strokeWidth) {
1813            mStrokeWidth = strokeWidth;
1814        }
1815
1816        @SuppressWarnings("unused")
1817        float getStrokeAlpha() {
1818            return mStrokeAlpha;
1819        }
1820
1821        @SuppressWarnings("unused")
1822        void setStrokeAlpha(float strokeAlpha) {
1823            mStrokeAlpha = strokeAlpha;
1824        }
1825
1826        @SuppressWarnings("unused")
1827        int getFillColor() {
1828            return mFillColor;
1829        }
1830
1831        @SuppressWarnings("unused")
1832        void setFillColor(int fillColor) {
1833            mFillColor = fillColor;
1834        }
1835
1836        @SuppressWarnings("unused")
1837        float getFillAlpha() {
1838            return mFillAlpha;
1839        }
1840
1841        @SuppressWarnings("unused")
1842        void setFillAlpha(float fillAlpha) {
1843            mFillAlpha = fillAlpha;
1844        }
1845
1846        @SuppressWarnings("unused")
1847        float getTrimPathStart() {
1848            return mTrimPathStart;
1849        }
1850
1851        @SuppressWarnings("unused")
1852        void setTrimPathStart(float trimPathStart) {
1853            mTrimPathStart = trimPathStart;
1854        }
1855
1856        @SuppressWarnings("unused")
1857        float getTrimPathEnd() {
1858            return mTrimPathEnd;
1859        }
1860
1861        @SuppressWarnings("unused")
1862        void setTrimPathEnd(float trimPathEnd) {
1863            mTrimPathEnd = trimPathEnd;
1864        }
1865
1866        @SuppressWarnings("unused")
1867        float getTrimPathOffset() {
1868            return mTrimPathOffset;
1869        }
1870
1871        @SuppressWarnings("unused")
1872        void setTrimPathOffset(float trimPathOffset) {
1873            mTrimPathOffset = trimPathOffset;
1874        }
1875    }
1876}
1877