Merge "Add primitive shadows support to LayoutLib" into lmp-dev
diff --git a/tools/layoutlib/bridge/resources/icons/shadow-b.png b/tools/layoutlib/bridge/resources/icons/shadow-b.png
new file mode 100644
index 0000000..68f4f4b
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/icons/shadow-b.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow-bl.png b/tools/layoutlib/bridge/resources/icons/shadow-bl.png
new file mode 100644
index 0000000..ee7dbe8
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/icons/shadow-bl.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow-br.png b/tools/layoutlib/bridge/resources/icons/shadow-br.png
new file mode 100644
index 0000000..c45ad77
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/icons/shadow-br.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow-l.png b/tools/layoutlib/bridge/resources/icons/shadow-l.png
new file mode 100644
index 0000000..77d0bd0
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/icons/shadow-l.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow-r.png b/tools/layoutlib/bridge/resources/icons/shadow-r.png
new file mode 100644
index 0000000..4af7a33
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/icons/shadow-r.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow-tl.png b/tools/layoutlib/bridge/resources/icons/shadow-tl.png
new file mode 100644
index 0000000..424fb36
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/icons/shadow-tl.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow-tr.png b/tools/layoutlib/bridge/resources/icons/shadow-tr.png
new file mode 100644
index 0000000..1fd0c77
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/icons/shadow-tr.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow2-b.png b/tools/layoutlib/bridge/resources/icons/shadow2-b.png
new file mode 100644
index 0000000..963973e
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/icons/shadow2-b.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow2-bl.png b/tools/layoutlib/bridge/resources/icons/shadow2-bl.png
new file mode 100644
index 0000000..7612487
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/icons/shadow2-bl.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow2-br.png b/tools/layoutlib/bridge/resources/icons/shadow2-br.png
new file mode 100644
index 0000000..8e20252
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/icons/shadow2-br.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow2-l.png b/tools/layoutlib/bridge/resources/icons/shadow2-l.png
new file mode 100644
index 0000000..2db18a0
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/icons/shadow2-l.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow2-r.png b/tools/layoutlib/bridge/resources/icons/shadow2-r.png
new file mode 100644
index 0000000..8e026f1
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/icons/shadow2-r.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow2-tl.png b/tools/layoutlib/bridge/resources/icons/shadow2-tl.png
new file mode 100644
index 0000000..a8045ed
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/icons/shadow2-tl.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow2-tr.png b/tools/layoutlib/bridge/resources/icons/shadow2-tr.png
new file mode 100644
index 0000000..590373c
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/icons/shadow2-tr.png
Binary files differ
diff --git a/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java b/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java
new file mode 100644
index 0000000..6c949d9
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java
@@ -0,0 +1,72 @@
+/*
+ * 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.view;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate implementing the native methods of {@link RenderNode}
+ * <p/>
+ * Through the layoutlib_create tool, some native methods of RenderNode have been replaced by calls
+ * to methods of the same name in this delegate class.
+ *
+ * @see DelegateManager
+ */
+public class RenderNode_Delegate {
+
+
+    // ---- delegate manager ----
+    private static final DelegateManager<RenderNode_Delegate> sManager =
+            new DelegateManager<RenderNode_Delegate>(RenderNode_Delegate.class);
+
+
+    private float mLift;
+    @SuppressWarnings("UnusedDeclaration")
+    private String mName;
+
+    @LayoutlibDelegate
+    /*package*/ static long nCreate(String name) {
+        RenderNode_Delegate renderNodeDelegate = new RenderNode_Delegate();
+        renderNodeDelegate.mName = name;
+        return sManager.addNewDelegate(renderNodeDelegate);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDestroyRenderNode(long renderNode) {
+        sManager.removeJavaReferenceFor(renderNode);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nSetElevation(long renderNode, float lift) {
+        RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
+        if (delegate != null && delegate.mLift != lift) {
+            delegate.mLift = lift;
+            return true;
+        }
+        return false;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static float nGetElevation(long renderNode) {
+        RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
+        if (delegate != null) {
+            return delegate.mLift;
+        }
+        return 0f;
+    }
+}
diff --git a/tools/layoutlib/bridge/src/android/view/ShadowPainter.java b/tools/layoutlib/bridge/src/android/view/ShadowPainter.java
new file mode 100644
index 0000000..38846bd
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/ShadowPainter.java
@@ -0,0 +1,415 @@
+/*
+ * 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.view;
+
+import com.android.annotations.NonNull;
+
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBufferInt;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.imageio.ImageIO;
+
+public class ShadowPainter {
+
+    /**
+     * Adds a drop shadow to a semi-transparent image (of an arbitrary shape) and returns it as a
+     * new image. This method attempts to mimic the same visual characteristics as the rectangular
+     * shadow painting methods in this class, {@link #createRectangularDropShadow(java.awt.image.BufferedImage)}
+     * and {@link #createSmallRectangularDropShadow(java.awt.image.BufferedImage)}.
+     *
+     * @param source the source image
+     * @param shadowSize the size of the shadow, normally {@link #SHADOW_SIZE or {@link
+     * #SMALL_SHADOW_SIZE}}
+     *
+     * @return a new image with the shadow painted in
+     */
+    @NonNull
+    public static BufferedImage createDropShadow(BufferedImage source, int shadowSize) {
+        shadowSize /= 2; // make shadow size have the same meaning as in the other shadow paint methods in this class
+
+        return createDropShadow(source, shadowSize, 0.7f, 0);
+    }
+
+    /**
+     * Creates a drop shadow of a given image and returns a new image which shows the input image on
+     * top of its drop shadow.
+     * <p/>
+     * <b>NOTE: If the shape is rectangular and opaque, consider using {@link
+     * #drawRectangleShadow(Graphics2D, int, int, int, int)} instead.</b>
+     *
+     * @param source the source image to be shadowed
+     * @param shadowSize the size of the shadow in pixels
+     * @param shadowOpacity the opacity of the shadow, with 0=transparent and 1=opaque
+     * @param shadowRgb the RGB int to use for the shadow color
+     *
+     * @return a new image with the source image on top of its shadow
+     */
+    @SuppressWarnings({"SuspiciousNameCombination", "UnnecessaryLocalVariable"})  // Imported code
+    public static BufferedImage createDropShadow(BufferedImage source, int shadowSize,
+            float shadowOpacity, int shadowRgb) {
+
+        // This code is based on
+        //      http://www.jroller.com/gfx/entry/non_rectangular_shadow
+
+        BufferedImage image;
+        int width = source.getWidth();
+        int height = source.getHeight();
+        image = new BufferedImage(width + SHADOW_SIZE, height + SHADOW_SIZE,
+                BufferedImage.TYPE_INT_ARGB);
+
+        Graphics2D g2 = image.createGraphics();
+        g2.drawImage(image, shadowSize, shadowSize, null);
+
+        int dstWidth = image.getWidth();
+        int dstHeight = image.getHeight();
+
+        int left = (shadowSize - 1) >> 1;
+        int right = shadowSize - left;
+        int xStart = left;
+        int xStop = dstWidth - right;
+        int yStart = left;
+        int yStop = dstHeight - right;
+
+        shadowRgb &= 0x00FFFFFF;
+
+        int[] aHistory = new int[shadowSize];
+        int historyIdx;
+
+        int aSum;
+
+        int[] dataBuffer = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
+        int lastPixelOffset = right * dstWidth;
+        float sumDivider = shadowOpacity / shadowSize;
+
+        // horizontal pass
+        for (int y = 0, bufferOffset = 0; y < dstHeight; y++, bufferOffset = y * dstWidth) {
+            aSum = 0;
+            historyIdx = 0;
+            for (int x = 0; x < shadowSize; x++, bufferOffset++) {
+                int a = dataBuffer[bufferOffset] >>> 24;
+                aHistory[x] = a;
+                aSum += a;
+            }
+
+            bufferOffset -= right;
+
+            for (int x = xStart; x < xStop; x++, bufferOffset++) {
+                int a = (int) (aSum * sumDivider);
+                dataBuffer[bufferOffset] = a << 24 | shadowRgb;
+
+                // subtract the oldest pixel from the sum
+                aSum -= aHistory[historyIdx];
+
+                // get the latest pixel
+                a = dataBuffer[bufferOffset + right] >>> 24;
+                aHistory[historyIdx] = a;
+                aSum += a;
+
+                if (++historyIdx >= shadowSize) {
+                    historyIdx -= shadowSize;
+                }
+            }
+        }
+        // vertical pass
+        for (int x = 0, bufferOffset = 0; x < dstWidth; x++, bufferOffset = x) {
+            aSum = 0;
+            historyIdx = 0;
+            for (int y = 0; y < shadowSize; y++, bufferOffset += dstWidth) {
+                int a = dataBuffer[bufferOffset] >>> 24;
+                aHistory[y] = a;
+                aSum += a;
+            }
+
+            bufferOffset -= lastPixelOffset;
+
+            for (int y = yStart; y < yStop; y++, bufferOffset += dstWidth) {
+                int a = (int) (aSum * sumDivider);
+                dataBuffer[bufferOffset] = a << 24 | shadowRgb;
+
+                // subtract the oldest pixel from the sum
+                aSum -= aHistory[historyIdx];
+
+                // get the latest pixel
+                a = dataBuffer[bufferOffset + lastPixelOffset] >>> 24;
+                aHistory[historyIdx] = a;
+                aSum += a;
+
+                if (++historyIdx >= shadowSize) {
+                    historyIdx -= shadowSize;
+                }
+            }
+        }
+
+        g2.drawImage(source, null, 0, 0);
+        g2.dispose();
+
+        return image;
+    }
+
+    /**
+     * Draws a rectangular drop shadow (of size {@link #SHADOW_SIZE} by {@link #SHADOW_SIZE} around
+     * the given source and returns a new image with both combined
+     *
+     * @param source the source image
+     *
+     * @return the source image with a drop shadow on the bottom and right
+     */
+    @SuppressWarnings("UnusedDeclaration")
+    public static BufferedImage createRectangularDropShadow(BufferedImage source) {
+        int type = source.getType();
+        if (type == BufferedImage.TYPE_CUSTOM) {
+            type = BufferedImage.TYPE_INT_ARGB;
+        }
+
+        int width = source.getWidth();
+        int height = source.getHeight();
+        BufferedImage image;
+        image = new BufferedImage(width + SHADOW_SIZE, height + SHADOW_SIZE, type);
+        Graphics2D g = image.createGraphics();
+        g.drawImage(source, 0, 0, null);
+        drawRectangleShadow(image, 0, 0, width, height);
+        g.dispose();
+
+        return image;
+    }
+
+    /**
+     * Draws a small rectangular drop shadow (of size {@link #SMALL_SHADOW_SIZE} by {@link
+     * #SMALL_SHADOW_SIZE} around the given source and returns a new image with both combined
+     *
+     * @param source the source image
+     *
+     * @return the source image with a drop shadow on the bottom and right
+     */
+    @SuppressWarnings("UnusedDeclaration")
+    public static BufferedImage createSmallRectangularDropShadow(BufferedImage source) {
+        int type = source.getType();
+        if (type == BufferedImage.TYPE_CUSTOM) {
+            type = BufferedImage.TYPE_INT_ARGB;
+        }
+
+        int width = source.getWidth();
+        int height = source.getHeight();
+
+        BufferedImage image;
+        image = new BufferedImage(width + SMALL_SHADOW_SIZE, height + SMALL_SHADOW_SIZE, type);
+
+        Graphics2D g = image.createGraphics();
+        g.drawImage(source, 0, 0, null);
+        drawSmallRectangleShadow(image, 0, 0, width, height);
+        g.dispose();
+
+        return image;
+    }
+
+    /**
+     * Draws a drop shadow for the given rectangle into the given context. It will not draw anything
+     * if the rectangle is smaller than a minimum determined by the assets used to draw the shadow
+     * graphics. The size of the shadow is {@link #SHADOW_SIZE}.
+     *
+     * @param image the image to draw the shadow into
+     * @param x the left coordinate of the left hand side of the rectangle
+     * @param y the top coordinate of the top of the rectangle
+     * @param width the width of the rectangle
+     * @param height the height of the rectangle
+     */
+    public static void drawRectangleShadow(BufferedImage image,
+            int x, int y, int width, int height) {
+        Graphics2D gc = image.createGraphics();
+        try {
+            drawRectangleShadow(gc, x, y, width, height);
+        } finally {
+            gc.dispose();
+        }
+    }
+
+    /**
+     * Draws a small drop shadow for the given rectangle into the given context. It will not draw
+     * anything if the rectangle is smaller than a minimum determined by the assets used to draw the
+     * shadow graphics. The size of the shadow is {@link #SMALL_SHADOW_SIZE}.
+     *
+     * @param image the image to draw the shadow into
+     * @param x the left coordinate of the left hand side of the rectangle
+     * @param y the top coordinate of the top of the rectangle
+     * @param width the width of the rectangle
+     * @param height the height of the rectangle
+     */
+    public static void drawSmallRectangleShadow(BufferedImage image,
+            int x, int y, int width, int height) {
+        Graphics2D gc = image.createGraphics();
+        try {
+            drawSmallRectangleShadow(gc, x, y, width, height);
+        } finally {
+            gc.dispose();
+        }
+    }
+
+    /**
+     * The width and height of the drop shadow painted by
+     * {@link #drawRectangleShadow(Graphics2D, int, int, int, int)}
+     */
+    public static final int SHADOW_SIZE = 20; // DO NOT EDIT. This corresponds to bitmap graphics
+
+    /**
+     * The width and height of the drop shadow painted by
+     * {@link #drawSmallRectangleShadow(Graphics2D, int, int, int, int)}
+     */
+    public static final int SMALL_SHADOW_SIZE = 10; // DO NOT EDIT. Corresponds to bitmap graphics
+
+    /**
+     * Draws a drop shadow for the given rectangle into the given context. It will not draw anything
+     * if the rectangle is smaller than a minimum determined by the assets used to draw the shadow
+     * graphics.
+     *
+     * @param gc the graphics context to draw into
+     * @param x the left coordinate of the left hand side of the rectangle
+     * @param y the top coordinate of the top of the rectangle
+     * @param width the width of the rectangle
+     * @param height the height of the rectangle
+     */
+    public static void drawRectangleShadow(Graphics2D gc, int x, int y, int width, int height) {
+        assert ShadowBottomLeft != null;
+        assert ShadowBottomRight.getWidth(null) == SHADOW_SIZE;
+        assert ShadowBottomRight.getHeight(null) == SHADOW_SIZE;
+
+        int blWidth = ShadowBottomLeft.getWidth(null);
+        int trHeight = ShadowTopRight.getHeight(null);
+        if (width < blWidth) {
+            return;
+        }
+        if (height < trHeight) {
+            return;
+        }
+
+        gc.drawImage(ShadowBottomLeft, x - ShadowBottomLeft.getWidth(null), y + height, null);
+        gc.drawImage(ShadowBottomRight, x + width, y + height, null);
+        gc.drawImage(ShadowTopRight, x + width, y, null);
+        gc.drawImage(ShadowTopLeft, x - ShadowTopLeft.getWidth(null), y, null);
+        gc.drawImage(ShadowBottom,
+                x, y + height, x + width, y + height + ShadowBottom.getHeight(null),
+                0, 0, ShadowBottom.getWidth(null), ShadowBottom.getHeight(null), null);
+        gc.drawImage(ShadowRight,
+                x + width, y + ShadowTopRight.getHeight(null), x + width + ShadowRight.getWidth(null), y + height,
+                0, 0, ShadowRight.getWidth(null), ShadowRight.getHeight(null), null);
+        gc.drawImage(ShadowLeft,
+                x - ShadowLeft.getWidth(null), y + ShadowTopLeft.getHeight(null), x, y + height,
+                0, 0, ShadowLeft.getWidth(null), ShadowLeft.getHeight(null), null);
+    }
+
+    /**
+     * Draws a small drop shadow for the given rectangle into the given context. It will not draw
+     * anything if the rectangle is smaller than a minimum determined by the assets used to draw the
+     * shadow graphics.
+     * <p/>
+     *
+     * @param gc the graphics context to draw into
+     * @param x the left coordinate of the left hand side of the rectangle
+     * @param y the top coordinate of the top of the rectangle
+     * @param width the width of the rectangle
+     * @param height the height of the rectangle
+     */
+    public static void drawSmallRectangleShadow(Graphics2D gc, int x, int y, int width,
+            int height) {
+        assert Shadow2BottomLeft != null;
+        assert Shadow2TopRight != null;
+        assert Shadow2BottomRight.getWidth(null) == SMALL_SHADOW_SIZE;
+        assert Shadow2BottomRight.getHeight(null) == SMALL_SHADOW_SIZE;
+
+        int blWidth = Shadow2BottomLeft.getWidth(null);
+        int trHeight = Shadow2TopRight.getHeight(null);
+        if (width < blWidth) {
+            return;
+        }
+        if (height < trHeight) {
+            return;
+        }
+
+        gc.drawImage(Shadow2BottomLeft, x - Shadow2BottomLeft.getWidth(null), y + height, null);
+        gc.drawImage(Shadow2BottomRight, x + width, y + height, null);
+        gc.drawImage(Shadow2TopRight, x + width, y, null);
+        gc.drawImage(Shadow2TopLeft, x - Shadow2TopLeft.getWidth(null), y, null);
+        gc.drawImage(Shadow2Bottom,
+                x, y + height, x + width, y + height + Shadow2Bottom.getHeight(null),
+                0, 0, Shadow2Bottom.getWidth(null), Shadow2Bottom.getHeight(null), null);
+        gc.drawImage(Shadow2Right,
+                x + width, y + Shadow2TopRight.getHeight(null), x + width + Shadow2Right.getWidth(null), y + height,
+                0, 0, Shadow2Right.getWidth(null), Shadow2Right.getHeight(null), null);
+        gc.drawImage(Shadow2Left,
+                x - Shadow2Left.getWidth(null), y + Shadow2TopLeft.getHeight(null), x, y + height,
+                0, 0, Shadow2Left.getWidth(null), Shadow2Left.getHeight(null), null);
+    }
+
+    private static Image loadIcon(String name) {
+        InputStream inputStream = ShadowPainter.class.getResourceAsStream(name);
+        if (inputStream == null) {
+            throw new RuntimeException("Unable to load image for shadow: " + name);
+        }
+        try {
+            return ImageIO.read(inputStream);
+        } catch (IOException e) {
+            throw new RuntimeException("Unable to load image for shadow:" + name, e);
+        } finally {
+            try {
+                inputStream.close();
+            } catch (IOException e) {
+                // ignore.
+            }
+        }
+    }
+
+    // Shadow graphics. This was generated by creating a drop shadow in
+    // Gimp, using the parameters x offset=10, y offset=10, blur radius=10,
+    // (for the small drop shadows x offset=10, y offset=10, blur radius=10)
+    // color=black, and opacity=51. These values attempt to make a shadow
+    // that is legible both for dark and light themes, on top of the
+    // canvas background (rgb(150,150,150). Darker shadows would tend to
+    // blend into the foreground for a dark holo screen, and lighter shadows
+    // would be hard to spot on the canvas background. If you make adjustments,
+    // make sure to check the shadow with both dark and light themes.
+    //
+    // After making the graphics, I cut out the top right, bottom left
+    // and bottom right corners as 20x20 images, and these are reproduced by
+    // painting them in the corresponding places in the target graphics context.
+    // I then grabbed a single horizontal gradient line from the middle of the
+    // right edge,and a single vertical gradient line from the bottom. These
+    // are then painted scaled/stretched in the target to fill the gaps between
+    // the three corner images.
+    //
+    // Filenames: bl=bottom left, b=bottom, br=bottom right, r=right, tr=top right
+
+    // Normal Drop Shadow
+    private static final Image ShadowBottom = loadIcon("/icons/shadow-b.png");
+    private static final Image ShadowBottomLeft = loadIcon("/icons/shadow-bl.png");
+    private static final Image ShadowBottomRight = loadIcon("/icons/shadow-br.png");
+    private static final Image ShadowRight = loadIcon("/icons/shadow-r.png");
+    private static final Image ShadowTopRight = loadIcon("/icons/shadow-tr.png");
+    private static final Image ShadowTopLeft = loadIcon("/icons/shadow-tl.png");
+    private static final Image ShadowLeft = loadIcon("/icons/shadow-l.png");
+
+    // Small Drop Shadow
+    private static final Image Shadow2Bottom = loadIcon("/icons/shadow2-b.png");
+    private static final Image Shadow2BottomLeft = loadIcon("/icons/shadow2-bl.png");
+    private static final Image Shadow2BottomRight = loadIcon("/icons/shadow2-br.png");
+    private static final Image Shadow2Right = loadIcon("/icons/shadow2-r.png");
+    private static final Image Shadow2TopRight = loadIcon("/icons/shadow2-tr.png");
+    private static final Image Shadow2TopLeft = loadIcon("/icons/shadow2-tl.png");
+    private static final Image Shadow2Left = loadIcon("/icons/shadow2-l.png");
+}
diff --git a/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java b/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java
new file mode 100644
index 0000000..a6c00f7
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java
@@ -0,0 +1,205 @@
+/*
+ * 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.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;
+import android.graphics.Outline;
+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;
+import java.awt.image.BufferedImage;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link ViewGroup}
+ * <p/>
+ * Through the layoutlib_create tool, the original  methods of ViewGroup have been replaced by calls
+ * to methods of the same name in this delegate class.
+ */
+public class ViewGroup_Delegate {
+
+    /**
+     * Overrides the original drawChild call in ViewGroup to draw the shadow.
+     */
+    @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 && !outline.mRect.isEmpty())) {
+                int restoreTo = transformCanvas(thisVG, canvas, child);
+                drawShadow(thisVG, canvas, child, outline);
+                canvas.restoreToCount(restoreTo);
+            }
+        }
+        return retVal;
+    }
+
+    private static void drawShadow(ViewGroup parent, Canvas canvas, View child,
+            Outline outline) {
+        BufferedImage shadow = null;
+        int x = 0;
+        if (outline.mRect != null) {
+            Shadow s = getRectShadow(parent, canvas, child, outline);
+            shadow = s.mShadow;
+            x = -s.mShadowWidth;
+        } else if (outline.mPath != null) {
+            shadow = getPathShadow(child, outline, canvas);
+        }
+        if (shadow == null) {
+            return;
+        }
+        Bitmap bitmap = Bitmap_Delegate.createBitmap(shadow, false,
+                Density.getEnum(canvas.getDensity()));
+        Rect clipBounds = canvas.getClipBounds();
+        Rect newBounds = new Rect(clipBounds);
+        newBounds.left = newBounds.left + x;
+        canvas.clipRect(newBounds, Op.REPLACE);
+        canvas.drawBitmap(bitmap, x, 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);
+    }
+
+    @NonNull
+    private static DisplayMetrics getMetrics(View view) {
+        Context context = view.getContext();
+        while (context instanceof ContextThemeWrapper) {
+            context = ((ContextThemeWrapper) context).getBaseContext();
+        }
+        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) {
+        Rect clipBounds = canvas.getClipBounds();
+        BufferedImage image = new BufferedImage(clipBounds.width(), clipBounds.height(),
+                BufferedImage.TYPE_INT_ARGB);
+        Graphics2D graphics = image.createGraphics();
+        graphics.draw(Path_Delegate.getDelegate(outline.mPath.mNativePath).getJavaShape());
+        graphics.dispose();
+        return ShadowPainter.createDropShadow(image, ((int) child.getZ()));
+    }
+
+    // Copied from android.view.View#draw(Canvas, ViewGroup, long) and removed code paths
+    // which were never taken. Ideally, we should hook up the shadow code in the same method so
+    // that we don't have to transform the canvas twice.
+    private static int transformCanvas(ViewGroup thisVG, Canvas canvas, View child) {
+        final int restoreTo = canvas.save();
+        final boolean childHasIdentityMatrix = child.hasIdentityMatrix();
+        int flags = thisVG.mGroupFlags;
+        Transformation transformToApply = null;
+        boolean concatMatrix = false;
+        if ((flags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
+            final Transformation t = thisVG.getChildTransformation();
+            final boolean hasTransform = thisVG.getChildStaticTransformation(child, t);
+            if (hasTransform) {
+                final int transformType = t.getTransformationType();
+                transformToApply = transformType != Transformation.TYPE_IDENTITY ? t : null;
+                concatMatrix = (transformType & Transformation.TYPE_MATRIX) != 0;
+            }
+        }
+        concatMatrix |= childHasIdentityMatrix;
+
+        child.computeScroll();
+        int sx = child.mScrollX;
+        int sy = child.mScrollY;
+
+        canvas.translate(child.mLeft - sx, child.mTop - sy);
+        float alpha = child.getAlpha() * child.getTransitionAlpha();
+
+        if (transformToApply != null || alpha < 1 || !childHasIdentityMatrix) {
+            if (transformToApply != null || !childHasIdentityMatrix) {
+                int transX = -sx;
+                int transY = -sy;
+
+                if (transformToApply != null) {
+                    if (concatMatrix) {
+                        // Undo the scroll translation, apply the transformation matrix,
+                        // then redo the scroll translate to get the correct result.
+                        canvas.translate(-transX, -transY);
+                        canvas.concat(transformToApply.getMatrix());
+                        canvas.translate(transX, transY);
+                    }
+                    if (!childHasIdentityMatrix) {
+                        canvas.translate(-transX, -transY);
+                        canvas.concat(child.getMatrix());
+                        canvas.translate(transX, transY);
+                    }
+                }
+
+            }
+        }
+        return restoreTo;
+    }
+
+    private static class Shadow {
+        public BufferedImage mShadow;
+        public int mShadowWidth;
+
+        public Shadow(BufferedImage shadow, int shadowWidth) {
+            mShadow = shadow;
+            mShadowWidth = shadowWidth;
+        }
+
+    }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/ActionBarLayout.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/ActionBarLayout.java
index 57fd68e..2ff8d37 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/ActionBarLayout.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/ActionBarLayout.java
@@ -18,6 +18,7 @@
 
 import com.android.annotations.NonNull;
 import com.android.annotations.Nullable;
+import com.android.ide.common.rendering.api.RenderResources;
 import com.android.ide.common.rendering.api.ResourceValue;
 import com.android.ide.common.rendering.api.SessionParams;
 import com.android.internal.R;
@@ -37,7 +38,6 @@
 import android.view.ViewGroup.LayoutParams;
 import android.widget.ActionMenuPresenter;
 import android.widget.FrameLayout;
-import android.widget.LinearLayout;
 import android.widget.ListAdapter;
 import android.widget.ListView;
 import android.widget.RelativeLayout;
@@ -49,15 +49,23 @@
     private static final String LAYOUT_ATTR_NAME = "windowActionBarFullscreenDecorLayout";
 
     // The Action Bar
-    @NonNull private CustomActionBarWrapper mActionBar;
+    @NonNull
+    private CustomActionBarWrapper mActionBar;
 
     // Store another reference to the context so that we don't have to cast it repeatedly.
-    @NonNull private final BridgeContext mBridgeContext;
+    @NonNull
+    private final BridgeContext mBridgeContext;
 
-    @NonNull private FrameLayout mContentRoot;
+    @NonNull
+    private FrameLayout mContentRoot;
 
     // A fake parent for measuring views.
-    @Nullable private ViewGroup mMeasureParent;
+    @Nullable
+    private ViewGroup mMeasureParent;
+
+    // A Layout that contains the inflated action bar. The menu popup is added to this layout.
+    @NonNull
+    private final RelativeLayout mEnclosingLayout;
 
     /**
      * Inflate the action bar and attach it to {@code parentView}
@@ -90,20 +98,25 @@
         if (layoutId == 0) {
             throw new RuntimeException(error);
         }
+        // Create a RelativeLayout to hold the action bar. The layout is needed so that we may
+        // add the menu popup to it.
+        mEnclosingLayout = new RelativeLayout(mBridgeContext);
+        setMatchParent(mEnclosingLayout);
+        parentView.addView(mEnclosingLayout);
+
         // Inflate action bar layout.
-        View decorContent = LayoutInflater.from(context).inflate(layoutId, parentView, true);
+        View decorContent = LayoutInflater.from(context).inflate(layoutId, mEnclosingLayout, true);
 
         mActionBar = CustomActionBarWrapper.getActionBarWrapper(context, params, decorContent);
 
-        FrameLayout contentRoot = (FrameLayout) parentView.findViewById(android.R.id.content);
+        FrameLayout contentRoot = (FrameLayout) mEnclosingLayout.findViewById(android.R.id.content);
 
         // If something went wrong and we were not able to initialize the content root,
         // just add a frame layout inside this and return.
         if (contentRoot == null) {
             contentRoot = new FrameLayout(context);
-            contentRoot.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
-                    LayoutParams.MATCH_PARENT));
-            parentView.addView(contentRoot);
+            setMatchParent(contentRoot);
+            mEnclosingLayout.addView(contentRoot);
             mContentRoot = contentRoot;
         } else {
             mContentRoot = contentRoot;
@@ -112,70 +125,49 @@
         }
     }
 
+    private void setMatchParent(View view) {
+        view.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
+                LayoutParams.MATCH_PARENT));
+    }
+
     /**
      * Creates a Popup and adds it to the content frame. It also adds another {@link FrameLayout} to
      * the content frame which shall serve as the new content root.
      */
     public void createMenuPopup() {
-        assert mContentRoot.getId() == android.R.id.content
+        assert mEnclosingLayout.getChildCount() == 1
                 : "Action Bar Menus have already been created.";
 
         if (!isOverflowPopupNeeded()) {
             return;
         }
 
-        // Create a layout to hold the menus and the user's content.
-        RelativeLayout layout = new RelativeLayout(mActionBar.getPopupContext());
-        layout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
-                LayoutParams.MATCH_PARENT));
-        mContentRoot.addView(layout);
-        // Create a layout for the user's content.
-        FrameLayout contentRoot = new FrameLayout(mBridgeContext);
-        contentRoot.setLayoutParams(new LayoutParams(
-                LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
-        // Add contentRoot and menus to the layout.
-        layout.addView(contentRoot);
-        layout.addView(createMenuView());
-        // ContentRoot is now the view we just created.
-        mContentRoot = contentRoot;
-    }
-
-    /**
-     * Returns a {@link LinearLayout} containing the menu list view to be embedded in a
-     * {@link RelativeLayout}
-     */
-    @NonNull
-    private View createMenuView() {
         DisplayMetrics metrics = mBridgeContext.getMetrics();
         MenuBuilder menu = mActionBar.getMenuBuilder();
         OverflowMenuAdapter adapter = new OverflowMenuAdapter(menu, mActionBar.getPopupContext());
 
-        LinearLayout layout = new LinearLayout(mActionBar.getPopupContext());
+        ListView listView = new ListView(mActionBar.getPopupContext(), null,
+                R.attr.dropDownListViewStyle);
         RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
                 measureContentWidth(adapter), LayoutParams.WRAP_CONTENT);
         layoutParams.addRule(RelativeLayout.ALIGN_PARENT_END);
         if (mActionBar.isSplit()) {
             layoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
-            // TODO: Find correct value instead of hardcoded 10dp.
-            layoutParams.bottomMargin = getPixelValue("-10dp", metrics);
+            layoutParams.bottomMargin = getActionBarHeight() + mActionBar.getMenuPopupMargin();
         } else {
-            layoutParams.topMargin = getPixelValue("-10dp", metrics);
+            layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP);
+            layoutParams.topMargin = getActionBarHeight() + mActionBar.getMenuPopupMargin();
         }
