Implement Path.approximate and add PathMeasure support

PathMeasure is needed to use the trimPath* properties in drawables.
These properties are used in the vector drawable for indeterminate
progress bars in Material so this is needed to be able to render them
correctly in the preview.
PathMeasure makes use of Path.approximate to calculate which segments to
paint.

Change-Id: Ic513f0a30a6aac0317f7c13cd75e9154c37405c8
diff --git a/tools/layoutlib/bridge/src/android/animation/AnimatorInflater_Delegate.java b/tools/layoutlib/bridge/src/android/animation/AnimatorInflater_Delegate.java
deleted file mode 100644
index 4475fa4..0000000
--- a/tools/layoutlib/bridge/src/android/animation/AnimatorInflater_Delegate.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * 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.animation;
-
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.Resources.NotFoundException;
-import android.content.res.Resources.Theme;
-import android.util.AttributeSet;
-
-/**
- * Delegate providing alternate implementation to static methods in {@link AnimatorInflater}.
- */
-public class AnimatorInflater_Delegate {
-
-    @LayoutlibDelegate
-    /*package*/ static Animator loadAnimator(Context context, int id)
-            throws NotFoundException {
-        return loadAnimator(context.getResources(), context.getTheme(), id);
-    }
-
-    @LayoutlibDelegate
-    /*package*/ static Animator loadAnimator(Resources resources, Theme theme, int id)
-            throws NotFoundException {
-        return loadAnimator(resources, theme, id, 1);
-    }
-
-    @LayoutlibDelegate
-    /*package*/ static Animator loadAnimator(Resources resources, Theme theme, int id,
-            float pathErrorScale) throws NotFoundException {
-        // This is a temporary fix to http://b.android.com/77865. This skips loading the
-        // animation altogether.
-        // TODO: Remove this override when Path.approximate() is supported.
-        return new FakeAnimator();
-    }
-
-    @LayoutlibDelegate
-    /*package*/ static ValueAnimator loadAnimator(Resources res, Theme theme,
-            AttributeSet attrs, ValueAnimator anim, float pathErrorScale)
-            throws NotFoundException {
-        return AnimatorInflater.loadAnimator_Original(res, theme, attrs, anim, pathErrorScale);
-    }
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/PathMeasure_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PathMeasure_Delegate.java
new file mode 100644
index 0000000..dd2978f
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/PathMeasure_Delegate.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2015 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.graphics;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import java.awt.geom.PathIterator;
+import java.awt.geom.Point2D;
+
+/**
+ * Delegate implementing the native methods of {@link android.graphics.PathMeasure}
+ * <p/>
+ * Through the layoutlib_create tool, the original native methods of PathMeasure have been
+ * replaced by
+ * calls to methods of the same name in this delegate class.
+ * <p/>
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between it
+ * and the original PathMeasure class.
+ *
+ * @see DelegateManager
+ */
+public final class PathMeasure_Delegate {
+    // ---- delegate manager ----
+    private static final DelegateManager<PathMeasure_Delegate> sManager =
+            new DelegateManager<PathMeasure_Delegate>(PathMeasure_Delegate.class);
+
+    // ---- delegate data ----
+    // This governs how accurate the approximation of the Path is.
+    private static final float PRECISION = 0.002f;
+
+    /**
+     * Array containing the path points components. There are three components for each point:
+     * <ul>
+     *     <li>Fraction along the length of the path that the point resides</li>
+     *     <li>The x coordinate of the point</li>
+     *     <li>The y coordinate of the point</li>
+     * </ul>
+     */
+    private float mPathPoints[];
+    private long mNativePath;
+
+    private PathMeasure_Delegate(long native_path, boolean forceClosed) {
+        mNativePath = native_path;
+        if (forceClosed && mNativePath != 0) {
+            // Copy the path and call close
+            mNativePath = Path_Delegate.init2(native_path);
+            Path_Delegate.native_close(mNativePath);
+        }
+
+        mPathPoints =
+                mNativePath != 0 ? Path_Delegate.native_approximate(mNativePath, PRECISION) : null;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long native_create(long native_path, boolean forceClosed) {
+        return sManager.addNewDelegate(new PathMeasure_Delegate(native_path, forceClosed));
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void native_destroy(long native_instance) {
+        sManager.removeJavaReferenceFor(native_instance);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean native_getPosTan(long native_instance, float distance, float pos[],
+            float tan[]) {
+        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+                "PathMeasure.getPostTan is not supported.", null, null);
+        return false;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean native_getMatrix(long native_instance, float distance, long
+            native_matrix, int flags) {
+        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+                "PathMeasure.getMatrix is not supported.", null, null);
+        return false;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean native_nextContour(long native_instance) {
+        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+                "PathMeasure.nextContour is not supported.", null, null);
+        return false;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void native_setPath(long native_instance, long native_path, boolean
+            forceClosed) {
+        PathMeasure_Delegate pathMeasure = sManager.getDelegate(native_instance);
+        assert pathMeasure != null;
+
+        if (forceClosed && native_path != 0) {
+            // Copy the path and call close
+            native_path = Path_Delegate.init2(native_path);
+            Path_Delegate.native_close(native_path);
+        }
+        pathMeasure.mNativePath = native_path;
+        pathMeasure.mPathPoints = Path_Delegate.native_approximate(native_path, PRECISION);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static float native_getLength(long native_instance) {
+        PathMeasure_Delegate pathMeasure = sManager.getDelegate(native_instance);
+        assert pathMeasure != null;
+
+        if (pathMeasure.mPathPoints == null) {
+            return 0;
+        }
+
+        float length = 0;
+        int nPoints = pathMeasure.mPathPoints.length / 3;
+        for (int i = 1; i < nPoints; i++) {
+            length += Point2D.distance(
+                    pathMeasure.mPathPoints[(i - 1) * 3 + 1],
+                    pathMeasure.mPathPoints[(i - 1) * 3 + 2],
+                    pathMeasure.mPathPoints[i*3 + 1],
+                    pathMeasure.mPathPoints[i*3 + 2]);
+        }
+
+        return length;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean native_isClosed(long native_instance) {
+        PathMeasure_Delegate pathMeasure = sManager.getDelegate(native_instance);
+        assert pathMeasure != null;
+
+        Path_Delegate path = Path_Delegate.getDelegate(pathMeasure.mNativePath);
+        if (path == null) {
+            return false;
+        }
+
+        PathIterator pathIterator = path.getJavaShape().getPathIterator(null);
+
+        int type = 0;
+        float segment[] = new float[6];
+        while (!pathIterator.isDone()) {
+            type = pathIterator.currentSegment(segment);
+            pathIterator.next();
+        }
+
+        // A path is a closed path if the last element is SEG_CLOSE
+        return type == PathIterator.SEG_CLOSE;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean native_getSegment(long native_instance, float startD, float stopD,
+            long native_dst_path, boolean startWithMoveTo) {
+        if (startD < 0) {
+            startD = 0;
+        }
+
+        if (startD >= stopD) {
+            return false;
+        }
+
+        PathMeasure_Delegate pathMeasure = sManager.getDelegate(native_instance);
+        assert pathMeasure != null;
+
+        if (pathMeasure.mPathPoints == null) {
+            return false;
+        }
+
+        float accLength = 0;
+        boolean isZeroLength = true; // Whether the output has zero length or not
+        int nPoints = pathMeasure.mPathPoints.length / 3;
+        for (int i = 0; i < nPoints; i++) {
+            float x = pathMeasure.mPathPoints[i * 3 + 1];
+            float y = pathMeasure.mPathPoints[i * 3 + 2];
+            if (accLength >= startD && accLength <= stopD) {
+                if (startWithMoveTo) {
+                    startWithMoveTo = false;
+                    Path_Delegate.native_moveTo(native_dst_path, x, y);
+                } else {
+                    isZeroLength = false;
+                    Path_Delegate.native_lineTo(native_dst_path, x, y);
+                }
+            }
+
+            if (i > 0) {
+                accLength += Point2D.distance(
+                        pathMeasure.mPathPoints[(i - 1) * 3 + 1],
+                        pathMeasure.mPathPoints[(i - 1) * 3 + 2],
+                        pathMeasure.mPathPoints[i * 3 + 1],
+                        pathMeasure.mPathPoints[i * 3 + 2]);
+            }
+        }
+
+        return !isZeroLength;
+    }
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java
index 3c9a062..a2a53fe 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java
@@ -36,6 +36,7 @@
 import java.awt.geom.Point2D;
 import java.awt.geom.Rectangle2D;
 import java.awt.geom.RoundRectangle2D;
+import java.util.ArrayList;
 
 /**
  * Delegate implementing the native methods of android.graphics.Path
@@ -173,11 +174,8 @@
     @LayoutlibDelegate
     /*package*/ static boolean native_isEmpty(long nPath) {
         Path_Delegate pathDelegate = sManager.getDelegate(nPath);
-        if (pathDelegate == null) {
-            return true;
-        }
+        return pathDelegate == null || pathDelegate.isEmpty();
 
-        return pathDelegate.isEmpty();
     }
 
     @LayoutlibDelegate
@@ -488,54 +486,44 @@
 
     @LayoutlibDelegate
     /*package*/ static float[] native_approximate(long nPath, float error) {
-        Bridge.getLog().warning(LayoutLog.TAG_UNSUPPORTED, "Path.approximate() not fully supported",
-                null);
         Path_Delegate pathDelegate = sManager.getDelegate(nPath);
         if (pathDelegate == null) {
             return null;
         }
-        PathIterator pathIterator = pathDelegate.mPath.getPathIterator(null);
-        float[] tmp = new float[6];
-        float[] coords = new float[6];
-        boolean isFirstPoint = true;
-        while (!pathIterator.isDone()) {
-            int type = pathIterator.currentSegment(tmp);
-            switch (type) {
-                case PathIterator.SEG_MOVETO:
-                case PathIterator.SEG_LINETO:
-                    store(tmp, coords, 1, isFirstPoint);
-                    break;
-                case PathIterator.SEG_QUADTO:
-                    store(tmp, coords, 2, isFirstPoint);
-                    break;
-                case PathIterator.SEG_CUBICTO:
-                    store(tmp, coords, 3, isFirstPoint);
-                    break;
-                case PathIterator.SEG_CLOSE:
-                    // No points returned.
-            }
-            isFirstPoint = false;
-            pathIterator.next();
-        }
-        if (isFirstPoint) {
-            // No points found
-            return new float[0];
-        } else {
-            return coords;
-        }
-    }
+        // Get a FlatteningIterator
+        PathIterator iterator = pathDelegate.getJavaShape().getPathIterator(null, error);
 
-    private static void store(float[] src, float[] dst, int count, boolean isFirst) {
-        if (isFirst) {
-            dst[0] = 0;       // fraction
-            dst[1] = src[0];  // abscissa
-            dst[2] = src[1];  // ordinate
+        float segment[] = new float[6];
+        float totalLength = 0;
+        ArrayList<Point2D.Float> points = new ArrayList<Point2D.Float>();
+        Point2D.Float previousPoint = null;
+        while (!iterator.isDone()) {
+            int type = iterator.currentSegment(segment);
+            Point2D.Float currentPoint = new Point2D.Float(segment[0], segment[1]);
+            // MoveTo shouldn't affect the length
+            if (previousPoint != null && type != PathIterator.SEG_MOVETO) {
+                totalLength += currentPoint.distance(previousPoint);
+            }
+            previousPoint = currentPoint;
+            points.add(currentPoint);
+            iterator.next();
         }
-        if (count > 1 || !isFirst) {
-            dst[3] = 1;
-            dst[4] = src[2 * count - 2];
-            dst[5] = src[2 * count - 1];
+
+        int nPoints = points.size();
+        float[] result = new float[nPoints * 3];
+        previousPoint = null;
+        for (int i = 0; i < nPoints; i++) {
+            Point2D.Float point = points.get(i);
+            float distance = previousPoint != null ? (float) previousPoint.distance(point) : .0f;
+            result[i * 3] = distance / totalLength;
+            result[i * 3 + 1] = point.x;
+            result[i * 3 + 2] = point.y;
+
+            totalLength += distance;
+            previousPoint = point;
         }
+
+        return result;
     }
 
     // ---- Private helper methods ----
@@ -735,6 +723,9 @@
      */
     private void cubicTo(float x1, float y1, float x2, float y2,
                         float x3, float y3) {
+        if (isEmpty()) {
+            mPath.moveTo(0, 0);
+        }
         mPath.curveTo(x1, y1, x2, y2, mLastX = x3, mLastY = y3);
     }
 
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index 484240f..9244310 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -157,7 +157,6 @@
      * The list of methods to rewrite as delegates.
      */
     public final static String[] DELEGATE_METHODS = new String[] {
-        "android.animation.AnimatorInflater#loadAnimator",  // TODO: remove when Path.approximate() is supported.
         "android.app.Fragment#instantiate", //(Landroid/content/Context;Ljava/lang/String;Landroid/os/Bundle;)Landroid/app/Fragment;",
         "android.content.res.Resources$Theme#obtainStyledAttributes",
         "android.content.res.Resources$Theme#resolveAttribute",
@@ -235,6 +234,7 @@
         "android.graphics.Path",
         "android.graphics.PathDashPathEffect",
         "android.graphics.PathEffect",
+        "android.graphics.PathMeasure",
         "android.graphics.PixelXorXfermode",
         "android.graphics.PorterDuffColorFilter",
         "android.graphics.PorterDuffXfermode",