LayoutLib: Support Region through delegates.

also finish supporting some clip operation
on the canvas.

Change-Id: I743b9e52a7aa6e9340506f1c904cc1cfbf3ff81f
diff --git a/bridge/src/android/graphics/Bitmap_Delegate.java b/bridge/src/android/graphics/Bitmap_Delegate.java
index 1e1b2cf..dd57bd1 100644
--- a/bridge/src/android/graphics/Bitmap_Delegate.java
+++ b/bridge/src/android/graphics/Bitmap_Delegate.java
@@ -384,7 +384,7 @@
         // This is only called by Bitmap.CREATOR (Parcelable.Creator<Bitmap>), which is only
         // used during aidl call so really this should not be called.
         Bridge.getLog().error(null,
-                "AIDL is not suppored, and therefore bitmap cannot be created from parcels");
+                "AIDL is not suppored, and therefore Bitmaps cannot be created from parcels.");
         return null;
     }
 
@@ -393,7 +393,7 @@
         // This is only called when sending a bitmap through aidl, so really this should not
         // be called.
         Bridge.getLog().error(null,
-                "AIDL is not suppored, and therefore bitmap cannot be written to parcels");
+                "AIDL is not suppored, and therefore Bitmaps cannot be written to parcels.");
         return false;
     }
 
diff --git a/bridge/src/android/graphics/Canvas_Delegate.java b/bridge/src/android/graphics/Canvas_Delegate.java
index b3a490f..def0f02 100644
--- a/bridge/src/android/graphics/Canvas_Delegate.java
+++ b/bridge/src/android/graphics/Canvas_Delegate.java
@@ -271,8 +271,7 @@
     }
 
     /*package*/ static void freeCaches() {
-        // FIXME
-        throw new UnsupportedOperationException();
+        // nothing to be done here.
     }
 
     /*package*/ static int initRaster(int nativeBitmapOrZero) {
@@ -440,15 +439,33 @@
     /*package*/ static boolean native_clipPath(int nativeCanvas,
                                                   int nativePath,
                                                   int regionOp) {
-        // FIXME
-        throw new UnsupportedOperationException();
+        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+        if (canvasDelegate == null) {
+            return true;
+        }
+
+        Path_Delegate pathDelegate = Path_Delegate.getDelegate(nativePath);
+        if (pathDelegate == null) {
+            return true;
+        }
+
+        return canvasDelegate.mSnapshot.clip(pathDelegate.getJavaShape(), regionOp);
     }
 
     /*package*/ static boolean native_clipRegion(int nativeCanvas,
                                                     int nativeRegion,
                                                     int regionOp) {
-        // FIXME
-        throw new UnsupportedOperationException();
+        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+        if (canvasDelegate == null) {
+            return true;
+        }
+
+        Region_Delegate region = Region_Delegate.getDelegate(nativeRegion);
+        if (region == null) {
+            return true;
+        }
+
+        return canvasDelegate.mSnapshot.clip(region.getJavaArea(), regionOp);
     }
 
     /*package*/ static void nativeSetDrawFilter(int nativeCanvas,
diff --git a/bridge/src/android/graphics/Paint_Delegate.java b/bridge/src/android/graphics/Paint_Delegate.java
index fc58475..9d4970f 100644
--- a/bridge/src/android/graphics/Paint_Delegate.java
+++ b/bridge/src/android/graphics/Paint_Delegate.java
@@ -25,6 +25,8 @@
 
 import java.awt.BasicStroke;
 import java.awt.Font;
+import java.awt.Shape;
+import java.awt.Stroke;
 import java.awt.Toolkit;
 import java.awt.font.FontRenderContext;
 import java.awt.geom.AffineTransform;
@@ -167,6 +169,30 @@
         }
     }
 