-        layout.setLayoutParams(layoutParams);
+        layoutParams.setMarginEnd(getPixelValue("5dp", metrics));
+        listView.setLayoutParams(layoutParams);
+        listView.setAdapter(adapter);
         final TypedArray a = mActionBar.getPopupContext().obtainStyledAttributes(null,
                 R.styleable.PopupWindow, R.attr.popupMenuStyle, 0);
-        layout.setBackground(a.getDrawable(R.styleable.PopupWindow_popupBackground));
-        layout.setDividerDrawable(a.getDrawable(R.attr.actionBarDivider));
+        listView.setBackground(a.getDrawable(R.styleable.PopupWindow_popupBackground));
+        listView.setDivider(a.getDrawable(R.attr.actionBarDivider));
         a.recycle();
-        layout.setOrientation(LinearLayout.VERTICAL);
-        layout.setDividerPadding(getPixelValue("12dp", metrics));
-        layout.setShowDividers(LinearLayout.SHOW_DIVIDER_MIDDLE);
-
-        ListView listView = new ListView(mActionBar.getPopupContext(), null,
-                R.attr.dropDownListViewStyle);
-        listView.setAdapter(adapter);
-        layout.addView(listView);
-        return layout;
+        listView.setElevation(mActionBar.getMenuPopupElevation());
+        mEnclosingLayout.addView(listView);
     }
 
     private boolean isOverflowPopupNeeded() {
@@ -244,9 +236,30 @@
         return maxWidth;
     }
 
