Add API for specifying popup window shadows and shadow insets

BUG: 14569120
BUG: 13211941

Change-Id: Ia21596b25a0471344d42d59377074f67fce00042
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index d69d01d..be677ea 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.graphics.Bitmap;
+import android.graphics.Rect;
 import android.util.DisplayMetrics;
 import android.view.Surface.OutOfResourcesException;
 
@@ -247,21 +248,24 @@
     abstract void detachSurfaceTexture(long hardwareLayer);
 
     /**
-     * Setup the hardware renderer for drawing. This is called whenever the
-     * size of the target surface changes or when the surface is first created.
+     * Setup the hardware renderer for drawing. This is called whenever the size
+     * of the target surface changes or when the surface is first created.
      *
      * @param width Width of the drawing surface.
      * @param height Height of the drawing surface.
+     * @param surfaceInsets Insets between the drawing surface and actual
+     *            surface bounds.
      * @param lightX X position of the shadow casting light
      * @param lightY Y position of the shadow casting light
      * @param lightZ Z position of the shadow casting light
      * @param lightRadius radius of the shadow casting light
      */
-    abstract void setup(int width, int height, float lightX, float lightY, float lightZ, float lightRadius);
+    abstract void setup(int width, int height, Rect surfaceInsets, float lightX, float lightY,
+            float lightZ, float lightRadius);
 
     /**
      * Gets the current width of the surface. This is the width that the surface
-     * was last set to in a call to {@link #setup(int, int, float, float, float, float)}.
+     * was last set to in a call to {@link #setup(int, int, Rect, float, float, float, float)}.
      *
      * @return the current width of the surface
      */
@@ -269,7 +273,7 @@
 
     /**
      * Gets the current height of the surface. This is the height that the surface
-     * was last set to in a call to {@link #setup(int, int, float, float, float, float)}.
+     * was last set to in a call to {@link #setup(int, int, Rect, float, float, float, float)}.
      *
      * @return the current width of the surface
      */
@@ -344,7 +348,6 @@
      * @param view The view to draw.
      * @param attachInfo AttachInfo tied to the specified view.
      * @param callbacks Callbacks invoked when drawing happens.
-     * @param dirty The dirty rectangle to update, can be null.
      */
     abstract void draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks);
 
@@ -369,17 +372,18 @@
      * @param height The height of the drawing surface.
      * @param surface The surface to hardware accelerate
      * @param metrics The display metrics used to draw the output.
+     * @param surfaceInsets The drawing surface insets to apply
      *
      * @return true if the surface was initialized, false otherwise. Returning
      *         false might mean that the surface was already initialized.
      */