+    public Stroke getJavaStroke() {
+        PathEffect_Delegate effectDelegate = PathEffect_Delegate.getDelegate(mPathEffect);
+        if (effectDelegate != null) {
+            if (effectDelegate.isSupported()) {
+                Stroke stroke = effectDelegate.getStroke(this);
+                assert stroke != null;
+                if (stroke != null) {
+                    return stroke;
+                }
+            } else {
+                Bridge.getLog().fidelityWarning(null,
+                        effectDelegate.getSupportMessage(),
+                        null);
+            }
+        }
+
+        // if no custom stroke as been set, set the default one.
+        return new BasicStroke(
+                    getStrokeWidth(),
+                    getJavaCap(),
+                    getJavaJoin(),
+                    getJavaStrokeMiter());
+    }
+
     /**
      * Returns the {@link Xfermode} delegate or null if none have been set
      *
@@ -195,15 +221,6 @@
     }
 
     /**
-     * Returns the {@link PathEffect} delegate or null if none have been set
-     *
-     * @return the delegate or null.
-     */
-    public PathEffect_Delegate getPathEffect() {
-        return PathEffect_Delegate.getDelegate(mPathEffect);
-    }
-
-    /**
      * Returns the {@link MaskFilter} delegate or null if none have been set
      *
      * @return the delegate or null.
@@ -628,8 +645,28 @@
     }
 
     /*package*/ static boolean native_getFillPath(int native_object, int src, int dst) {
-        // FIXME
-        throw new UnsupportedOperationException();
+        Paint_Delegate paint = sManager.getDelegate(native_object);
+        if (paint == null) {
+            return false;
+        }
+
+        Path_Delegate srcPath = Path_Delegate.getDelegate(src);
+        if (srcPath == null) {
+            return true;
+        }
+
+        Path_Delegate dstPath = Path_Delegate.getDelegate(dst);
+        if (dstPath == null) {
+            return true;
+        }
+
+        Stroke stroke = paint.getJavaStroke();
+        Shape strokeShape = stroke.createStrokedShape(srcPath.getJavaShape());
+
+        dstPath.setJavaShape(strokeShape);
+
+        // FIXME figure out the return value?
+        return true;
     }
 
     /*package*/ static int native_setShader(int native_object, int shader) {
diff --git a/bridge/src/android/graphics/Path_Delegate.java b/bridge/src/android/graphics/Path_Delegate.java
index c5c3220..66ab29c 100644
--- a/bridge/src/android/graphics/Path_Delegate.java
+++ b/bridge/src/android/graphics/Path_Delegate.java
@@ -67,6 +67,20 @@
         return mPath;
     }
 
