Merge "Add path support into xml files for PathInterpolator and ObjectAnimator."
diff --git a/api/current.txt b/api/current.txt
index 65589d4..e85e1d3 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -937,6 +937,8 @@
     field public static final int progressTintMode = 16843878; // 0x1010466
     field public static final int prompt = 16843131; // 0x101017b
     field public static final int propertyName = 16843489; // 0x10102e1
+    field public static final int propertyXName = 16843894; // 0x1010476
+    field public static final int propertyYName = 16843895; // 0x1010477
     field public static final int protectionLevel = 16842761; // 0x1010009
     field public static final int publicKey = 16843686; // 0x10103a6
     field public static final int queryActionMsg = 16843227; // 0x10101db
diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java
index 06f5aca..39fcf73 100644
--- a/core/java/android/animation/AnimatorInflater.java
+++ b/core/java/android/animation/AnimatorInflater.java
@@ -17,11 +17,13 @@
 
 import android.content.Context;
 import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
 import android.content.res.Resources.Theme;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
-import android.content.res.Resources.NotFoundException;
+import android.graphics.Path;
 import android.util.AttributeSet;
+import android.util.PathParser;
 import android.util.StateSet;
 import android.util.TypedValue;
 import android.util.Xml;
@@ -158,7 +160,7 @@
                         int stateIndex = 0;
                         for (int i = 0; i < attributeCount; i++) {
                             int attrName = attributeSet.getAttributeNameResource(i);
-                            if (attrName == com.android.internal.R.attr.animation) {
+                            if (attrName == R.attr.animation) {
                                 animator = loadAnimator(context,
                                         attributeSet.getAttributeResourceValue(i, 0));
                             } else {
@@ -186,36 +188,43 @@
         }
     }
 
+    /**
+     * @param anim Null if this is a ValueAnimator, otherwise this is an
+     *            ObjectAnimator
+     * @param arrayAnimator Incoming typed array for Animator's attributes.
+     * @param arrayObjectAnimator Incoming typed array for Object Animator's
+     *            attributes.
+     */
+    private static void parseAnimatorFromTypeArray(ValueAnimator anim,
+            TypedArray arrayAnimator, TypedArray arrayObjectAnimator) {
+        long duration = arrayAnimator.getInt(R.styleable.Animator_duration, 300);
 
-    private static void parseAnimatorFromTypeArray(ValueAnimator anim, TypedArray a) {
-        long duration = a.getInt(com.android.internal.R.styleable.Animator_duration, 300);
+        long startDelay = arrayAnimator.getInt(R.styleable.Animator_startOffset, 0);
 
-        long startDelay = a.getInt(com.android.internal.R.styleable.Animator_startOffset, 0);
-
-        int valueType = a.getInt(com.android.internal.R.styleable.Animator_valueType,
+        int valueType = arrayAnimator.getInt(R.styleable.Animator_valueType,
                 VALUE_TYPE_FLOAT);
 
         if (anim == null) {
             anim = new ValueAnimator();
         }
-        TypeEvaluator evaluator = null;
 
-        int valueFromIndex = com.android.internal.R.styleable.Animator_valueFrom;
-        int valueToIndex = com.android.internal.R.styleable.Animator_valueTo;
+        TypeEvaluator evaluator = null;
+        int valueFromIndex = R.styleable.Animator_valueFrom;
+        int valueToIndex = R.styleable.Animator_valueTo;
 
         boolean getFloats = (valueType == VALUE_TYPE_FLOAT);
 
-        TypedValue tvFrom = a.peekValue(valueFromIndex);
+        TypedValue tvFrom = arrayAnimator.peekValue(valueFromIndex);
         boolean hasFrom = (tvFrom != null);
         int fromType = hasFrom ? tvFrom.type : 0;
-        TypedValue tvTo = a.peekValue(valueToIndex);
+        TypedValue tvTo = arrayAnimator.peekValue(valueToIndex);
         boolean hasTo = (tvTo != null);
         int toType = hasTo ? tvTo.type : 0;
 
         if ((hasFrom && (fromType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
                 (fromType <= TypedValue.TYPE_LAST_COLOR_INT)) ||
-            (hasTo && (toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
-                (toType <= TypedValue.TYPE_LAST_COLOR_INT))) {
+                (hasTo && (toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
+                        (toType <= TypedValue.TYPE_LAST_COLOR_INT))) {
             // special case for colors: ignore valueType and get ints
             getFloats = false;
             evaluator = ArgbEvaluator.getInstance();
@@ -226,15 +235,15 @@
             float valueTo;
             if (hasFrom) {
                 if (fromType == TypedValue.TYPE_DIMENSION) {
-                    valueFrom = a.getDimension(valueFromIndex, 0f);
+                    valueFrom = arrayAnimator.getDimension(valueFromIndex, 0f);
                 } else {
-                    valueFrom = a.getFloat(valueFromIndex, 0f);
+                    valueFrom = arrayAnimator.getFloat(valueFromIndex, 0f);
                 }
                 if (hasTo) {
                     if (toType == TypedValue.TYPE_DIMENSION) {
-                        valueTo = a.getDimension(valueToIndex, 0f);
+                        valueTo = arrayAnimator.getDimension(valueToIndex, 0f);
                     } else {
-                        valueTo = a.getFloat(valueToIndex, 0f);
+                        valueTo = arrayAnimator.getFloat(valueToIndex, 0f);
                     }
                     anim.setFloatValues(valueFrom, valueTo);
                 } else {
@@ -242,9 +251,9 @@
                 }
             } else {
                 if (toType == TypedValue.TYPE_DIMENSION) {
-                    valueTo = a.getDimension(valueToIndex, 0f);
+                    valueTo = arrayAnimator.getDimension(valueToIndex, 0f);
                 } else {
-                    valueTo = a.getFloat(valueToIndex, 0f);
+                    valueTo = arrayAnimator.getFloat(valueToIndex, 0f);
                 }
                 anim.setFloatValues(valueTo);
             }
@@ -253,21 +262,21 @@
             int valueTo;
             if (hasFrom) {
                 if (fromType == TypedValue.TYPE_DIMENSION) {
-                    valueFrom = (int) a.getDimension(valueFromIndex, 0f);
+                    valueFrom = (int) arrayAnimator.getDimension(valueFromIndex, 0f);
                 } else if ((fromType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
                         (fromType <= TypedValue.TYPE_LAST_COLOR_INT)) {
-                    valueFrom = a.getColor(valueFromIndex, 0);
+                    valueFrom = arrayAnimator.getColor(valueFromIndex, 0);
                 } else {
-                    valueFrom = a.getInt(valueFromIndex, 0);
+                    valueFrom = arrayAnimator.getInt(valueFromIndex, 0);
                 }
                 if (hasTo) {
                     if (toType == TypedValue.TYPE_DIMENSION) {
-                        valueTo = (int) a.getDimension(valueToIndex, 0f);
+                        valueTo = (int) arrayAnimator.getDimension(valueToIndex, 0f);
                     } else if ((toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
                             (toType <= TypedValue.TYPE_LAST_COLOR_INT)) {
-                        valueTo = a.getColor(valueToIndex, 0);
+                        valueTo = arrayAnimator.getColor(valueToIndex, 0);
                     } else {
-                        valueTo = a.getInt(valueToIndex, 0);
+                        valueTo = arrayAnimator.getInt(valueToIndex, 0);
                     }
                     anim.setIntValues(valueFrom, valueTo);
                 } else {
@@ -276,12 +285,12 @@
             } else {
                 if (hasTo) {
                     if (toType == TypedValue.TYPE_DIMENSION) {
-                        valueTo = (int) a.getDimension(valueToIndex, 0f);
+                        valueTo = (int) arrayAnimator.getDimension(valueToIndex, 0f);
                     } else if ((toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
-                        (toType <= TypedValue.TYPE_LAST_COLOR_INT)) {
-                        valueTo = a.getColor(valueToIndex, 0);
+                            (toType <= TypedValue.TYPE_LAST_COLOR_INT)) {
+                        valueTo = arrayAnimator.getColor(valueToIndex, 0);
                     } else {
-                        valueTo = a.getInt(valueToIndex, 0);
+                        valueTo = arrayAnimator.getInt(valueToIndex, 0);
                     }
                     anim.setIntValues(valueTo);
                 }
@@ -291,18 +300,59 @@
         anim.setDuration(duration);
         anim.setStartDelay(startDelay);
 
-        if (a.hasValue(com.android.internal.R.styleable.Animator_repeatCount)) {
+        if (arrayAnimator.hasValue(R.styleable.Animator_repeatCount)) {
             anim.setRepeatCount(
-                    a.getInt(com.android.internal.R.styleable.Animator_repeatCount, 0));
+                    arrayAnimator.getInt(R.styleable.Animator_repeatCount, 0));
         }
-        if (a.hasValue(com.android.internal.R.styleable.Animator_repeatMode)) {
+        if (arrayAnimator.hasValue(R.styleable.Animator_repeatMode)) {
             anim.setRepeatMode(
-                    a.getInt(com.android.internal.R.styleable.Animator_repeatMode,
+                    arrayAnimator.getInt(R.styleable.Animator_repeatMode,
                             ValueAnimator.RESTART));
         }
         if (evaluator != null) {
             anim.setEvaluator(evaluator);
         }
+
+        if (arrayObjectAnimator != null) {
+            ObjectAnimator oa = (ObjectAnimator) anim;
+            String pathData = arrayObjectAnimator.getString(R.styleable.PropertyAnimator_pathData);
+
+            // Note that if there is a pathData defined in the Object Animator,
+            // valueFrom / valueTo will be overwritten by the pathData.
+            if (pathData != null) {
+                String propertyXName =
+                        arrayObjectAnimator.getString(R.styleable.PropertyAnimator_propertyXName);
+                String propertyYName =
+                        arrayObjectAnimator.getString(R.styleable.PropertyAnimator_propertyYName);
+
+                if (propertyXName == null && propertyYName == null) {
+                    throw new IllegalArgumentException("propertyXName or propertyYName"
+                            + " is needed for PathData in Object Animator");
+                } else {
+                    Path path = PathParser.createPathFromPathData(pathData);
+                    Keyframe[][] keyframes = PropertyValuesHolder.createKeyframes(path, !getFloats);
+                    PropertyValuesHolder x = null;
+                    PropertyValuesHolder y = null;
+                    if (propertyXName != null) {
+                        x = PropertyValuesHolder.ofKeyframe(propertyXName, keyframes[0]);
+                    }
+                    if (propertyYName != null) {
+                        y = PropertyValuesHolder.ofKeyframe(propertyYName, keyframes[1]);
+                    }
+                    if (x == null) {
+                        oa.setValues(y);
+                    } else if (y == null) {
+                        oa.setValues(x);
+                    } else {
+                        oa.setValues(x, y);
+                    }
+                }
+            } else {
+                String propertyName =
+                        arrayObjectAnimator.getString(R.styleable.PropertyAnimator_propertyName);
+                oa.setPropertyName(propertyName);
+            }
+        }
     }
 
     private static Animator createAnimatorFromXml(Resources res, Theme theme, XmlPullParser parser)
@@ -338,11 +388,11 @@
                 anim = new AnimatorSet();
                 TypedArray a;
                 if (theme != null) {
-                    a = theme.obtainStyledAttributes(attrs, com.android.internal.R.styleable.AnimatorSet, 0, 0);
+                    a = theme.obtainStyledAttributes(attrs, R.styleable.AnimatorSet, 0, 0);
                 } else {
-                    a = res.obtainAttributes(attrs, com.android.internal.R.styleable.AnimatorSet);
+                    a = res.obtainAttributes(attrs, R.styleable.AnimatorSet);
                 }
-                int ordering = a.getInt(com.android.internal.R.styleable.AnimatorSet_ordering,
+                int ordering = a.getInt(R.styleable.AnimatorSet_ordering,
                         TOGETHER);
                 createAnimatorFromXml(res, theme, parser, attrs, (AnimatorSet) anim, ordering);
                 a.recycle();
@@ -380,19 +430,6 @@
 
         loadAnimator(res, theme, attrs, anim);
 
-        TypedArray a;
-        if (theme != null) {
-            a = theme.obtainStyledAttributes(attrs, R.styleable.PropertyAnimator, 0, 0);
-        } else {
-            a = res.obtainAttributes(attrs, R.styleable.PropertyAnimator);
-        }
-
-        String propertyName = a.getString(R.styleable.PropertyAnimator_propertyName);
-
-        anim.setPropertyName(propertyName);
-
-        a.recycle();
-
         return anim;
     }
 
@@ -402,26 +439,41 @@
      *
      * @param res The resources
      * @param attrs The set of attributes holding the animation parameters
+     * @param anim Null if this is a ValueAnimator, otherwise this is an
+     *            ObjectAnimator
      */
     private static ValueAnimator loadAnimator(Resources res, Theme theme,
             AttributeSet attrs, ValueAnimator anim)
             throws NotFoundException {
 
-        TypedArray a;
+        TypedArray arrayAnimator = null;
+        TypedArray arrayObjectAnimator = null;
+
         if (theme != null) {
-            a = theme.obtainStyledAttributes(attrs, R.styleable.Animator, 0, 0);
+            arrayAnimator = theme.obtainStyledAttributes(attrs, R.styleable.Animator, 0, 0);
         } else {
-            a = res.obtainAttributes(attrs, R.styleable.Animator);
+            arrayAnimator = res.obtainAttributes(attrs, R.styleable.Animator);
         }
 
-        parseAnimatorFromTypeArray(anim, a);
+        // If anim is not null, then it is an object animator.
+        if (anim != null) {
+            if (theme != null) {
+                arrayObjectAnimator = theme.obtainStyledAttributes(attrs,
+                        R.styleable.PropertyAnimator, 0, 0);
+            } else {
+                arrayObjectAnimator = res.obtainAttributes(attrs, R.styleable.PropertyAnimator);
+            }
+        }
+        parseAnimatorFromTypeArray(anim, arrayAnimator, arrayObjectAnimator);
 
         final int resID =
-                a.getResourceId(com.android.internal.R.styleable.Animator_interpolator, 0);
+                arrayAnimator.getResourceId(R.styleable.Animator_interpolator, 0);
         if (resID > 0) {
             anim.setInterpolator(AnimationUtils.loadInterpolator(res, theme, resID));
         }
-        a.recycle();
+
+        arrayAnimator.recycle();
+        arrayObjectAnimator.recycle();
 
         return anim;
     }
diff --git a/core/java/android/util/PathParser.java b/core/java/android/util/PathParser.java
new file mode 100644
index 0000000..f90ce51
--- /dev/null
+++ b/core/java/android/util/PathParser.java
@@ -0,0 +1,528 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.util;
+
+import android.graphics.Path;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * @hide
+ */
+public class PathParser {
+    static final String LOGTAG = PathParser.class.getSimpleName();
+
+    /**
+     * @param pathData The string representing a path, the same as "d" string in svg file.
+     * @return the generated Path object.
+     */
+    public static Path createPathFromPathData(String pathData) {
+        Path path = new Path();
+        PathDataNode[] nodes = createNodesFromPathData(pathData);
+        if (nodes != null) {
+            PathDataNode.nodesToPath(nodes, path);
+            return path;
+        }
+        return null;
+    }
+
+    /**
+     * @param pathData The string representing a path, the same as "d" string in svg file.
+     * @return an array of the PathDataNode.
+     */
+    public static PathDataNode[] createNodesFromPathData(String pathData) {
+        int start = 0;
+        int end = 1;
+
+        ArrayList<PathDataNode> list = new ArrayList<PathDataNode>();
+        while (end < pathData.length()) {
+            end = nextStart(pathData, end);
+            String s = pathData.substring(start, end);
+            float[] val = getFloats(s);
+            addNode(list, s.charAt(0), val);
+
+            start = end;
+            end++;
+        }
+        if ((end - start) == 1 && start < pathData.length()) {
+            addNode(list, pathData.charAt(start), new float[0]);
+        }
+        return list.toArray(new PathDataNode[list.size()]);
+    }
+
+    private static int nextStart(String s, int end) {
+        char c;
+
+        while (end < s.length()) {
+            c = s.charAt(end);
+            if (((c - 'A') * (c - 'Z') <= 0) || (((c - 'a') * (c - 'z') <= 0))) {
+                return end;
+            }
+            end++;
+        }
+        return end;
+    }
+
+    private static void addNode(ArrayList<PathDataNode> list, char cmd, float[] val) {
+        list.add(new PathDataNode(cmd, val));
+    }
+
+
+    /**
+     * Parse the floats in the string.
+     * This is an optimized version of parseFloat(s.split(",|\\s"));
+     *
+     * @param s the string containing a command and list of floats
+     * @return array of floats
+     */
+    private static float[] getFloats(String s) {
+        if (s.charAt(0) == 'z' | s.charAt(0) == 'Z') {
+            return new float[0];
+        }
+        try {
+            float[] tmp = new float[s.length()];
+            int count = 0;
+            int pos = 1, end;
+            while ((end = extract(s, pos)) >= 0) {
+                if (pos < end) {
+                    tmp[count++] = Float.parseFloat(s.substring(pos, end));
+                }
+                pos = end + 1;
+            }
+            // handle the final float if there is one
+            if (pos < s.length()) {
+                tmp[count++] = Float.parseFloat(s.substring(pos, s.length()));
+            }
+            return Arrays.copyOf(tmp, count);
+        } catch (NumberFormatException e){
+            Log.e(LOGTAG,"error in parsing \""+s+"\"");
+            throw e;
+        }
+    }
+
+    /**
+     * Calculate the position of the next comma or space
+     * @param s the string to search
+     * @param start the position to start searching
+     * @return the position of the next comma or space or -1 if none found
+     */
+    private static int extract(String s, int start) {
+        int space = s.indexOf(' ', start);
+        int comma = s.indexOf(',', start);
+        if (space == -1) {
+            return comma;
+        }
+        if (comma == -1) {
+            return space;
+        }
+        return (comma > space) ? space : comma;
+    }
+
+    public static class PathDataNode {
+        private char mType;
+        private float[] mParams;
+
+        private PathDataNode(char type, float[] params) {
+            mType = type;
+            mParams = params;
+        }
+
+        private PathDataNode(PathDataNode n) {
+            mType = n.mType;
+            mParams = Arrays.copyOf(n.mParams, n.mParams.length);
+        }
+
+        public static void nodesToPath(PathDataNode[] node, Path path) {
+            float[] current = new float[4];
+            char previousCommand = 'm';
+            for (int i = 0; i < node.length; i++) {
+                addCommand(path, current, previousCommand, node[i].mType, node[i].mParams);
+                previousCommand = node[i].mType;
+            }
+        }
+
+        private static void addCommand(Path path, float[] current,
+                char previousCmd, char cmd, float[] val) {
+
+            int incr = 2;
+            float currentX = current[0];
+            float currentY = current[1];
+            float ctrlPointX = current[2];
+            float ctrlPointY = current[3];
+            float reflectiveCtrlPointX;
+            float reflectiveCtrlPointY;
+
+            switch (cmd) {
+                case 'z':
+                case 'Z':
+                    path.close();
+                    return;
+                case 'm':
+                case 'M':
+                case 'l':
+                case 'L':
+                case 't':
+                case 'T':
+                    incr = 2;
+                    break;
+                case 'h':
+                case 'H':
+                case 'v':
+                case 'V':
+                    incr = 1;
+                    break;
+                case 'c':
+                case 'C':
+                    incr = 6;
+                    break;
+                case 's':
+                case 'S':
+                case 'q':
+                case 'Q':
+                    incr = 4;
+                    break;
+                case 'a':
+                case 'A':
+                    incr = 7;
+                    break;
+            }
+            for (int k = 0; k < val.length; k += incr) {
+                switch (cmd) {
+                    case 'm': // moveto - Start a new sub-path (relative)
+                        path.rMoveTo(val[k + 0], val[k + 1]);
+                        currentX += val[k + 0];
+                        currentY += val[k + 1];
+                        break;
+                    case 'M': // moveto - Start a new sub-path
+                        path.moveTo(val[k + 0], val[k + 1]);
+                        currentX = val[k + 0];
+                        currentY = val[k + 1];
+                        break;
+                    case 'l': // lineto - Draw a line from the current point (relative)
+                        path.rLineTo(val[k + 0], val[k + 1]);
+                        currentX += val[k + 0];
+                        currentY += val[k + 1];
+                        break;
+                    case 'L': // lineto - Draw a line from the current point
+                        path.lineTo(val[k + 0], val[k + 1]);
+                        currentX = val[k + 0];
+                        currentY = val[k + 1];
+                        break;
+                    case 'z': // closepath - Close the current subpath
+                    case 'Z': // closepath - Close the current subpath
+                        path.close();
+                        break;
+                    case 'h': // horizontal lineto - Draws a horizontal line (relative)
+                        path.rLineTo(val[k + 0], 0);
+                        currentX += val[k + 0];
+                        break;
+                    case 'H': // horizontal lineto - Draws a horizontal line
+                        path.lineTo(val[k + 0], currentY);
+                        currentX = val[k + 0];
+                        break;
+                    case 'v': // vertical lineto - Draws a vertical line from the current point (r)
+                        path.rLineTo(0, val[k + 0]);
+                        currentY += val[k + 0];
+                        break;
+                    case 'V': // vertical lineto - Draws a vertical line from the current point
+                        path.lineTo(currentX, val[k + 0]);
+                        currentY = val[k + 0];
+                        break;
+                    case 'c': // curveto - Draws a cubic Bézier curve (relative)
+                        path.rCubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],
+                                val[k + 4], val[k + 5]);
+
+                        ctrlPointX = currentX + val[k + 2];
+                        ctrlPointY = currentY + val[k + 3];
+                        currentX += val[k + 4];
+                        currentY += val[k + 5];
+
+                        break;
+                    case 'C': // curveto - Draws a cubic Bézier curve
+                        path.cubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],
+                                val[k + 4], val[k + 5]);
+                        currentX = val[k + 4];
+                        currentY = val[k + 5];
+                        ctrlPointX = val[k + 2];
+                        ctrlPointY = val[k + 3];
+                        break;
+                    case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp)
+                        reflectiveCtrlPointX = 0;
+                        reflectiveCtrlPointY = 0;
+                        if (previousCmd == 'c' || previousCmd == 's'
+                                || previousCmd == 'C' || previousCmd == 'S') {
+                            reflectiveCtrlPointX = currentX - ctrlPointX;
+                            reflectiveCtrlPointY = currentY - ctrlPointY;
+                        }
+                        path.rCubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
+                                val[k + 0], val[k + 1],
+                                val[k + 2], val[k + 3]);
+
+                        ctrlPointX = currentX + val[k + 0];
+                        ctrlPointY = currentY + val[k + 1];
+                        currentX += val[k + 2];
+                        currentY += val[k + 3];
+                        break;
+                    case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp)
+                        reflectiveCtrlPointX = currentX;
+                        reflectiveCtrlPointY = currentY;
+                        if (previousCmd == 'c' || previousCmd == 's'
+                                || previousCmd == 'C' || previousCmd == 'S') {
+                            reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
+                            reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
+                        }
+                        path.cubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
+                                val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
+                        ctrlPointX = val[k + 0];
+                        ctrlPointY = val[k + 1];
+                        currentX = val[k + 2];
+                        currentY = val[k + 3];
+                        break;
+                    case 'q': // Draws a quadratic Bézier (relative)
+                        path.rQuadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
+                        ctrlPointX = currentX + val[k + 0];
+                        ctrlPointY = currentY + val[k + 1];
+                        currentX += val[k + 2];
+                        currentY += val[k + 3];
+                        break;
+                    case 'Q': // Draws a quadratic Bézier
+                        path.quadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
+                        ctrlPointX = val[k + 0];
+                        ctrlPointY = val[k + 1];
+                        currentX = val[k + 2];
+                        currentY = val[k + 3];
+                        break;
+                    case 't': // Draws a quadratic Bézier curve(reflective control point)(relative)
+                        reflectiveCtrlPointX = 0;
+                        reflectiveCtrlPointY = 0;
+                        if (previousCmd == 'q' || previousCmd == 't'
+                                || previousCmd == 'Q' || previousCmd == 'T') {
+                            reflectiveCtrlPointX = currentX - ctrlPointX;
+                            reflectiveCtrlPointY = currentY - ctrlPointY;
+                        }
+                        path.rQuadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
+                                val[k + 0], val[k + 1]);
+                        ctrlPointX = currentX + reflectiveCtrlPointX;
+                        ctrlPointY = currentY + reflectiveCtrlPointY;
+                        currentX += val[k + 0];
+                        currentY += val[k + 1];
+                        break;
+                    case 'T': // Draws a quadratic Bézier curve (reflective control point)
+                        reflectiveCtrlPointX = currentX;
+                        reflectiveCtrlPointY = currentY;
+                        if (previousCmd == 'q' || previousCmd == 't'
+                                || previousCmd == 'Q' || previousCmd == 'T') {
+                            reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
+                            reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
+                        }
+                        path.quadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
+                                val[k + 0], val[k + 1]);
+                        ctrlPointX = reflectiveCtrlPointX;
+                        ctrlPointY = reflectiveCtrlPointY;
+                        currentX = val[k + 0];
+                        currentY = val[k + 1];
+                        break;
+                    case 'a': // Draws an elliptical arc
+                        // (rx ry x-axis-rotation large-arc-flag sweep-flag x y)
+                        drawArc(path,
+                                currentX,
+                                currentY,
+                                val[k + 5] + currentX,
+                                val[k + 6] + currentY,
+                                val[k + 0],
+                                val[k + 1],
+                                val[k + 2],
+                                val[k + 3] != 0,
+                                val[k + 4] != 0);
+                        currentX += val[k + 5];
+                        currentY += val[k + 6];
+                        ctrlPointX = currentX;
+                        ctrlPointY = currentY;
+                        break;
+                    case 'A': // Draws an elliptical arc
+                        drawArc(path,
+                                currentX,
+                                currentY,
+                                val[k + 5],
+                                val[k + 6],
+                                val[k + 0],
+                                val[k + 1],
+                                val[k + 2],
+                                val[k + 3] != 0,
+                                val[k + 4] != 0);
+                        currentX = val[k + 5];
+                        currentY = val[k + 6];
+                        ctrlPointX = currentX;
+                        ctrlPointY = currentY;
+                        break;
+                }
+                previousCmd = cmd;
+            }
+            current[0] = currentX;
+            current[1] = currentY;
+            current[2] = ctrlPointX;
+            current[3] = ctrlPointY;
+        }
+
+        private static void drawArc(Path p,
+                float x0,
+                float y0,
+                float x1,
+                float y1,
+                float a,
+                float b,
+                float theta,
+                boolean isMoreThanHalf,
+                boolean isPositiveArc) {
+
+            /* Convert rotation angle from degrees to radians */
+            double thetaD = Math.toRadians(theta);
+            /* Pre-compute rotation matrix entries */
+            double cosTheta = Math.cos(thetaD);
+            double sinTheta = Math.sin(thetaD);
+            /* Transform (x0, y0) and (x1, y1) into unit space */
+            /* using (inverse) rotation, followed by (inverse) scale */
+            double x0p = (x0 * cosTheta + y0 * sinTheta) / a;
+            double y0p = (-x0 * sinTheta + y0 * cosTheta) / b;
+            double x1p = (x1 * cosTheta + y1 * sinTheta) / a;
+            double y1p = (-x1 * sinTheta + y1 * cosTheta) / b;
+
+            /* Compute differences and averages */
+            double dx = x0p - x1p;
+            double dy = y0p - y1p;
+            double xm = (x0p + x1p) / 2;
+            double ym = (y0p + y1p) / 2;
+            /* Solve for intersecting unit circles */
+            double dsq = dx * dx + dy * dy;
+            if (dsq == 0.0) {
+                Log.w(LOGTAG, " Points are coincident");
+                return; /* Points are coincident */
+            }
+            double disc = 1.0 / dsq - 1.0 / 4.0;
+            if (disc < 0.0) {
+                Log.w(LOGTAG, "Points are too far apart " + dsq);
+                float adjust = (float) (Math.sqrt(dsq) / 1.99999);
+                drawArc(p, x0, y0, x1, y1, a * adjust,
+                        b * adjust, theta, isMoreThanHalf, isPositiveArc);
+                return; /* Points are too far apart */
+            }
+            double s = Math.sqrt(disc);
+            double sdx = s * dx;
+            double sdy = s * dy;
+            double cx;
+            double cy;
+            if (isMoreThanHalf == isPositiveArc) {
+                cx = xm - sdy;
+                cy = ym + sdx;
+            } else {
+                cx = xm + sdy;
+                cy = ym - sdx;
+            }
+
+            double eta0 = Math.atan2((y0p - cy), (x0p - cx));
+
+            double eta1 = Math.atan2((y1p - cy), (x1p - cx));
+
+            double sweep = (eta1 - eta0);
+            if (isPositiveArc != (sweep >= 0)) {
+                if (sweep > 0) {
+                    sweep -= 2 * Math.PI;
+                } else {
+                    sweep += 2 * Math.PI;
+                }
+            }
+
+            cx *= a;
+            cy *= b;
+            double tcx = cx;
+            cx = cx * cosTheta - cy * sinTheta;
+            cy = tcx * sinTheta + cy * cosTheta;
+
+            arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep);
+        }
+
+        /**
+         * Converts an arc to cubic Bezier segments and records them in p.
+         *
+         * @param p The target for the cubic Bezier segments
+         * @param cx The x coordinate center of the ellipse
+         * @param cy The y coordinate center of the ellipse
+         * @param a The radius of the ellipse in the horizontal direction
+         * @param b The radius of the ellipse in the vertical direction
+         * @param e1x E(eta1) x coordinate of the starting point of the arc
+         * @param e1y E(eta2) y coordinate of the starting point of the arc
+         * @param theta The angle that the ellipse bounding rectangle makes with horizontal plane
+         * @param start The start angle of the arc on the ellipse
+         * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse
+         */
+        private static void arcToBezier(Path p,
+                double cx,
+                double cy,
+                double a,
+                double b,
+                double e1x,
+                double e1y,
+                double theta,
+                double start,
+                double sweep) {
+            // Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html
+            // and http://www.spaceroots.org/documents/ellipse/node22.html
+
+            // Maximum of 45 degrees per cubic Bezier segment
+            int numSegments = Math.abs((int) Math.ceil(sweep * 4 / Math.PI));
+
+            double eta1 = start;
+            double cosTheta = Math.cos(theta);
+            double sinTheta = Math.sin(theta);
+            double cosEta1 = Math.cos(eta1);
+            double sinEta1 = Math.sin(eta1);
+            double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);
+            double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1);
+
+            double anglePerSegment = sweep / numSegments;
+            for (int i = 0; i < numSegments; i++) {
+                double eta2 = eta1 + anglePerSegment;
+                double sinEta2 = Math.sin(eta2);
+                double cosEta2 = Math.cos(eta2);
+                double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2);
+                double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2);
+                double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2;
+                double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2;
+                double tanDiff2 = Math.tan((eta2 - eta1) / 2);
+                double alpha =
+                        Math.sin(eta2 - eta1) * (Math.sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3;
+                double q1x = e1x + alpha * ep1x;
+                double q1y = e1y + alpha * ep1y;
+                double q2x = e2x - alpha * ep2x;
+                double q2y = e2y - alpha * ep2y;
+
+                p.cubicTo((float) q1x,
+                        (float) q1y,
+                        (float) q2x,
+                        (float) q2y,
+                        (float) e2x,
+                        (float) e2y);
+                eta1 = eta2;
+                e1x = e2x;
+                e1y = e2y;
+                ep1x = ep2x;
+                ep1y = ep2y;
+            }
+        }
+
+    }
+}
diff --git a/core/java/android/view/animation/PathInterpolator.java b/core/java/android/view/animation/PathInterpolator.java
index da12ffb..945ecf0 100644
--- a/core/java/android/view/animation/PathInterpolator.java
+++ b/core/java/android/view/animation/PathInterpolator.java
@@ -21,6 +21,7 @@
 import android.content.res.TypedArray;
 import android.graphics.Path;
 import android.util.AttributeSet;
+import android.util.PathParser;
 import android.view.InflateException;
 
 import com.android.internal.R;
@@ -102,28 +103,40 @@
     }
 
     private void parseInterpolatorFromTypeArray(TypedArray a) {
-        if (!a.hasValue(R.styleable.PathInterpolator_controlX1)) {
-            throw new InflateException("pathInterpolator requires the controlX1 attribute");
-        } else if (!a.hasValue(R.styleable.PathInterpolator_controlY1)) {
-            throw new InflateException("pathInterpolator requires the controlY1 attribute");
-        }
-        float x1 = a.getFloat(R.styleable.PathInterpolator_controlX1, 0);
-        float y1 = a.getFloat(R.styleable.PathInterpolator_controlY1, 0);
-
-        boolean hasX2 = a.hasValue(R.styleable.PathInterpolator_controlX2);
-        boolean hasY2 = a.hasValue(R.styleable.PathInterpolator_controlY2);
-
-        if (hasX2 != hasY2) {
-            throw new InflateException(
-                    "pathInterpolator requires both controlX2 and controlY2 for cubic Beziers.");
-        }
-
-        if (!hasX2) {
-            initQuad(x1, y1);
+        // If there is pathData defined in the xml file, then the controls points
+        // will be all coming from pathData.
+        if (a.hasValue(R.styleable.PathInterpolator_pathData)) {
+            String pathData = a.getString(R.styleable.PathInterpolator_pathData);
+            Path path = PathParser.createPathFromPathData(pathData);
+            if (path == null) {
+                throw new InflateException("The path is null, which is created"
+                        + " from " + pathData);
+            }
+            initPath(path);
         } else {
-            float x2 = a.getFloat(R.styleable.PathInterpolator_controlX2, 0);
-            float y2 = a.getFloat(R.styleable.PathInterpolator_controlY2, 0);
-            initCubic(x1, y1, x2, y2);
+            if (!a.hasValue(R.styleable.PathInterpolator_controlX1)) {
+                throw new InflateException("pathInterpolator requires the controlX1 attribute");
+            } else if (!a.hasValue(R.styleable.PathInterpolator_controlY1)) {
+                throw new InflateException("pathInterpolator requires the controlY1 attribute");
+            }
+            float x1 = a.getFloat(R.styleable.PathInterpolator_controlX1, 0);
+            float y1 = a.getFloat(R.styleable.PathInterpolator_controlY1, 0);
+
+            boolean hasX2 = a.hasValue(R.styleable.PathInterpolator_controlX2);
+            boolean hasY2 = a.hasValue(R.styleable.PathInterpolator_controlY2);
+
+            if (hasX2 != hasY2) {
+                throw new InflateException(
+                        "pathInterpolator requires both controlX2 and controlY2 for cubic Beziers.");
+            }
+
+            if (!hasX2) {
+                initQuad(x1, y1);
+            } else {
+                float x2 = a.getFloat(R.styleable.PathInterpolator_controlX2, 0);
+                float y2 = a.getFloat(R.styleable.PathInterpolator_controlY2, 0);
+                initCubic(x1, y1, x2, y2);
+            }
         }
     }
 
@@ -216,5 +229,4 @@
         float endY = mY[endIndex];
         return startY + (fraction * (endY - startY));
     }
