VectorDrawable.java revision 4b3988d159e1c9faa2a7e16c9aca9951264bb429
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.animation.ObjectAnimator;
18import android.animation.ValueAnimator;
19import android.content.res.Resources;
20import android.content.res.Resources.Theme;
21import android.content.res.TypedArray;
22import android.graphics.Canvas;
23import android.graphics.ColorFilter;
24import android.graphics.Matrix;
25import android.graphics.Paint;
26import android.graphics.Path;
27import android.graphics.PathMeasure;
28import android.graphics.PixelFormat;
29import android.graphics.Rect;
30import android.graphics.Region;
31import android.util.AttributeSet;
32import android.util.Log;
33import android.view.animation.AccelerateDecelerateInterpolator;
34import android.view.animation.Interpolator;
35import android.view.animation.LinearInterpolator;
36
37import com.android.internal.R;
38
39import org.xmlpull.v1.XmlPullParser;
40import org.xmlpull.v1.XmlPullParserException;
41
42import java.io.IOException;
43import java.util.ArrayList;
44import java.util.Arrays;
45import java.util.Collection;
46import java.util.HashMap;
47import java.util.HashSet;
48
49/**
50 * This lets you create a drawable based on an XML vector graphic
51 * It can be defined in an XML file with the <code>&lt;vector></code> element.
52 * <p/>
53 * The vector drawable has 6 elements:
54 * <p/>
55 * <dl>
56 * <dt><code>&lt;vector></code></dt>
57 * <dd>The attribute <code>android:trigger</code> defines a state change that
58 * will drive the animation </dd>
59 * <dd>The attribute <code>android:versionCode</code> defines the version of
60 * VectorDrawable </dd>
61 * <dt><code>&lt;size></code></dt>
62 * <dd>Used to defined the intrinsic Width Height size of the drawable using
63 * <code>android:width</code> and <code>android:height</code> </dd>
64 * <dt><code>&lt;viewport></code></dt>
65 * <dd>Used to defined the size of the virtual canvas the paths are drawn on.
66 * The size is defined using the attributes <code>android:viewportHeight
67 * </code> <code>android:viewportWidth</code></dd>
68 * <dt><code>&lt;group></code></dt>
69 * <dd>Defines a "key frame" in the animation if there is only one group the
70 * drawable is static 2D image that has no animation.</dd>
71 * <dt><code>&lt;path></code></dt>
72 * <dd>Defines paths to be drawn. The path elements must be within a group
73 * <dl>
74 * <dt><code>android:name</code>
75 * <dd>Defines the name of the path.</dd></dt>
76 * <dt><code>android:pathData</code>
77 * <dd>Defines path string.</dd></dt>
78 * <dt><code>android:fill</code>
79 * <dd>Defines the color to fill the path (none if not present).</dd></dt>
80 * <dt><code>android:stroke</code>
81 * <dd>Defines the color to draw the path outline (none if not present).</dd></dt>
82 * <dt><code>android:strokeWidth</code>
83 * <dd>The width a path stroke</dd></dt>
84 * <dt><code>android:strokeOpacity</code>
85 * <dd>The opacity of a path stroke</dd></dt>
86 * <dt><code>android:rotation</code>
87 * <dd>The amount to rotation the path stroke.</dd></dt>
88 * <dt><code>android:pivotX</code>
89 * <dd>The X coordinate of the center of rotation of a path</dd></dt>
90 * <dt><code>android:pivotY</code>
91 * <dd>The Y coordinate of the center of rotation of a path</dd></dt>
92 * <dt><code>android:fillOpacity</code>
93 * <dd>The opacity to fill the path with</dd></dt>
94 * <dt><code>android:trimPathStart</code>
95 * <dd>The fraction of the path to trim from the start from 0 to 1</dd></dt>
96 * <dt><code>android:trimPathEnd</code>
97 * <dd>The fraction of the path to trim from the end from 0 to 1</dd></dt>
98 * <dt><code>android:trimPathOffset</code>
99 * <dd>Shift trim region (allows showed region to include the start and end) from 0 to 1</dd></dt>
100 * <dt><code>android:clipToPath</code>
101 * <dd>Path will set the clip path</dd></dt>
102 * <dt><code>android:strokeLineCap</code>
103 * <dd>Sets the linecap for a stroked path: butt, round, square</dd></dt>
104 * <dt><code>android:strokeLineJoin</code>
105 * <dd>Sets the lineJoin for a stroked path: miter,round,bevel</dd></dt>
106 * <dt><code>android:strokeMiterLimit</code>
107 * <dd>Sets the Miter limit for a stroked path</dd></dt>
108 * <dt><code>android:state_pressed</code>
109 * <dd>Sets a condition to be met to draw path</dd></dt>
110 * <dt><code>android:state_focused</code>
111 * <dd>Sets a condition to be met to draw path</dd></dt>
112 * <dt><code>android:state_selected</code>
113 * <dd>Sets a condition to be met to draw path</dd></dt>
114 * <dt><code>android:state_window_focused</code>
115 * <dd>Sets a condition to be met to draw path</dd></dt>
116 * <dt><code>android:state_enabled</code>
117 * <dd>Sets a condition to be met to draw path</dd></dt>
118 * <dt><code>android:state_activated</code>
119 * <dd>Sets a condition to be met to draw path</dd></dt>
120 * <dt><code>android:state_accelerated</code>
121 * <dd>Sets a condition to be met to draw path</dd></dt>
122 * <dt><code>android:state_hovered</code>
123 * <dd>Sets a condition to be met to draw path</dd></dt>
124 * <dt><code>android:state_checked</code>
125 * <dd>Sets a condition to be met to draw path</dd></dt>
126 * <dt><code>android:state_checkable</code>
127 * <dd>Sets a condition to be met to draw path</dd></dt>
128 * </dl>
129 * </dd>
130 * <dt><code>&lt;animation></code></dt>
131 * <dd>Used to customize the transition between two paths
132 * <dl>
133 * <dt><code>android:sequence</code>
134 * <dd>Configures this animation sequence between the named paths.</dd></dt>
135 * <dt><code>android:limitTo</code>
136 * <dd>Limits an animation to only interpolate the selected variable
137 * unlimited, path, rotation, trimPathStart, trimPathEnd, trimPathOffset</dd></dt>
138 * <dt><code>android:repeatCount</code>
139 * <dd>Number of times to loop this aspect of the animation</dd></dt>
140 * <dt><code>android:durations</code>
141 * <dd>The duration of each step in the animation in milliseconds
142 * Must contain the number of named paths - 1</dd></dt>
143 * <dt><code>android:startDelay</code>
144 * <dd></dd></dt>
145 * <dt><code>android:repeatStyle</code>
146 *  <dd>when repeating how does it repeat back and forth or a to b: forward, inAndOut</dd></dt>
147 * <dt><code>android:animate</code>
148 *  <dd>linear, accelerate, decelerate, easing</dd></dt>
149 * </dl>
150 * </dd>
151 */
152public class VectorDrawable extends Drawable {
153    private static final String LOGTAG = VectorDrawable.class.getSimpleName();
154
155    private static final String SHAPE_SIZE = "size";
156    private static final String SHAPE_VIEWPORT = "viewport";
157    private static final String SHAPE_GROUP = "group";
158    private static final String SHAPE_PATH = "path";
159    private static final String SHAPE_TRANSITION = "transition";
160    private static final String SHAPE_ANIMATION = "animation";
161    private static final String SHAPE_VECTOR = "vector";
162
163    private static final int LINECAP_BUTT = 0;
164    private static final int LINECAP_ROUND = 1;
165    private static final int LINECAP_SQUARE = 2;
166
167    private static final int LINEJOIN_MITER = 0;
168    private static final int LINEJOIN_ROUND = 1;
169    private static final int LINEJOIN_BEVEL = 2;
170
171    private static final int DEFAULT_DURATION = 1000;
172    private static final long DEFAULT_INFINITE_DURATION = 60 * 60 * 1000;
173
174    private final VectorDrawableState mVectorState;
175
176    private int mAlpha = 0xFF;
177
178    public VectorDrawable() {
179        mVectorState = new VectorDrawableState(null);
180        mVectorState.mBasicAnimator = ObjectAnimator.ofFloat(this, "AnimationFraction", 0, 1);
181
182        setDuration(DEFAULT_DURATION);
183    }
184
185    private VectorDrawable(VectorDrawableState state, Resources res, Theme theme) {
186        mVectorState = new VectorDrawableState(state);
187        mVectorState.mBasicAnimator = ObjectAnimator.ofFloat(this, "AnimationFraction", 0, 1);
188
189        if (theme != null && canApplyTheme()) {
190            applyTheme(theme);
191        }
192
193        long duration = mVectorState.mVAnimatedPath.getTotalAnimationDuration();
194        if (duration == -1) { // if it set to infinite set to 1 hour
195            duration = DEFAULT_INFINITE_DURATION; // TODO define correct approach for infinite
196            mVectorState.mBasicAnimator.setFloatValues(0, duration / 1000);
197            mVectorState.mBasicAnimator.setInterpolator(new LinearInterpolator());
198        }
199        setDuration(duration);
200    }
201
202    @Override
203    public ConstantState getConstantState() {
204        return mVectorState;
205    }
206
207    /**
208     * Starts the animation.
209     */
210    public void start() {
211        mVectorState.mBasicAnimator.start();
212    }
213
214    /**
215     * Stops the animation.
216     */
217    public void stop() {
218        mVectorState.mBasicAnimator.end();
219    }
220
221    /**
222     * Returns the current completion value for the animation.
223     *
224     * @return the current point on the animation, typically between 0 and 1
225     */
226    public float geAnimationFraction() {
227        return mVectorState.mVAnimatedPath.getValue();
228    }
229
230    /**
231     * Set the current completion value for the animation.
232     *
233     * @param value the point along the animation, typically between 0 and 1
234     */
235    public void setAnimationFraction(float value) {
236        mVectorState.mVAnimatedPath.setAnimationFraction(value);
237        invalidateSelf();
238    }
239
240    /**
241     * set the amount of time the animation will take
242     *
243     * @param duration amount of time in milliseconds
244     */
245    public void setDuration(long duration) {
246        mVectorState.mBasicAnimator.setDuration(duration);
247    }
248
249    /**
250     * Defines what this animation should do when it reaches the end. This
251     * setting is applied only when the repeat count is either greater than
252     * 0 or {@link ValueAnimator#INFINITE}.
253     *
254     * @param mode the animation mode, either {@link ValueAnimator#RESTART}
255     *        or {@link ValueAnimator#REVERSE}
256     */
257    public void setRepeatMode(int mode) {
258        mVectorState.mBasicAnimator.setRepeatMode(mode);
259    }
260
261    /**
262     * Sets animation to repeat
263     *
264     * @param repeat True if this drawable repeats its animation
265     */
266    public void setRepeatCount(int repeat) {
267        mVectorState.mBasicAnimator.setRepeatCount(repeat);
268    }
269
270    /**
271     * @return the animation repeat count, either a value greater than 0 or
272     *         {@link ValueAnimator#INFINITE}
273     */
274    public int getRepeatCount() {
275        return mVectorState.mBasicAnimator.getRepeatCount();
276    }
277
278    @Override
279    public boolean isStateful() {
280        return true;
281    }
282
283    @Override
284    protected boolean onStateChange(int[] state) {
285        super.onStateChange(state);
286
287        mVectorState.mVAnimatedPath.setState(state);
288
289        final int direction = mVectorState.mVAnimatedPath.getTrigger(state);
290        if (direction > 0) {
291            animateForward();
292        } else if (direction < 0) {
293            animateBackward();
294        }
295
296        invalidateSelf();
297        return true;
298    }
299
300    private void animateForward(){
301        if (!mVectorState.mBasicAnimator.isStarted()) {
302            mVectorState.mBasicAnimator.setFloatValues(0,1);
303            start();
304        }
305    }
306
307    private void animateBackward(){
308        if (!mVectorState.mBasicAnimator.isStarted()) {
309            mVectorState.mBasicAnimator.setFloatValues(.99f,0);
310            start();
311        }
312    }
313
314    @Override
315    public void draw(Canvas canvas) {
316        final int saveCount = canvas.save();
317        final Rect bounds = getBounds();
318        canvas.translate(bounds.left, bounds.top);
319        mVectorState.mVAnimatedPath.draw(canvas, bounds.width(), bounds.height());
320        canvas.restoreToCount(saveCount);
321    }
322
323    @Override
324    public void setAlpha(int alpha) {
325        // TODO correct handling of transparent
326        if (mAlpha != alpha) {
327            mAlpha = alpha;
328            invalidateSelf();
329        }
330    }
331
332    @Override
333    public void setColorFilter(ColorFilter colorFilter) {
334        // TODO: support color filter
335    }
336
337    @Override
338    public int getOpacity() {
339        return PixelFormat.TRANSLUCENT;
340    }
341
342    /**
343     * Sets padding for this shape, defined by a Rect object. Define the padding in the Rect object
344     * as: left, top, right, bottom.
345     */
346    public void setPadding(Rect padding) {
347        setPadding(padding.left, padding.top, padding.right, padding.bottom);
348    }
349
350    /**
351     * Sets padding for the shape.
352     *
353     * @param left padding for the left side (in pixels)
354     * @param top padding for the top (in pixels)
355     * @param right padding for the right side (in pixels)
356     * @param bottom padding for the bottom (in pixels)
357     */
358    public void setPadding(int left, int top, int right, int bottom) {
359        if ((left | top | right | bottom) == 0) {
360            mVectorState.mPadding = null;
361        } else {
362            if (mVectorState.mPadding == null) {
363                mVectorState.mPadding = new Rect();
364            }
365            mVectorState.mPadding.set(left, top, right, bottom);
366        }
367        invalidateSelf();
368    }
369
370    @Override
371    public int getIntrinsicWidth() {
372        return (int) mVectorState.mVAnimatedPath.mBaseWidth;
373    }
374
375    @Override
376    public int getIntrinsicHeight() {
377        return (int) mVectorState.mVAnimatedPath.mBaseHeight;
378    }
379
380    @Override
381    public boolean getPadding(Rect padding) {
382        if (mVectorState.mPadding != null) {
383            padding.set(mVectorState.mPadding);
384            return true;
385        } else {
386            return super.getPadding(padding);
387        }
388    }
389
390    @Override
391    public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)
392            throws XmlPullParserException, IOException {
393        final VAnimatedPath p = inflateInternal(res, parser, attrs, theme);
394        setAnimatedPath(p);
395    }
396
397    @Override
398    public boolean canApplyTheme() {
399        return super.canApplyTheme() || mVectorState != null && mVectorState.canApplyTheme();
400    }
401
402    @Override
403    public void applyTheme(Theme t) {
404        super.applyTheme(t);
405
406        final VectorDrawableState state = mVectorState;
407        final VAnimatedPath path = state.mVAnimatedPath;
408        if (path != null && path.canApplyTheme()) {
409            path.applyTheme(t);
410        }
411    }
412
413    private VAnimatedPath inflateInternal(Resources res, XmlPullParser parser, AttributeSet attrs,
414            Theme theme) throws XmlPullParserException, IOException {
415        final VAnimatedPath animatedPath = new VAnimatedPath();
416
417        boolean noSizeTag = true;
418        boolean noViewportTag = true;
419        boolean noGroupTag = true;
420        boolean noPathTag = true;
421
422        VGroup currentGroup = null;
423
424        int eventType = parser.getEventType();
425        while (eventType != XmlPullParser.END_DOCUMENT) {
426            if (eventType == XmlPullParser.START_TAG) {
427                final String tagName = parser.getName();
428                if (SHAPE_PATH.equals(tagName)) {
429                    final VPath path = new VPath();
430                    path.inflate(res, attrs, theme);
431                    currentGroup.add(path);
432                    noPathTag = false;
433                } else if (SHAPE_ANIMATION.equals(tagName)) {
434                    final VAnimation anim = new VAnimation();
435                    anim.inflate(animatedPath.mGroupList, res, attrs, theme);
436                    animatedPath.addAnimation(anim);
437                } else if (SHAPE_SIZE.equals(tagName)) {
438                    animatedPath.parseSize(res, attrs);
439                    noSizeTag = false;
440                } else if (SHAPE_VIEWPORT.equals(tagName)) {
441                    animatedPath.parseViewport(res, attrs);
442                    noViewportTag = false;
443                } else if (SHAPE_GROUP.equals(tagName)) {
444                    currentGroup = new VGroup();
445                    animatedPath.mGroupList.add(currentGroup);
446                    noGroupTag = false;
447                }  else if (SHAPE_VECTOR.equals(tagName)) {
448                    final TypedArray a = res.obtainAttributes(attrs, R.styleable.VectorDrawable);
449                    animatedPath.setTrigger(a.getInteger(R.styleable.VectorDrawable_trigger, 0));
450
451                    // Parsing the version information.
452                    // Right now, we only support version "1".
453                    // If the xml didn't specify the version number, the default version is "1".
454                    final int versionCode = a.getInt(R.styleable.VectorDrawable_versionCode, 1);
455                    if (versionCode != 1) {
456                        throw new IllegalArgumentException(
457                                "So far, VectorDrawable only support version 1");
458                    }
459
460                    a.recycle();
461                }
462            }
463
464            eventType = parser.next();
465        }
466
467        if (noSizeTag || noViewportTag || noGroupTag || noPathTag) {
468            final StringBuffer tag = new StringBuffer();
469
470            if (noSizeTag) {
471                tag.append(SHAPE_SIZE);
472            }
473
474            if  (noViewportTag){
475                if (tag.length()>0) {
476                    tag.append(" & ");
477                }
478                tag.append(SHAPE_SIZE);
479            }
480
481            if  (noGroupTag){
482                if (tag.length()>0) {
483                    tag.append(" & ");
484                }
485                tag.append(SHAPE_GROUP);
486            }
487
488            if  (noPathTag){
489                if (tag.length()>0) {
490                    tag.append(" or ");
491                }
492                tag.append(SHAPE_PATH);
493            }
494
495            throw new XmlPullParserException("no " + tag + " defined");
496        }
497
498        // post parse cleanup
499        animatedPath.parseFinish();
500        return animatedPath;
501    }
502
503    private void setAnimatedPath(VAnimatedPath animatedPath) {
504        mVectorState.mVAnimatedPath = animatedPath;
505
506        long duration = mVectorState.mVAnimatedPath.getTotalAnimationDuration();
507        if (duration == -1) { // if it set to infinite set to 1 hour
508            duration = DEFAULT_INFINITE_DURATION; // TODO define correct approach for infinite
509            mVectorState.mBasicAnimator.setFloatValues(0, duration / 1000);
510            mVectorState.mBasicAnimator.setInterpolator(new LinearInterpolator());
511        }
512
513        setDuration(duration);
514        setAnimationFraction(0);
515    }
516
517    @Override
518    public boolean setVisible(boolean visible, boolean restart) {
519        boolean changed = super.setVisible(visible, restart);
520        if (visible) {
521            if (changed || restart) {
522                setAnimationFraction(0);
523            }
524        } else {
525            stop();
526        }
527        return changed;
528    }
529
530    private static class VectorDrawableState extends ConstantState {
531        int mChangingConfigurations;
532        ValueAnimator mBasicAnimator;
533        VAnimatedPath mVAnimatedPath;
534        Rect mPadding;
535
536        public VectorDrawableState(VectorDrawableState copy) {
537            if (copy != null) {
538                mChangingConfigurations = copy.mChangingConfigurations;
539                mVAnimatedPath = new VAnimatedPath(copy.mVAnimatedPath);
540                mPadding = new Rect(copy.mPadding);
541            }
542        }
543
544        @Override
545        public Drawable newDrawable() {
546            return new VectorDrawable(this, null, null);
547        }
548
549        @Override
550        public Drawable newDrawable(Resources res) {
551            return new VectorDrawable(this, res, null);
552        }
553
554        @Override
555        public Drawable newDrawable(Resources res, Theme theme) {
556            return new VectorDrawable(this, res, theme);
557        }
558
559        @Override
560        public int getChangingConfigurations() {
561            return mChangingConfigurations;
562        }
563    }
564
565    private static class VAnimatedPath {
566        private static final int [] TRIGGER_MAP = {
567                0,
568                R.attr.state_pressed,
569                R.attr.state_focused,
570                R.attr.state_hovered,
571                R.attr.state_selected,
572                R.attr.state_checkable,
573                R.attr.state_checked,
574                R.attr.state_activated,
575                R.attr.state_focused
576        };
577
578        private final Path mPath = new Path();
579        private final Path mRenderPath = new Path();
580        private final Matrix mMatrix = new Matrix();
581
582        private ArrayList<VAnimation> mCurrentAnimList;
583        private VPath[] mCurrentPaths;
584        private Paint mStrokePaint;
585        private Paint mFillPaint;
586        private PathMeasure mPathMeasure;
587
588        private int[] mCurrentState = new int[0];
589        private float mAnimationValue;
590        private long mTotalDuration;
591        private int mTrigger;
592        private boolean mTriggerState;
593
594        final ArrayList<VGroup> mGroupList = new ArrayList<VGroup>();
595
596        float mBaseWidth = 1;
597        float mBaseHeight = 1;
598        float mViewportWidth;
599        float mViewportHeight;
600
601        public VAnimatedPath() {
602        }
603
604        public VAnimatedPath(VAnimatedPath copy) {
605            mCurrentAnimList = new ArrayList<VAnimation>(copy.mCurrentAnimList);
606            mGroupList.addAll(copy.mGroupList);
607            if (copy.mCurrentPaths != null) {
608                mCurrentPaths = new VPath[copy.mCurrentPaths.length];
609                for (int i = 0; i < mCurrentPaths.length; i++) {
610                    mCurrentPaths[i] = new VPath(copy.mCurrentPaths[i]);
611                }
612            }
613            mAnimationValue = copy.mAnimationValue; // time goes from 0 to 1
614
615            mBaseWidth = copy.mBaseWidth;
616            mBaseHeight = copy.mBaseHeight;
617            mViewportWidth = copy.mViewportHeight;
618            mViewportHeight = copy.mViewportHeight;
619            mTotalDuration = copy.mTotalDuration;
620            mTrigger = copy.mTrigger;
621            mCurrentState = new int[0];
622        }
623
624        public boolean canApplyTheme() {
625            final ArrayList<VGroup> groups = mGroupList;
626            for (int i = groups.size() - 1; i >= 0; i--) {
627                final ArrayList<VPath> paths = groups.get(i).mVGList;
628                for (int j = paths.size() - 1; j >= 0; j--) {
629                    final VPath path = paths.get(j);
630                    if (path.canApplyTheme()) {
631                        return true;
632                    }
633                }
634            }
635
636            final ArrayList<VAnimation> anims = mCurrentAnimList;
637            for (int i = anims.size() - 1; i >= 0; i--) {
638                final VAnimation anim = anims.get(i);
639                if (anim.canApplyTheme()) {
640                    return true;
641                }
642            }
643
644            return false;
645        }
646
647        public void applyTheme(Theme t) {
648            final ArrayList<VGroup> groups = mGroupList;
649            for (int i = groups.size() - 1; i >= 0; i--) {
650                final ArrayList<VPath> paths = groups.get(i).mVGList;
651                for (int j = paths.size() - 1; j >= 0; j--) {
652                    final VPath path = paths.get(j);
653                    if (path.canApplyTheme()) {
654                        path.applyTheme(t);
655                    }
656                }
657            }
658
659            final ArrayList<VAnimation> anims = mCurrentAnimList;
660            for (int i = anims.size() - 1; i >= 0; i--) {
661                final VAnimation anim = anims.get(i);
662                if (anim.canApplyTheme()) {
663                    anim.applyTheme(t);
664                }
665            }
666        }
667
668        public void setTrigger(int trigger){
669            mTrigger = VAnimatedPath.getStateForTrigger(trigger);
670        }
671
672        public long getTotalAnimationDuration() {
673            mTotalDuration = 0;
674            int size = mCurrentAnimList.size();
675            for (int i = 0; i < size; i++) {
676                VAnimation vAnimation = mCurrentAnimList.get(i);
677                long t = vAnimation.getTotalDuration();
678                if (t == -1) {
679                    mTotalDuration = -1;
680                    return -1;
681                }
682                mTotalDuration = Math.max(mTotalDuration, t);
683            }
684
685            return mTotalDuration;
686        }
687
688        public float getValue() {
689            return mAnimationValue;
690        }
691
692        /**
693         * @param value the point along the animations to show typically between 0.0f and 1.0f
694         * @return true if you need to keep repeating
695         */
696        public boolean setAnimationFraction(float value) {
697            getTotalAnimationDuration();
698
699            long animationTime = (long) (value * mTotalDuration);
700
701            final int len = mCurrentPaths.length;
702            for (int i = 0; i < len; i++) {
703                animationTime =
704                        (long) ((mTotalDuration == -1) ? value * 1000 : mTotalDuration * value);
705
706                final VPath path = mCurrentPaths[i];
707                final int size = mCurrentAnimList.size();
708                for (int j = 0; j < size; j++) {
709                    final VAnimation vAnimation = mCurrentAnimList.get(j);
710                    if (vAnimation.doesAdjustPath(path)) {
711                        mCurrentPaths[i] =  vAnimation.getPathAtTime(animationTime, path);
712                    }
713                }
714            }
715
716            mAnimationValue = value;
717
718            if (mTotalDuration == -1) {
719                return true;
720            } else {
721                return animationTime < mTotalDuration;
722            }
723        }
724
725        public void draw(Canvas canvas, int w, int h) {
726            if (mCurrentPaths == null) {
727                Log.e(LOGTAG,"mCurrentPaths == null");
728                return;
729            }
730
731            for (int i = 0; i < mCurrentPaths.length; i++) {
732                if (mCurrentPaths[i] != null && mCurrentPaths[i].isVisible(mCurrentState)) {
733                    drawPath(mCurrentPaths[i], canvas, w, h);
734                }
735            }
736        }
737
738        private void drawPath(VPath vPath, Canvas canvas, int w, int h) {
739            final float scale = Math.min(h / mViewportHeight, w / mViewportWidth);
740
741            vPath.toPath(mPath);
742            final Path path = mPath;
743
744            if (vPath.mTrimPathStart != 0.0f || vPath.mTrimPathEnd != 1.0f) {
745                float start = (vPath.mTrimPathStart + vPath.mTrimPathOffset) % 1.0f;
746                float end = (vPath.mTrimPathEnd + vPath.mTrimPathOffset) % 1.0f;
747
748                if (mPathMeasure == null) {
749                    mPathMeasure = new PathMeasure();
750                }
751                mPathMeasure.setPath(mPath, false);
752
753                float len = mPathMeasure.getLength();
754                start = start * len;
755                end = end * len;
756                path.reset();
757                if (start > end) {
758                    mPathMeasure.getSegment(start, len, path, true);
759                    mPathMeasure.getSegment(0f, end, path, true);
760                } else {
761                    mPathMeasure.getSegment(start, end, path, true);
762                }
763                path.rLineTo(0, 0); // fix bug in measure
764            }
765
766            mRenderPath.reset();
767            mMatrix.reset();
768
769            mMatrix.postRotate(vPath.mRotate, vPath.mPivotX, vPath.mPivotY);
770            mMatrix.postScale(scale, scale, mViewportWidth / 2f, mViewportHeight / 2f);
771            mMatrix.postTranslate(w / 2f - mViewportWidth / 2f, h / 2f - mViewportHeight / 2f);
772
773            mRenderPath.addPath(path, mMatrix);
774
775            if (vPath.mClip) {
776                canvas.clipPath(mRenderPath, Region.Op.REPLACE);
777            }
778
779            if (vPath.mFillColor != 0) {
780                if (mFillPaint == null) {
781                    mFillPaint = new Paint();
782                    mFillPaint.setStyle(Paint.Style.FILL);
783                    mFillPaint.setAntiAlias(true);
784                }
785
786                mFillPaint.setColor(vPath.mFillColor);
787                canvas.drawPath(mRenderPath, mFillPaint);
788            }
789
790            if (vPath.mStrokeColor != 0) {
791                if (mStrokePaint == null) {
792                    mStrokePaint = new Paint();
793                    mStrokePaint.setStyle(Paint.Style.STROKE);
794                    mStrokePaint.setAntiAlias(true);
795                }
796
797                final Paint strokePaint = mStrokePaint;
798                if (vPath.mStrokeLineJoin != null) {
799                    strokePaint.setStrokeJoin(vPath.mStrokeLineJoin);
800                }
801
802                if (vPath.mStrokeLineCap != null) {
803                    strokePaint.setStrokeCap(vPath.mStrokeLineCap);
804                }
805
806                strokePaint.setStrokeMiter(vPath.mStrokeMiterlimit * scale);
807                strokePaint.setColor(vPath.mStrokeColor);
808                strokePaint.setStrokeWidth(vPath.mStrokeWidth * scale);
809                canvas.drawPath(mRenderPath, strokePaint);
810            }
811        }
812
813        /**
814         * Ensure there is at least one animation for every path in group (linking them by names)
815         * Build the "current" path based on the first group
816         * TODO: improve memory use & performance or move to C++
817         */
818        public void parseFinish() {
819            final HashMap<String, VAnimation> newAnimations = new HashMap<String, VAnimation>();
820            for (VGroup group : mGroupList) {
821                for (VPath vPath : group.getPaths()) {
822                    if (!vPath.mAnimated) {
823                        VAnimation ap = null;
824
825                        if (!newAnimations.containsKey(vPath.getID())) {
826                            newAnimations.put(vPath.getID(), ap = new VAnimation());
827                        } else {
828                            ap = newAnimations.get(vPath.getID());
829                        }
830
831                        ap.addPath(vPath);
832                        vPath.mAnimated = true;
833                    }
834                }
835            }
836
837            if (mCurrentAnimList == null) {
838                mCurrentAnimList = new ArrayList<VectorDrawable.VAnimation>();
839            }
840            mCurrentAnimList.addAll(newAnimations.values());
841
842            final Collection<VPath> paths = mGroupList.get(0).getPaths();
843            mCurrentPaths = paths.toArray(new VPath[paths.size()]);
844            for (int i = 0; i < mCurrentPaths.length; i++) {
845                mCurrentPaths[i] = new VPath(mCurrentPaths[i]);
846            }
847        }
848
849        public void setState(int[] state) {
850            mCurrentState = Arrays.copyOf(state, state.length);
851        }
852
853        int getTrigger(int []state){
854            if (mTrigger == 0) return 0;
855            for (int i = 0; i < state.length; i++) {
856                if (state[i] == mTrigger){
857                    if (mTriggerState)
858                        return 0;
859                    mTriggerState = true;
860                    return 1;
861                }
862            }
863            if (mTriggerState) {
864                mTriggerState = false;
865                return -1;
866            }
867            return 0;
868        }
869
870        public void addAnimation(VAnimation anim) {
871            if (mCurrentAnimList == null) {
872                mCurrentAnimList = new ArrayList<VectorDrawable.VAnimation>();
873            }
874            mCurrentAnimList.add(anim);
875        }
876
877        private void parseViewport(Resources r, AttributeSet attrs)
878                throws XmlPullParserException {
879            final TypedArray a = r.obtainAttributes(attrs, R.styleable.VectorDrawableViewport);
880            mViewportWidth = a.getFloat(R.styleable.VectorDrawableViewport_viewportWidth, 0);
881            mViewportHeight = a.getFloat(R.styleable.VectorDrawableViewport_viewportHeight, 0);
882            if (mViewportWidth == 0 || mViewportHeight == 0) {
883                throw new XmlPullParserException(a.getPositionDescription()+
884                        "<viewport> tag requires viewportWidth & viewportHeight to be set");
885            }
886            a.recycle();
887        }
888
889        private void parseSize(Resources r, AttributeSet attrs)
890                throws XmlPullParserException  {
891            final TypedArray a = r.obtainAttributes(attrs, R.styleable.VectorDrawableSize);
892            mBaseWidth = a.getDimension(R.styleable.VectorDrawableSize_width, 0);
893            mBaseHeight = a.getDimension(R.styleable.VectorDrawableSize_height, 0);
894            if (mBaseWidth == 0 || mBaseHeight == 0) {
895                throw new XmlPullParserException(a.getPositionDescription()+
896                        "<size> tag requires width & height to be set");
897            }
898            a.recycle();
899        }
900
901        private static final int getStateForTrigger(int trigger) {
902            return TRIGGER_MAP[trigger];
903        }
904    }
905
906    private static class VAnimation {
907        private static final String SEPARATOR = ",";
908
909        private static final int DIRECTION_FORWARD = 0;
910        private static final int DIRECTION_IN_AND_OUT = 1;
911
912        public enum Style {
913            INTERPOLATE, CROSSFADE, WIPE
914        }
915
916        private final HashSet<String> mSeqMap = new HashSet<String>();
917
918        private Interpolator mAnimInterpolator = new AccelerateDecelerateInterpolator();
919        private VPath[] mPaths = new VPath[0];
920        private long[] mDuration = { DEFAULT_DURATION };
921
922        private int[] mThemeAttrs;
923        private Style mStyle;
924        private int mLimitProperty = 0;
925        private long mStartOffset;
926        private long mRepeat = 1;
927        private long mWipeDirection;
928        private int mMode = DIRECTION_FORWARD;
929        private int mInterpolatorType;
930        private String mId;
931
932        public VAnimation() {
933            // Empty constructor.
934        }
935
936        public void inflate(ArrayList<VGroup> groups, Resources r, AttributeSet attrs, Theme theme)
937                throws XmlPullParserException {
938            String value;
939            String[] sp;
940            int name;
941
942            final TypedArray a = r.obtainAttributes(attrs, R.styleable.VectorDrawableAnimation);
943            final int[] themeAttrs = a.extractThemeAttrs();
944            mThemeAttrs = themeAttrs;
945
946            value = a.getString(R.styleable.VectorDrawableAnimation_sequence);
947            if (value != null) {
948                sp = value.split(SEPARATOR);
949                final VectorDrawable.VPath[] paths = new VectorDrawable.VPath[sp.length];
950
951                for (int j = 0; j < sp.length; j++) {
952                    mSeqMap.add(sp[j].trim());
953                    VectorDrawable.VPath path = groups.get(j).get(sp[j]);
954                    path.mAnimated = true;
955                    paths[j] = path;
956                }
957
958                setPaths(paths);
959            }
960
961            name = R.styleable.VectorDrawableAnimation_durations;
962            value = a.getString(name);
963            if (value != null) {
964                long totalDuration = 0;
965                sp = value.split(SEPARATOR);
966
967                final long[] dur = new long[sp.length];
968                for (int j = 0; j < dur.length; j++) {
969                    dur[j] = Long.parseLong(sp[j]);
970                    totalDuration +=  dur[j];
971                }
972
973                if (totalDuration == 0){
974                    throw new XmlPullParserException(a.getPositionDescription()
975                            + " total duration must not be zero");
976                }
977
978                setDuration(dur);
979            }
980
981            setLimitProperty(a.getInt(R.styleable.VectorDrawableAnimation_limitTo, 0));
982            setRepeat(a.getInt(R.styleable.VectorDrawableAnimation_repeatCount, 1));
983            setStartOffset(a.getInt(R.styleable.VectorDrawableAnimation_startDelay, 0));
984            setMode(a.getInt(R.styleable.VectorDrawableAnimation_repeatStyle, 0));
985
986            fixMissingParameters();
987
988            a.recycle();
989        }
990
991        public boolean canApplyTheme() {
992            return mThemeAttrs != null;
993        }
994
995        public void applyTheme(Theme t) {
996            // TODO: Apply theme.
997        }
998
999        public boolean doesAdjustPath(VPath path) {
1000            return mSeqMap.contains(path.getID());
1001        }
1002
1003        public String getId() {
1004            if (mId == null) {
1005                mId = mPaths[0].getID();
1006                for (int i = 1; i < mPaths.length; i++) {
1007                    mId += mPaths[i].getID();
1008                }
1009            }
1010            return mId;
1011        }
1012
1013        public String getPathName() {
1014            return mPaths[0].getID();
1015        }
1016
1017        public Style getStyle() {
1018            return mStyle;
1019        }
1020
1021        public void setStyle(Style style) {
1022            mStyle = style;
1023        }
1024
1025        public int getLimitProperty() {
1026            return mLimitProperty;
1027        }
1028
1029        public void setLimitProperty(int limitProperty) {
1030            mLimitProperty = limitProperty;
1031        }
1032
1033        public long[] getDuration() {
1034            return mDuration;
1035        }
1036
1037        public void setDuration(long[] duration) {
1038            mDuration = duration;
1039        }
1040
1041        public long getRepeat() {
1042            return mRepeat;
1043        }
1044
1045        public void setRepeat(long repeat) {
1046            mRepeat = repeat;
1047        }
1048
1049        public long getStartOffset() {
1050            return mStartOffset;
1051        }
1052
1053        public void setStartOffset(long startOffset) {
1054            mStartOffset = startOffset;
1055        }
1056
1057        public long getWipeDirection() {
1058            return mWipeDirection;
1059        }
1060
1061        public void setWipeDirection(long wipeDirection) {
1062            mWipeDirection = wipeDirection;
1063        }
1064
1065        public int getMode() {
1066            return mMode;
1067        }
1068
1069        public void setMode(int mode) {
1070            mMode = mode;
1071        }
1072
1073        public int getInterpolator() {
1074            return mInterpolatorType;
1075        }
1076
1077        public void setInterpolator(int interpolator) {
1078            mInterpolatorType = interpolator;
1079        }
1080
1081        /**
1082         * compute the total time in milliseconds
1083         *
1084         * @return the total time in milliseconds the animation will take
1085         */
1086        public long getTotalDuration() {
1087            long total = mStartOffset;
1088            if (getRepeat() == -1) {
1089                return -1;
1090            }
1091            for (int i = 0; i < mDuration.length; i++) {
1092                if (mRepeat > 1) {
1093                    total += mDuration[i] * mRepeat;
1094                } else {
1095                    total += mDuration[i];
1096                }
1097            }
1098            return total;
1099        }
1100
1101        public void setPaths(VPath[] paths) {
1102            mPaths = paths;
1103        }
1104
1105        public void addPath(VPath path) {
1106            mPaths = Arrays.copyOf(mPaths, mPaths.length + 1);
1107            mPaths[mPaths.length - 1] = path;
1108        }
1109
1110        public boolean containsPath(String pathid) {
1111            for (int i = 0; i < mPaths.length; i++) {
1112                if (mPaths[i].getID().equals(pathid)) {
1113                    return true;
1114                }
1115            }
1116            return false;
1117        }
1118
1119        public void interpolate(VPath p1, VPath p2, float time, VPath dest) {
1120            VPath.interpolate(time, p1, p2, dest, mLimitProperty);
1121        }
1122
1123        public VPath getPathAtTime(long milliseconds, VPath dest) {
1124            if (mPaths.length == 1) {
1125                dest.copyFrom(mPaths[0]);
1126                return dest;
1127            }
1128            long point = milliseconds - mStartOffset;
1129            if (point < 0) {
1130                point = 0;
1131            }
1132            float time = 0;
1133            long sum = mDuration[0];
1134            for (int i = 1; i < mDuration.length; i++) {
1135                sum += mDuration[i];
1136            }
1137
1138            if (mRepeat > 1) {
1139                time = point / (float) (sum * mRepeat);
1140                time = mAnimInterpolator.getInterpolation(time);
1141
1142                if (mMode == DIRECTION_IN_AND_OUT) {
1143                    point = ((long) (time * sum * 2 * mRepeat)) % (sum * 2);
1144                    if (point > sum) {
1145                        point = sum * 2 - point;
1146                    }
1147                } else {
1148                    point = ((long) (time * sum * mRepeat)) % sum;
1149                }
1150            } else if (mRepeat == 1) {
1151                time = point / (float) (sum * mRepeat);
1152                time = mAnimInterpolator.getInterpolation(time);
1153                if (mMode == DIRECTION_IN_AND_OUT) {
1154                    point = ((long) (time * sum * 2 * mRepeat));
1155                    if (point > sum) {
1156                        point = sum * 2 - point;
1157                    }
1158                } else {
1159                    point = Math.min(((long) (time * sum * mRepeat)), sum);
1160                }
1161
1162            } else { // repeat = -1
1163                if (mMode == DIRECTION_IN_AND_OUT) {
1164                    point = point % (sum * 2);
1165                    if (point > sum) {
1166                        point = sum * 2 - point;
1167                    }
1168                    time = point / (float) sum;
1169                } else {
1170                    point = point % sum;
1171                    time = point / (float) sum;
1172                }
1173            }
1174
1175            int transition = 0;
1176            while (point > mDuration[transition]) {
1177                point -= mDuration[transition++];
1178            }
1179            if (mPaths.length > (transition + 1)) {
1180                if (mPaths[transition].getID() != dest.getID()) {
1181                    dest.copyFrom(mPaths[transition]);
1182                }
1183                interpolate(mPaths[transition], mPaths[transition + 1],
1184                        point / (float) mDuration[transition], dest);
1185            } else {
1186                interpolate(mPaths[transition], mPaths[transition], 0, dest);
1187            }
1188            return dest;
1189        }
1190
1191        void fixMissingParameters() {
1192            // fix missing points
1193            float rotation = Float.NaN;
1194            float rotationY = Float.NaN;
1195            float rotationX = Float.NaN;
1196            for (int i = 0; i < mPaths.length; i++) {
1197                if (mPaths[i].mPivotX > 0) {
1198                    rotationX = mPaths[i].mPivotX;
1199                }
1200                if (mPaths[i].mPivotY > 0) {
1201                    rotationY = mPaths[i].mPivotY;
1202                }
1203                if (mPaths[i].mRotate > 0) {
1204                    rotation = mPaths[i].mRotate;
1205                }
1206            }
1207            if (rotation > 0) {
1208                for (int i = 0; i < mPaths.length; i++) {
1209                    if (mPaths[i].mPivotX == 0) {
1210                        mPaths[i].mPivotX = rotationX;
1211                    }
1212                    if (mPaths[i].mPivotY == 0) {
1213                        mPaths[i].mPivotY = rotationY;
1214                    }
1215                }
1216            }
1217        }
1218    }
1219
1220    private static class VGroup {
1221        private final HashMap<String, VPath> mVGPathMap = new HashMap<String, VPath>();
1222        private final ArrayList<VPath> mVGList = new ArrayList<VPath>();
1223
1224        public void add(VPath path) {
1225            String id = path.getID();
1226            mVGPathMap.put(id, path);
1227            mVGList.add(path);
1228         }
1229
1230        public VPath get(String name) {
1231            return mVGPathMap.get(name);
1232        }
1233
1234        /**
1235         * Must return in order of adding
1236         * @return ordered list of paths
1237         */
1238        public Collection<VPath> getPaths() {
1239            return mVGList;
1240        }
1241
1242        public int size() {
1243            return mVGPathMap.size();
1244        }
1245    }
1246
1247    private static class VPath {
1248        private static final int LIMIT_ALL = 0;
1249        private static final int LIMIT_PATH = 1;
1250        private static final int LIMIT_ROTATE = 2;
1251        private static final int LIMIT_TRIM_PATH_START = 3;
1252        private static final int LIMIT_TRIM_PATH_OFFSET = 5;
1253        private static final int LIMIT_TRIM_PATH_END = 4;
1254
1255        private static final int STATE_UNDEFINED=0;
1256        private static final int STATE_TRUE=1;
1257        private static final int STATE_FALSE=2;
1258
1259        private static final int MAX_STATES = 10;
1260
1261        private int[] mThemeAttrs;
1262
1263        int mStrokeColor = 0;
1264        float mStrokeWidth = 0;
1265        float mStrokeOpacity = Float.NaN;
1266
1267        int mFillColor = 0;
1268        int mFillRule;
1269        float mFillOpacity = Float.NaN;
1270
1271        float mRotate = 0;
1272        float mPivotX = 0;
1273        float mPivotY = 0;
1274
1275        float mTrimPathStart = 0;
1276        float mTrimPathEnd = 1;
1277        float mTrimPathOffset = 0;
1278
1279        boolean mAnimated = false;
1280        boolean mClip = false;
1281        Paint.Cap mStrokeLineCap = Paint.Cap.BUTT;
1282        Paint.Join mStrokeLineJoin = Paint.Join.MITER;
1283        float mStrokeMiterlimit = 4;
1284
1285        private VNode[] mNode = null;
1286        private String mId;
1287        private int[] mCheckState = new int[MAX_STATES];
1288        private boolean[] mCheckValue = new boolean[MAX_STATES];
1289        private int mNumberOfStates = 0;
1290        private int mNumberOfTrue = 0;
1291
1292        public VPath() {
1293            // Empty constructor.
1294        }
1295
1296        public VPath(VPath p) {
1297            copyFrom(p);
1298        }
1299
1300        public void addStateFilter(int state, boolean condition) {
1301            int k = 0;
1302            while (k < mNumberOfStates) {
1303                if (mCheckState[mNumberOfStates] == state)
1304                    break;
1305                k++;
1306            }
1307            mCheckState[k] = state;
1308            mCheckValue[k] = condition;
1309            if (k==mNumberOfStates){
1310                mNumberOfStates++;
1311            }
1312            if (condition) {
1313                mNumberOfTrue++;
1314            }
1315        }
1316
1317        private int getState(int state){
1318            for (int i = 0; i < mNumberOfStates; i++) {
1319                if (mCheckState[mNumberOfStates] == state){
1320                    return (mCheckValue[i])?STATE_TRUE:STATE_FALSE;
1321                }
1322            }
1323            return STATE_UNDEFINED;
1324        }
1325        /**
1326         * @return the name of the path
1327         */
1328        public String getName() {
1329            return mId;
1330        }
1331
1332        public void toPath(Path path) {
1333            path.reset();
1334            if (mNode != null) {
1335                VNode.createPath(mNode, path);
1336            }
1337        }
1338
1339        public String getID() {
1340            return mId;
1341        }
1342
1343        private Paint.Cap getStrokeLineCap(int id, Paint.Cap defValue) {
1344            switch (id) {
1345                case LINECAP_BUTT:
1346                    return Paint.Cap.BUTT;
1347                case LINECAP_ROUND:
1348                    return Paint.Cap.ROUND;
1349                case LINECAP_SQUARE:
1350                    return Paint.Cap.SQUARE;
1351                default:
1352                    return defValue;
1353            }
1354        }
1355
1356        private Paint.Join getStrokeLineJoin(int id, Paint.Join defValue) {
1357            switch (id) {
1358                case LINEJOIN_MITER:
1359                    return Paint.Join.MITER;
1360                case LINEJOIN_ROUND:
1361                    return Paint.Join.ROUND;
1362                case LINEJOIN_BEVEL:
1363                    return Paint.Join.BEVEL;
1364                default:
1365                    return defValue;
1366            }
1367        }
1368
1369        public void inflate(Resources r, AttributeSet attrs, Theme theme) {
1370            final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.VectorDrawablePath);
1371            final int[] themeAttrs = a.extractThemeAttrs();
1372            mThemeAttrs = themeAttrs;
1373
1374            // NOTE: The set of attributes loaded here MUST match the
1375            // set of attributes loaded in applyTheme.
1376            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_clipToPath] == 0) {
1377                mClip = a.getBoolean(R.styleable.VectorDrawablePath_clipToPath, mClip);
1378            }
1379
1380            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_name] == 0) {
1381                mId = a.getString(R.styleable.VectorDrawablePath_name);
1382            }
1383
1384            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_pathData] == 0) {
1385                mNode = parsePath(a.getString(R.styleable.VectorDrawablePath_pathData));
1386            }
1387
1388            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_fill] == 0) {
1389                mFillColor = a.getColor(R.styleable.VectorDrawablePath_fill, mFillColor);
1390            }
1391
1392            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_fillOpacity] == 0) {
1393                mFillOpacity = a.getFloat(R.styleable.VectorDrawablePath_fillOpacity, mFillOpacity);
1394            }
1395
1396            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_rotation] == 0) {
1397                mRotate = a.getFloat(R.styleable.VectorDrawablePath_rotation, mRotate);
1398            }
1399
1400            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_pivotX] == 0) {
1401                mPivotX = a.getFloat(R.styleable.VectorDrawablePath_pivotX, mPivotX);
1402            }
1403
1404            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_pivotY] == 0) {
1405                mPivotY = a.getFloat(R.styleable.VectorDrawablePath_pivotY, mPivotY);
1406            }
1407
1408            if (themeAttrs == null
1409                    || themeAttrs[R.styleable.VectorDrawablePath_strokeLineCap] == 0) {
1410                mStrokeLineCap = getStrokeLineCap(
1411                        a.getInt(R.styleable.VectorDrawablePath_strokeLineCap, -1), mStrokeLineCap);
1412            }
1413
1414            if (themeAttrs == null
1415                    || themeAttrs[R.styleable.VectorDrawablePath_strokeLineJoin] == 0) {
1416                mStrokeLineJoin = getStrokeLineJoin(
1417                        a.getInt(R.styleable.VectorDrawablePath_strokeLineJoin, -1), mStrokeLineJoin);
1418            }
1419
1420            if (themeAttrs == null
1421                    || themeAttrs[R.styleable.VectorDrawablePath_strokeMiterLimit] == 0) {
1422                mStrokeMiterlimit = a.getFloat(
1423                        R.styleable.VectorDrawablePath_strokeMiterLimit, mStrokeMiterlimit);
1424            }
1425
1426            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_stroke] == 0) {
1427                mStrokeColor = a.getColor(R.styleable.VectorDrawablePath_stroke, mStrokeColor);
1428            }
1429
1430            if (themeAttrs == null
1431                    || themeAttrs[R.styleable.VectorDrawablePath_strokeOpacity] == 0) {
1432                mStrokeOpacity = a.getFloat(
1433                        R.styleable.VectorDrawablePath_strokeOpacity, mStrokeOpacity);
1434            }
1435
1436            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_strokeWidth] == 0) {
1437                mStrokeWidth = a.getFloat(R.styleable.VectorDrawablePath_strokeWidth, mStrokeWidth);
1438            }
1439
1440            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_trimPathEnd] == 0) {
1441                mTrimPathEnd = a.getFloat(R.styleable.VectorDrawablePath_trimPathEnd, mTrimPathEnd);
1442            }
1443
1444            if (themeAttrs == null
1445                    || themeAttrs[R.styleable.VectorDrawablePath_trimPathOffset] == 0) {
1446                mTrimPathOffset = a.getFloat(
1447                        R.styleable.VectorDrawablePath_trimPathOffset, mTrimPathOffset);
1448            }
1449
1450            if (themeAttrs == null
1451                    || themeAttrs[R.styleable.VectorDrawablePath_trimPathStart] == 0) {
1452                mTrimPathStart = a.getFloat(
1453                        R.styleable.VectorDrawablePath_trimPathStart, mTrimPathStart);
1454            }
1455
1456            // TODO: Consider replacing this with existing state attributes.
1457            final int[] states = {
1458                    R.styleable.VectorDrawablePath_state_activated,
1459                    R.styleable.VectorDrawablePath_state_checkable,
1460                    R.styleable.VectorDrawablePath_state_checked,
1461                    R.styleable.VectorDrawablePath_state_enabled,
1462                    R.styleable.VectorDrawablePath_state_focused,
1463                    R.styleable.VectorDrawablePath_state_hovered,
1464                    R.styleable.VectorDrawablePath_state_pressed,
1465                    R.styleable.VectorDrawablePath_state_selected,
1466                    R.styleable.VectorDrawablePath_state_window_focused
1467            };
1468
1469            final int N = states.length;
1470            for (int i = 0; i < N; i++) {
1471                final int state = states[i];
1472                if (a.hasValue(state)) {
1473                    addStateFilter(state, a.getBoolean(state, false));
1474                }
1475            }
1476
1477            updateColorAlphas();
1478
1479            a.recycle();
1480        }
1481
1482        public boolean canApplyTheme() {
1483            return mThemeAttrs != null;
1484        }
1485
1486        public void applyTheme(Theme t) {
1487            if (mThemeAttrs == null) {
1488                return;
1489            }
1490
1491            final TypedArray a = t.resolveAttributes(
1492                    mThemeAttrs, R.styleable.VectorDrawablePath, 0, 0);
1493
1494            mClip = a.getBoolean(R.styleable.VectorDrawablePath_clipToPath, mClip);
1495
1496            if (a.hasValue(R.styleable.VectorDrawablePath_name)) {
1497                mId = a.getString(R.styleable.VectorDrawablePath_name);
1498            }
1499
1500            if (a.hasValue(R.styleable.VectorDrawablePath_pathData)) {
1501                mNode = parsePath(a.getString(R.styleable.VectorDrawablePath_pathData));
1502            }
1503
1504            mFillColor = a.getColor(R.styleable.VectorDrawablePath_fill, mFillColor);
1505            mFillOpacity = a.getFloat(R.styleable.VectorDrawablePath_fillOpacity, mFillOpacity);
1506
1507            mRotate = a.getFloat(R.styleable.VectorDrawablePath_rotation, mRotate);
1508            mPivotX = a.getFloat(R.styleable.VectorDrawablePath_pivotX, mPivotX);
1509            mPivotY = a.getFloat(R.styleable.VectorDrawablePath_pivotY, mPivotY);
1510
1511            mStrokeLineCap = getStrokeLineCap(a.getInt(
1512                    R.styleable.VectorDrawablePath_strokeLineCap, -1), mStrokeLineCap);
1513            mStrokeLineJoin = getStrokeLineJoin(a.getInt(
1514                    R.styleable.VectorDrawablePath_strokeLineJoin, -1), mStrokeLineJoin);
1515            mStrokeMiterlimit = a.getFloat(
1516                    R.styleable.VectorDrawablePath_strokeMiterLimit, mStrokeMiterlimit);
1517            mStrokeColor = a.getColor(R.styleable.VectorDrawablePath_stroke, mStrokeColor);
1518            mStrokeOpacity = a.getFloat(
1519                    R.styleable.VectorDrawablePath_strokeOpacity, mStrokeOpacity);
1520            mStrokeWidth = a.getFloat(R.styleable.VectorDrawablePath_strokeWidth, mStrokeWidth);
1521
1522            mTrimPathEnd = a.getFloat(R.styleable.VectorDrawablePath_trimPathEnd, mTrimPathEnd);
1523            mTrimPathOffset = a.getFloat(
1524                    R.styleable.VectorDrawablePath_trimPathOffset, mTrimPathOffset);
1525            mTrimPathStart = a.getFloat(
1526                    R.styleable.VectorDrawablePath_trimPathStart, mTrimPathStart);
1527
1528            updateColorAlphas();
1529        }
1530
1531        private void updateColorAlphas() {
1532            if (!Float.isNaN(mFillOpacity)) {
1533                mFillColor &= 0x00FFFFFF;
1534                mFillColor |= ((int) (0xFF * mFillOpacity)) << 24;
1535            }
1536
1537            if (!Float.isNaN(mStrokeOpacity)) {
1538                mStrokeColor &= 0x00FFFFFF;
1539                mStrokeColor |= ((int) (0xFF * mStrokeOpacity)) << 24;
1540            }
1541        }
1542
1543        private static int nextStart(String s, int end) {
1544            char c;
1545
1546            while (end < s.length()) {
1547                c = s.charAt(end);
1548                if (((c - 'A') * (c - 'Z') <= 0) || (((c - 'a') * (c - 'z') <= 0))) {
1549                    return end;
1550                }
1551                end++;
1552            }
1553            return end;
1554        }
1555
1556        private void addNode(ArrayList<VectorDrawable.VNode> list, char cmd, float[] val) {
1557            list.add(new VectorDrawable.VNode(cmd, val));
1558        }
1559
1560        /**
1561         * parse the floats in the string
1562         * this is an optimized version of
1563         * parseFloat(s.split(",|\\s"));
1564         *
1565         * @param s the string containing a command and list of floats
1566         * @return array of floats
1567         */
1568        private static float[] getFloats(String s) {
1569            if (s.charAt(0) == 'z' | s.charAt(0) == 'Z') {
1570                return new float[0];
1571            }
1572            try {
1573                float[] tmp = new float[s.length()];
1574                int count = 0;
1575                int pos = 1, end;
1576                while ((end = extract(s, pos)) >= 0) {
1577                    if (pos < end) {
1578                        tmp[count++] = Float.parseFloat(s.substring(pos, end));
1579                    }
1580                    pos = end + 1;
1581                }
1582                // handle the final float if there is one
1583                if (pos < s.length()) {
1584                    tmp[count++] = Float.parseFloat(s.substring(pos, s.length()));
1585                }
1586                return Arrays.copyOf(tmp, count);
1587            } catch (NumberFormatException e){
1588                Log.e(LOGTAG,"error in parsing \""+s+"\"");
1589                throw e;
1590            }
1591        }
1592
1593        /**
1594         * calculate the position of the next comma or space
1595         * @param s the string to search
1596         * @param start the position to start searching
1597         * @return the position of the next comma or space or -1 if none found
1598         */
1599        private static int extract(String s, int start) {
1600            int space = s.indexOf(' ', start);
1601            int comma = s.indexOf(',', start);
1602            if (space == -1) {
1603                return comma;
1604            }
1605            if (comma == -1) {
1606                return space;
1607            }
1608            return (comma > space) ? space : comma;
1609        }
1610
1611        private VectorDrawable.VNode[] parsePath(String value) {
1612            int start = 0;
1613            int end = 1;
1614
1615            ArrayList<VectorDrawable.VNode> list = new ArrayList<VectorDrawable.VNode>();
1616            while (end < value.length()) {
1617                end = nextStart(value, end);
1618                String s = value.substring(start, end);
1619                float[] val = getFloats(s);
1620                addNode(list, s.charAt(0), val);
1621
1622                start = end;
1623                end++;
1624            }
1625            if ((end - start) == 1 && start < value.length()) {
1626
1627                addNode(list, value.charAt(start), new float[0]);
1628            }
1629            return list.toArray(new VectorDrawable.VNode[list.size()]);
1630        }
1631
1632        public void copyFrom(VPath p1) {
1633            mNode = new VNode[p1.mNode.length];
1634            for (int i = 0; i < mNode.length; i++) {
1635                mNode[i] = new VNode(p1.mNode[i]);
1636            }
1637            mId = p1.mId;
1638            mStrokeColor = p1.mStrokeColor;
1639            mFillColor = p1.mFillColor;
1640            mStrokeWidth = p1.mStrokeWidth;
1641            mRotate = p1.mRotate;
1642            mPivotX = p1.mPivotX;
1643            mPivotY = p1.mPivotY;
1644            mAnimated = p1.mAnimated;
1645            mTrimPathStart = p1.mTrimPathStart;
1646            mTrimPathEnd = p1.mTrimPathEnd;
1647            mTrimPathOffset = p1.mTrimPathOffset;
1648            mStrokeLineCap = p1.mStrokeLineCap;
1649            mStrokeLineJoin = p1.mStrokeLineJoin;
1650            mStrokeMiterlimit = p1.mStrokeMiterlimit;
1651            mNumberOfStates = p1.mNumberOfStates;
1652            for (int i = 0; i < mNumberOfStates; i++) {
1653                mCheckState[i] = p1.mCheckState[i];
1654                mCheckValue[i] = p1.mCheckValue[i];
1655            }
1656
1657            mFillRule = p1.mFillRule;
1658        }
1659
1660        public static VPath interpolate(float t, VPath p1, VPath p2, VPath returnPath, int limit) {
1661            if (limit == LIMIT_ALL || limit == LIMIT_PATH) {
1662                if (returnPath.mNode == null || returnPath.mNode.length != p1.mNode.length) {
1663                    returnPath.mNode = new VNode[p1.mNode.length];
1664                }
1665                for (int i = 0; i < returnPath.mNode.length; i++) {
1666                    if (returnPath.mNode[i] == null) {
1667                        returnPath.mNode[i] = new VNode(p1.mNode[i], p2.mNode[i], t);
1668                    } else {
1669                        returnPath.mNode[i].interpolate(p1.mNode[i], p2.mNode[i], t);
1670                    }
1671                }
1672            }
1673            float t1 = 1 - t;
1674            switch (limit) {
1675                case LIMIT_ALL:
1676                    returnPath.mRotate = t1 * p1.mRotate + t * p2.mRotate;
1677                    returnPath.mPivotX = t1 * p1.mPivotX + t * p2.mPivotX;
1678                    returnPath.mPivotY = t1 * p1.mPivotY + t * p2.mPivotY;
1679                    returnPath.mClip = p1.mClip | p2.mClip;
1680
1681                    returnPath.mTrimPathStart = t1 * p1.mTrimPathStart + t * p2.mTrimPathStart;
1682                    returnPath.mTrimPathEnd = t1 * p1.mTrimPathEnd + t * p2.mTrimPathEnd;
1683                    returnPath.mTrimPathOffset = t1 * p1.mTrimPathOffset + t * p2.mTrimPathOffset;
1684                    returnPath.mStrokeMiterlimit =
1685                            t1 * p1.mStrokeMiterlimit + t * p2.mStrokeMiterlimit;
1686                    returnPath.mStrokeLineCap = p1.mStrokeLineCap;
1687                    if (returnPath.mStrokeLineCap == null) {
1688                        returnPath.mStrokeLineCap = p2.mStrokeLineCap;
1689                    }
1690                    returnPath.mStrokeLineJoin = p1.mStrokeLineJoin;
1691                    if (returnPath.mStrokeLineJoin == null) {
1692                        returnPath.mStrokeLineJoin = p2.mStrokeLineJoin;
1693                    }
1694                    returnPath.mFillRule = p1.mFillRule;
1695
1696                    returnPath.mStrokeColor = rgbInterpolate(t, p1.mStrokeColor, p2.mStrokeColor);
1697                    returnPath.mFillColor = rgbInterpolate(t, p1.mFillColor, p2.mFillColor);
1698                    returnPath.mStrokeWidth = t1 * p1.mStrokeWidth + t * p2.mStrokeWidth;
1699                    returnPath.mNumberOfStates = p1.mNumberOfStates;
1700                    for (int i = 0; i < returnPath.mNumberOfStates; i++) {
1701                        returnPath.mCheckState[i] = p1.mCheckState[i];
1702                        returnPath.mCheckValue[i] = p1.mCheckValue[i];
1703                    }
1704                    for (int i = 0; i < p2.mNumberOfStates; i++) {
1705                        returnPath.addStateFilter(p2.mCheckState[i], p2.mCheckValue[i]);
1706                    }
1707
1708                    int count = 0;
1709                    for (int i = 0; i < returnPath.mNumberOfStates; i++) {
1710                        if (returnPath.mCheckValue[i]) {
1711                            count++;
1712                        }
1713                    }
1714                    returnPath.mNumberOfTrue = count;
1715                    break;
1716                case LIMIT_ROTATE:
1717                    returnPath.mRotate = t1 * p1.mRotate + t * p2.mRotate;
1718                    break;
1719                case LIMIT_TRIM_PATH_END:
1720                    returnPath.mTrimPathEnd = t1 * p1.mTrimPathEnd + t * p2.mTrimPathEnd;
1721                    break;
1722                case LIMIT_TRIM_PATH_OFFSET:
1723                    returnPath.mTrimPathOffset = t1 * p1.mTrimPathOffset + t * p2.mTrimPathOffset;
1724                    break;
1725                case LIMIT_TRIM_PATH_START:
1726                    returnPath.mTrimPathStart = t1 * p1.mTrimPathStart + t * p2.mTrimPathStart;
1727                    break;
1728            }
1729            return returnPath;
1730        }
1731
1732        private static int rgbInterpolate(float fraction, int startColor, int endColor) {
1733            if (startColor == endColor) {
1734                return startColor;
1735            } else if (startColor == 0) {
1736                return endColor;
1737            } else if (endColor == 0) {
1738                return startColor;
1739            }
1740
1741            final int startA = (startColor >> 24) & 0xff;
1742            final int startR = (startColor >> 16) & 0xff;
1743            final int startG = (startColor >> 8) & 0xff;
1744            final int startB = startColor & 0xff;
1745
1746            final int endA = (endColor >> 24) & 0xff;
1747            final int endR = (endColor >> 16) & 0xff;
1748            final int endG = (endColor >> 8) & 0xff;
1749            final int endB = endColor & 0xff;
1750
1751            return ((startA + (int)(fraction * (endA - startA))) << 24) |
1752                    ((startR + (int)(fraction * (endR - startR))) << 16) |
1753                    ((startG + (int)(fraction * (endG - startG))) << 8) |
1754                    ((startB + (int)(fraction * (endB - startB))));
1755        }
1756
1757        public boolean isVisible(int[] state) {
1758            int match = 0;
1759            for (int i = 0; i < state.length; i++) {
1760                int v = getState(state[i]);
1761                if (v != STATE_UNDEFINED) {
1762                    if (v==STATE_TRUE) {
1763                        match++;
1764                    } else {
1765                        return false;
1766                    }
1767                }
1768            }
1769            return match == mNumberOfTrue;
1770        }
1771    }
1772
1773    private static class VNode {
1774        private char mType;
1775        private float[] mParams;
1776
1777        public VNode(char type, float[] params) {
1778            mType = type;
1779            mParams = params;
1780        }
1781
1782        public VNode(VNode n) {
1783            mType = n.mType;
1784            mParams = Arrays.copyOf(n.mParams, n.mParams.length);
1785        }
1786
1787        public VNode(VNode n1, VNode n2, float t) {
1788            mType = n1.mType;
1789            mParams = new float[n1.mParams.length];
1790            interpolate(n1, n2, t);
1791        }
1792
1793        private boolean match(VNode n) {
1794            if (n.mType != mType) {
1795                return false;
1796            }
1797            return (mParams.length == n.mParams.length);
1798        }
1799
1800        public void interpolate(VNode n1, VNode n2, float t) {
1801            for (int i = 0; i < n1.mParams.length; i++) {
1802                mParams[i] = n1.mParams[i] * (1 - t) + n2.mParams[i] * t;
1803            }
1804        }
1805
1806        private void nodeListToPath(VNode[] node, Path path) {
1807            float[] current = new float[4];
1808            for (int i = 0; i < node.length; i++) {
1809                addCommand(path, current, node[i].mType, node[i].mParams);
1810            }
1811        }
1812
1813        public static void createPath(VNode[] node, Path path) {
1814            float[] current = new float[4];
1815            for (int i = 0; i < node.length; i++) {
1816                addCommand(path, current, node[i].mType, node[i].mParams);
1817            }
1818        }
1819
1820        private static void addCommand(Path path, float[] current, char cmd, float[] val) {
1821
1822            int incr = 2;
1823            float currentX = current[0];
1824            float currentY = current[1];
1825            float ctrlPointX = current[2];
1826            float ctrlPointY = current[3];
1827
1828            switch (cmd) {
1829                case 'z':
1830                case 'Z':
1831                    path.close();
1832                    return;
1833                case 'm':
1834                case 'M':
1835                case 'l':
1836                case 'L':
1837                case 't':
1838                case 'T':
1839                    incr = 2;
1840                    break;
1841                case 'h':
1842                case 'H':
1843                case 'v':
1844                case 'V':
1845                    incr = 1;
1846                    break;
1847                case 'c':
1848                case 'C':
1849                    incr = 6;
1850                    break;
1851                case 's':
1852                case 'S':
1853                case 'q':
1854                case 'Q':
1855                    incr = 4;
1856                    break;
1857                case 'a':
1858                case 'A':
1859                    incr = 7;
1860                    break;
1861            }
1862            for (int k = 0; k < val.length; k += incr) {
1863                // TODO: build test to prove all permutations work
1864                switch (cmd) {
1865                    case 'm': // moveto - Start a new sub-path (relative)
1866                        path.rMoveTo(val[k + 0], val[k + 1]);
1867                        currentX += val[k + 0];
1868                        currentY += val[k + 1];
1869                        break;
1870                    case 'M': // moveto - Start a new sub-path
1871                        path.moveTo(val[k + 0], val[k + 1]);
1872                        currentX = val[k + 0];
1873                        currentY = val[k + 1];
1874                        break;
1875                    case 'l': // lineto - Draw a line from the current point (relative)
1876                        path.rLineTo(val[k + 0], val[k + 1]);
1877                        currentX += val[k + 0];
1878                        currentY += val[k + 1];
1879                        break;
1880                    case 'L': // lineto - Draw a line from the current point
1881                        path.lineTo(val[k + 0], val[k + 1]);
1882                        currentX = val[k + 0];
1883                        currentY = val[k + 1];
1884                        break;
1885                    case 'z': // closepath - Close the current subpath
1886                    case 'Z': // closepath - Close the current subpath
1887                        path.close();
1888                        break;
1889                    case 'h': // horizontal lineto - Draws a horizontal line (relative)
1890                        path.rLineTo(val[k + 0], 0);
1891                        currentX += val[k + 0];
1892                        break;
1893                    case 'H': // horizontal lineto - Draws a horizontal line
1894                        path.lineTo(val[k + 0], currentY);
1895                        currentX = val[k + 0];
1896                        break;
1897                    case 'v': // vertical lineto - Draws a vertical line from the current point (r)
1898                        path.rLineTo(0, val[k + 0]);
1899                        currentY += val[k + 0];
1900                        break;
1901                    case 'V': // vertical lineto - Draws a vertical line from the current point
1902                        path.lineTo(currentX, val[k + 0]);
1903                        currentY = val[k + 0];
1904                        break;
1905                    case 'c': // curveto - Draws a cubic Bézier curve (relative)
1906                        path.rCubicTo(val[k + 0],
1907                                val[k + 1],
1908                                val[k + 2],
1909                                val[k + 3],
1910                                val[k + 4],
1911                                val[k + 5]);
1912
1913                        ctrlPointX = currentX + val[k + 2];
1914                        ctrlPointY = currentY + val[k + 3];
1915                        currentX += val[k + 4];
1916                        currentY += val[k + 5];
1917
1918                        break;
1919                    case 'C': // curveto - Draws a cubic Bézier curve
1920                        path.cubicTo(val[k + 0],
1921                                val[k + 1],
1922                                val[k + 2],
1923                                val[k + 3],
1924                                val[k + 4],
1925                                val[k + 5]);
1926                        currentX = val[k + 4];
1927                        currentY = val[k + 5];
1928                        ctrlPointX = val[k + 2];
1929                        ctrlPointY = val[k + 3];
1930
1931                        break;
1932                    case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp)
1933                        path.rCubicTo(currentX - ctrlPointX, currentY  - ctrlPointY,
1934                                val[k + 0], val[k + 1],
1935                                val[k + 2], val[k + 3]);
1936
1937                        ctrlPointX = currentX + val[k + 0];
1938                        ctrlPointY = currentY + val[k + 1];
1939                        currentX += val[k + 2];
1940                        currentY += val[k + 3];
1941                        break;
1942                    case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp)
1943                        path.cubicTo(2 * currentX - ctrlPointX,
1944                                2 * currentY - ctrlPointY,
1945                                val[k + 0],
1946                                val[k + 1],
1947                                val[k + 2],
1948                                val[k + 3]);
1949                        currentX = val[k + 2];
1950                        currentY = val[k + 3];
1951                        ctrlPointX = val[k + 0];
1952                        ctrlPointY = val[k + 1];
1953                        break;
1954                    case 'q': // Draws a quadratic Bézier (relative)
1955                        path.rQuadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
1956                        currentX += val[k + 2];
1957                        currentY += val[k + 3];
1958                        ctrlPointX = val[k + 0];
1959                        ctrlPointY = val[k + 1];
1960                        break;
1961                    case 'Q': // Draws a quadratic Bézier
1962                        path.quadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
1963                        currentX = val[k + 2];
1964                        currentY = val[k + 3];
1965                        ctrlPointX = val[k + 0];
1966                        ctrlPointY = val[k + 1];
1967                        break;
1968                    case 't': // Draws a quadratic Bézier curve(reflective control point)(relative)
1969                        path.rQuadTo(currentX - ctrlPointX, currentY - ctrlPointY,
1970                                val[k + 0], val[k + 1]);
1971                        ctrlPointX = ctrlPointX + currentX;
1972                        ctrlPointY = ctrlPointY + currentY;
1973                        currentX += val[k + 0];
1974                        currentY += val[k + 1];
1975
1976                        break;
1977                    case 'T': // Draws a quadratic Bézier curve (reflective control point)
1978                        path.quadTo(currentX * 2 - ctrlPointX, currentY * 2 - ctrlPointY,
1979                                val[k + 0], val[k + 1]);
1980                        currentX = val[k + 0];
1981                        currentY = val[k + 1]; // TODO: Check this logic
1982                        ctrlPointX = -(val[k + 0] - currentX);
1983                        ctrlPointY = -(val[k + 1] - currentY);
1984                        break;
1985                    case 'a': // Draws an elliptical arc
1986                        // (rx ry x-axis-rotation large-arc-flag sweep-flag x y)
1987                        drawArc(path,
1988                                currentX,
1989                                currentY,
1990                                val[k + 5] + currentX,
1991                                val[k + 6] + currentY,
1992                                val[k + 0],
1993                                val[k + 1],
1994                                val[k + 2],
1995                                val[k + 3] != 0,
1996                                val[k + 4] != 0);
1997                        currentX += val[k + 5];
1998                        currentY += val[k + 6];
1999                        ctrlPointX = currentX;
2000                        ctrlPointY = currentY;
2001
2002                        break;
2003                    case 'A': // Draws an elliptical arc
2004                        drawArc(path,
2005                                currentX,
2006                                currentY,
2007                                val[k + 5],
2008                                val[k + 6],
2009                                val[k + 0],
2010                                val[k + 1],
2011                                val[k + 2],
2012                                val[k + 3] != 0,
2013                                val[k + 4] != 0);
2014                        currentX = val[k + 5];
2015                        currentY = val[k + 6];
2016                        ctrlPointX = currentX;
2017                        ctrlPointY = currentY;
2018                        break;
2019                }
2020            }
2021            current[0] = currentX;
2022            current[1] = currentY;
2023            current[2] = ctrlPointX;
2024            current[3] = ctrlPointY;
2025        }
2026
2027        private static void drawArc(Path p,
2028                float x0,
2029                float y0,
2030                float x1,
2031                float y1,
2032                float a,
2033                float b,
2034                float theta,
2035                boolean isMoreThanHalf,
2036                boolean isPositiveArc) {
2037
2038            /* Convert rotation angle from degrees to radians */
2039            double thetaD = Math.toRadians(theta);
2040            /* Pre-compute rotation matrix entries */
2041            double cosTheta = Math.cos(thetaD);
2042            double sinTheta = Math.sin(thetaD);
2043            /* Transform (x0, y0) and (x1, y1) into unit space */
2044            /* using (inverse) rotation, followed by (inverse) scale */
2045            double x0p = (x0 * cosTheta + y0 * sinTheta) / a;
2046            double y0p = (-x0 * sinTheta + y0 * cosTheta) / b;
2047            double x1p = (x1 * cosTheta + y1 * sinTheta) / a;
2048            double y1p = (-x1 * sinTheta + y1 * cosTheta) / b;
2049
2050            /* Compute differences and averages */
2051            double dx = x0p - x1p;
2052            double dy = y0p - y1p;
2053            double xm = (x0p + x1p) / 2;
2054            double ym = (y0p + y1p) / 2;
2055            /* Solve for intersecting unit circles */
2056            double dsq = dx * dx + dy * dy;
2057            if (dsq == 0.0) {
2058                Log.w(LOGTAG, " Points are coincident");
2059                return; /* Points are coincident */
2060            }
2061            double disc = 1.0 / dsq - 1.0 / 4.0;
2062            if (disc < 0.0) {
2063                Log.w(LOGTAG, "Points are too far apart " + dsq);
2064                float adjust = (float) (Math.sqrt(dsq) / 1.99999);
2065                drawArc(p, x0, y0, x1, y1, a * adjust,
2066                        b * adjust, theta, isMoreThanHalf, isPositiveArc);
2067                return; /* Points are too far apart */
2068            }
2069            double s = Math.sqrt(disc);
2070            double sdx = s * dx;
2071            double sdy = s * dy;
2072            double cx;
2073            double cy;
2074            if (isMoreThanHalf == isPositiveArc) {
2075                cx = xm - sdy;
2076                cy = ym + sdx;
2077            } else {
2078                cx = xm + sdy;
2079                cy = ym - sdx;
2080            }
2081
2082            double eta0 = Math.atan2((y0p - cy), (x0p - cx));
2083
2084            double eta1 = Math.atan2((y1p - cy), (x1p - cx));
2085
2086            double sweep = (eta1 - eta0);
2087            if (isPositiveArc != (sweep >= 0)) {
2088                if (sweep > 0) {
2089                    sweep -= 2 * Math.PI;
2090                } else {
2091                    sweep += 2 * Math.PI;
2092                }
2093            }
2094
2095            cx *= a;
2096            cy *= b;
2097            double tcx = cx;
2098            cx = cx * cosTheta - cy * sinTheta;
2099            cy = tcx * sinTheta + cy * cosTheta;
2100
2101            arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep);
2102        }
2103
2104        /**
2105         * Converts an arc to cubic Bezier segments and records them in p.
2106         *
2107         * @param p The target for the cubic Bezier segments
2108         * @param cx The x coordinate center of the ellipse
2109         * @param cy The y coordinate center of the ellipse
2110         * @param a The radius of the ellipse in the horizontal direction
2111         * @param b The radius of the ellipse in the vertical direction
2112         * @param e1x E(eta1) x coordinate of the starting point of the arc
2113         * @param e1y E(eta2) y coordinate of the starting point of the arc
2114         * @param theta The angle that the ellipse bounding rectangle makes with horizontal plane
2115         * @param start The start angle of the arc on the ellipse
2116         * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse
2117         */
2118        private static void arcToBezier(Path p,
2119                double cx,
2120                double cy,
2121                double a,
2122                double b,
2123                double e1x,
2124                double e1y,
2125                double theta,
2126                double start,
2127                double sweep) {
2128            // Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html
2129            // and http://www.spaceroots.org/documents/ellipse/node22.html
2130
2131            // Maximum of 45 degrees per cubic Bezier segment
2132            int numSegments = Math.abs((int) Math.ceil(sweep * 4 / Math.PI));
2133
2134            double eta1 = start;
2135            double cosTheta = Math.cos(theta);
2136            double sinTheta = Math.sin(theta);
2137            double cosEta1 = Math.cos(eta1);
2138            double sinEta1 = Math.sin(eta1);
2139            double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);
2140            double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1);
2141
2142            double anglePerSegment = sweep / numSegments;
2143            for (int i = 0; i < numSegments; i++) {
2144                double eta2 = eta1 + anglePerSegment;
2145                double sinEta2 = Math.sin(eta2);
2146                double cosEta2 = Math.cos(eta2);
2147                double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2);
2148                double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2);
2149                double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2;
2150                double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2;
2151                double tanDiff2 = Math.tan((eta2 - eta1) / 2);
2152                double alpha =
2153                        Math.sin(eta2 - eta1) * (Math.sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3;
2154                double q1x = e1x + alpha * ep1x;
2155                double q1y = e1y + alpha * ep1y;
2156                double q2x = e2x - alpha * ep2x;
2157                double q2y = e2y - alpha * ep2y;
2158
2159                p.cubicTo((float) q1x,
2160                        (float) q1y,
2161                        (float) q2x,
2162                        (float) q2y,
2163                        (float) e2x,
2164                        (float) e2y);
2165                eta1 = eta2;
2166                e1x = e2x;
2167                e1y = e2y;
2168                ep1x = ep2x;
2169                ep1y = ep2y;
2170            }
2171        }
2172
2173    }
2174}
2175