+    public void setJavaShape(Shape shape) {
+        mPath.reset();
+        mPath.append(shape, false /*connect*/);
+    }
+
+    public void reset() {
+        mPath.reset();
+    }
+
+    public void setPathIterator(PathIterator iterator) {
+        mPath.reset();
+        mPath.append(iterator, false /*connect*/);
+    }
+
     // ---- native methods ----
 
     /*package*/ static int init1() {
diff --git a/bridge/src/android/graphics/Region_Delegate.java b/bridge/src/android/graphics/Region_Delegate.java
new file mode 100644
index 0000000..684bb90
--- /dev/null
+++ b/bridge/src/android/graphics/Region_Delegate.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2010 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.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+
+import android.os.Parcel;
+
+import java.awt.Rectangle;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Area;
+import java.awt.geom.Rectangle2D;
+
+/**
+ * Delegate implementing the native methods of android.graphics.Region
+ *
+ * Through the layoutlib_create tool, the original native methods of Region have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ * 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 Region class.
+ *
+ * This also serve as a base class for all Region delegate classes.
+ *
+ * @see DelegateManager
+ *
+ */
+public class Region_Delegate {
+
+    // ---- delegate manager ----
+    protected static final DelegateManager<Region_Delegate> sManager =
+            new DelegateManager<Region_Delegate>();
+
+    // ---- delegate helper data ----
+
+    // ---- delegate data ----
+    private Area mArea = new Area();
+
+    // ---- Public Helper methods ----
+
+    public static Region_Delegate getDelegate(int nativeShader) {
+        return sManager.getDelegate(nativeShader);
+    }
+
+    public Area getJavaArea() {
+        return mArea;
+    }
+
+    /**
+     * Combines two {@link Shape} into another one (actually an {@link Area}), according
+     * to the given {@link Region.Op}.
+     *
+     * If the Op is not one that combines two shapes, then this return null
+     *
+     * @param shape1 the firt shape to combine
+     * @param shape2 the 2nd shape to combine
+     * @param regionOp the operande for the combine
+     * @return a new area or null.
+     */
+    public static Area combineShapes(Shape shape1, Shape shape2, int regionOp) {
+        if (regionOp == Region.Op.DIFFERENCE.nativeInt) {
+            // result is always a new area.
+            Area result = new Area(shape1);
+            result.subtract(shape2 instanceof Area ? (Area) shape2 : new Area(shape2));
+            return result;
+
+        } else if (regionOp == Region.Op.INTERSECT.nativeInt) {
+            // result is always a new area.
+            Area result = new Area(shape1);
+            result.intersect(shape2 instanceof Area ? (Area) shape2 : new Area(shape2));
+            return result;
+
+        } else if (regionOp == Region.Op.UNION.nativeInt) {
+            // result is always a new area.
+            Area result = new Area(shape1);
+            result.add(shape2 instanceof Area ? (Area) shape2 : new Area(shape2));
+            return result;
+
+        } else if (regionOp == Region.Op.XOR.nativeInt) {
+            // result is always a new area.
+            Area result = new Area(shape1);
+            result.exclusiveOr(shape2 instanceof Area ? (Area) shape2 : new Area(shape2));
+
+        } else if (regionOp == Region.Op.REVERSE_DIFFERENCE.nativeInt) {
+            // result is always a new area.
+            Area result = new Area(shape2);
+            result.subtract(shape1 instanceof Area ? (Area) shape1 : new Area(shape1));
+            return result;
+        }
+
+        return null;
+    }
+
+    // ---- native methods ----
+
+    /*package*/ static int nativeConstructor() {
+        Region_Delegate newDelegate = new Region_Delegate();
+        return sManager.addDelegate(newDelegate);
+    }
+
+    /*package*/ static void nativeDestructor(int native_region) {
+        sManager.removeDelegate(native_region);
+    }
+
+    /*package*/ static boolean nativeSetRegion(int native_dst, int native_src) {
+        Region_Delegate dstRegion = sManager.getDelegate(native_dst);
+        if (dstRegion == null) {
+            return true;
+        }
+
+        Region_Delegate srcRegion = sManager.getDelegate(native_src);
+        if (srcRegion == null) {
+            return true;
+        }
+
+        dstRegion.mArea.reset();
+        dstRegion.mArea.add(srcRegion.mArea);
+
+        return true;
+    }
+
+    /*package*/ static boolean nativeSetRect(int native_dst,
+            int left, int top, int right, int bottom) {
+        Region_Delegate dstRegion = sManager.getDelegate(native_dst);
+        if (dstRegion == null) {
+            return true;
+        }
+
+        dstRegion.mArea = new Area(new Rectangle2D.Float(left, top, right - left, bottom - top));
+        return dstRegion.mArea.getBounds().isEmpty() == false;
+    }
+
+    /*package*/ static boolean nativeSetPath(int native_dst, int native_path, int native_clip) {
+        Region_Delegate dstRegion = sManager.getDelegate(native_dst);
+        if (dstRegion == null) {
+            return true;
+        }
+
+        Path_Delegate path = Path_Delegate.getDelegate(native_path);
+        if (path == null) {
+            return true;
+        }
+
+        dstRegion.mArea = new Area(path.getJavaShape());
+
+        Region_Delegate clip = sManager.getDelegate(native_clip);
+        if (clip != null) {
+            dstRegion.mArea.subtract(clip.getJavaArea());
+        }
+
+        return dstRegion.mArea.getBounds().isEmpty() == false;
+    }
+
+    /*package*/ static boolean nativeGetBounds(int native_region, Rect rect) {
+        Region_Delegate region = sManager.getDelegate(native_region);
+        if (region == null) {
+            return true;
+        }
+
+        Rectangle bounds = region.mArea.getBounds();
+        if (bounds.isEmpty()) {
+            rect.left = rect.top = rect.right = rect.bottom = 0;
+            return false;
+        }
+
+        rect.left = bounds.x;
+        rect.top = bounds.y;
+        rect.right = bounds.x + bounds.width;
+        rect.bottom = bounds.y + bounds.height;
+        return true;
+    }
+
+    /*package*/ static boolean nativeGetBoundaryPath(int native_region, int native_path) {
+        Region_Delegate region = sManager.getDelegate(native_region);
+        if (region == null) {
+            return false;
+        }
+
+        Path_Delegate path = Path_Delegate.getDelegate(native_path);
+        if (path == null) {
+            return false;
+        }
+
+        if (region.mArea.isEmpty()) {
+            path.reset();
+            return false;
+        }
+
+        path.setPathIterator(region.mArea.getPathIterator(new AffineTransform()));
+        return true;
+    }
+
+    /*package*/ static boolean nativeOp(int native_dst,
+            int left, int top, int right, int bottom, int op) {
+        Region_Delegate region = sManager.getDelegate(native_dst);
+        if (region == null) {
+            return false;
+        }
+
+        region.mArea = combineShapes(region.mArea,
+                new Rectangle2D.Float(left, top, right - left, bottom - top), op);
+
+        assert region.mArea != null;
+        if (region.mArea != null) {
+            region.mArea = new Area();
+        }
+
+        return region.mArea.getBounds().isEmpty() == false;
+    }
+
+    /*package*/ static boolean nativeOp(int native_dst, Rect rect, int native_region, int op) {
+        Region_Delegate region = sManager.getDelegate(native_dst);
+        if (region == null) {
+            return false;
+        }
+
+        region.mArea = combineShapes(region.mArea,
+                new Rectangle2D.Float(rect.left, rect.top, rect.width(), rect.height()), op);
+
+        assert region.mArea != null;
+        if (region.mArea != null) {
+            region.mArea = new Area();
+        }
+
+        return region.mArea.getBounds().isEmpty() == false;
+    }
+
+    /*package*/ static boolean nativeOp(int native_dst,
+            int native_region1, int native_region2, int op) {
+        Region_Delegate dstRegion = sManager.getDelegate(native_dst);
+        if (dstRegion == null) {
+            return true;
+        }
+
+        Region_Delegate region1 = sManager.getDelegate(native_region1);
+        if (region1 == null) {
+            return false;
+        }
+
+        Region_Delegate region2 = sManager.getDelegate(native_region2);
+        if (region2 == null) {
+            return false;
+        }
+
+        dstRegion.mArea = combineShapes(region1.mArea, region2.mArea, op);
+
+        assert dstRegion.mArea != null;
+        if (dstRegion.mArea != null) {
+            dstRegion.mArea = new Area();
+        }
+
+        return dstRegion.mArea.getBounds().isEmpty() == false;
+
+    }
+
+    /*package*/ static int nativeCreateFromParcel(Parcel p) {
+        // This is only called by Region.CREATOR (Parcelable.Creator<Region>), which is only
+        // used during aidl call so really this should not be called.
+        Bridge.getLog().error(null,
+                "AIDL is not suppored, and therefore Regions cannot be created from parcels.");
+        return 0;
+    }
+
+    /*package*/ static boolean nativeWriteToParcel(int native_region,
+                                                      Parcel p) {
+        // This is only called when sending a region through aidl, so really this should not
+        // be called.
+        Bridge.getLog().error(null,
+                "AIDL is not suppored, and therefore Regions cannot be written to parcels.");
+        return false;
+    }
+
+    /*package*/ static boolean nativeEquals(int native_r1, int native_r2) {
+        Region_Delegate region1 = sManager.getDelegate(native_r1);
+        if (region1 == null) {
+            return false;
+        }
+
+        Region_Delegate region2 = sManager.getDelegate(native_r2);
+        if (region2 == null) {
+            return false;
+        }
+
+        return region1.mArea.equals(region2.mArea);
+    }
+
+    // ---- Private delegate/helper methods ----
+
+}
diff --git a/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java b/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java
index afe75a0..a2fcb3b 100644
--- a/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java
+++ b/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java
@@ -22,21 +22,19 @@
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Paint_Delegate;
-import android.graphics.PathEffect_Delegate;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Region;
+import android.graphics.Region_Delegate;
 import android.graphics.Shader_Delegate;
 import android.graphics.Xfermode_Delegate;
 
 import java.awt.AlphaComposite;
-import java.awt.BasicStroke;
 import java.awt.Color;
 import java.awt.Composite;
 import java.awt.Graphics2D;
 import java.awt.RenderingHints;
 import java.awt.Shape;
-import java.awt.Stroke;
 import java.awt.geom.AffineTransform;
 import java.awt.geom.Area;
 import java.awt.geom.Rectangle2D;
@@ -448,72 +446,39 @@
         }
     }
 
