LayoutLib: fix clipping issues.

There were two issues:
- Graphics2D.setClip only works on rectangular shapes.
  This means doing a setClip on a non rectangular shape should
  basically reset the clip and intersect with the new shape.

- the current clip can be null, so the combineShape method
  must handle it.

Change-Id: Id2cd7475e991d8b533ff2e8850cc2c27663f9e52
diff --git a/bridge/src/android/graphics/Canvas_Delegate.java b/bridge/src/android/graphics/Canvas_Delegate.java
index def0f02..61bf33b 100644
--- a/bridge/src/android/graphics/Canvas_Delegate.java
+++ b/bridge/src/android/graphics/Canvas_Delegate.java
@@ -499,13 +499,14 @@
         }
 
         Rectangle rect = canvasDelegate.getSnapshot().getClip().getBounds();
-        if (rect != null) {
+        if (rect != null && rect.isEmpty() == false) {
             bounds.left = rect.x;
             bounds.top = rect.y;
             bounds.right = rect.x + rect.width;
             bounds.bottom = rect.y + rect.height;
             return true;
         }
+
         return false;
     }
 
diff --git a/bridge/src/android/graphics/Region_Delegate.java b/bridge/src/android/graphics/Region_Delegate.java
index 684bb90..bc13b52 100644
--- a/bridge/src/android/graphics/Region_Delegate.java
+++ b/bridge/src/android/graphics/Region_Delegate.java
@@ -69,39 +69,64 @@
      *
      * If the Op is not one that combines two shapes, then this return null
      *
-     * @param shape1 the firt shape to combine
+     * @param shape1 the firt shape to combine which can be null if there's no original clip.
      * @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) {
+            // if shape1 is null (empty), then the result is null.
+            if (shape1 == null) {
+                return null;
+            }
+
             // 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) {
+            // if shape1 is null, then the result is simply shape2.
+            if (shape1 == null) {
+                return new Area(shape2);
+            }
+
             // 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) {
+            // if shape1 is null, then the result is simply shape2.
+            if (shape1 == null) {
+                return new Area(shape2);
+            }
+
             // 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) {
+            // if shape1 is null, then the result is simply shape2
+            if (shape1 == null) {
+                return new Area(shape2);
+            }
+
             // result is always a new area.
             Area result = new Area(shape1);
             result.exclusiveOr(shape2 instanceof Area ? (Area) shape2 : new Area(shape2));
+            return result;
 
         } 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));
+
+            if (shape1 != null) {
+                result.subtract(shape1 instanceof Area ? (Area) shape1 : new Area(shape1));
+            }
+
             return result;
         }
 
diff --git a/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java b/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java
index a2fcb3b..72773c8 100644
--- a/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java
+++ b/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java
@@ -170,6 +170,29 @@
                 mBitmap.change();
             }
         }
+
+        /**
+         * Sets the clip for the graphics2D object associated with the layer.
+         * This should be used over the normal Graphics2D setClip method.
+         *
+         * @param clipShape the shape to use a the clip shape.
+         */
+        void setClip(Shape clipShape) {
+            // because setClip is only guaranteed to work with rectangle shape,
+            // first reset the clip to max and then intersect the current (empty)
+            // clip with the shap.
+            mGraphics.setClip(null);
+            mGraphics.clip(clipShape);
+        }
+
+        /**
+         * Clips the layer with the given shape. This performs an intersect between the current
+         * clip shape and the given shape.
+         * @param shape the new clip shape.
+         */
+        public void clip(Shape shape) {
+            mGraphics.clip(shape);
+        }
     }
 
     /**
@@ -287,12 +310,13 @@
             AffineTransform currentMtx = baseLayer.getGraphics().getTransform();
             layerGraphics.setTransform(currentMtx);
 
-            Shape currentClip = baseLayer.getGraphics().getClip();
-            layerGraphics.setClip(currentClip);
-
             // create a new layer for this new layer and add it to the list at the end.
             mLayers.add(mLocalLayer = new Layer(layerGraphics, layerImage, flags));
 
+            // set the clip on it.
+            Shape currentClip = baseLayer.getGraphics().getClip();
+            mLocalLayer.setClip(currentClip);
+
             // if the drawing is not clipped to the local layer only, we save the current content
             // of all other layers. We are only interested in the part that will actually
             // be drawn, so we create as small bitmaps as we can.
@@ -369,15 +393,24 @@
      */
     public void setBitmap(Bitmap_Delegate bitmap) {
         assert mLayers.size() == 0;
+
+        // create a new Layer for the bitmap. This will be the base layer.
         Graphics2D graphics2D = bitmap.getImage().createGraphics();
-        mLayers.add(new Layer(graphics2D, bitmap));
+        Layer baseLayer = new Layer(graphics2D, bitmap);
+
+        // add it to the list.
+        mLayers.add(baseLayer);
+
+        // if transform and clip where modified before, get the information and give it to the
+        // layer.
+
         if (mTransform != null) {
             graphics2D.setTransform(mTransform);
             mTransform = null;
         }
 
         if (mClip != null) {
-            graphics2D.setClip(mClip);
+            baseLayer.setClip(mClip);
             mClip = null;
         }
     }
@@ -447,7 +480,20 @@
     }
 
     public boolean clip(Shape shape, int regionOp) {
+        // Simple case of intersect with existing layers.
+        // Because Graphics2D#setClip works a bit peculiarly, we optimize
+        // the case of clipping by intersection, as it's supported natively.
+        if (regionOp == Region.Op.INTERSECT.nativeInt && mLayers.size() > 0) {
+            for (Layer layer : mLayers) {
+                layer.clip(shape);
+            }
+
+            Shape currentClip = getClip();
+            return currentClip != null && currentClip.getBounds().isEmpty() == false;
+        }
+
         Area area = null;
+
         if (regionOp == Region.Op.REPLACE.nativeInt) {
             area = new Area(shape);
         } else {
@@ -459,11 +505,12 @@
         if (mLayers.size() > 0) {
             if (area != null) {
                 for (Layer layer : mLayers) {
-                    layer.getGraphics().setClip(area);
+                    layer.setClip(area);
                 }
             }
 
-            return getClip().getBounds().isEmpty() == false;
+            Shape currentClip = getClip();
+            return currentClip != null && currentClip.getBounds().isEmpty() == false;
         } else {
             if (area != null) {
                 mClip = area;
@@ -479,14 +526,14 @@
         return clip(new Rectangle2D.Float(left, top, right - left, bottom - top), regionOp);
     }
 
+    /**
+     * Returns the current clip, or null if none have been setup.
+     */
     public Shape getClip() {
         if (mLayers.size() > 0) {
             // they all have the same clip
             return mLayers.get(0).getGraphics().getClip();
         } else {
-            if (mClip == null) {
-                mClip = new Area();
-            }
             return mClip;
         }
     }
@@ -603,7 +650,7 @@
             if ((mFlags & Canvas.CLIP_SAVE_FLAG) == 0) {
                 Shape clip = getClip();
                 for (Layer layer : mPrevious.mLayers) {
-                    layer.getGraphics().setClip(clip);
+                    layer.setClip(clip);
                 }
             }
         }