Insetting notifications on the lock screen

We are now insetting the notifications on the
lockscreen.
This also improves the clipping to the notifications
since we can't just clip the whole content to the
outline bounds.

Test: add notifications on lockscreen, observe inset. Try swiping too.
Test: runtest -x packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationCustomViewWrapperTest.java
Bug: 69168591
Change-Id: Ie3f57dd4938bc97124fb98230cc9427fd4973ead
diff --git a/core/res/res/layout/notification_material_action_list.xml b/core/res/res/layout/notification_material_action_list.xml
index caeb43a..49b0ee7 100644
--- a/core/res/res/layout/notification_material_action_list.xml
+++ b/core/res/res/layout/notification_material_action_list.xml
@@ -23,7 +23,8 @@
             android:id="@+id/actions"
             android:layout_width="match_parent"
             android:layout_height="@dimen/notification_action_list_height"
-            android:paddingEnd="4dp"
+            android:paddingEnd="12dp"
+            android:paddingStart="8dp"
             android:orientation="horizontal"
             android:gravity="center_vertical"
             android:visibility="gone"
diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml
index 6df1657..2e35d86 100644
--- a/packages/SystemUI/res/layout/notification_info.xml
+++ b/packages/SystemUI/res/layout/notification_info.xml
@@ -126,6 +126,7 @@
         android:layout_height="48dp"
         android:orientation="horizontal"
         android:gravity="end"
+        android:layout_marginEnd="@*android:dimen/notification_content_margin_end"
         android:layout_marginBottom="8dp" >
         <TextView
             android:id="@+id/more_settings"
@@ -139,7 +140,6 @@
             android:text="@string/notification_done"
             android:layout_width="wrap_content"
             android:layout_height="match_parent"
-            android:layout_marginEnd="8dp"
             style="@style/TextAppearance.NotificationInfo.Button"/>
     </LinearLayout>
 </com.android.systemui.statusbar.NotificationInfo>
diff --git a/packages/SystemUI/res/layout/notification_snooze.xml b/packages/SystemUI/res/layout/notification_snooze.xml
index 3209f27..7476abd 100644
--- a/packages/SystemUI/res/layout/notification_snooze.xml
+++ b/packages/SystemUI/res/layout/notification_snooze.xml
@@ -35,7 +35,7 @@
             android:layout_height="wrap_content"
             android:layout_alignParentStart="true"
             android:layout_centerVertical="true"
-            android:paddingStart="16dp"
+            android:paddingStart="@*android:dimen/notification_content_margin_start"
             android:textColor="#DD000000"
             android:paddingEnd="4dp"/>
 
@@ -53,7 +53,7 @@
             style="@style/TextAppearance.NotificationInfo.Button"
             android:layout_width="wrap_content"
             android:layout_height="36dp"
-            android:layout_marginEnd="8dp"
+            android:layout_marginEnd="@*android:dimen/notification_content_margin_end"
             android:layout_alignParentEnd="true"
             android:layout_centerVertical="true"
             android:text="@string/snooze_undo" />
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index e33b9aa..7449a77 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -101,8 +101,11 @@
     <!-- Height of a heads up notification in the status bar -->
     <dimen name="notification_max_heads_up_height_increased">188dp</dimen>
 
+    <!-- Side padding on the lockscreen on the side of notifications -->
+    <dimen name="notification_lockscreen_side_paddings">8dp</dimen>
+
     <!-- Additional side padding for custom content if the app doesn't target P yet -->
-    <dimen name="notification_content_custom_view_side_padding">8dp</dimen>
+    <dimen name="notification_content_custom_view_side_padding">@dimen/notification_lockscreen_side_paddings</dimen>
 
     <!-- Height of a messaging notifications with actions at least. Not that this is an upper bound
          and the notification won't use this much, but is measured with wrap_content -->
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index d491df3..35bb946 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -85,6 +85,10 @@
     <item type="id" name="top_roundess_animator_start_tag"/>
     <item type="id" name="top_roundess_animator_end_tag"/>
 