-
 }
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 8d8f69e..f74a356 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5250,10 +5250,18 @@
     </declare-styleable>
 
     <declare-styleable name="PathInterpolator">
+        <!-- The x coordinate of the first control point of the cubic Bezier -->
         <attr name="controlX1" format="float" />
+        <!-- The y coordinate of the first control point of the cubic Bezier -->
         <attr name="controlY1" format="float" />
+        <!-- The x coordinate of the second control point of the cubic Bezier -->
         <attr name="controlX2" format="float" />
+        <!-- The y coordinate of the second control point of the cubic Bezier -->
         <attr name="controlY2" format="float" />
+        <!-- The control points defined as a path.
+             When pathData is defined, then both of the control points of the
+             cubic Bezier will be ignored. -->
+        <attr name="pathData"/>
     </declare-styleable>
 
     <!-- ========================== -->
@@ -5403,6 +5411,12 @@
     <declare-styleable name="PropertyAnimator">
         <!-- Name of the property being animated. -->
         <attr name="propertyName" format="string"/>
+        <!-- Name of the property being animated as the X coordinate of the pathData. -->
+        <attr name="propertyXName" format="string"/>
+        <!-- Name of the property being animated as the Y coordinate of the pathData. -->
+        <attr name="propertyYName" format="string"/>
+        <!-- The path used to animate the properties in the ObjectAnimator -->
+        <attr name="pathData"/>
     </declare-styleable>
 
 
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index f6ad78b..f73c958 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2207,6 +2207,8 @@
   <public type="attr" name="thumbTint" />
   <public type="attr" name="thumbTintMode" />
   <public type="attr" name="fullBackupOnly" />
