am 0b76cf6f: am 34751c79: Merge "Better shadows." into lmp-dev

* commit '0b76cf6ff56c59a04783714d40163576e3081cf1':
  Better shadows.
diff --git a/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java b/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java
new file mode 100644
index 0000000..ec3a8d6
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java
@@ -0,0 +1,156 @@
+/*
+ * 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.view;
+
+import com.android.layoutlib.bridge.impl.ResourceHelper;
+
+import android.graphics.Canvas;
+import android.graphics.LinearGradient;
+import android.graphics.Outline;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
+import android.graphics.Path;
+import android.graphics.Path.FillType;
+import android.graphics.RadialGradient;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region.Op;
+import android.graphics.Shader.TileMode;
+
+/**
+ * Paints shadow for rounded rectangles. Inspiration from CardView. Couldn't use that directly,
+ * since it modifies the size of the content, that we can't do.
+ */
+public class RectShadowPainter {
+
+
+    private static final int START_COLOR = ResourceHelper.getColor("#37000000");
+    private static final int END_COLOR = ResourceHelper.getColor("#03000000");
+    private static final float PERPENDICULAR_ANGLE = 90f;
+
+    public static void paintShadow(Outline viewOutline, float elevation, Canvas canvas) {
+        float shadowSize = elevationToShadow(elevation);
+        int saved = modifyCanvas(canvas, shadowSize);
+        if (saved == -1) {
+            return;
+        }
+        try {
+            Paint cornerPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
+            cornerPaint.setStyle(Style.FILL);
+            Paint edgePaint = new Paint(cornerPaint);
+            edgePaint.setAntiAlias(false);
+            Rect outline = viewOutline.mRect;
+            float radius = viewOutline.mRadius;
+            float outerArcRadius = radius + shadowSize;
+            int[] colors = {START_COLOR, START_COLOR, END_COLOR};
+            cornerPaint.setShader(new RadialGradient(0, 0, outerArcRadius, colors,
+                    new float[]{0f, radius / outerArcRadius, 1f}, TileMode.CLAMP));
+            edgePaint.setShader(new LinearGradient(0, 0, -shadowSize, 0, START_COLOR, END_COLOR,
+                    TileMode.CLAMP));
+            Path path = new Path();
+            path.setFillType(FillType.EVEN_ODD);
+            // A rectangle bounding the complete shadow.
+            RectF shadowRect = new RectF(outline);
+            shadowRect.inset(-shadowSize, -shadowSize);
+            // A rectangle with edges corresponding to the straight edges of the outline.
+            RectF inset = new RectF(outline);
+            inset.inset(radius, radius);
+            // A rectangle used to represent the edge shadow.
+            RectF edgeShadowRect = new RectF();
+
+
+            // left and right sides.
+            edgeShadowRect.set(-shadowSize, 0f, 0f, inset.height());
+            // Left shadow
+            sideShadow(canvas, edgePaint, edgeShadowRect, outline.left, inset.top, 0);
+            // Right shadow
+            sideShadow(canvas, edgePaint, edgeShadowRect, outline.right, inset.bottom, 2);
+            // Top shadow
+            edgeShadowRect.set(-shadowSize, 0, 0, inset.width());
+            sideShadow(canvas, edgePaint, edgeShadowRect, inset.right, outline.top, 1);
+            // bottom shadow. This needs an inset so that blank doesn't appear when the content is
+            // moved up.
+            edgeShadowRect.set(-shadowSize, 0, shadowSize / 2f, inset.width());
+            edgePaint.setShader(new LinearGradient(edgeShadowRect.right, 0, edgeShadowRect.left, 0,
+                    colors, new float[]{0f, 1 / 3f, 1f}, TileMode.CLAMP));
+            sideShadow(canvas, edgePaint, edgeShadowRect, inset.left, outline.bottom, 3);
+
+            // Draw corners.
+            drawCorner(canvas, cornerPaint, path, inset.right, inset.bottom, outerArcRadius, 0);
+            drawCorner(canvas, cornerPaint, path, inset.left, inset.bottom, outerArcRadius, 1);
+            drawCorner(canvas, cornerPaint, path, inset.left, inset.top, outerArcRadius, 2);
+            drawCorner(canvas, cornerPaint, path, inset.right, inset.top, outerArcRadius, 3);
+        } finally {
+            canvas.restoreToCount(saved);
+        }
+    }
+
+    private static float elevationToShadow(float elevation) {
+        // The factor is chosen by eyeballing the shadow size on device and preview.
+        return elevation * 0.5f;
+    }
+
+    /**
+     * Translate canvas by half of shadow size up, so that it appears that light is coming
+     * slightly from above. Also, remove clipping, so that shadow is not clipped.
+     */
+    private static int modifyCanvas(Canvas canvas, float shadowSize) {
+        Rect clipBounds = canvas.getClipBounds();
+        if (clipBounds.isEmpty()) {
+            return -1;
+        }
+        int saved = canvas.save();
+        // Usually canvas has been translated to the top left corner of the view when this is
+        // called. So, setting a clip rect at 0,0 will clip the top left part of the shadow.
+        // Thus, we just expand in each direction by width and height of the canvas.
+        canvas.clipRect(-canvas.getWidth(), -canvas.getHeight(), canvas.getWidth(),
+                canvas.getHeight(), Op.REPLACE);
+        canvas.translate(0, shadowSize / 2f);
+        return saved;
+    }
+
+    private static void sideShadow(Canvas canvas, Paint edgePaint,
+            RectF edgeShadowRect, float dx, float dy, int rotations) {
+        int saved = canvas.save();
+        canvas.translate(dx, dy);
+        canvas.rotate(rotations * PERPENDICULAR_ANGLE);
+        canvas.drawRect(edgeShadowRect, edgePaint);
+        canvas.restoreToCount(saved);
+    }
+
+    /**
+     * @param canvas Canvas to draw the rectangle on.
+     * @param paint Paint to use when drawing the corner.
+     * @param path A path to reuse. Prevents allocating memory for each path.
+     * @param x Center of circle, which this corner is a part of.
+     * @param y Center of circle, which this corner is a part of.
+     * @param radius radius of the arc
+     * @param rotations number of quarter rotations before starting to paint the arc.
+     */
+    private static void drawCorner(Canvas canvas, Paint paint, Path path, float x, float y,
+            float radius, int rotations) {
+        int saved = canvas.save();
+        canvas.translate(x, y);
+        path.reset();
+        path.arcTo(-radius, -radius, radius, radius, rotations * PERPENDICULAR_ANGLE,
+                PERPENDICULAR_ANGLE, false);
+        path.lineTo(0, 0);
+        path.close();
+        canvas.drawPath(path, paint);
+        canvas.restoreToCount(saved);
+    }
+}
diff --git a/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java b/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java
index 82ae1df..e72a0db 100644
--- a/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java
@@ -16,12 +16,9 @@
 
 package android.view;
 