+    <item type="id" name="side_padding_animator_tag"/>
+    <item type="id" name="side_padding_animator_start_tag"/>
+    <item type="id" name="side_padding_animator_end_tag"/>
+
     <!-- Accessibility actions for the notification menu -->
     <item type="id" name="action_snooze_undo"/>
     <item type="id" name="action_snooze_shorter"/>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index 06bee43..86c9d0c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -913,12 +913,33 @@
         mBackgroundNormal.setRoundness(topRadius, bottomRadius);
     }
 
+    @Override
+    protected void setBackgroundTop(int backgroundTop) {
+        mBackgroundDimmed.setBackgroundTop(backgroundTop);
+        mBackgroundNormal.setBackgroundTop(backgroundTop);
+    }
+
     protected abstract View getContentView();
 
     public int calculateBgColor() {
         return calculateBgColor(true /* withTint */, true /* withOverRide */);
     }
 
+    @Override
+    public void setCurrentSidePaddings(float currentSidePaddings) {
+        super.setCurrentSidePaddings(currentSidePaddings);
+        mBackgroundNormal.setCurrentSidePaddings(currentSidePaddings);
+        mBackgroundDimmed.setCurrentSidePaddings(currentSidePaddings);
+    }
+
+    @Override
+    protected boolean childNeedsClipping(View child) {
+        if (child instanceof NotificationBackgroundView && isClippingNeeded()) {
+            return true;
+        }
+        return super.childNeedsClipping(child);
+    }
+
     /**
      * @param withTint should a possible tint be factored in?
      * @param withOverRide should the value be interpolated with {@link #mOverrideTint}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 495858b..8d5ec09 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -26,6 +26,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.Configuration;
+import android.graphics.Path;
 import android.graphics.drawable.AnimatedVectorDrawable;
 import android.graphics.drawable.AnimationDrawable;
 import android.graphics.drawable.ColorDrawable;
@@ -65,7 +66,6 @@
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
 import com.android.systemui.statusbar.NotificationGuts.GutsContent;
 import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
-import com.android.systemui.statusbar.notification.AboveShelfObserver;
 import com.android.systemui.statusbar.notification.HybridNotificationView;
 import com.android.systemui.statusbar.notification.NotificationInflater;
 import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -2342,8 +2342,35 @@
     }
 
     @Override
-    protected boolean needsContentClipping() {
-        return true;
+    protected boolean childNeedsClipping(View child) {
+        if (child instanceof NotificationContentView) {
+            NotificationContentView contentView = (NotificationContentView) child;
+            if (isClippingNeeded()) {
+                return true;
+            } else if (!hasNoRoundingAndNoPadding() && contentView.shouldClipToSidePaddings()) {
+                return true;
+            }
+        } else if (child == mChildrenContainer) {
+            if (isClippingNeeded()) {
+                return true;
+            }
+        } else if (child instanceof NotificationGuts) {
+            return !hasNoRoundingAndNoPadding();
+        }
+        return super.childNeedsClipping(child);
+    }
+
+    @Override
+    public Path getCustomClipPath(View child) {
+        if (child instanceof NotificationGuts) {
+            return getClipPath(true /* ignoreTranslation */);
+        }
+        return super.getCustomClipPath(child);
+    }
+
+    private boolean hasNoRoundingAndNoPadding() {
+        return mCurrentSidePaddings == 0 && getCurrentBottomRoundness() == 0.0f
+                && getCurrentTopRoundness() == 0.0f;
     }
 
     public boolean isShowingAmbient() {
@@ -2358,6 +2385,20 @@
         }
     }
 