+  <public type="attr" name="propertyXName" />
+  <public type="attr" name="propertyYName" />
 
   <public-padding type="dimen" name="l_resource_pad" end="0x01050010" />
 
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index a1e1f76..b4d1fdc 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -33,6 +33,7 @@
 import android.util.ArrayMap;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.util.PathParser;
 import android.util.Xml;
 
 import com.android.internal.R;
@@ -956,7 +957,7 @@
 
     }
 
-    static class VPath {
+    private static class VPath {
         private int[] mThemeAttrs;
 
         int mStrokeColor = 0;
@@ -974,7 +975,7 @@
         Paint.Join mStrokeLineJoin = Paint.Join.MITER;
         float mStrokeMiterlimit = 4;
 
-        private VNode[] mNode = null;
+        private PathParser.PathDataNode[] mNode = null;
         private String mPathName;
 
         public VPath() {
@@ -984,7 +985,7 @@
         public void toPath(Path path) {
             path.reset();
             if (mNode != null) {
-                VNode.createPath(mNode, path);
+                PathParser.PathDataNode.nodesToPath(mNode, path);
             }
         }
 
@@ -1099,7 +1100,8 @@
             }
 
             if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_pathData] == 0) {
-                mNode = parsePath(a.getString(R.styleable.VectorDrawablePath_pathData));
+                mNode = PathParser.createNodesFromPathData(a.getString(
+                        R.styleable.VectorDrawablePath_pathData));
             }
 
             if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_fill] == 0) {
@@ -1182,7 +1184,8 @@
             }
 
             if (a.hasValue(R.styleable.VectorDrawablePath_pathData)) {
-                mNode = parsePath(a.getString(R.styleable.VectorDrawablePath_pathData));
+                mNode = PathParser.createNodesFromPathData(a.getString(
+                        R.styleable.VectorDrawablePath_pathData));
             }
 
             mFillColor = a.getColor(R.styleable.VectorDrawablePath_fill, mFillColor);