-import com.android.annotations.NonNull;
-import com.android.layoutlib.bridge.android.BridgeContext;
 import com.android.resources.Density;
 import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
 
-import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap_Delegate;
 import android.graphics.Canvas;
@@ -29,8 +26,6 @@
 import android.graphics.Path_Delegate;
 import android.graphics.Rect;
 import android.graphics.Region.Op;
-import android.util.DisplayMetrics;
-import android.util.TypedValue;
 import android.view.animation.Transformation;
 
 import java.awt.Graphics2D;
@@ -50,33 +45,36 @@
     @LayoutlibDelegate
     /*package*/ static boolean drawChild(ViewGroup thisVG, Canvas canvas, View child,
             long drawingTime) {
-        boolean retVal = thisVG.drawChild_Original(canvas, child, drawingTime);
         if (child.getZ() > thisVG.getZ()) {
             ViewOutlineProvider outlineProvider = child.getOutlineProvider();
             Outline outline = new Outline();
             outlineProvider.getOutline(child, outline);
-
+            if (outline.mPath == null && outline.mRect == null) {
+                // Sometimes, the bounds of the background drawable are not set until View.draw()
+                // is called. So, we set the bounds manually and try to get the outline again.
+                child.getBackground().setBounds(0, 0, child.mRight - child.mLeft,
+                        child.mBottom - child.mTop);
+                outlineProvider.getOutline(child, outline);
+            }
             if (outline.mPath != null || (outline.mRect != null && !outline.mRect.isEmpty())) {
                 int restoreTo = transformCanvas(thisVG, canvas, child);
                 drawShadow(thisVG, canvas, child, outline);
                 canvas.restoreToCount(restoreTo);
             }
         }
-        return retVal;
+        return thisVG.drawChild_Original(canvas, child, drawingTime);
     }
 
     private static void drawShadow(ViewGroup parent, Canvas canvas, View child,
             Outline outline) {
+        float elevation = getElevation(child, parent);
+        if(outline.mRect != null) {
+            RectShadowPainter.paintShadow(outline, elevation, canvas);
+            return;
+        }
         BufferedImage shadow = null;
-        int x = 0;
-        if (outline.mRect != null) {
-            Shadow s = getRectShadow(parent, canvas, child, outline);
-            if (s != null) {
-              shadow = s.mShadow;
-              x = -s.mShadowWidth;
-            }
-        } else if (outline.mPath != null) {
-            shadow = getPathShadow(child, outline, canvas);
+        if (outline.mPath != null) {
+            shadow = getPathShadow(outline, canvas, elevation);
         }
         if (shadow == null) {
             return;
@@ -85,52 +83,17 @@
                 Density.getEnum(canvas.getDensity()));
         Rect clipBounds = canvas.getClipBounds();
         Rect newBounds = new Rect(clipBounds);
-        newBounds.left = newBounds.left + x;
+        newBounds.inset((int)-elevation, (int)-elevation);
         canvas.clipRect(newBounds, Op.REPLACE);
-        canvas.drawBitmap(bitmap, x, 0, null);
+        canvas.drawBitmap(bitmap, 0, 0, null);
         canvas.clipRect(clipBounds, Op.REPLACE);
     }
 
-    private static Shadow getRectShadow(ViewGroup parent, Canvas canvas, View child,
-            Outline outline) {
-        BufferedImage shadow;
-        Rect clipBounds = canvas.getClipBounds();
-        if (clipBounds.isEmpty()) {
-            return null;
-        }
-        float height = child.getZ() - parent.getZ();
-        // Draw large shadow if difference in z index is more than 10dp
-        float largeShadowThreshold = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10f,
-                getMetrics(child));
-        boolean largeShadow = height > largeShadowThreshold;
-        int shadowSize = largeShadow ? ShadowPainter.SHADOW_SIZE : ShadowPainter.SMALL_SHADOW_SIZE;
-        shadow = new BufferedImage(clipBounds.width() + shadowSize, clipBounds.height(),
-                BufferedImage.TYPE_INT_ARGB);
-        Graphics2D graphics = shadow.createGraphics();
-        Rect rect = outline.mRect;
-        if (largeShadow) {
-            ShadowPainter.drawRectangleShadow(graphics,
-                    rect.left + shadowSize, rect.top, rect.width(), rect.height());
-        } else {
-            ShadowPainter.drawSmallRectangleShadow(graphics,
-                    rect.left + shadowSize, rect.top, rect.width(), rect.height());
-        }
-        graphics.dispose();
-        return new Shadow(shadow, shadowSize);
+    private static float getElevation(View child, ViewGroup parent) {
+        return child.getZ() - parent.getZ();
     }
 
-    @NonNull
-    private static DisplayMetrics getMetrics(View view) {
-        Context context = view.getContext();
-        context = BridgeContext.getBaseContext(context);
-        if (context instanceof BridgeContext) {
-            return ((BridgeContext) context).getMetrics();
-        }
-        throw new RuntimeException("View " + view.getClass().getName() + " not created with the " +
-                "right context");
-    }
-
-    private static BufferedImage getPathShadow(View child, Outline outline, Canvas canvas) {
+    private static BufferedImage getPathShadow(Outline outline, Canvas canvas, float elevation) {
         Rect clipBounds = canvas.getClipBounds();
         if (clipBounds.isEmpty()) {
           return null;
@@ -140,7 +103,7 @@
         Graphics2D graphics = image.createGraphics();
         graphics.draw(Path_Delegate.getDelegate(outline.mPath.mNativePath).getJavaShape());
         graphics.dispose();
-        return ShadowPainter.createDropShadow(image, ((int) child.getZ()));
+        return ShadowPainter.createDropShadow(image, (int) elevation);
     }
 
     // Copied from android.view.View#draw(Canvas, ViewGroup, long) and removed code paths
@@ -194,15 +157,4 @@
         }
         return restoreTo;
     }
-
-    private static class Shadow {
-        public BufferedImage mShadow;
-        public int mShadowWidth;
-
-        public Shadow(BufferedImage shadow, int shadowWidth) {
-            mShadow = shadow;
-            mShadowWidth = shadowWidth;
-        }
-
-    }
 }