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