@@ -1218,488 +1221,5 @@
                 mStrokeColor = applyAlpha(mStrokeColor, mStrokeOpacity);
             }
         }
-
-        private static int nextStart(String s, int end) {
-            char c;
-
-            while (end < s.length()) {
-                c = s.charAt(end);
-                if (((c - 'A') * (c - 'Z') <= 0) || (((c - 'a') * (c - 'z') <= 0))) {
-                    return end;
-                }
-                end++;
-            }
-            return end;
-        }
-
-        private void addNode(ArrayList<VectorDrawable.VNode> list, char cmd, float[] val) {
-            list.add(new VectorDrawable.VNode(cmd, val));
-        }
-
-        /**
-         * parse the floats in the string
-         * this is an optimized version of
-         * parseFloat(s.split(",|\\s"));
-         *
-         * @param s the string containing a command and list of floats
-         * @return array of floats
-         */
-        private static float[] getFloats(String s) {
-            if (s.charAt(0) == 'z' | s.charAt(0) == 'Z') {
-                return new float[0];
-            }
-            try {
-                float[] tmp = new float[s.length()];
-                int count = 0;
-                int pos = 1, end;
-                while ((end = extract(s, pos)) >= 0) {
-                    if (pos < end) {
-                        tmp[count++] = Float.parseFloat(s.substring(pos, end));
-                    }
-                    pos = end + 1;
-                }
-                // handle the final float if there is one
-                if (pos < s.length()) {
-                    tmp[count++] = Float.parseFloat(s.substring(pos, s.length()));
-                }
-                return Arrays.copyOf(tmp, count);
-            } catch (NumberFormatException e){
-                Log.e(LOGTAG,"error in parsing \""+s+"\"");
-                throw e;
-            }
-        }
-
-        /**
-         * calculate the position of the next comma or space
-         * @param s the string to search
-         * @param start the position to start searching
-         * @return the position of the next comma or space or -1 if none found
-         */
-        private static int extract(String s, int start) {
-            int space = s.indexOf(' ', start);
-            int comma = s.indexOf(',', start);
-            if (space == -1) {
-                return comma;
-            }
-            if (comma == -1) {
-                return space;
-            }
-            return (comma > space) ? space : comma;
-        }
-
-        private VectorDrawable.VNode[] parsePath(String value) {
-            int start = 0;
-            int end = 1;
-
-            ArrayList<VectorDrawable.VNode> list = new ArrayList<VectorDrawable.VNode>();
-            while (end < value.length()) {
-                end = nextStart(value, end);
-                String s = value.substring(start, end);
-                float[] val = getFloats(s);
-                addNode(list, s.charAt(0), val);
-
-                start = end;
-                end++;
-            }
-            if ((end - start) == 1 && start < value.length()) {
-
-                addNode(list, value.charAt(start), new float[0]);
-            }
-            return list.toArray(new VectorDrawable.VNode[list.size()]);
-        }
-    }
-
-    private static class VNode {
-        private char mType;
-        private float[] mParams;
-
-        public VNode(char type, float[] params) {
-            mType = type;
-            mParams = params;
-        }
-
-        public VNode(VNode n) {
-            mType = n.mType;
-            mParams = Arrays.copyOf(n.mParams, n.mParams.length);
-        }
-
-        public static void createPath(VNode[] node, Path path) {
-            float[] current = new float[4];
-            char previousCommand = 'm';
-            for (int i = 0; i < node.length; i++) {
-                addCommand(path, current, previousCommand, node[i].mType, node[i].mParams);
-                previousCommand = node[i].mType;
-            }
-        }
-
-        private static void addCommand(Path path, float[] current,
-                char previousCmd, char cmd, float[] val) {
-
-            int incr = 2;
-            float currentX = current[0];
-            float currentY = current[1];
-            float ctrlPointX = current[2];
-            float ctrlPointY = current[3];
-            float reflectiveCtrlPointX;
-            float reflectiveCtrlPointY;
-
-            switch (cmd) {
-                case 'z':
-                case 'Z':
-                    path.close();
-                    return;
-                case 'm':
-                case 'M':
-                case 'l':
-                case 'L':
-                case 't':
-                case 'T':
-                    incr = 2;
-                    break;
-                case 'h':
-                case 'H':
-                case 'v':
-                case 'V':
-                    incr = 1;
-                    break;
-                case 'c':
-                case 'C':
-                    incr = 6;
-                    break;
-                case 's':
-                case 'S':
-                case 'q':
-                case 'Q':
-                    incr = 4;
-                    break;
-                case 'a':
-                case 'A':
-                    incr = 7;
-                    break;
-            }
-            for (int k = 0; k < val.length; k += incr) {
-                switch (cmd) {
-                    case 'm': // moveto - Start a new sub-path (relative)
-                        path.rMoveTo(val[k + 0], val[k + 1]);
-                        currentX += val[k + 0];
-                        currentY += val[k + 1];
-                        break;
-                    case 'M': // moveto - Start a new sub-path
-                        path.moveTo(val[k + 0], val[k + 1]);
-                        currentX = val[k + 0];
-                        currentY = val[k + 1];
-                        break;
-                    case 'l': // lineto - Draw a line from the current point (relative)
-                        path.rLineTo(val[k + 0], val[k + 1]);
-                        currentX += val[k + 0];
-                        currentY += val[k + 1];
-                        break;
-                    case 'L': // lineto - Draw a line from the current point
-                        path.lineTo(val[k + 0], val[k + 1]);
-                        currentX = val[k + 0];
-                        currentY = val[k + 1];
-                        break;
-                    case 'z': // closepath - Close the current subpath
-                    case 'Z': // closepath - Close the current subpath
-                        path.close();
-                        break;
-                    case 'h': // horizontal lineto - Draws a horizontal line (relative)
-                        path.rLineTo(val[k + 0], 0);
-                        currentX += val[k + 0];
-                        break;
-                    case 'H': // horizontal lineto - Draws a horizontal line
-                        path.lineTo(val[k + 0], currentY);
-                        currentX = val[k + 0];
-                        break;
-                    case 'v': // vertical lineto - Draws a vertical line from the current point (r)
-                        path.rLineTo(0, val[k + 0]);
-                        currentY += val[k + 0];
-                        break;
-                    case 'V': // vertical lineto - Draws a vertical line from the current point
-                        path.lineTo(currentX, val[k + 0]);
-                        currentY = val[k + 0];
-                        break;
-                    case 'c': // curveto - Draws a cubic Bézier curve (relative)
-                        path.rCubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],
-                                val[k + 4], val[k + 5]);
-
-                        ctrlPointX = currentX + val[k + 2];
-                        ctrlPointY = currentY + val[k + 3];
-                        currentX += val[k + 4];
-                        currentY += val[k + 5];
-
-                        break;
-                    case 'C': // curveto - Draws a cubic Bézier curve
-                        path.cubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],
-                                val[k + 4], val[k + 5]);
-                        currentX = val[k + 4];
-                        currentY = val[k + 5];
-                        ctrlPointX = val[k + 2];
-                        ctrlPointY = val[k + 3];
-                        break;
-                    case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp)
-                        reflectiveCtrlPointX = 0;
-                        reflectiveCtrlPointY = 0;
-                        if (previousCmd == 'c' || previousCmd == 's'
-                                || previousCmd == 'C' || previousCmd == 'S') {
-                            reflectiveCtrlPointX = currentX - ctrlPointX;
-                            reflectiveCtrlPointY = currentY - ctrlPointY;
-                        }
-                        path.rCubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
-                                val[k + 0], val[k + 1],
-                                val[k + 2], val[k + 3]);
-
-                        ctrlPointX = currentX + val[k + 0];
-                        ctrlPointY = currentY + val[k + 1];
-                        currentX += val[k + 2];
-                        currentY += val[k + 3];
-                        break;
-                    case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp)
-                        reflectiveCtrlPointX = currentX;
-                        reflectiveCtrlPointY = currentY;
-                        if (previousCmd == 'c' || previousCmd == 's'
-                                || previousCmd == 'C' || previousCmd == 'S') {
-                            reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
-                            reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
-                        }
-                        path.cubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
-                                val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
-                        ctrlPointX = val[k + 0];
-                        ctrlPointY = val[k + 1];
-                        currentX = val[k + 2];
-                        currentY = val[k + 3];
-                        break;
-                    case 'q': // Draws a quadratic Bézier (relative)
-                        path.rQuadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
-                        ctrlPointX = currentX + val[k + 0];
-                        ctrlPointY = currentY + val[k + 1];
-                        currentX += val[k + 2];
-                        currentY += val[k + 3];
-                        break;
-                    case 'Q': // Draws a quadratic Bézier
-                        path.quadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
-                        ctrlPointX = val[k + 0];
-                        ctrlPointY = val[k + 1];
-                        currentX = val[k + 2];
-                        currentY = val[k + 3];
-                        break;
-                    case 't': // Draws a quadratic Bézier curve(reflective control point)(relative)
-                        reflectiveCtrlPointX = 0;
-                        reflectiveCtrlPointY = 0;
-                        if (previousCmd == 'q' || previousCmd == 't'
-                                || previousCmd == 'Q' || previousCmd == 'T') {
-                            reflectiveCtrlPointX = currentX - ctrlPointX;
-                            reflectiveCtrlPointY = currentY - ctrlPointY;
-                        }
-                        path.rQuadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
-                                val[k + 0], val[k + 1]);
-                        ctrlPointX = currentX + reflectiveCtrlPointX;
-                        ctrlPointY = currentY + reflectiveCtrlPointY;
-                        currentX += val[k + 0];
-                        currentY += val[k + 1];
-                        break;
-                    case 'T': // Draws a quadratic Bézier curve (reflective control point)
-                        reflectiveCtrlPointX = currentX;
-                        reflectiveCtrlPointY = currentY;
-                        if (previousCmd == 'q' || previousCmd == 't'
-                                || previousCmd == 'Q' || previousCmd == 'T') {
-                            reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
-                            reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
-                        }
-                        path.quadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
-                                val[k + 0], val[k + 1]);
-                        ctrlPointX = reflectiveCtrlPointX;
-                        ctrlPointY = reflectiveCtrlPointY;
-                        currentX = val[k + 0];
-                        currentY = val[k + 1];
-                        break;
-                    case 'a': // Draws an elliptical arc
-                        // (rx ry x-axis-rotation large-arc-flag sweep-flag x y)
-                        drawArc(path,
-                                currentX,
-                                currentY,
-                                val[k + 5] + currentX,
-                                val[k + 6] + currentY,
-                                val[k + 0],
-                                val[k + 1],
-                                val[k + 2],
-                                val[k + 3] != 0,
-                                val[k + 4] != 0);
-                        currentX += val[k + 5];
-                        currentY += val[k + 6];
-                        ctrlPointX = currentX;
-                        ctrlPointY = currentY;
-                        break;
-                    case 'A': // Draws an elliptical arc
-                        drawArc(path,
-                                currentX,
-                                currentY,
-                                val[k + 5],
-                                val[k + 6],
-                                val[k + 0],
-                                val[k + 1],
-                                val[k + 2],
-                                val[k + 3] != 0,
-                                val[k + 4] != 0);
-                        currentX = val[k + 5];
-                        currentY = val[k + 6];
-                        ctrlPointX = currentX;
-                        ctrlPointY = currentY;
-                        break;
-                }
-                previousCmd = cmd;
-            }
-            current[0] = currentX;
-            current[1] = currentY;
-            current[2] = ctrlPointX;
-            current[3] = ctrlPointY;
-        }
-
-        private static void drawArc(Path p,
-                float x0,
-                float y0,
-                float x1,
-                float y1,
-                float a,
-                float b,
-                float theta,
-                boolean isMoreThanHalf,
-                boolean isPositiveArc) {
-
-            /* Convert rotation angle from degrees to radians */
-            double thetaD = Math.toRadians(theta);
-            /* Pre-compute rotation matrix entries */
-            double cosTheta = Math.cos(thetaD);
-            double sinTheta = Math.sin(thetaD);
-            /* Transform (x0, y0) and (x1, y1) into unit space */
-            /* using (inverse) rotation, followed by (inverse) scale */
-            double x0p = (x0 * cosTheta + y0 * sinTheta) / a;
-            double y0p = (-x0 * sinTheta + y0 * cosTheta) / b;
-            double x1p = (x1 * cosTheta + y1 * sinTheta) / a;
-            double y1p = (-x1 * sinTheta + y1 * cosTheta) / b;
-
-            /* Compute differences and averages */
-            double dx = x0p - x1p;
-            double dy = y0p - y1p;
-            double xm = (x0p + x1p) / 2;
-            double ym = (y0p + y1p) / 2;
-            /* Solve for intersecting unit circles */
-            double dsq = dx * dx + dy * dy;
-            if (dsq == 0.0) {
-                Log.w(LOGTAG, " Points are coincident");
-                return; /* Points are coincident */
-            }
-            double disc = 1.0 / dsq - 1.0 / 4.0;
-            if (disc < 0.0) {
-                Log.w(LOGTAG, "Points are too far apart " + dsq);
-                float adjust = (float) (Math.sqrt(dsq) / 1.99999);
-                drawArc(p, x0, y0, x1, y1, a * adjust,
-                        b * adjust, theta, isMoreThanHalf, isPositiveArc);
-                return; /* Points are too far apart */
-            }
-            double s = Math.sqrt(disc);
-            double sdx = s * dx;
-            double sdy = s * dy;
-            double cx;
-            double cy;
-            if (isMoreThanHalf == isPositiveArc) {
-                cx = xm - sdy;
-                cy = ym + sdx;
-            } else {
-                cx = xm + sdy;
-                cy = ym - sdx;
-            }
-
-            double eta0 = Math.atan2((y0p - cy), (x0p - cx));
-
-            double eta1 = Math.atan2((y1p - cy), (x1p - cx));
-
-            double sweep = (eta1 - eta0);
-            if (isPositiveArc != (sweep >= 0)) {
-                if (sweep > 0) {
-                    sweep -= 2 * Math.PI;
-                } else {
-                    sweep += 2 * Math.PI;
-                }
-            }
-
-            cx *= a;
-            cy *= b;
-            double tcx = cx;
-            cx = cx * cosTheta - cy * sinTheta;
-            cy = tcx * sinTheta + cy * cosTheta;
-
-            arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep);
-        }
-
-        /**
-         * Converts an arc to cubic Bezier segments and records them in p.
-         *
-         * @param p The target for the cubic Bezier segments
-         * @param cx The x coordinate center of the ellipse
-         * @param cy The y coordinate center of the ellipse
-         * @param a The radius of the ellipse in the horizontal direction
-         * @param b The radius of the ellipse in the vertical direction
-         * @param e1x E(eta1) x coordinate of the starting point of the arc
-         * @param e1y E(eta2) y coordinate of the starting point of the arc
-         * @param theta The angle that the ellipse bounding rectangle makes with horizontal plane
-         * @param start The start angle of the arc on the ellipse
-         * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse
-         */
-        private static void arcToBezier(Path p,
-                double cx,
-                double cy,
-                double a,
-                double b,
-                double e1x,
-                double e1y,
-                double theta,
-                double start,
-                double sweep) {
-            // Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html
-            // and http://www.spaceroots.org/documents/ellipse/node22.html
-
-            // Maximum of 45 degrees per cubic Bezier segment
-            int numSegments = Math.abs((int) Math.ceil(sweep * 4 / Math.PI));
-
-            double eta1 = start;
-            double cosTheta = Math.cos(theta);
-            double sinTheta = Math.sin(theta);
-            double cosEta1 = Math.cos(eta1);
-            double sinEta1 = Math.sin(eta1);
-            double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);
-            double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1);
-
-            double anglePerSegment = sweep / numSegments;
-            for (int i = 0; i < numSegments; i++) {
-                double eta2 = eta1 + anglePerSegment;
-                double sinEta2 = Math.sin(eta2);
-                double cosEta2 = Math.cos(eta2);
-                double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2);
-                double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2);
-                double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2;
-                double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2;
-                double tanDiff2 = Math.tan((eta2 - eta1) / 2);
-                double alpha =
-                        Math.sin(eta2 - eta1) * (Math.sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3;
-                double q1x = e1x + alpha * ep1x;
-                double q1y = e1y + alpha * ep1y;
-                double q2x = e2x - alpha * ep2x;
-                double q2y = e2y - alpha * ep2y;
-
-                p.cubicTo((float) q1x,
-                        (float) q1y,
-                        (float) q2x,
-                        (float) q2y,
-                        (float) e2x,
-                        (float) e2y);
-                eta1 = eta2;
-                e1x = e2x;
-                e1y = e2y;
-                ep1x = ep2x;
-                ep1y = ep2y;
-            }
-        }
-
     }
 }