-    public boolean clipRect(float left, float top, float right, float bottom, int regionOp) {
+    public boolean clip(Shape shape, int regionOp) {
+        Area area = null;
+        if (regionOp == Region.Op.REPLACE.nativeInt) {
+            area = new Area(shape);
+        } else {
+            area = Region_Delegate.combineShapes(getClip(), shape, regionOp);
+        }
+
+        assert area != null;
+
         if (mLayers.size() > 0) {
-            Shape clip = null;
-            if (regionOp == Region.Op.DIFFERENCE.nativeInt) {
-                Area newClip = new Area(getClip());
-                newClip.subtract(new Area(
-                        new Rectangle2D.Float(left, top, right - left, bottom - top)));
-                clip = newClip;
-
-            } else if (regionOp == Region.Op.INTERSECT.nativeInt) {
+            if (area != null) {
                 for (Layer layer : mLayers) {
-                    layer.getGraphics().clipRect(
-                            (int) left, (int) top, (int) (right - left), (int) (bottom - top));
-                }
-
-            } else if (regionOp == Region.Op.UNION.nativeInt) {
-                Area newClip = new Area(getClip());
-                newClip.add(new Area(
-                        new Rectangle2D.Float(left, top, right - left, bottom - top)));
-                clip = newClip;
-
-            } else if (regionOp == Region.Op.XOR.nativeInt) {
-                Area newClip = new Area(getClip());
-                newClip.exclusiveOr(new Area(
-                        new Rectangle2D.Float(left, top, right - left, bottom - top)));
-                clip = newClip;
-
-            } else if (regionOp == Region.Op.REVERSE_DIFFERENCE.nativeInt) {
-                Area newClip = new Area(
-                        new Rectangle2D.Float(left, top, right - left, bottom - top));
-                newClip.subtract(new Area(getClip()));
-                clip = newClip;
-
-            } else if (regionOp == Region.Op.REPLACE.nativeInt) {
-                for (Layer layer : mLayers) {
-                    layer.getGraphics().setClip(
-                            (int) left, (int) top, (int) (right - left), (int) (bottom - top));
-                }
-            }
-
-            if (clip != null) {
-                for (Layer layer : mLayers) {
-                    layer.getGraphics().setClip(clip);
+                    layer.getGraphics().setClip(area);
                 }
             }
 
             return getClip().getBounds().isEmpty() == false;
         } else {
-            if (mClip == null) {
+            if (area != null) {
+                mClip = area;
+            } else {
                 mClip = new Area();
             }
 
-            if (regionOp == Region.Op.DIFFERENCE.nativeInt) {
-                //FIXME
-            } else if (regionOp == Region.Op.DIFFERENCE.nativeInt) {
-            } else if (regionOp == Region.Op.INTERSECT.nativeInt) {
-            } else if (regionOp == Region.Op.UNION.nativeInt) {
-            } else if (regionOp == Region.Op.XOR.nativeInt) {
-            } else if (regionOp == Region.Op.REVERSE_DIFFERENCE.nativeInt) {
-            } else if (regionOp == Region.Op.REPLACE.nativeInt) {
-            }
-
             return mClip.getBounds().isEmpty() == false;
         }
     }
 
+    public boolean clipRect(float left, float top, float right, float bottom, int regionOp) {
+        return clip(new Rectangle2D.Float(left, top, right - left, bottom - top), regionOp);
+    }
+
     public Shape getClip() {
         if (mLayers.size() > 0) {
             // they all have the same clip
@@ -727,31 +692,8 @@
                 g.setColor(new Color(paint.getColor(), true /*hasAlpha*/));
             }
 
-            boolean customStroke = false;
-            PathEffect_Delegate effectDelegate = paint.getPathEffect();
-            if (effectDelegate != null) {
-                if (effectDelegate.isSupported()) {
-                    Stroke stroke = effectDelegate.getStroke(paint);
-                    assert stroke != null;
-                    if (stroke != null) {
-                        g.setStroke(stroke);
-                        customStroke = true;
-                    }
-                } else {
-                    Bridge.getLog().fidelityWarning(null,
-                            effectDelegate.getSupportMessage(),
-                            null);
-                }
-            }
-
-            // if no custom stroke as been set, set the default one.
-            if (customStroke == false) {
-                g.setStroke(new BasicStroke(
-                        paint.getStrokeWidth(),
-                        paint.getJavaCap(),
-                        paint.getJavaJoin(),
-                        paint.getJavaStrokeMiter()));
-            }
+            // set the stroke
+            g.setStroke(paint.getJavaStroke());
         }
 
         // the alpha for the composite. Always opaque if the normal paint color is used since