-    private int getPixelValue(@NonNull String value, @NonNull DisplayMetrics metrics) {
+    static int getPixelValue(@NonNull String value, @NonNull DisplayMetrics metrics) {
         TypedValue typedValue = ResourceHelper.getValue(null, value, false /*requireUnit*/);
         return (int) typedValue.getDimension(metrics);
     }
 
+    // TODO: This is duplicated from RenderSessionImpl.
+    private int getActionBarHeight() {
+        RenderResources resources = mBridgeContext.getRenderResources();
+        DisplayMetrics metrics = mBridgeContext.getMetrics();
+        ResourceValue value = resources.findItemInTheme("actionBarSize", true);
+
+        // resolve it
+        value = resources.resolveResValue(value);
+
+        if (value != null) {
+            // get the numerical value, if available
+            TypedValue typedValue = ResourceHelper.getValue("actionBarSize", value.getValue(),
+                    true);
+            if (typedValue != null) {
+                // compute the pixel value based on the display metrics
+                return (int) typedValue.getDimension(metrics);
+
+            }
+        }
+        return 0;
+    }
 }
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomActionBarWrapper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomActionBarWrapper.java
index 70b9cc3..6db722e 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomActionBarWrapper.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomActionBarWrapper.java
@@ -65,18 +65,17 @@
      * Returns a wrapper around different implementations of the Action Bar to provide a common API.
      *
      * @param decorContent the top level view returned by inflating