diff --git a/tests/VectorDrawableTest/res/anim/trim_path_animation03.xml b/tests/VectorDrawableTest/res/anim/trim_path_animation03.xml
index 72beba2..0c1073e 100644
--- a/tests/VectorDrawableTest/res/anim/trim_path_animation03.xml
+++ b/tests/VectorDrawableTest/res/anim/trim_path_animation03.xml
@@ -21,6 +21,8 @@
         android:duration="6000"
         android:propertyName="rotation"
         android:valueFrom="0"
-        android:valueTo="360"/>
+        android:valueTo="360"
+        android:interpolator="@interpolator/custom_path_interpolator"
+        />
 
 </set>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/anim/trim_path_animation04.xml b/tests/VectorDrawableTest/res/anim/trim_path_animation04.xml
index ff86668..4d0aae1 100644
--- a/tests/VectorDrawableTest/res/anim/trim_path_animation04.xml
+++ b/tests/VectorDrawableTest/res/anim/trim_path_animation04.xml
@@ -16,11 +16,9 @@
 -->
 
 <set xmlns:android="http://schemas.android.com/apk/res/android" >
-
     <objectAnimator
         android:duration="9000"
-        android:propertyName="rotation"
-        android:valueFrom="0"
-        android:valueTo="360"/>
-
+        android:propertyXName="translateX"
+        android:propertyYName="translateY"
+        android:pathData="m0,0 q 150, 300 150, 0 t 150, 0, t 150, 0 t -150 0 t -150 0 t -150 0 z" />
 </set>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/animation_vector_drawable01.xml b/tests/VectorDrawableTest/res/drawable/animation_vector_drawable01.xml
index b8681b6..0900b7c 100644
--- a/tests/VectorDrawableTest/res/drawable/animation_vector_drawable01.xml
+++ b/tests/VectorDrawableTest/res/drawable/animation_vector_drawable01.xml
@@ -32,5 +32,8 @@
     <target
         android:name="rotationGroupBlue"
         android:animation="@anim/trim_path_animation03" />
+    <target
+        android:name="rotationGroup"
+        android:animation="@anim/trim_path_animation04" />
 
 </animated-vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/interpolator/custom_path_interpolator.xml b/tests/VectorDrawableTest/res/interpolator/custom_path_interpolator.xml
new file mode 100644
index 0000000..0cffa0a
--- /dev/null
+++ b/tests/VectorDrawableTest/res/interpolator/custom_path_interpolator.xml
@@ -0,0 +1,2 @@
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:pathData="m0,0q0.4,0.05 0.6,0.3t0.3,0.3l0.1,0.4" />
\ No newline at end of file