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