-                     * ?attr/windowActionBarFullscreenDecorLayout
+     *                     ?attr/windowActionBarFullscreenDecorLayout
      */
     @NonNull
     public static CustomActionBarWrapper getActionBarWrapper(@NonNull BridgeContext context,
             @NonNull SessionParams params, @NonNull View decorContent) {
         View view = decorContent.findViewById(R.id.action_bar);
         if (view instanceof Toolbar) {
-            return new ToolbarWrapper(context, params, ((Toolbar) view)
-            );
+            return new ToolbarWrapper(context, params, ((Toolbar) view));
         } else if (view instanceof ActionBarView) {
-            return new WindowActionBarWrapper(context, params, decorContent, ((ActionBarView) view)
-            );
+            return new WindowActionBarWrapper(context, params, decorContent,
+                    ((ActionBarView) view));
         } else {
             throw new IllegalStateException("Can't make an action bar out of " +
                     view.getClass().getSimpleName());
@@ -174,6 +173,13 @@
     @NonNull
     abstract DecorToolbar getDecorToolbar();
 
+    abstract int getMenuPopupElevation();
+
+    /**
+     * Margin between the menu popup and the action bar.
+     */
+    abstract int getMenuPopupMargin();
+
     // ---- The implementations ----
 
     /**
@@ -226,25 +232,38 @@
         DecorToolbar getDecorToolbar() {
             return mToolbar.getWrapper();
         }
+
+        @Override
+        int getMenuPopupElevation() {
+            return 10;
+        }
+
+        @Override
+        int getMenuPopupMargin() {
+            return 0;
+        }
     }
 
     /**
      * Holo theme uses {@link WindowDecorActionBar} as the action bar. This wrapper provides
      * access to it using a common API.
      */
-    private static class WindowActionBarWrapper extends CustomActionBarWrapper{
+    private static class WindowActionBarWrapper extends CustomActionBarWrapper {
 
         @NonNull
         private final WindowDecorActionBar mActionBar;
+        @NonNull
         private final ActionBarView mActionBarView;
+        @NonNull
+        private final View mDecorContentRoot;
         private MenuBuilder mMenuBuilder;
 
         public WindowActionBarWrapper(@NonNull BridgeContext context, @NonNull SessionParams params,
                 @NonNull View decorContentRoot, @NonNull ActionBarView actionBarView) {
-            super(context, params, new WindowDecorActionBar(decorContentRoot)
-            );
+            super(context, params, new WindowDecorActionBar(decorContentRoot));
             mActionBarView = actionBarView;
             mActionBar = ((WindowDecorActionBar) super.mActionBar);
+            mDecorContentRoot = decorContentRoot;
         }
 
         @Override
@@ -270,7 +289,7 @@
             }
 
             // Set action bar to be split, if needed.
-            ViewGroup splitView = (ViewGroup) mActionBarView.findViewById(R.id.split_action_bar);
+            ViewGroup splitView = (ViewGroup) mDecorContentRoot.findViewById(R.id.split_action_bar);
             if (splitView != null) {
                 mActionBarView.setSplitView(splitView);
                 Resources res = mContext.getResources();
@@ -314,6 +333,16 @@
             return mActionBarView;
         }
 
+        @Override
+        int getMenuPopupElevation() {
+            return 0;
+        }
+
+        @Override
+        int getMenuPopupMargin() {
+            return -ActionBarLayout.getPixelValue("10dp", mContext.getMetrics());
+        }
+
         // TODO: Use an adapter, like List View to set up tabs.
         @SuppressWarnings("deprecation")  // For Tab
         private void setupTabs(int num) {
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 2fcdf34..4e6f456 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
@@ -162,6 +162,11 @@
         "android.view.WindowManagerGlobal#getWindowManagerService",
         "android.view.inputmethod.InputMethodManager#getInstance",
         "android.view.MenuInflater#registerMenu",
+        "android.view.RenderNode#nCreate",
+        "android.view.RenderNode#nDestroyRenderNode",
+        "android.view.RenderNode#nSetElevation",
+        "android.view.RenderNode#nGetElevation",
+        "android.view.ViewGroup#drawChild",
         "com.android.internal.view.menu.MenuBuilder#createNewMenuItem",
         "com.android.internal.util.XmlUtils#convertValueToInt",
         "com.android.internal.textservice.ITextServicesManager$Stub#asInterface",