Round rect clipping for notification contents

Enable round rect clipping on notifications for the following kind
of notifications:
- Big picture style.
- Big media narrow style.
- Custom notifications.

Bug: 16142505
Change-Id: I157650fe470636ed624a81557c08135827eac0cb
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index 110b14c..dec2fc7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar;
 
 import android.content.Context;
+import android.graphics.Outline;
 import android.graphics.Paint;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffXfermode;
@@ -24,6 +25,7 @@
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
 import android.view.ViewTreeObserver;
 import android.view.animation.Interpolator;
 import android.view.animation.LinearInterpolator;
@@ -46,21 +48,26 @@
     private final Rect mClipBounds = new Rect();
     private final int mSmallHeight;
     private final int mHeadsUpHeight;
+    private final int mRoundRectRadius;
     private final Interpolator mLinearInterpolator = new LinearInterpolator();
+    private final boolean mRoundRectClippingEnabled;
 
     private View mContractedChild;
     private View mExpandedChild;
     private View mHeadsUpChild;
 
     private NotificationViewWrapper mContractedWrapper;
+    private NotificationViewWrapper mExpandedWrapper;
+    private NotificationViewWrapper mHeadsUpWrapper;
     private int mClipTopAmount;
     private int mContentHeight;
+    private int mUnrestrictedContentHeight;
     private int mVisibleType = VISIBLE_TYPE_CONTRACTED;
     private boolean mDark;
     private final Paint mFadePaint = new Paint();
     private boolean mAnimate;
     private boolean mIsHeadsUp;
-    private ViewTreeObserver.OnPreDrawListener mEnableAnimationPredrawListener
+    private final ViewTreeObserver.OnPreDrawListener mEnableAnimationPredrawListener
             = new ViewTreeObserver.OnPreDrawListener() {
         @Override
         public boolean onPreDraw() {
@@ -70,12 +77,25 @@
         }
     };
 
+    private final ViewOutlineProvider mOutlineProvider = new ViewOutlineProvider() {
+        @Override
+        public void getOutline(View view, Outline outline) {
+            outline.setRoundRect(0, 0, view.getWidth(), mUnrestrictedContentHeight,
+                    mRoundRectRadius);
+        }
+    };
+
     public NotificationContentView(Context context, AttributeSet attrs) {
         super(context, attrs);
         mFadePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
         mSmallHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
         mHeadsUpHeight = getResources().getDimensionPixelSize(R.dimen.notification_mid_height);
+        mRoundRectRadius = getResources().getDimensionPixelSize(
+                R.dimen.notification_material_rounded_rect_radius);
+        mRoundRectClippingEnabled = getResources().getBoolean(
+                R.bool.config_notifications_round_rect_clipping);
         reset(true);
+        setOutlineProvider(mOutlineProvider);
     }
 
     @Override
@@ -127,6 +147,7 @@
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
         updateClipping();
+        invalidateOutline();
     }
 
     @Override
@@ -177,6 +198,7 @@
         mContractedWrapper = NotificationViewWrapper.wrap(getContext(), child);
         selectLayout(false /* animate */, true /* force */);
         mContractedWrapper.setDark(mDark, false /* animate */, 0 /* delay */);
+        updateRoundRectClipping();
     }
 
     public void setExpandedChild(View child) {
@@ -186,7 +208,9 @@
         }
         addView(child);
         mExpandedChild = child;
+        mExpandedWrapper = NotificationViewWrapper.wrap(getContext(), child);
         selectLayout(false /* animate */, true /* force */);
+        updateRoundRectClipping();
     }
 
     public void setHeadsUpChild(View child) {
@@ -196,7 +220,9 @@
         }
         addView(child);
         mHeadsUpChild = child;
+        mHeadsUpWrapper = NotificationViewWrapper.wrap(getContext(), child);
         selectLayout(false /* animate */, true /* force */);
+        updateRoundRectClipping();
     }
 
     @Override
@@ -222,10 +248,11 @@
     }
 
     public void setContentHeight(int contentHeight) {
-        contentHeight = Math.max(Math.min(contentHeight, getHeight()), getMinHeight());
-        mContentHeight = contentHeight;
+        mContentHeight = Math.max(Math.min(contentHeight, getHeight()), getMinHeight());;
+        mUnrestrictedContentHeight = Math.max(contentHeight, getMinHeight());
         selectLayout(mAnimate /* animate */, false /* force */);
         updateClipping();
+        invalidateOutline();
     }
 
     public int getContentHeight() {
@@ -250,6 +277,27 @@
         updateClipping();
     }
 
+    private void updateRoundRectClipping() {
+        boolean enabled = needsRoundRectClipping();
+        setClipToOutline(enabled);
+    }
+
+    private boolean needsRoundRectClipping() {
+        if (!mRoundRectClippingEnabled) {
+            return false;
+        }
+        boolean needsForContracted = mContractedChild != null
+                && mContractedChild.getVisibility() == View.VISIBLE
+                && mContractedWrapper.needsRoundRectClipping();
+        boolean needsForExpanded = mExpandedChild != null
+                && mExpandedChild.getVisibility() == View.VISIBLE
+                && mExpandedWrapper.needsRoundRectClipping();
+        boolean needsForHeadsUp = mExpandedChild != null
+                && mExpandedChild.getVisibility() == View.VISIBLE
+                && mExpandedWrapper.needsRoundRectClipping();
+        return needsForContracted || needsForExpanded || needsForHeadsUp;
+    }
+
     private void updateClipping() {
         mClipBounds.set(0, mClipTopAmount, getWidth(), mContentHeight);
         setClipBounds(mClipBounds);
@@ -290,6 +338,7 @@
             mHeadsUpChild.setLayerType(LAYER_TYPE_NONE, null);
         }
         setLayerType(LAYER_TYPE_NONE, null);
+        updateRoundRectClipping();
     }
 
     private void runSwitchAnimation(int visibleType) {
@@ -315,6 +364,7 @@
                         updateViewVisibilities(mVisibleType);
                     }
                 });
+        updateRoundRectClipping();
     }
 
     /**
@@ -358,6 +408,10 @@
             mContractedWrapper.notifyContentUpdated();
             mContractedWrapper.setDark(mDark, false /* animate */, 0 /* delay */);
         }
+        if (mExpandedChild != null) {
+            mExpandedWrapper.notifyContentUpdated();
+        }
+        updateRoundRectClipping();
     }
 
     public boolean isContentExpandable() {