+    @Override
+    public void setCurrentSidePaddings(float currentSidePaddings) {
+        if (mIsSummaryWithChildren) {
+            List<ExpandableNotificationRow> notificationChildren =
+                    mChildrenContainer.getNotificationChildren();
+            int size = notificationChildren.size();
+            for (int i = 0; i < size; i++) {
+                ExpandableNotificationRow row = notificationChildren.get(i);
+                row.setCurrentSidePaddings(currentSidePaddings);
+            }
+        }
+        super.setCurrentSidePaddings(currentSidePaddings);
+    }
+
     public static class NotificationViewState extends ExpandableViewState {
 
         private final StackScrollState mOverallState;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
index 14ffb42..26083c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
@@ -55,6 +55,7 @@
             R.id.bottom_roundess_animator_start_tag);
     private static final AnimationProperties ROUNDNESS_PROPERTIES =
             new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+    private static final Path EMPTY_PATH = new Path();
 
     private final Rect mOutlineRect = new Rect();
     private boolean mCustomOutline;
@@ -67,6 +68,8 @@
     private float mCurrentTopRoundness;
     private float mBottomRoundness;
     private float mTopRoundness;
+    private int mBackgroundTop;
+    protected int mCurrentSidePaddings;
 
     /**
      * {@code true} if the children views of the {@link ExpandableOutlineView} are translated when
@@ -87,6 +90,10 @@
     };
 
     private Path getClipPath() {
+        return getClipPath(false /* ignoreTranslation */);
+    }
+
+    protected Path getClipPath(boolean ignoreTranslation) {
         int left;
         int top;
         int right;
@@ -94,10 +101,11 @@
         int height;
         Path intersectPath = null;
         if (!mCustomOutline) {
-            left = mShouldTranslateContents ? (int) getTranslation() : 0;
-            top = mClipTopAmount;
-            right = getWidth() + Math.min(left, 0);
-            left = Math.max(left, 0);
+            int translation = mShouldTranslateContents && !ignoreTranslation
+                    ? (int) getTranslation() : 0;
+            left = Math.max(translation + mCurrentSidePaddings, mCurrentSidePaddings);
+            top = mClipTopAmount + mBackgroundTop;
+            right = getWidth() - mCurrentSidePaddings + Math.min(translation, 0);
             bottom = Math.max(getActualHeight(), top);
             int intersectBottom = Math.max(getActualHeight() - mClipBottomAmount, top);
             if (bottom != intersectBottom) {
@@ -111,10 +119,12 @@
             top = mOutlineRect.top;
             right = mOutlineRect.right;
             bottom = mOutlineRect.bottom;
+            left = Math.max(mCurrentSidePaddings, left);
+            right = Math.min(getWidth() - mCurrentSidePaddings, right);
         }
         height = bottom - top;
         if (height == 0) {
-            return null;
+            return EMPTY_PATH;
         }
         float topRoundness = mAlwaysRoundBothCorners
                 ? mOutlineRadius : mCurrentTopRoundness * mOutlineRadius;
@@ -179,11 +189,10 @@
     }
 
     @Override
-    protected void dispatchDraw(Canvas canvas) {
+    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
         canvas.save();
-        if (needsContentClipping() && (mAlwaysRoundBothCorners || mCurrentTopRoundness > 0
-                || mCurrentBottomRoundness > 0 || mCustomOutline)) {
-            Path clipPath = getCustomClipPath();
+        if (childNeedsClipping(child)) {
+            Path clipPath = getCustomClipPath(child);
             if (clipPath == null) {
                 clipPath = getClipPath();
             }
@@ -191,14 +200,20 @@
                 canvas.clipPath(clipPath);
             }
         }
-        super.dispatchDraw(canvas);
+        boolean result = super.drawChild(canvas, child, drawingTime);
         canvas.restore();
+        return result;
     }
 
-    protected boolean needsContentClipping() {
+    protected boolean childNeedsClipping(View child) {
         return false;
     }
 
+    protected boolean isClippingNeeded() {
+        return mAlwaysRoundBothCorners || mCustomOutline || getTranslation() != 0 ;
+
+    }
+
     private void initDimens() {
         Resources res = getResources();
         mShouldTranslateContents =
@@ -225,11 +240,11 @@
         invalidate();
     }
 
-    protected float getCurrentBackgroundRadiusTop() {
+    public float getCurrentBackgroundRadiusTop() {
         return mCurrentTopRoundness * mOutlineRadius;
     }
 
-    protected float getCurrentTopRoundness() {
+    public float getCurrentTopRoundness() {
         return mCurrentTopRoundness;
     }
 
@@ -249,6 +264,12 @@
         }
     }
 
+    protected void setBackgroundTop(int backgroundTop) {
+        if (mBackgroundTop != backgroundTop) {
+            mBackgroundTop = backgroundTop;
+            invalidateOutline();
+        }
+    }
 
     private void setTopRoundnessInternal(float topRoundness) {
         mCurrentTopRoundness = topRoundness;
@@ -355,7 +376,13 @@
         applyRoundness();
     }
 
-    public Path getCustomClipPath() {
+    public Path getCustomClipPath(View child) {
         return null;
     }
+
+    public void setCurrentSidePaddings(float currentSidePaddings) {
+        mCurrentSidePaddings = (int) currentSidePaddings;
+        invalidateOutline();
+        invalidate();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java
index 1a02e4e..3437ca7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java
@@ -40,9 +40,10 @@
     private int mActualHeight;
     private int mClipBottomAmount;
     private int mTintColor;
-    private float mTopRoundness;
-    private float mBottomRoundness;
     private float[] mCornerRadii = new float[8];
+    private int mCurrentSidePaddings;
+    private boolean mBottomIsRounded;
+    private int mBackgroundTop;
 
     public NotificationBackgroundView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -52,13 +53,22 @@
 
     @Override
     protected void onDraw(Canvas canvas) {
-        draw(canvas, mBackground);
+        if (mClipTopAmount + mClipBottomAmount < mActualHeight - mBackgroundTop) {
+            canvas.save();
+            canvas.clipRect(0, mClipTopAmount, getWidth(), mActualHeight - mClipBottomAmount);
+            draw(canvas, mBackground);
+            canvas.restore();
+        }
     }
 
     private void draw(Canvas canvas, Drawable drawable) {
-        int bottom = mActualHeight - mClipBottomAmount;
-        if (drawable != null && bottom > mClipTopAmount) {
-            drawable.setBounds(0, mClipTopAmount, getWidth(), bottom);
+        if (drawable != null) {
+            int bottom = mActualHeight;
+            if (mBottomIsRounded) {
+                bottom -= mClipBottomAmount;
+            }
+            drawable.setBounds(mCurrentSidePaddings, mBackgroundTop,
+                    getWidth() - mCurrentSidePaddings, bottom);
             drawable.draw(canvas);
         }
     }
@@ -165,6 +175,7 @@
     }
 
     public void setRoundness(float topRoundness, float bottomRoundNess) {
+        mBottomIsRounded = bottomRoundNess != 0.0f;
         mCornerRadii[0] = topRoundness;
         mCornerRadii[1] = topRoundness;
         mCornerRadii[2] = topRoundness;
@@ -188,4 +199,13 @@
         }
     }
 
+    public void setCurrentSidePaddings(float currentSidePaddings) {
+        mCurrentSidePaddings = (int) currentSidePaddings;
+        invalidate();
+    }
+
+    public void setBackgroundTop(int backgroundTop) {
+        mBackgroundTop = backgroundTop;
+        invalidate();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index 0271b40..6d4776c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -1500,4 +1500,20 @@
         }
         return false;
     }
+
+    public boolean shouldClipToSidePaddings() {
+        boolean needsPaddings = shouldClipToSidePaddings(getVisibleType());
+        if (mUserExpanding) {
+             needsPaddings |= shouldClipToSidePaddings(mTransformationStartVisibleType);
+        }
+        return needsPaddings;
+    }
+
+    private boolean shouldClipToSidePaddings(int visibleType) {
+        NotificationViewWrapper visibleWrapper = getVisibleWrapper(visibleType);
+        if (visibleWrapper == null) {
+            return false;
+        }
+        return visibleWrapper.shouldClipToSidePaddings();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
index 99b4b07..b2604fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
@@ -88,6 +88,7 @@
     private float mHorizSpaceForIcon = -1;
     private int mVertSpaceForIcons = -1;
     private int mIconPadding = -1;
+    private int mSidePadding;
 
     private float mAlpha = 0f;
     private float mPrevX;
@@ -175,6 +176,7 @@
         final Resources res = mContext.getResources();
         mHorizSpaceForIcon = res.getDimensionPixelSize(R.dimen.notification_menu_icon_size);
         mVertSpaceForIcons = res.getDimensionPixelSize(R.dimen.notification_min_height);
+        mSidePadding = res.getDimensionPixelSize(R.dimen.notification_lockscreen_side_paddings);
         mIconPadding = res.getDimensionPixelSize(R.dimen.notification_menu_icon_padding);
         mMenuItems.clear();
         // Construct the menu items based on the notification
@@ -496,8 +498,8 @@
         final int count = mMenuContainer.getChildCount();
         for (int i = 0; i < count; i++) {
             final View v = mMenuContainer.getChildAt(i);
-            final float left = i * mHorizSpaceForIcon;
-            final float right = mParent.getWidth() - (mHorizSpaceForIcon * (i + 1));
+            final float left = mSidePadding + i * mHorizSpaceForIcon;
+            final float right = mParent.getWidth() - (mHorizSpaceForIcon * (i + 1)) - mSidePadding;
             v.setX(showOnLeft ? left : right);
         }
         mOnLeft = showOnLeft;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 5911a3d..f29c6a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -86,9 +86,7 @@
     private boolean mVibrationOnAnimation;
     private boolean mUserTouchingScreen;
     private boolean mTouchActive;
-    private boolean mContentNeedsClipping;
-    private int mCustomClipTop;
-    private float mFirstElementTopRoundness;
+    private float mFirstElementRoundness;
 
     public NotificationShelf(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -257,7 +255,8 @@
         boolean expandingAnimated = mAmbientState.isExpansionChanging()
                 && !mAmbientState.isPanelTracking();
         int baseZHeight = mAmbientState.getBaseZHeight();
-        boolean contentNeedsClipping = false;
+        int backgroundTop = 0;
+        float firstElementRoundness = 0.0f;
         while (notificationIndex < mHostLayout.getChildCount()) {
             ExpandableView child = (ExpandableView) mHostLayout.getChildAt(notificationIndex);
             notificationIndex++;
@@ -313,15 +312,15 @@
                 NotificationIconContainer.IconState iconState = getIconState(icon);
                 if (iconState.clampedAppearAmount == 1.0f) {
                     // only if the first icon is fully in the shelf we want to clip to it!
-                    mCustomClipTop = (int) (row.getTranslationY() - getTranslationY());
-                    mFirstElementTopRoundness = row.getCurrentBackgroundRadiusTop();
-                    contentNeedsClipping = true;
+                    backgroundTop = (int) (row.getTranslationY() - getTranslationY());
+                    firstElementRoundness = row.getCurrentTopRoundness();
                 }
             }
             notGoneIndex++;
             previousColor = ownColorUntinted;
         }
-        setContentNeedsClipping(contentNeedsClipping);
+        setBackgroundTop(backgroundTop);
+        setFirstElementRoundness(firstElementRoundness);
         mShelfIcons.setSpeedBumpIndex(mAmbientState.getSpeedBumpIndex());
         mShelfIcons.calculateIconTranslations();
         mShelfIcons.applyIconStates();
@@ -342,28 +341,13 @@
         }
     }
 
-    private void setContentNeedsClipping(boolean contentNeedsClipping) {
-        boolean changed = mContentNeedsClipping != contentNeedsClipping;
-        mContentNeedsClipping = contentNeedsClipping;
-        if (changed || contentNeedsClipping) {
-            invalidate();
+    private void setFirstElementRoundness(float firstElementRoundness) {
+        if (mFirstElementRoundness != firstElementRoundness) {
+            mFirstElementRoundness = firstElementRoundness;
+            setTopRoundness(firstElementRoundness, false /* animate */);
         }
     }
 
-    @Override
-    public Path getCustomClipPath() {
-        if (!mContentNeedsClipping) {
-            return null;
-        }
-        return getRoundedRectPath(0, mCustomClipTop, getWidth(), getHeight(),
-                mFirstElementTopRoundness, getCurrentBackgroundRadiusBottom());
-    }
-
-    @Override
-    protected boolean needsContentClipping() {
-        return mContentNeedsClipping;
-    }
-
     private void updateIconClipAmount(ExpandableNotificationRow row) {
         float maxTop = row.getTranslationY();
         StatusBarIconView icon = row.getEntry().expandedIcon;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
index bca4b43..66682e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.graphics.ColorMatrixColorFilter;
 import android.graphics.Paint;
+import android.os.Build;
 import android.view.View;
 
 import com.android.systemui.R;
@@ -37,6 +38,7 @@
     private final Paint mGreyPaint = new Paint();
     private boolean mIsLegacy;
     private int mLegacyColor;
+    private boolean mBeforeP;
 
     protected NotificationCustomViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
         super(ctx, view, row);
@@ -115,4 +117,17 @@
         super.setLegacy(legacy);
         mIsLegacy = legacy;
     }
+
+    @Override
+    public boolean shouldClipToSidePaddings() {
+        // Before P we ensure that they are now drawing inside out content bounds since we inset
+        // the view. If they target P, then we don't have that guarantee and we need to be safe.
+        return !mBeforeP;
+    }
+
+    @Override
+    public void onContentUpdated(ExpandableNotificationRow row) {
+        super.onContentUpdated(row);
+        mBeforeP = row.getEntry().targetSdk < Build.VERSION_CODES.P;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java
index eb211a1..732b134 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java
@@ -60,4 +60,9 @@
     public boolean isDimmable() {
         return false;
     }
+
+    @Override
+    public boolean shouldClipToSidePaddings() {
+        return true;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
index fd085d9..e07112f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
@@ -265,6 +265,11 @@
         updateActionOffset();
     }
 
+    @Override
+    public boolean shouldClipToSidePaddings() {
+        return mActionsContainer != null && mActionsContainer.getVisibility() != View.GONE;
+    }
+
     private void updateActionOffset() {
         if (mActionsContainer != null) {
             // We should never push the actions higher than they are in the headsup view.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
index 1cd5f15..8a767bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
@@ -194,4 +194,8 @@
     public int getMinLayoutHeight() {
         return 0;
     }
+
+    public boolean shouldClipToSidePaddings() {
+        return false;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index e3c58e2..8a3c0e8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -30,6 +30,7 @@
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Paint;
+import android.graphics.Path;
 import android.graphics.PointF;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffXfermode;
@@ -75,6 +76,7 @@
 import com.android.systemui.statusbar.DismissView;
 import com.android.systemui.statusbar.EmptyShadeView;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.ExpandableOutlineView;
 import com.android.systemui.statusbar.ExpandableView;
 import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.NotificationGuts;
@@ -82,8 +84,10 @@
 import com.android.systemui.statusbar.NotificationSnooze;
 import com.android.systemui.statusbar.StackScrollerDecorView;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.FakeShadowView;
 import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.PropertyAnimator;
 import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -121,12 +125,23 @@
      * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}.
      */
     private static final int INVALID_POINTER = -1;
+    private static final AnimatableProperty SIDE_PADDINGS = AnimatableProperty.from(
+            "sidePaddings",
+            NotificationStackScrollLayout::setCurrentSidePadding,
+            NotificationStackScrollLayout::getCurrentSidePadding,
+            R.id.side_padding_animator_tag,
+            R.id.side_padding_animator_end_tag,
+            R.id.side_padding_animator_start_tag);
+    private static final AnimationProperties SIDE_PADDING_PROPERTIES =
+            new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
 
     private ExpandHelper mExpandHelper;
     private NotificationSwipeHelper mSwipeHelper;
     private boolean mSwipingInProgress;
     private int mCurrentStackHeight = Integer.MAX_VALUE;
     private final Paint mBackgroundPaint = new Paint();
+    private final Path mBackgroundPath = new Path();
+    private final float[] mBackgroundRadii = new float[8];
     private final boolean mShouldDrawNotificationBackground;
 
     private float mExpandedHeight;
@@ -157,6 +172,7 @@
     private int mTopPadding;
     private int mBottomMargin;
     private int mBottomInset = 0;
+    private float mCurrentSidePadding;
 
     /**
      * The algorithm which calculates the properties for our children
@@ -384,6 +400,8 @@
     private boolean mHeadsUpGoingAwayAnimationsAllowed = true;
     private Runnable mAnimateScroll = this::animateScroll;
     private int mCornerRadius;
+    private int mLockscreenSidePaddings;
+    private int mSidePaddings;
 
     public NotificationStackScrollLayout(Context context) {
         this(context, null);
@@ -420,6 +438,8 @@
                 res.getBoolean(R.bool.config_fadeNotificationsOnDismiss);
 
         updateWillNotDraw();
+        mBackgroundPaint.setAntiAlias(true);
+        mBackgroundPaint.setStyle(Paint.Style.FILL);
         if (DEBUG) {
             mDebugPaint = new Paint();
             mDebugPaint.setColor(0xffff0000);
@@ -467,8 +487,7 @@
     protected void onDraw(Canvas canvas) {
         if (mShouldDrawNotificationBackground && !mAmbientState.isDark()
                 && mCurrentBounds.top < mCurrentBounds.bottom) {
-            canvas.drawRoundRect(0, mCurrentBounds.top, getWidth(), mCurrentBounds.bottom,
-                    mCornerRadius, mCornerRadius, mBackgroundPaint);
+            canvas.drawPath(mBackgroundPath, mBackgroundPaint);
         }
 
         if (DEBUG) {
@@ -521,6 +540,8 @@
                 R.dimen.min_top_overscroll_to_qs);
         mStatusBarHeight = res.getDimensionPixelOffset(R.dimen.status_bar_height);
         mBottomMargin = res.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom);
+        mLockscreenSidePaddings = res.getDimensionPixelSize(
+                R.dimen.notification_lockscreen_side_paddings);
         mMinInteractionHeight = res.getDimensionPixelSize(
                 R.dimen.notification_min_interaction_height);
         mCornerRadius = res.getDimensionPixelSize(
@@ -2224,9 +2245,31 @@
         mScrimController.setExcludedBackgroundArea(
                 mFadingOut || mParentNotFullyVisible || mAmbientState.isDark() || mIsClipped ? null
                         : mCurrentBounds);
+        updateBackgroundPath();
         invalidate();
     }
 
+    private void updateBackgroundPath() {
+        mBackgroundPath.reset();
+        float topRoundness = 0;
+        if (mFirstVisibleBackgroundChild != null) {
+            topRoundness = mFirstVisibleBackgroundChild.getCurrentBackgroundRadiusTop();
+        }
+        topRoundness = onKeyguard() ? mCornerRadius : topRoundness;
+        float bottomRoundNess = mCornerRadius;
+        mBackgroundRadii[0] = topRoundness;
+        mBackgroundRadii[1] = topRoundness;
+        mBackgroundRadii[2] = topRoundness;
+        mBackgroundRadii[3] = topRoundness;
+        mBackgroundRadii[4] = bottomRoundNess;
+        mBackgroundRadii[5] = bottomRoundNess;
+        mBackgroundRadii[6] = bottomRoundNess;
+        mBackgroundRadii[7] = bottomRoundNess;
+        mBackgroundPath.addRoundRect(mCurrentSidePadding, mCurrentBounds.top,
+                getWidth() - mCurrentSidePadding, mCurrentBounds.bottom, mBackgroundRadii,
+                Path.Direction.CCW);
+    }
+
     /**
      * Update the background bounds to the new desired bounds
      */
@@ -2239,6 +2282,8 @@
             mBackgroundBounds.left = mTempInt2[0];
             mBackgroundBounds.right = mTempInt2[0] + getWidth();
         }
+        mBackgroundBounds.left += mCurrentSidePadding;
+        mBackgroundBounds.right -= mCurrentSidePadding;
         if (!mIsExpanded) {
             mBackgroundBounds.top = 0;
             mBackgroundBounds.bottom = 0;
@@ -2843,8 +2888,8 @@
         }
         mFirstVisibleBackgroundChild = firstChild;
         mLastVisibleBackgroundChild = lastChild;
-        applyRoundedNess();
         mAmbientState.setLastVisibleBackgroundChild(lastChild);
+        applyRoundedNess();
     }
 
     private void applyRoundedNess() {
@@ -2859,6 +2904,8 @@
                     mLastVisibleBackgroundChild.isShown()
                             && !mChildrenToAddAnimated.contains(mLastVisibleBackgroundChild));
         }
+        updateBackgroundPath();
+        invalidate();
     }
 
     private void onViewAddedInternal(View child) {
@@ -2867,6 +2914,7 @@
         generateAddAnimation(child, false /* fromMoreCard */);
         updateAnimationState(child);
         updateChronometerForChild(child);
+        updateCurrentSidePaddings(child);
     }
 
     private void updateHideSensitiveForChild(View child) {
@@ -4314,6 +4362,42 @@
         mStatusBarState = statusBarState;
         mAmbientState.setStatusBarState(statusBarState);
         applyRoundedNess();
+        updateSidePaddings();
+    }
+
+    private void updateSidePaddings() {
+        int sidePaddings = mStatusBarState == StatusBarState.KEYGUARD ? mLockscreenSidePaddings : 0;
+        if (sidePaddings != mSidePaddings) {
+            boolean animate = isShown();
+            mSidePaddings = sidePaddings;
+            PropertyAnimator.setProperty(this, SIDE_PADDINGS, sidePaddings,
+                    SIDE_PADDING_PROPERTIES, animate);
+        }
+    }
+
+    protected void setCurrentSidePadding(float sidePadding) {
+        mCurrentSidePadding = sidePadding;
+        updateBackground();
+        applySidePaddingsToChildren();
+    }
+
+    private void applySidePaddingsToChildren() {
+        for (int i = 0; i < getChildCount(); i++) {
+            View view = getChildAt(i);
+            updateCurrentSidePaddings(view);
+        }
+    }
+
+    private void updateCurrentSidePaddings(View view) {
+        if (!(view instanceof ExpandableOutlineView)) {
+            return;
+        }
+        ExpandableOutlineView outlineView = (ExpandableOutlineView) view;
+        outlineView.setCurrentSidePaddings(mCurrentSidePadding);
+    }
+
+    protected float getCurrentSidePadding() {
+        return mCurrentSidePadding;
     }
 
     public void setExpandingVelocity(float expandingVelocity) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationCustomViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationCustomViewWrapperTest.java
index 6e59d10..2ff86c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationCustomViewWrapperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationCustomViewWrapperTest.java
@@ -39,9 +39,8 @@
     private ExpandableNotificationRow mRow;
 
     @Before
-    @UiThreadTest
-    public void setUp() {
-        mRow = new ExpandableNotificationRow(mContext, null);
+    public void setUp() throws Exception {
+        mRow = new NotificationTestHelper(mContext).createRow();
     }
 
     @Test