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.GROUP_ID;
18
19import android.annotation.TargetApi;
20import android.content.res.ColorStateList;
21import android.content.res.Resources;
22import android.content.res.Resources.Theme;
23import android.content.res.TypedArray;
24import android.graphics.Bitmap;
25import android.graphics.Canvas;
26import android.graphics.Color;
27import android.graphics.ColorFilter;
28import android.graphics.Matrix;
29import android.graphics.Paint;
30import android.graphics.Path;
31import android.graphics.PathMeasure;
32import android.graphics.PixelFormat;
33import android.graphics.PorterDuff;
34import android.graphics.PorterDuff.Mode;
35import android.graphics.PorterDuffColorFilter;
36import android.graphics.Rect;
37import android.graphics.drawable.Drawable;
38import android.graphics.drawable.VectorDrawable;
39import android.os.Build;
40import android.support.annotation.DrawableRes;
41import android.support.annotation.NonNull;
42import android.support.annotation.Nullable;
43import android.support.annotation.RestrictTo;
44import android.support.v4.content.res.ResourcesCompat;
45import android.support.v4.graphics.drawable.DrawableCompat;
46import android.support.v4.util.ArrayMap;
47import android.util.AttributeSet;
48import android.util.LayoutDirection;
49import android.util.Log;
50import android.util.Xml;
51
52import org.xmlpull.v1.XmlPullParser;
53import org.xmlpull.v1.XmlPullParserException;
54
55import java.io.IOException;
56import java.util.ArrayList;
57import java.util.Stack;
58
59/**
60 * For API 24 and above, this class is delegating to the framework's {@link VectorDrawable}.
61 * For older API version, this class lets you create a drawable based on an XML vector graphic.
62 * <p/>
63 * You can always create a VectorDrawableCompat object and use it as a Drawable by the Java API.
64 * In order to refer to VectorDrawableCompat inside a XML file,  you can use app:srcCompat attribute
65 * in AppCompat library's ImageButton or ImageView.
66 * <p/>
67 * <strong>Note:</strong> To optimize for the re-drawing performance, one bitmap cache is created
68 * for each VectorDrawableCompat. Therefore, referring to the same VectorDrawableCompat means
69 * sharing the same bitmap cache. If these references don't agree upon on the same size, the bitmap
70 * will be recreated and redrawn every time size is changed. In other words, if a VectorDrawable is
71 * used for different sizes, it is more efficient to create multiple VectorDrawables, one for each
72 * size.
73 * <p/>
74 * VectorDrawableCompat can be defined in an XML file with the <code>&lt;vector></code> element.
75 * <p/>
76 * The VectorDrawableCompat has the following elements:
77 * <p/>
78 * <dt><code>&lt;vector></code></dt>
79 * <dl>
80 * <dd>Used to define a vector drawable
81 * <dl>
82 * <dt><code>android:name</code></dt>
83 * <dd>Defines the name of this vector drawable.</dd>
84 * <dt><code>android:width</code></dt>
85 * <dd>Used to define the intrinsic width of the drawable.
86 * This support all the dimension units, normally specified with dp.</dd>
87 * <dt><code>android:height</code></dt>
88 * <dd>Used to define the intrinsic height the drawable.
89 * This support all the dimension units, normally specified with dp.</dd>
90 * <dt><code>android:viewportWidth</code></dt>
91 * <dd>Used to define the width of the viewport space. Viewport is basically
92 * the virtual canvas where the paths are drawn on.</dd>
93 * <dt><code>android:viewportHeight</code></dt>
94 * <dd>Used to define the height of the viewport space. Viewport is basically
95 * the virtual canvas where the paths are drawn on.</dd>
96 * <dt><code>android:tint</code></dt>
97 * <dd>The color to apply to the drawable as a tint. By default, no tint is applied.</dd>
98 * <dt><code>android:tintMode</code></dt>
99 * <dd>The Porter-Duff blending mode for the tint color. Default is src_in.</dd>
100 * <dt><code>android:autoMirrored</code></dt>
101 * <dd>Indicates if the drawable needs to be mirrored when its layout direction is
102 * RTL (right-to-left). Default is false.</dd>
103 * <dt><code>android:alpha</code></dt>
104 * <dd>The opacity of this drawable. Default is 1.</dd>
105 * </dl></dd>
106 * </dl>
107 *
108 * <dl>
109 * <dt><code>&lt;group></code></dt>
110 * <dd>Defines a group of paths or subgroups, plus transformation information.
111 * The transformations are defined in the same coordinates as the viewport.
112 * And the transformations are applied in the order of scale, rotate then translate.
113 * <dl>
114 * <dt><code>android:name</code></dt>
115 * <dd>Defines the name of the group.</dd>
116 * <dt><code>android:rotation</code></dt>
117 * <dd>The degrees of rotation of the group. Default is 0.</dd>
118 * <dt><code>android:pivotX</code></dt>
119 * <dd>The X coordinate of the pivot for the scale and rotation of the group.
120 * This is defined in the viewport space. Default is 0.</dd>
121 * <dt><code>android:pivotY</code></dt>
122 * <dd>The Y coordinate of the pivot for the scale and rotation of the group.
123 * This is defined in the viewport space. Default is 0.</dd>
124 * <dt><code>android:scaleX</code></dt>
125 * <dd>The amount of scale on the X Coordinate. Default is 1.</dd>
126 * <dt><code>android:scaleY</code></dt>
127 * <dd>The amount of scale on the Y coordinate. Default is 1.</dd>
128 * <dt><code>android:translateX</code></dt>
129 * <dd>The amount of translation on the X coordinate.
130 * This is defined in the viewport space. Default is 0.</dd>
131 * <dt><code>android:translateY</code></dt>
132 * <dd>The amount of translation on the Y coordinate.
133 * This is defined in the viewport space. Default is 0.</dd>
134 * </dl></dd>
135 * </dl>
136 *
137 * <dl>
138 * <dt><code>&lt;path></code></dt>
139 * <dd>Defines paths to be drawn.
140 * <dl>
141 * <dt><code>android:name</code></dt>
142 * <dd>Defines the name of the path.</dd>
143 * <dt><code>android:pathData</code></dt>
144 * <dd>Defines path data using exactly same format as "d" attribute
145 * in the SVG's path data. This is defined in the viewport space.</dd>
146 * <dt><code>android:fillColor</code></dt>
147 * <dd>Specifies the color used to fill the path.
148 * If this property is animated, any value set by the animation will override the original value.
149 * No path fill is drawn if this property is not specified.</dd>
150 * <dt><code>android:strokeColor</code></dt>
151 * <dd>Specifies the color used to draw the path outline.
152 * If this property is animated, any value set by the animation will override the original value.
153 * No path outline is drawn if this property is not specified.</dd>
154 * <dt><code>android:strokeWidth</code></dt>
155 * <dd>The width a path stroke. Default is 0.</dd>
156 * <dt><code>android:strokeAlpha</code></dt>
157 * <dd>The opacity of a path stroke. Default is 1.</dd>
158 * <dt><code>android:fillAlpha</code></dt>
159 * <dd>The opacity to fill the path with. Default is 1.</dd>
160 * <dt><code>android:trimPathStart</code></dt>
161 * <dd>The fraction of the path to trim from the start, in the range from 0 to 1. Default is 0.</dd>
162 * <dt><code>android:trimPathEnd</code></dt>
163 * <dd>The fraction of the path to trim from the end, in the range from 0 to 1. Default is 1.</dd>
164 * <dt><code>android:trimPathOffset</code></dt>
165 * <dd>Shift trim region (allows showed region to include the start and end), in the range
166 * from 0 to 1. Default is 0.</dd>
167 * <dt><code>android:strokeLineCap</code></dt>
168 * <dd>Sets the linecap for a stroked path: butt, round, square. Default is butt.</dd>
169 * <dt><code>android:strokeLineJoin</code></dt>
170 * <dd>Sets the lineJoin for a stroked path: miter,round,bevel. Default is miter.</dd>
171 * <dt><code>android:strokeMiterLimit</code></dt>
172 * <dd>Sets the Miter limit for a stroked path. Default is 4.</dd>
173 * </dl></dd>
174 * </dl>
175 *
176 * <dl>
177 * <dt><code>&lt;clip-path></code></dt>
178 * <dd>Defines path to be the current clip. Note that the clip path only apply to
179 * the current group and its children.
180 * <dl>
181 * <dt><code>android:name</code></dt>
182 * <dd>Defines the name of the clip path.</dd>
183 * <dt><code>android:pathData</code></dt>
184 * <dd>Defines clip path using the same format as "d" attribute
185 * in the SVG's path data.</dd>
186 * </dl></dd>
187 * </dl>
188 * <p/>
189 * Note that theme attributes in XML file are supported through
190 * <code>{@link #inflate(Resources, XmlPullParser, AttributeSet, Theme)}</code>.
191 */
192
193@TargetApi(Build.VERSION_CODES.LOLLIPOP)
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) {
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(GROUP_ID)
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 = obtainAttributes(res, theme, attrs,
620                AndroidResources.styleable_VectorDrawableTypeArray);
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                return Mode.ADD;
650            default:
651                return defaultMode;
652        }
653    }
654
655    private void updateStateFromTypedArray(TypedArray a, XmlPullParser parser)
656            throws XmlPullParserException {
657        final VectorDrawableCompatState state = mVectorState;
658        final VPathRenderer pathRenderer = state.mVPathRenderer;
659
660        // Account for any configuration changes.
661        // state.mChangingConfigurations |= Utils.getChangingConfigurations(a);
662
663        final int mode = TypedArrayUtils.getNamedInt(a, parser, "tintMode",
664                AndroidResources.styleable_VectorDrawable_tintMode, -1);
665        state.mTintMode = parseTintModeCompat(mode, Mode.SRC_IN);
666
667        final ColorStateList tint =
668                a.getColorStateList(AndroidResources.styleable_VectorDrawable_tint);
669        if (tint != null) {
670            state.mTint = tint;
671        }
672
673        state.mAutoMirrored = TypedArrayUtils.getNamedBoolean(a, parser, "autoMirrored",
674                AndroidResources.styleable_VectorDrawable_autoMirrored, state.mAutoMirrored);
675
676        pathRenderer.mViewportWidth = TypedArrayUtils.getNamedFloat(a, parser, "viewportWidth",
677                AndroidResources.styleable_VectorDrawable_viewportWidth,
678                pathRenderer.mViewportWidth);
679
680        pathRenderer.mViewportHeight = TypedArrayUtils.getNamedFloat(a, parser, "viewportHeight",
681                AndroidResources.styleable_VectorDrawable_viewportHeight,
682                pathRenderer.mViewportHeight);
683
684        if (pathRenderer.mViewportWidth <= 0) {
685            throw new XmlPullParserException(a.getPositionDescription() +
686                    "<vector> tag requires viewportWidth > 0");
687        } else if (pathRenderer.mViewportHeight <= 0) {
688            throw new XmlPullParserException(a.getPositionDescription() +
689                    "<vector> tag requires viewportHeight > 0");
690        }
691
692        pathRenderer.mBaseWidth = a.getDimension(
693                AndroidResources.styleable_VectorDrawable_width, pathRenderer.mBaseWidth);
694        pathRenderer.mBaseHeight = a.getDimension(
695                AndroidResources.styleable_VectorDrawable_height, pathRenderer.mBaseHeight);
696        if (pathRenderer.mBaseWidth <= 0) {
697            throw new XmlPullParserException(a.getPositionDescription() +
698                    "<vector> tag requires width > 0");
699        } else if (pathRenderer.mBaseHeight <= 0) {
700            throw new XmlPullParserException(a.getPositionDescription() +
701                    "<vector> tag requires height > 0");
702        }
703
704        // shown up from API 11.
705        final float alphaInFloat = TypedArrayUtils.getNamedFloat(a, parser, "alpha",
706                AndroidResources.styleable_VectorDrawable_alpha, pathRenderer.getAlpha());
707        pathRenderer.setAlpha(alphaInFloat);
708
709        final String name = a.getString(AndroidResources.styleable_VectorDrawable_name);
710        if (name != null) {
711            pathRenderer.mRootName = name;
712            pathRenderer.mVGTargetsMap.put(name, pathRenderer);
713        }
714    }
715
716    private void inflateInternal(Resources res, XmlPullParser parser, AttributeSet attrs,
717                                 Theme theme) throws XmlPullParserException, IOException {
718        final VectorDrawableCompatState state = mVectorState;
719        final VPathRenderer pathRenderer = state.mVPathRenderer;
720        boolean noPathTag = true;
721
722        // Use a stack to help to build the group tree.
723        // The top of the stack is always the current group.
724        final Stack<VGroup> groupStack = new Stack<VGroup>();
725        groupStack.push(pathRenderer.mRootGroup);
726
727        int eventType = parser.getEventType();
728        final int innerDepth = parser.getDepth() + 1;
729
730        // Parse everything until the end of the vector element.
731        while (eventType != XmlPullParser.END_DOCUMENT
732                && (parser.getDepth() >= innerDepth || eventType != XmlPullParser.END_TAG)) {
733            if (eventType == XmlPullParser.START_TAG) {
734                final String tagName = parser.getName();
735                final VGroup currentGroup = groupStack.peek();
736                if (SHAPE_PATH.equals(tagName)) {
737                    final VFullPath path = new VFullPath();
738                    path.inflate(res, attrs, theme, parser);
739                    currentGroup.mChildren.add(path);
740                    if (path.getPathName() != null) {
741                        pathRenderer.mVGTargetsMap.put(path.getPathName(), path);
742                    }
743                    noPathTag = false;
744                    state.mChangingConfigurations |= path.mChangingConfigurations;
745                } else if (SHAPE_CLIP_PATH.equals(tagName)) {
746                    final VClipPath path = new VClipPath();
747                    path.inflate(res, attrs, theme, parser);
748                    currentGroup.mChildren.add(path);
749                    if (path.getPathName() != null) {
750                        pathRenderer.mVGTargetsMap.put(path.getPathName(), path);
751                    }
752                    state.mChangingConfigurations |= path.mChangingConfigurations;
753                } else if (SHAPE_GROUP.equals(tagName)) {
754                    VGroup newChildGroup = new VGroup();
755                    newChildGroup.inflate(res, attrs, theme, parser);
756                    currentGroup.mChildren.add(newChildGroup);
757                    groupStack.push(newChildGroup);
758                    if (newChildGroup.getGroupName() != null) {
759                        pathRenderer.mVGTargetsMap.put(newChildGroup.getGroupName(),
760                                newChildGroup);
761                    }
762                    state.mChangingConfigurations |= newChildGroup.mChangingConfigurations;
763                }
764            } else if (eventType == XmlPullParser.END_TAG) {
765                final String tagName = parser.getName();
766                if (SHAPE_GROUP.equals(tagName)) {
767                    groupStack.pop();
768                }
769            }
770            eventType = parser.next();
771        }
772
773        // Print the tree out for debug.
774        if (DBG_VECTOR_DRAWABLE) {
775            printGroupTree(pathRenderer.mRootGroup, 0);
776        }
777
778        if (noPathTag) {
779            final StringBuffer tag = new StringBuffer();
780
781            if (tag.length() > 0) {
782                tag.append(" or ");
783            }
784            tag.append(SHAPE_PATH);
785
786            throw new XmlPullParserException("no " + tag + " defined");
787        }
788    }
789
790    private void printGroupTree(VGroup currentGroup, int level) {
791        String indent = "";
792        for (int i = 0; i < level; i++) {
793            indent += "    ";
794        }
795        // Print the current node
796        Log.v(LOGTAG, indent + "current group is :" + currentGroup.getGroupName()
797                + " rotation is " + currentGroup.mRotate);
798        Log.v(LOGTAG, indent + "matrix is :" + currentGroup.getLocalMatrix().toString());
799        // Then print all the children groups
800        for (int i = 0; i < currentGroup.mChildren.size(); i++) {
801            Object child = currentGroup.mChildren.get(i);
802            if (child instanceof VGroup) {
803                printGroupTree((VGroup) child, level + 1);
804            } else {
805                ((VPath) child).printVPath(level + 1);
806            }
807        }
808    }
809
810    void setAllowCaching(boolean allowCaching) {
811        mAllowCaching = allowCaching;
812    }
813
814    // We don't support RTL auto mirroring since the getLayoutDirection() is for API 17+.
815    private boolean needMirroring() {
816        if (Build.VERSION.SDK_INT < 17) {
817            return false;
818        } else {
819            return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL;
820        }
821    }
822
823    // Extra override functions for delegation for SDK >= 7.
824    @Override
825    protected void onBoundsChange(Rect bounds) {
826        if (mDelegateDrawable != null) {
827            mDelegateDrawable.setBounds(bounds);
828        }
829    }
830
831    @Override
832    public int getChangingConfigurations() {
833        if (mDelegateDrawable != null) {
834            return mDelegateDrawable.getChangingConfigurations();
835        }
836        return super.getChangingConfigurations() | mVectorState.getChangingConfigurations();
837    }
838
839    @Override
840    public void invalidateSelf() {
841        if (mDelegateDrawable != null) {
842            mDelegateDrawable.invalidateSelf();
843            return;
844        }
845        super.invalidateSelf();
846    }
847
848    @Override
849    public void scheduleSelf(Runnable what, long when) {
850        if (mDelegateDrawable != null) {
851            mDelegateDrawable.scheduleSelf(what, when);
852            return;
853        }
854        super.scheduleSelf(what, when);
855    }
856
857    @Override
858    public boolean setVisible(boolean visible, boolean restart) {
859        if (mDelegateDrawable != null) {
860            return mDelegateDrawable.setVisible(visible, restart);
861        }
862        return super.setVisible(visible, restart);
863    }
864
865    @Override
866    public void unscheduleSelf(Runnable what) {
867        if (mDelegateDrawable != null) {
868            mDelegateDrawable.unscheduleSelf(what);
869            return;
870        }
871        super.unscheduleSelf(what);
872    }
873
874    /**
875     * Constant state for delegating the creating drawable job for SDK >= 24.
876     * Instead of creating a VectorDrawable, create a VectorDrawableCompat instance which contains
877     * a delegated VectorDrawable instance.
878     */
879    private static class VectorDrawableDelegateState extends ConstantState {
880        private final ConstantState mDelegateState;
881
882        public VectorDrawableDelegateState(ConstantState state) {
883            mDelegateState = state;
884        }
885
886        @Override
887        public Drawable newDrawable() {
888            VectorDrawableCompat drawableCompat = new VectorDrawableCompat();
889            drawableCompat.mDelegateDrawable = (VectorDrawable) mDelegateState.newDrawable();
890            return drawableCompat;
891        }
892
893        @Override
894        public Drawable newDrawable(Resources res) {
895            VectorDrawableCompat drawableCompat = new VectorDrawableCompat();
896            drawableCompat.mDelegateDrawable = (VectorDrawable) mDelegateState.newDrawable(res);
897            return drawableCompat;
898        }
899
900        @Override
901        public Drawable newDrawable(Resources res, Theme theme) {
902            VectorDrawableCompat drawableCompat = new VectorDrawableCompat();
903            drawableCompat.mDelegateDrawable =
904                    (VectorDrawable) mDelegateState.newDrawable(res, theme);
905            return drawableCompat;
906        }
907
908        @Override
909        public boolean canApplyTheme() {
910            return mDelegateState.canApplyTheme();
911        }
912
913        @Override
914        public int getChangingConfigurations() {
915            return mDelegateState.getChangingConfigurations();
916        }
917    }
918
919    private static class VectorDrawableCompatState extends ConstantState {
920        int mChangingConfigurations;
921        VPathRenderer mVPathRenderer;
922        ColorStateList mTint = null;
923        Mode mTintMode = DEFAULT_TINT_MODE;
924        boolean mAutoMirrored;
925
926        Bitmap mCachedBitmap;
927        int[] mCachedThemeAttrs;
928        ColorStateList mCachedTint;
929        Mode mCachedTintMode;
930        int mCachedRootAlpha;
931        boolean mCachedAutoMirrored;
932        boolean mCacheDirty;
933
934        /**
935         * Temporary paint object used to draw cached bitmaps.
936         */
937        Paint mTempPaint;
938
939        // Deep copy for mutate() or implicitly mutate.
940        public VectorDrawableCompatState(VectorDrawableCompatState copy) {
941            if (copy != null) {
942                mChangingConfigurations = copy.mChangingConfigurations;
943                mVPathRenderer = new VPathRenderer(copy.mVPathRenderer);
944                if (copy.mVPathRenderer.mFillPaint != null) {
945                    mVPathRenderer.mFillPaint = new Paint(copy.mVPathRenderer.mFillPaint);
946                }
947                if (copy.mVPathRenderer.mStrokePaint != null) {
948                    mVPathRenderer.mStrokePaint = new Paint(copy.mVPathRenderer.mStrokePaint);
949                }
950                mTint = copy.mTint;
951                mTintMode = copy.mTintMode;
952                mAutoMirrored = copy.mAutoMirrored;
953            }
954        }
955
956        public void drawCachedBitmapWithRootAlpha(Canvas canvas, ColorFilter filter,
957                                                  Rect originalBounds) {
958            // The bitmap's size is the same as the bounds.
959            final Paint p = getPaint(filter);
960            canvas.drawBitmap(mCachedBitmap, null, originalBounds, p);
961        }
962
963        public boolean hasTranslucentRoot() {
964            return mVPathRenderer.getRootAlpha() < 255;
965        }
966
967        /**
968         * @return null when there is no need for alpha paint.
969         */
970        public Paint getPaint(ColorFilter filter) {
971            if (!hasTranslucentRoot() && filter == null) {
972                return null;
973            }
974
975            if (mTempPaint == null) {
976                mTempPaint = new Paint();
977                mTempPaint.setFilterBitmap(true);
978            }
979            mTempPaint.setAlpha(mVPathRenderer.getRootAlpha());
980            mTempPaint.setColorFilter(filter);
981            return mTempPaint;
982        }
983
984        public void updateCachedBitmap(int width, int height) {
985            mCachedBitmap.eraseColor(Color.TRANSPARENT);
986            Canvas tmpCanvas = new Canvas(mCachedBitmap);
987            mVPathRenderer.draw(tmpCanvas, width, height, null);
988        }
989
990        public void createCachedBitmapIfNeeded(int width, int height) {
991            if (mCachedBitmap == null || !canReuseBitmap(width, height)) {
992                mCachedBitmap = Bitmap.createBitmap(width, height,
993                        Bitmap.Config.ARGB_8888);
994                mCacheDirty = true;
995            }
996
997        }
998
999        public boolean canReuseBitmap(int width, int height) {
1000            if (width == mCachedBitmap.getWidth()
1001                    && height == mCachedBitmap.getHeight()) {
1002                return true;
1003            }
1004            return false;
1005        }
1006
1007        public boolean canReuseCache() {
1008            if (!mCacheDirty
1009                    && mCachedTint == mTint
1010                    && mCachedTintMode == mTintMode
1011                    && mCachedAutoMirrored == mAutoMirrored
1012                    && mCachedRootAlpha == mVPathRenderer.getRootAlpha()) {
1013                return true;
1014            }
1015            return false;
1016        }
1017
1018        public void updateCacheStates() {
1019            // Use shallow copy here and shallow comparison in canReuseCache(),
1020            // likely hit cache miss more, but practically not much difference.
1021            mCachedTint = mTint;
1022            mCachedTintMode = mTintMode;
1023            mCachedRootAlpha = mVPathRenderer.getRootAlpha();
1024            mCachedAutoMirrored = mAutoMirrored;
1025            mCacheDirty = false;
1026        }
1027
1028        public VectorDrawableCompatState() {
1029            mVPathRenderer = new VPathRenderer();
1030        }
1031
1032        @Override
1033        public Drawable newDrawable() {
1034            return new VectorDrawableCompat(this);
1035        }
1036
1037        @Override
1038        public Drawable newDrawable(Resources res) {
1039            return new VectorDrawableCompat(this);
1040        }
1041
1042        @Override
1043        public int getChangingConfigurations() {
1044            return mChangingConfigurations;
1045        }
1046    }
1047
1048    private static class VPathRenderer {
1049        /* Right now the internal data structure is organized as a tree.
1050         * Each node can be a group node, or a path.
1051         * A group node can have groups or paths as children, but a path node has
1052         * no children.
1053         * One example can be:
1054         *                 Root Group
1055         *                /    |     \
1056         *           Group    Path    Group
1057         *          /     \             |
1058         *         Path   Path         Path
1059         *
1060         */
1061        // Variables that only used temporarily inside the draw() call, so there
1062        // is no need for deep copying.
1063        private final Path mPath;
1064        private final Path mRenderPath;
1065        private static final Matrix IDENTITY_MATRIX = new Matrix();
1066        private final Matrix mFinalPathMatrix = new Matrix();
1067
1068        private Paint mStrokePaint;
1069        private Paint mFillPaint;
1070        private PathMeasure mPathMeasure;
1071
1072        /////////////////////////////////////////////////////
1073        // Variables below need to be copied (deep copy if applicable) for mutation.
1074        private int mChangingConfigurations;
1075        final VGroup mRootGroup;
1076        float mBaseWidth = 0;
1077        float mBaseHeight = 0;
1078        float mViewportWidth = 0;
1079        float mViewportHeight = 0;
1080        int mRootAlpha = 0xFF;
1081        String mRootName = null;
1082
1083        final ArrayMap<String, Object> mVGTargetsMap = new ArrayMap<String, Object>();
1084
1085        public VPathRenderer() {
1086            mRootGroup = new VGroup();
1087            mPath = new Path();
1088            mRenderPath = new Path();
1089        }
1090
1091        public void setRootAlpha(int alpha) {
1092            mRootAlpha = alpha;
1093        }
1094
1095        public int getRootAlpha() {
1096            return mRootAlpha;
1097        }
1098
1099        // setAlpha() and getAlpha() are used mostly for animation purpose, since
1100        // Animator like to use alpha from 0 to 1.
1101        public void setAlpha(float alpha) {
1102            setRootAlpha((int) (alpha * 255));
1103        }
1104
1105        @SuppressWarnings("unused")
1106        public float getAlpha() {
1107            return getRootAlpha() / 255.0f;
1108        }
1109
1110        public VPathRenderer(VPathRenderer copy) {
1111            mRootGroup = new VGroup(copy.mRootGroup, mVGTargetsMap);
1112            mPath = new Path(copy.mPath);
1113            mRenderPath = new Path(copy.mRenderPath);
1114            mBaseWidth = copy.mBaseWidth;
1115            mBaseHeight = copy.mBaseHeight;
1116            mViewportWidth = copy.mViewportWidth;
1117            mViewportHeight = copy.mViewportHeight;
1118            mChangingConfigurations = copy.mChangingConfigurations;
1119            mRootAlpha = copy.mRootAlpha;
1120            mRootName = copy.mRootName;
1121            if (copy.mRootName != null) {
1122                mVGTargetsMap.put(copy.mRootName, this);
1123            }
1124        }
1125
1126        private void drawGroupTree(VGroup currentGroup, Matrix currentMatrix,
1127                                   Canvas canvas, int w, int h, ColorFilter filter) {
1128            // Calculate current group's matrix by preConcat the parent's and
1129            // and the current one on the top of the stack.
1130            // Basically the Mfinal = Mviewport * M0 * M1 * M2;
1131            // Mi the local matrix at level i of the group tree.
1132            currentGroup.mStackedMatrix.set(currentMatrix);
1133
1134            currentGroup.mStackedMatrix.preConcat(currentGroup.mLocalMatrix);
1135
1136            // Save the current clip information, which is local to this group.
1137            canvas.save();
1138
1139            // Draw the group tree in the same order as the XML file.
1140            for (int i = 0; i < currentGroup.mChildren.size(); i++) {
1141                Object child = currentGroup.mChildren.get(i);
1142                if (child instanceof VGroup) {
1143                    VGroup childGroup = (VGroup) child;
1144                    drawGroupTree(childGroup, currentGroup.mStackedMatrix,
1145                            canvas, w, h, filter);
1146                } else if (child instanceof VPath) {
1147                    VPath childPath = (VPath) child;
1148                    drawPath(currentGroup, childPath, canvas, w, h, filter);
1149                }
1150            }
1151
1152            canvas.restore();
1153        }
1154
1155        public void draw(Canvas canvas, int w, int h, ColorFilter filter) {
1156            // Traverse the tree in pre-order to draw.
1157            drawGroupTree(mRootGroup, IDENTITY_MATRIX, canvas, w, h, filter);
1158        }
1159
1160        private void drawPath(VGroup vGroup, VPath vPath, Canvas canvas, int w, int h,
1161                              ColorFilter filter) {
1162            final float scaleX = w / mViewportWidth;
1163            final float scaleY = h / mViewportHeight;
1164            final float minScale = Math.min(scaleX, scaleY);
1165            final Matrix groupStackedMatrix = vGroup.mStackedMatrix;
1166
1167            mFinalPathMatrix.set(groupStackedMatrix);
1168            mFinalPathMatrix.postScale(scaleX, scaleY);
1169
1170
1171            final float matrixScale = getMatrixScale(groupStackedMatrix);
1172            if (matrixScale == 0) {
1173                // When either x or y is scaled to 0, we don't need to draw anything.
1174                return;
1175            }
1176            vPath.toPath(mPath);
1177            final Path path = mPath;
1178
1179            mRenderPath.reset();
1180
1181            if (vPath.isClipPath()) {
1182                mRenderPath.addPath(path, mFinalPathMatrix);
1183                canvas.clipPath(mRenderPath);
1184            } else {
1185                VFullPath fullPath = (VFullPath) vPath;
1186                if (fullPath.mTrimPathStart != 0.0f || fullPath.mTrimPathEnd != 1.0f) {
1187                    float start = (fullPath.mTrimPathStart + fullPath.mTrimPathOffset) % 1.0f;
1188                    float end = (fullPath.mTrimPathEnd + fullPath.mTrimPathOffset) % 1.0f;
1189
1190                    if (mPathMeasure == null) {
1191                        mPathMeasure = new PathMeasure();
1192                    }
1193                    mPathMeasure.setPath(mPath, false);
1194
1195                    float len = mPathMeasure.getLength();
1196                    start = start * len;
1197                    end = end * len;
1198                    path.reset();
1199                    if (start > end) {
1200                        mPathMeasure.getSegment(start, len, path, true);
1201                        mPathMeasure.getSegment(0f, end, path, true);
1202                    } else {
1203                        mPathMeasure.getSegment(start, end, path, true);
1204                    }
1205                    path.rLineTo(0, 0); // fix bug in measure
1206                }
1207                mRenderPath.addPath(path, mFinalPathMatrix);
1208
1209                if (fullPath.mFillColor != Color.TRANSPARENT) {
1210                    if (mFillPaint == null) {
1211                        mFillPaint = new Paint();
1212                        mFillPaint.setStyle(Paint.Style.FILL);
1213                        mFillPaint.setAntiAlias(true);
1214                    }
1215
1216                    final Paint fillPaint = mFillPaint;
1217                    fillPaint.setColor(applyAlpha(fullPath.mFillColor, fullPath.mFillAlpha));
1218                    fillPaint.setColorFilter(filter);
1219                    canvas.drawPath(mRenderPath, fillPaint);
1220                }
1221
1222                if (fullPath.mStrokeColor != Color.TRANSPARENT) {
1223                    if (mStrokePaint == null) {
1224                        mStrokePaint = new Paint();
1225                        mStrokePaint.setStyle(Paint.Style.STROKE);
1226                        mStrokePaint.setAntiAlias(true);
1227                    }
1228
1229                    final Paint strokePaint = mStrokePaint;
1230                    if (fullPath.mStrokeLineJoin != null) {
1231                        strokePaint.setStrokeJoin(fullPath.mStrokeLineJoin);
1232                    }
1233
1234                    if (fullPath.mStrokeLineCap != null) {
1235                        strokePaint.setStrokeCap(fullPath.mStrokeLineCap);
1236                    }
1237
1238                    strokePaint.setStrokeMiter(fullPath.mStrokeMiterlimit);
1239                    strokePaint.setColor(applyAlpha(fullPath.mStrokeColor, fullPath.mStrokeAlpha));
1240                    strokePaint.setColorFilter(filter);
1241                    final float finalStrokeScale = minScale * matrixScale;
1242                    strokePaint.setStrokeWidth(fullPath.mStrokeWidth * finalStrokeScale);
1243                    canvas.drawPath(mRenderPath, strokePaint);
1244                }
1245            }
1246        }
1247
1248        private static float cross(float v1x, float v1y, float v2x, float v2y) {
1249            return v1x * v2y - v1y * v2x;
1250        }
1251
1252        private float getMatrixScale(Matrix groupStackedMatrix) {
1253            // Given unit vectors A = (0, 1) and B = (1, 0).
1254            // After matrix mapping, we got A' and B'. Let theta = the angel b/t A' and B'.
1255            // Therefore, the final scale we want is min(|A'| * sin(theta), |B'| * sin(theta)),
1256            // which is (|A'| * |B'| * sin(theta)) / max (|A'|, |B'|);
1257            // If  max (|A'|, |B'|) = 0, that means either x or y has a scale of 0.
1258            //
1259            // For non-skew case, which is most of the cases, matrix scale is computing exactly the
1260            // scale on x and y axis, and take the minimal of these two.
1261            // For skew case, an unit square will mapped to a parallelogram. And this function will
1262            // return the minimal height of the 2 bases.
1263            float[] unitVectors = new float[]{0, 1, 1, 0};
1264            groupStackedMatrix.mapVectors(unitVectors);
1265            float scaleX = (float) Math.hypot(unitVectors[0], unitVectors[1]);
1266            float scaleY = (float) Math.hypot(unitVectors[2], unitVectors[3]);
1267            float crossProduct = cross(unitVectors[0], unitVectors[1], unitVectors[2],
1268                    unitVectors[3]);
1269            float maxScale = Math.max(scaleX, scaleY);
1270
1271            float matrixScale = 0;
1272            if (maxScale > 0) {
1273                matrixScale = Math.abs(crossProduct) / maxScale;
1274            }
1275            if (DBG_VECTOR_DRAWABLE) {
1276                Log.d(LOGTAG, "Scale x " + scaleX + " y " + scaleY + " final " + matrixScale);
1277            }
1278            return matrixScale;
1279        }
1280    }
1281
1282    private static class VGroup {
1283        // mStackedMatrix is only used temporarily when drawing, it combines all
1284        // the parents' local matrices with the current one.
1285        private final Matrix mStackedMatrix = new Matrix();
1286
1287        /////////////////////////////////////////////////////
1288        // Variables below need to be copied (deep copy if applicable) for mutation.
1289        final ArrayList<Object> mChildren = new ArrayList<Object>();
1290
1291        float mRotate = 0;
1292        private float mPivotX = 0;
1293        private float mPivotY = 0;
1294        private float mScaleX = 1;
1295        private float mScaleY = 1;
1296        private float mTranslateX = 0;
1297        private float mTranslateY = 0;
1298
1299        // mLocalMatrix is updated based on the update of transformation information,
1300        // either parsed from the XML or by animation.
1301        private final Matrix mLocalMatrix = new Matrix();
1302        int mChangingConfigurations;
1303        private int[] mThemeAttrs;
1304        private String mGroupName = null;
1305
1306        public VGroup(VGroup copy, ArrayMap<String, Object> targetsMap) {
1307            mRotate = copy.mRotate;
1308            mPivotX = copy.mPivotX;
1309            mPivotY = copy.mPivotY;
1310            mScaleX = copy.mScaleX;
1311            mScaleY = copy.mScaleY;
1312            mTranslateX = copy.mTranslateX;
1313            mTranslateY = copy.mTranslateY;
1314            mThemeAttrs = copy.mThemeAttrs;
1315            mGroupName = copy.mGroupName;
1316            mChangingConfigurations = copy.mChangingConfigurations;
1317            if (mGroupName != null) {
1318                targetsMap.put(mGroupName, this);
1319            }
1320
1321            mLocalMatrix.set(copy.mLocalMatrix);
1322
1323            final ArrayList<Object> children = copy.mChildren;
1324            for (int i = 0; i < children.size(); i++) {
1325                Object copyChild = children.get(i);
1326                if (copyChild instanceof VGroup) {
1327                    VGroup copyGroup = (VGroup) copyChild;
1328                    mChildren.add(new VGroup(copyGroup, targetsMap));
1329                } else {
1330                    VPath newPath = null;
1331                    if (copyChild instanceof VFullPath) {
1332                        newPath = new VFullPath((VFullPath) copyChild);
1333                    } else if (copyChild instanceof VClipPath) {
1334                        newPath = new VClipPath((VClipPath) copyChild);
1335                    } else {
1336                        throw new IllegalStateException("Unknown object in the tree!");
1337                    }
1338                    mChildren.add(newPath);
1339                    if (newPath.mPathName != null) {
1340                        targetsMap.put(newPath.mPathName, newPath);
1341                    }
1342                }
1343            }
1344        }
1345
1346        public VGroup() {
1347        }
1348
1349        public String getGroupName() {
1350            return mGroupName;
1351        }
1352
1353        public Matrix getLocalMatrix() {
1354            return mLocalMatrix;
1355        }
1356
1357        public void inflate(Resources res, AttributeSet attrs, Theme theme, XmlPullParser parser) {
1358            final TypedArray a = obtainAttributes(res, theme, attrs,
1359                    AndroidResources.styleable_VectorDrawableGroup);
1360            updateStateFromTypedArray(a, parser);
1361            a.recycle();
1362        }
1363
1364        private void updateStateFromTypedArray(TypedArray a, XmlPullParser parser) {
1365            // Account for any configuration changes.
1366            // mChangingConfigurations |= Utils.getChangingConfigurations(a);
1367
1368            // Extract the theme attributes, if any.
1369            mThemeAttrs = null; // TODO TINT THEME Not supported yet a.extractThemeAttrs();
1370
1371            // This is added in API 11
1372            mRotate = TypedArrayUtils.getNamedFloat(a, parser, "rotation",
1373                    AndroidResources.styleable_VectorDrawableGroup_rotation, mRotate);
1374
1375            mPivotX = a.getFloat(AndroidResources.styleable_VectorDrawableGroup_pivotX, mPivotX);
1376            mPivotY = a.getFloat(AndroidResources.styleable_VectorDrawableGroup_pivotY, mPivotY);
1377
1378            // This is added in API 11
1379            mScaleX = TypedArrayUtils.getNamedFloat(a, parser, "scaleX",
1380                    AndroidResources.styleable_VectorDrawableGroup_scaleX, mScaleX);
1381
1382            // This is added in API 11
1383            mScaleY = TypedArrayUtils.getNamedFloat(a, parser, "scaleY",
1384                    AndroidResources.styleable_VectorDrawableGroup_scaleY, mScaleY);
1385
1386            mTranslateX = TypedArrayUtils.getNamedFloat(a, parser, "translateX",
1387                    AndroidResources.styleable_VectorDrawableGroup_translateX, mTranslateX);
1388            mTranslateY = TypedArrayUtils.getNamedFloat(a, parser, "translateY",
1389                    AndroidResources.styleable_VectorDrawableGroup_translateY, mTranslateY);
1390
1391            final String groupName =
1392                    a.getString(AndroidResources.styleable_VectorDrawableGroup_name);
1393            if (groupName != null) {
1394                mGroupName = groupName;
1395            }
1396
1397            updateLocalMatrix();
1398        }
1399
1400        private void updateLocalMatrix() {
1401            // The order we apply is the same as the
1402            // RenderNode.cpp::applyViewPropertyTransforms().
1403            mLocalMatrix.reset();
1404            mLocalMatrix.postTranslate(-mPivotX, -mPivotY);
1405            mLocalMatrix.postScale(mScaleX, mScaleY);
1406            mLocalMatrix.postRotate(mRotate, 0, 0);
1407            mLocalMatrix.postTranslate(mTranslateX + mPivotX, mTranslateY + mPivotY);
1408        }
1409
1410        /* Setters and Getters, used by animator from AnimatedVectorDrawable. */
1411        @SuppressWarnings("unused")
1412        public float getRotation() {
1413            return mRotate;
1414        }
1415
1416        @SuppressWarnings("unused")
1417        public void setRotation(float rotation) {
1418            if (rotation != mRotate) {
1419                mRotate = rotation;
1420                updateLocalMatrix();
1421            }
1422        }
1423
1424        @SuppressWarnings("unused")
1425        public float getPivotX() {
1426            return mPivotX;
1427        }
1428
1429        @SuppressWarnings("unused")
1430        public void setPivotX(float pivotX) {
1431            if (pivotX != mPivotX) {
1432                mPivotX = pivotX;
1433                updateLocalMatrix();
1434            }
1435        }
1436
1437        @SuppressWarnings("unused")
1438        public float getPivotY() {
1439            return mPivotY;
1440        }
1441
1442        @SuppressWarnings("unused")
1443        public void setPivotY(float pivotY) {
1444            if (pivotY != mPivotY) {
1445                mPivotY = pivotY;
1446                updateLocalMatrix();
1447            }
1448        }
1449
1450        @SuppressWarnings("unused")
1451        public float getScaleX() {
1452            return mScaleX;
1453        }
1454
1455        @SuppressWarnings("unused")
1456        public void setScaleX(float scaleX) {
1457            if (scaleX != mScaleX) {
1458                mScaleX = scaleX;
1459                updateLocalMatrix();
1460            }
1461        }
1462
1463        @SuppressWarnings("unused")
1464        public float getScaleY() {
1465            return mScaleY;
1466        }
1467
1468        @SuppressWarnings("unused")
1469        public void setScaleY(float scaleY) {
1470            if (scaleY != mScaleY) {
1471                mScaleY = scaleY;
1472                updateLocalMatrix();
1473            }
1474        }
1475
1476        @SuppressWarnings("unused")
1477        public float getTranslateX() {
1478            return mTranslateX;
1479        }
1480
1481        @SuppressWarnings("unused")
1482        public void setTranslateX(float translateX) {
1483            if (translateX != mTranslateX) {
1484                mTranslateX = translateX;
1485                updateLocalMatrix();
1486            }
1487        }
1488
1489        @SuppressWarnings("unused")
1490        public float getTranslateY() {
1491            return mTranslateY;
1492        }
1493
1494        @SuppressWarnings("unused")
1495        public void setTranslateY(float translateY) {
1496            if (translateY != mTranslateY) {
1497                mTranslateY = translateY;
1498                updateLocalMatrix();
1499            }
1500        }
1501    }
1502
1503    /**
1504     * Common Path information for clip path and normal path.
1505     */
1506    private static class VPath {
1507        protected PathParser.PathDataNode[] mNodes = null;
1508        String mPathName;
1509        int mChangingConfigurations;
1510
1511        public VPath() {
1512            // Empty constructor.
1513        }
1514
1515        public void printVPath(int level) {
1516            String indent = "";
1517            for (int i = 0; i < level; i++) {
1518                indent += "    ";
1519            }
1520            Log.v(LOGTAG, indent + "current path is :" + mPathName +
1521                    " pathData is " + NodesToString(mNodes));
1522
1523        }
1524
1525        public String NodesToString(PathParser.PathDataNode[] nodes) {
1526            String result = " ";
1527            for (int i = 0; i < nodes.length; i++) {
1528                result += nodes[i].type + ":";
1529                float[] params = nodes[i].params;
1530                for (int j = 0; j < params.length; j++) {
1531                    result += params[j] + ",";
1532                }
1533            }
1534            return result;
1535        }
1536
1537        public VPath(VPath copy) {
1538            mPathName = copy.mPathName;
1539            mChangingConfigurations = copy.mChangingConfigurations;
1540            mNodes = PathParser.deepCopyNodes(copy.mNodes);
1541        }
1542
1543        public void toPath(Path path) {
1544            path.reset();
1545            if (mNodes != null) {
1546                PathParser.PathDataNode.nodesToPath(mNodes, path);
1547            }
1548        }
1549
1550        public String getPathName() {
1551            return mPathName;
1552        }
1553
1554        public boolean canApplyTheme() {
1555            return false;
1556        }
1557
1558        public void applyTheme(Theme t) {
1559        }
1560
1561        public boolean isClipPath() {
1562            return false;
1563        }
1564
1565        /* Setters and Getters, used by animator from AnimatedVectorDrawable. */
1566        @SuppressWarnings("unused")
1567        public PathParser.PathDataNode[] getPathData() {
1568            return mNodes;
1569        }
1570
1571        @SuppressWarnings("unused")
1572        public void setPathData(PathParser.PathDataNode[] nodes) {
1573            if (!PathParser.canMorph(mNodes, nodes)) {
1574                // This should not happen in the middle of animation.
1575                mNodes = PathParser.deepCopyNodes(nodes);
1576            } else {
1577                PathParser.updateNodes(mNodes, nodes);
1578            }
1579        }
1580    }
1581
1582    /**
1583     * Clip path, which only has name and pathData.
1584     */
1585    private static class VClipPath extends VPath {
1586        public VClipPath() {
1587            // Empty constructor.
1588        }
1589
1590        public VClipPath(VClipPath copy) {
1591            super(copy);
1592        }
1593
1594        public void inflate(Resources r, AttributeSet attrs, Theme theme, XmlPullParser parser) {
1595            // TODO TINT THEME Not supported yet
1596            final boolean hasPathData = TypedArrayUtils.hasAttribute(parser, "pathData");
1597            if (!hasPathData) {
1598                return;
1599            }
1600            final TypedArray a = obtainAttributes(r, theme, attrs,
1601                    AndroidResources.styleable_VectorDrawableClipPath);
1602            updateStateFromTypedArray(a);
1603            a.recycle();
1604        }
1605
1606        private void updateStateFromTypedArray(TypedArray a) {
1607            // Account for any configuration changes.
1608            // mChangingConfigurations |= Utils.getChangingConfigurations(a);;
1609
1610            final String pathName =
1611                    a.getString(AndroidResources.styleable_VectorDrawableClipPath_name);
1612            if (pathName != null) {
1613                mPathName = pathName;
1614            }
1615
1616            final String pathData =
1617                    a.getString(AndroidResources.styleable_VectorDrawableClipPath_pathData);
1618            if (pathData != null) {
1619                mNodes = PathParser.createNodesFromPathData(pathData);
1620            }
1621        }
1622
1623        @Override
1624        public boolean isClipPath() {
1625            return true;
1626        }
1627    }
1628
1629    /**
1630     * Normal path, which contains all the fill / paint information.
1631     */
1632    private static class VFullPath extends VPath {
1633        /////////////////////////////////////////////////////
1634        // Variables below need to be copied (deep copy if applicable) for mutation.
1635        private int[] mThemeAttrs;
1636
1637        int mStrokeColor = Color.TRANSPARENT;
1638        float mStrokeWidth = 0;
1639
1640        int mFillColor = Color.TRANSPARENT;
1641        float mStrokeAlpha = 1.0f;
1642        int mFillRule;
1643        float mFillAlpha = 1.0f;
1644        float mTrimPathStart = 0;
1645        float mTrimPathEnd = 1;
1646        float mTrimPathOffset = 0;
1647
1648        Paint.Cap mStrokeLineCap = Paint.Cap.BUTT;
1649        Paint.Join mStrokeLineJoin = Paint.Join.MITER;
1650        float mStrokeMiterlimit = 4;
1651
1652        public VFullPath() {
1653            // Empty constructor.
1654        }
1655
1656        public VFullPath(VFullPath copy) {
1657            super(copy);
1658            mThemeAttrs = copy.mThemeAttrs;
1659
1660            mStrokeColor = copy.mStrokeColor;
1661            mStrokeWidth = copy.mStrokeWidth;
1662            mStrokeAlpha = copy.mStrokeAlpha;
1663            mFillColor = copy.mFillColor;
1664            mFillRule = copy.mFillRule;
1665            mFillAlpha = copy.mFillAlpha;
1666            mTrimPathStart = copy.mTrimPathStart;
1667            mTrimPathEnd = copy.mTrimPathEnd;
1668            mTrimPathOffset = copy.mTrimPathOffset;
1669
1670            mStrokeLineCap = copy.mStrokeLineCap;
1671            mStrokeLineJoin = copy.mStrokeLineJoin;
1672            mStrokeMiterlimit = copy.mStrokeMiterlimit;
1673        }
1674
1675        private Paint.Cap getStrokeLineCap(int id, Paint.Cap defValue) {
1676            switch (id) {
1677                case LINECAP_BUTT:
1678                    return Paint.Cap.BUTT;
1679                case LINECAP_ROUND:
1680                    return Paint.Cap.ROUND;
1681                case LINECAP_SQUARE:
1682                    return Paint.Cap.SQUARE;
1683                default:
1684                    return defValue;
1685            }
1686        }
1687
1688        private Paint.Join getStrokeLineJoin(int id, Paint.Join defValue) {
1689            switch (id) {
1690                case LINEJOIN_MITER:
1691                    return Paint.Join.MITER;
1692                case LINEJOIN_ROUND:
1693                    return Paint.Join.ROUND;
1694                case LINEJOIN_BEVEL:
1695                    return Paint.Join.BEVEL;
1696                default:
1697                    return defValue;
1698            }
1699        }
1700
1701        @Override
1702        public boolean canApplyTheme() {
1703            return mThemeAttrs != null;
1704        }
1705
1706        public void inflate(Resources r, AttributeSet attrs, Theme theme, XmlPullParser parser) {
1707            final TypedArray a = obtainAttributes(r, theme, attrs,
1708                    AndroidResources.styleable_VectorDrawablePath);
1709            updateStateFromTypedArray(a, parser);
1710            a.recycle();
1711        }
1712
1713        private void updateStateFromTypedArray(TypedArray a, XmlPullParser parser) {
1714            // Account for any configuration changes.
1715            // mChangingConfigurations |= Utils.getChangingConfigurations(a);
1716
1717            // Extract the theme attributes, if any.
1718            mThemeAttrs = null; // TODO TINT THEME Not supported yet a.extractThemeAttrs();
1719
1720            // In order to work around the conflicting id issue, we need to double check the
1721            // existence of the attribute.
1722            // B/c if the attribute existed in the compiled XML, then calling TypedArray will be
1723            // safe since the framework will look up in the XML first.
1724            // Note that each getAttributeValue take roughly 0.03ms, it is a price we have to pay.
1725            final boolean hasPathData = TypedArrayUtils.hasAttribute(parser, "pathData");
1726            if (!hasPathData) {
1727                // If there is no pathData in the <path> tag, then this is an empty path,
1728                // nothing need to be drawn.
1729                return;
1730            }
1731
1732            final String pathName = a.getString(AndroidResources.styleable_VectorDrawablePath_name);
1733            if (pathName != null) {
1734                mPathName = pathName;
1735            }
1736            final String pathData =
1737                    a.getString(AndroidResources.styleable_VectorDrawablePath_pathData);
1738            if (pathData != null) {
1739                mNodes = PathParser.createNodesFromPathData(pathData);
1740            }
1741
1742            mFillColor = TypedArrayUtils.getNamedColor(a, parser, "fillColor",
1743                    AndroidResources.styleable_VectorDrawablePath_fillColor, mFillColor);
1744            mFillAlpha = TypedArrayUtils.getNamedFloat(a, parser, "fillAlpha",
1745                    AndroidResources.styleable_VectorDrawablePath_fillAlpha, mFillAlpha);
1746            final int lineCap = TypedArrayUtils.getNamedInt(a, parser, "strokeLineCap",
1747                    AndroidResources.styleable_VectorDrawablePath_strokeLineCap, -1);
1748            mStrokeLineCap = getStrokeLineCap(lineCap, mStrokeLineCap);
1749            final int lineJoin = TypedArrayUtils.getNamedInt(a, parser, "strokeLineJoin",
1750                    AndroidResources.styleable_VectorDrawablePath_strokeLineJoin, -1);
1751            mStrokeLineJoin = getStrokeLineJoin(lineJoin, mStrokeLineJoin);
1752            mStrokeMiterlimit = TypedArrayUtils.getNamedFloat(a, parser, "strokeMiterLimit",
1753                    AndroidResources.styleable_VectorDrawablePath_strokeMiterLimit,
1754                    mStrokeMiterlimit);
1755            mStrokeColor = TypedArrayUtils.getNamedColor(a, parser, "strokeColor",
1756                    AndroidResources.styleable_VectorDrawablePath_strokeColor, mStrokeColor);
1757            mStrokeAlpha = TypedArrayUtils.getNamedFloat(a, parser, "strokeAlpha",
1758                    AndroidResources.styleable_VectorDrawablePath_strokeAlpha, mStrokeAlpha);
1759            mStrokeWidth = TypedArrayUtils.getNamedFloat(a, parser, "strokeWidth",
1760                    AndroidResources.styleable_VectorDrawablePath_strokeWidth, mStrokeWidth);
1761            mTrimPathEnd = TypedArrayUtils.getNamedFloat(a, parser, "trimPathEnd",
1762                    AndroidResources.styleable_VectorDrawablePath_trimPathEnd, mTrimPathEnd);
1763            mTrimPathOffset = TypedArrayUtils.getNamedFloat(a, parser, "trimPathOffset",
1764                    AndroidResources.styleable_VectorDrawablePath_trimPathOffset, mTrimPathOffset);
1765            mTrimPathStart = TypedArrayUtils.getNamedFloat(a, parser, "trimPathStart",
1766                    AndroidResources.styleable_VectorDrawablePath_trimPathStart, mTrimPathStart);
1767        }
1768
1769        @Override
1770        public void applyTheme(Theme t) {
1771            if (mThemeAttrs == null) {
1772                return;
1773            }
1774
1775            /*
1776             * TODO TINT THEME Not supported yet final TypedArray a =
1777             * t.resolveAttributes(mThemeAttrs, styleable_VectorDrawablePath);
1778             * updateStateFromTypedArray(a); a.recycle();
1779             */
1780        }
1781
1782        /* Setters and Getters, used by animator from AnimatedVectorDrawable. */
1783        @SuppressWarnings("unused")
1784        int getStrokeColor() {
1785            return mStrokeColor;
1786        }
1787
1788        @SuppressWarnings("unused")
1789        void setStrokeColor(int strokeColor) {
1790            mStrokeColor = strokeColor;
1791        }
1792
1793        @SuppressWarnings("unused")
1794        float getStrokeWidth() {
1795            return mStrokeWidth;
1796        }
1797
1798        @SuppressWarnings("unused")
1799        void setStrokeWidth(float strokeWidth) {
1800            mStrokeWidth = strokeWidth;
1801        }
1802
1803        @SuppressWarnings("unused")
1804        float getStrokeAlpha() {
1805            return mStrokeAlpha;
1806        }
1807
1808        @SuppressWarnings("unused")
1809        void setStrokeAlpha(float strokeAlpha) {
1810            mStrokeAlpha = strokeAlpha;
1811        }
1812
1813        @SuppressWarnings("unused")
1814        int getFillColor() {
1815            return mFillColor;
1816        }
1817
1818        @SuppressWarnings("unused")
1819        void setFillColor(int fillColor) {
1820            mFillColor = fillColor;
1821        }
1822
1823        @SuppressWarnings("unused")
1824        float getFillAlpha() {
1825            return mFillAlpha;
1826        }
1827
1828        @SuppressWarnings("unused")
1829        void setFillAlpha(float fillAlpha) {
1830            mFillAlpha = fillAlpha;
1831        }
1832
1833        @SuppressWarnings("unused")
1834        float getTrimPathStart() {
1835            return mTrimPathStart;
1836        }
1837
1838        @SuppressWarnings("unused")
1839        void setTrimPathStart(float trimPathStart) {
1840            mTrimPathStart = trimPathStart;
1841        }
1842
1843        @SuppressWarnings("unused")
1844        float getTrimPathEnd() {
1845            return mTrimPathEnd;
1846        }
1847
1848        @SuppressWarnings("unused")
1849        void setTrimPathEnd(float trimPathEnd) {
1850            mTrimPathEnd = trimPathEnd;
1851        }
1852
1853        @SuppressWarnings("unused")
1854        float getTrimPathOffset() {
1855            return mTrimPathOffset;
1856        }
1857
1858        @SuppressWarnings("unused")
1859        void setTrimPathOffset(float trimPathOffset) {
1860            mTrimPathOffset = trimPathOffset;
1861        }
1862    }
1863}
1864