-    boolean initializeIfNeeded(int width, int height, Surface surface, DisplayMetrics metrics)
+    boolean initializeIfNeeded(int width, int height, Surface surface, Rect surfaceInsets, DisplayMetrics metrics)
             throws OutOfResourcesException {
         if (isRequested()) {
             // We lost the gl context, so recreate it.
             if (!isEnabled()) {
                 if (initialize(surface)) {
-                    setup(width, height, metrics);
+                    setup(width, height, surfaceInsets, metrics);
                     return true;
                 }
             }
@@ -387,12 +391,12 @@
         return false;
     }
 
-    void setup(int width, int height, DisplayMetrics metrics) {
+    void setup(int width, int height, Rect surfaceInsets, DisplayMetrics metrics) {
         float lightX = width / 2.0f;
         float lightY = -400 * metrics.density;
         float lightZ = 800 * metrics.density;
         float lightRadius = 800 * metrics.density;
-        setup(width, height, lightX, lightY, lightZ, lightRadius);
+        setup(width, height, surfaceInsets, lightX, lightY, lightZ, lightRadius);
     }
 
     /**
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 57d1beb..acb2fe4 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
+import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -67,7 +68,16 @@
         PROFILE_PROPERTY_VISUALIZE_BARS,
     };
 
+    // Size of the rendered content.
     private int mWidth, mHeight;
+
+    // Actual size of the drawing surface.
+    private int mSurfaceWidth, mSurfaceHeight;
+
+    // Insets between the drawing surface and rendered content. These are
+    // applied as translation when updating the root render node.
+    private int mInsetTop, mInsetLeft;
+
     private long mNativeProxy;
     private boolean mInitialized = false;
     private RenderNode mRootNode;
@@ -154,11 +164,23 @@
     }
 
     @Override
-    void setup(int width, int height, float lightX, float lightY, float lightZ, float lightRadius) {
+    void setup(int width, int height, Rect surfaceInsets, float lightX, float lightY, float lightZ,
+            float lightRadius) {
         mWidth = width;
         mHeight = height;
-        mRootNode.setLeftTopRightBottom(0, 0, mWidth, mHeight);
-        nSetup(mNativeProxy, width, height, lightX, lightY, lightZ, lightRadius);
+        if (surfaceInsets != null) {
+            mInsetLeft = surfaceInsets.left;
+            mInsetTop = surfaceInsets.top;
+            mSurfaceWidth = width + mInsetLeft + surfaceInsets.right;
+            mSurfaceHeight = height + mInsetTop + surfaceInsets.bottom;
+        } else {
+            mInsetLeft = 0;
+            mInsetTop = 0;
+            mSurfaceWidth = width;
+            mSurfaceHeight = height;
+        }
+        mRootNode.setLeftTopRightBottom(-mInsetLeft, -mInsetTop, mSurfaceWidth, mSurfaceHeight);
+        nSetup(mNativeProxy, mSurfaceWidth, mSurfaceHeight, lightX, lightY, lightZ, lightRadius);
     }
 
     @Override
@@ -214,9 +236,10 @@
         view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
 
         Trace.traceBegin(Trace.TRACE_TAG_VIEW, "getDisplayList");
-        HardwareCanvas canvas = mRootNode.start(mWidth, mHeight);
+        HardwareCanvas canvas = mRootNode.start(mSurfaceWidth, mSurfaceHeight);
         try {
             canvas.save();
+            canvas.translate(mInsetLeft, mInsetTop);
             callbacks.onHardwarePreDraw(canvas);
             canvas.drawRenderNode(view.getDisplayList());
             callbacks.onHardwarePostDraw(canvas);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 5def940..9405299 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1713,7 +1713,8 @@
                 if (hwInitialized ||
                         mWidth != mAttachInfo.mHardwareRenderer.getWidth() ||
                         mHeight != mAttachInfo.mHardwareRenderer.getHeight()) {
-                    mAttachInfo.mHardwareRenderer.setup(mWidth, mHeight,
+                    final Rect shadowInsets = params != null ? params.shadowInsets : null;
+                    mAttachInfo.mHardwareRenderer.setup(mWidth, mHeight, shadowInsets,
                             mAttachInfo.mRootView.getResources().getDisplayMetrics());
                     if (!hwInitialized) {
                         mAttachInfo.mHardwareRenderer.invalidate(mSurface);
@@ -2211,20 +2212,22 @@
         return measureSpec;
     }
 
+    int mHardwareXOffset;
     int mHardwareYOffset;
     int mResizeAlpha;
     final Paint mResizePaint = new Paint();
 
     @Override
     public void onHardwarePreDraw(HardwareCanvas canvas) {
-        canvas.translate(0, -mHardwareYOffset);
+        canvas.translate(-mHardwareXOffset, -mHardwareYOffset);
     }
 
     @Override
     public void onHardwarePostDraw(HardwareCanvas canvas) {
         if (mResizeBuffer != null) {
             mResizePaint.setAlpha(mResizeAlpha);
-            canvas.drawHardwareLayer(mResizeBuffer, 0.0f, mHardwareYOffset, mResizePaint);
+            canvas.drawHardwareLayer(mResizeBuffer, mHardwareXOffset, mHardwareYOffset,
+                    mResizePaint);
         }
         drawAccessibilityFocusedDrawableIfNeeded(canvas);
     }
@@ -2368,15 +2371,17 @@
             attachInfo.mTreeObserver.dispatchOnScrollChanged();
         }
 
-        int yoff;
+        final WindowManager.LayoutParams params = mWindowAttributes;
+        final Rect surfaceInsets = params != null ? params.shadowInsets : null;
         boolean animating = mScroller != null && mScroller.computeScrollOffset();
+        final int curScrollY;
         if (animating) {
-            yoff = mScroller.getCurrY();
+            curScrollY = mScroller.getCurrY();
         } else {
-            yoff = mScrollY;
+            curScrollY = mScrollY;
         }
-        if (mCurScrollY != yoff) {
-            mCurScrollY = yoff;
+        if (mCurScrollY != curScrollY) {
+            mCurScrollY = curScrollY;
             fullRedrawNeeded = true;
         }
 
@@ -2425,11 +2430,14 @@
 
         attachInfo.mTreeObserver.dispatchOnDraw();
 
+        final int xOffset = surfaceInsets != null ? -surfaceInsets.left : 0;
+        final int yOffset = curScrollY + (surfaceInsets != null ? -surfaceInsets.top : 0);
         if (!dirty.isEmpty() || mIsAnimating) {
             if (attachInfo.mHardwareRenderer != null && attachInfo.mHardwareRenderer.isEnabled()) {
                 // Draw with hardware renderer.
                 mIsAnimating = false;
-                mHardwareYOffset = yoff;
+                mHardwareYOffset = yOffset;
+                mHardwareXOffset = xOffset;
                 mResizeAlpha = resizeAlpha;
 
                 dirty.setEmpty();
@@ -2450,8 +2458,9 @@
                         attachInfo.mHardwareRenderer.isRequested()) {
 
                     try {
-                        attachInfo.mHardwareRenderer.initializeIfNeeded(mWidth, mHeight,
-                                mSurface, attachInfo.mRootView.getResources().getDisplayMetrics());
+                        attachInfo.mHardwareRenderer.initializeIfNeeded(
+                                mWidth, mHeight, mSurface, surfaceInsets,
+                                attachInfo.mRootView.getResources().getDisplayMetrics());
                     } catch (OutOfResourcesException e) {
                         handleOutOfResourcesException(e);
                         return;
@@ -2462,7 +2471,7 @@
                     return;
                 }
 
-                if (!drawSoftware(surface, attachInfo, yoff, scalingRequired, dirty)) {
+                if (!drawSoftware(surface, attachInfo, xOffset, yOffset, scalingRequired, dirty)) {
                     return;
                 }
             }
@@ -2475,9 +2484,9 @@
     }
 
     /**
-     * @return true if drawing was succesfull, false if an error occurred
+     * @return true if drawing was successful, false if an error occurred
      */
-    private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int yoff,
+    private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
             boolean scalingRequired, Rect dirty) {
 
         // Draw with software renderer.
@@ -2526,7 +2535,7 @@
             // If we are applying an offset, we need to clear the area
             // where the offset doesn't appear to avoid having garbage
             // left in the blank areas.
-            if (!canvas.isOpaque() || yoff != 0) {
+            if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
                 canvas.drawColor(0, PorterDuff.Mode.CLEAR);
             }
 
@@ -2542,7 +2551,7 @@
                         ", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo());
             }
             try {
-                canvas.translate(0, -yoff);
+                canvas.translate(-xoff, -yoff);
                 if (mTranslator != null) {
                     mTranslator.translateCanvas(canvas);
                 }
@@ -3147,8 +3156,10 @@
                         if (mAttachInfo.mHardwareRenderer != null && mSurface.isValid()){
                             mFullRedrawNeeded = true;
                             try {
+                                final WindowManager.LayoutParams lp = mWindowAttributes;
+                                final Rect surfaceInsets = lp != null ? lp.shadowInsets : null;
                                 mAttachInfo.mHardwareRenderer.initializeIfNeeded(
-                                        mWidth, mHeight, mSurface,
+                                        mWidth, mHeight, mSurface, surfaceInsets,
                                         mAttachInfo.mRootView.getResources().getDisplayMetrics());
                             } catch (OutOfResourcesException e) {
                                 Log.e(TAG, "OutOfResourcesException locking surface", e);
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 4eecc6a..c06b5d8 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -19,7 +19,9 @@
 import android.app.Presentation;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
+import android.graphics.Insets;
 import android.graphics.PixelFormat;
+import android.graphics.Rect;
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -1290,6 +1292,13 @@
          * field is added with {@link #y} to supply the <var>yAdj</var> parameter.
          */
         public float verticalMargin;
+
+        /**
+         * Positive insets between the drawing surface and window content.
+         *
+         * @hide
+         */
+        public Rect shadowInsets = new Rect();
     
         /**
          * The desired bitmap format.  May be one of the constants in
@@ -1571,6 +1580,10 @@
             out.writeInt(hasSystemUiListeners ? 1 : 0);
             out.writeInt(inputFeatures);
             out.writeLong(userActivityTimeout);
+            out.writeInt(shadowInsets.left);
+            out.writeInt(shadowInsets.top);
+            out.writeInt(shadowInsets.right);
+            out.writeInt(shadowInsets.bottom);
         }
         
         public static final Parcelable.Creator<LayoutParams> CREATOR
@@ -1613,6 +1626,7 @@
             hasSystemUiListeners = in.readInt() != 0;
             inputFeatures = in.readInt();
             userActivityTimeout = in.readLong();
+            shadowInsets.set(in.readInt(), in.readInt(), in.readInt(), in.readInt());
         }
     
         @SuppressWarnings({"PointlessBitwiseExpression"})
@@ -1644,6 +1658,8 @@
         /** {@hide} */
         public static final int TRANSLUCENT_FLAGS_CHANGED = 1<<19;
         /** {@hide} */
+        public static final int SHADOW_INSETS_CHANGED = 1<<20;
+        /** {@hide} */
         public static final int EVERYTHING_CHANGED = 0xffffffff;
 
         // internal buffer to backup/restore parameters under compatibility mode.
@@ -1778,6 +1794,11 @@
                 changes |= USER_ACTIVITY_TIMEOUT_CHANGED;
             }
 
+            if (!shadowInsets.equals(o.shadowInsets)) {
+                shadowInsets.set(o.shadowInsets);
+                changes |= SHADOW_INSETS_CHANGED;
+            }
+
             return changes;
         }
     
@@ -1877,6 +1898,9 @@
             if (userActivityTimeout >= 0) {
                 sb.append(" userActivityTimeout=").append(userActivityTimeout);
             }
+            if (!shadowInsets.equals(Insets.NONE)) {
+                sb.append(" shadowInsets=").append(shadowInsets);
+            }
             sb.append('}');
             return sb.toString();
         }
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 01632ae..a35d447 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
+import android.graphics.Insets;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
@@ -109,6 +110,8 @@
     private int mPopupWidth;
     private int mPopupHeight;
 
+    private float mElevation;
+
     private int[] mDrawingLocation = new int[2];
     private int[] mScreenLocation = new int[2];
     private Rect mTempRect = new Rect();
@@ -196,6 +199,7 @@
                 attrs, com.android.internal.R.styleable.PopupWindow, defStyleAttr, defStyleRes);
 
         mBackground = a.getDrawable(R.styleable.PopupWindow_popupBackground);
+        mElevation = a.getDimension(R.styleable.PopupWindow_popupElevation, 0);
         mOverlapAnchor = a.getBoolean(R.styleable.PopupWindow_overlapAnchor, false);
 
         final int animStyle = a.getResourceId(R.styleable.PopupWindow_popupAnimationStyle, -1);
@@ -319,25 +323,49 @@
     }
 
     /**
-     * <p>Return the drawable used as the popup window's background.</p>
+     * Return the drawable used as the popup window's background.
      *
-     * @return the background drawable or null
+     * @return the background drawable or {@code null} if not set
+     * @see #setBackgroundDrawable(Drawable)
+     * @attr ref android.R.styleable#PopupWindow_popupBackground
      */
     public Drawable getBackground() {
         return mBackground;
     }
 
     /**
-     * <p>Change the background drawable for this popup window. The background
-     * can be set to null.</p>
+     * Specifies the background drawable for this popup window. The background
+     * can be set to {@code null}.
      *
      * @param background the popup's background
+     * @see #getBackground()
+     * @attr ref android.R.styleable#PopupWindow_popupBackground
      */
     public void setBackgroundDrawable(Drawable background) {
         mBackground = background;
     }
 
     /**
+     * @return the elevation for this popup window in pixels
+     * @see #setElevation(float)
+     * @attr ref android.R.styleable#PopupWindow_popupElevation
+     */
+    public float getElevation() {
+        return mElevation;
+    }
+
+    /**
+     * Specifies the elevation for this popup window.
+     *
+     * @param elevation the popup's elevation in pixels
+     * @see #getElevation()
+     * @attr ref android.R.styleable#PopupWindow_popupElevation
+     */
+    public void setElevation(float elevation) {
+        mElevation = elevation;
+    }
+
+    /**
      * <p>Return the animation style to use the popup appears and disappears</p>
      *
      * @return the animation style to use the popup appears and disappears
@@ -973,7 +1001,7 @@
     /**
      * <p>Prepare the popup by embedding in into a new ViewGroup if the
      * background drawable is not null. If embedding is required, the layout
-     * parameters' height is mnodified to take into account the background's
+     * parameters' height is modified to take into account the background's
      * padding.</p>
      *
      * @param p the layout parameters of the popup's content view
@@ -998,13 +1026,15 @@
             PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams(
                     ViewGroup.LayoutParams.MATCH_PARENT, height
             );
-            popupViewContainer.setBackgroundDrawable(mBackground);
+            popupViewContainer.setBackground(mBackground);
             popupViewContainer.addView(mContentView, listParams);
 
             mPopupView = popupViewContainer;
         } else {
             mPopupView = mContentView;
         }
+
+        mPopupView.setElevation(mElevation);
         mPopupViewInitialLayoutDirectionInherited =
                 (mPopupView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT);
         mPopupWidth = p.width;
@@ -1066,6 +1096,10 @@
         p.softInputMode = mSoftInputMode;
         p.setTitle("PopupWindow:" + Integer.toHexString(hashCode()));
 
+        // TODO: Use real shadow insets once that algorithm is finalized.
+        final int shadowInset = (int) Math.ceil(mElevation * 2);
+        p.shadowInsets.set(shadowInset, shadowInset, shadowInset, shadowInset);
+
         return p;
     }
 
diff --git a/core/res/res/drawable/popup_background_material.xml b/core/res/res/drawable/popup_background_material.xml
index 9e50790..b1f0cf5 100644
--- a/core/res/res/drawable/popup_background_material.xml
+++ b/core/res/res/drawable/popup_background_material.xml
@@ -14,7 +14,12 @@
      limitations under the License.
 -->
 
-<nine-patch xmlns:android="http://schemas.android.com/apk/res/android"
-    android:src="@drawable/popup_background_mtrl_mult"
-    android:tint="?attr/colorBackground"
-    android:tintMode="multiply" />
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+
+    <corners
+            android:radius="2dp" />
+    <solid
+            android:color="?attr/colorBackground" />
+
+</shape>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 4b708a7..e95239c 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4057,6 +4057,8 @@
     <declare-styleable name="PopupWindow">
         <!-- The background to use for the popup window. -->
         <attr name="popupBackground" format="reference|color" />
+        <!-- Window elevation to use for the popup window. -->
+        <attr name="popupElevation" format="dimension" />
         <!-- The animation style to use for the popup window. -->
         <attr name="popupAnimationStyle" format="reference" />
         <!-- Whether the popup window should overlap its anchor view. -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 1eb8946..2dd67ba 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2231,6 +2231,7 @@
   <public type="attr" name="buttonBarPositiveButtonStyle" />
   <public type="attr" name="buttonBarNeutralButtonStyle" />
   <public type="attr" name="buttonBarNegativeButtonStyle" />
+  <public type="attr" name="popupElevation" />
 
   <public-padding type="dimen" name="l_resource_pad" end="0x01050010" />
 
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index c1eb999..3880fb3 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -733,6 +733,7 @@
     <style name="Widget.Material.ListPopupWindow" parent="Widget.ListPopupWindow">
         <item name="dropDownSelector">?attr/listChoiceBackgroundIndicator</item>
         <item name="popupBackground">@drawable/popup_background_material</item>
+        <item name="popupElevation">@dimen/floating_window_z</item>
         <item name="popupAnimationStyle">@style/Animation.Material.Popup</item>
         <item name="dropDownVerticalOffset">0dip</item>
         <item name="dropDownHorizontalOffset">0dip</item>