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