Update notification view to match newer specs

- Use smaller radius for notifications round rect background
- Remove "Notifications" header, and clip children to round rect path
- Flip main notification so that icon shows on the right instead of
  left; footer is also flipped so animation makes sense
- Clean up animations to animate view outline instead of height

Bug: 32410600
Change-Id: I6bd1e1f8395b3703f28c3b0056a89e67672368ab
diff --git a/res/drawable/bg_white_pill_top.xml b/res/drawable/bg_white_pill_top.xml
deleted file mode 100644
index 9988b29..0000000
--- a/res/drawable/bg_white_pill_top.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 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.
--->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-       android:shape="rectangle">
-    <solid android:color="#FFFFFF" />
-    <corners android:topLeftRadius="@dimen/bg_pill_radius"
-             android:topRightRadius="@dimen/bg_pill_radius" />
-</shape>
\ No newline at end of file
diff --git a/res/drawable/bg_white_pill_bottom.xml b/res/drawable/bg_white_round_rect.xml
similarity index 86%
rename from res/drawable/bg_white_pill_bottom.xml
rename to res/drawable/bg_white_round_rect.xml
index a1ea48c..c7f786f 100644
--- a/res/drawable/bg_white_pill_bottom.xml
+++ b/res/drawable/bg_white_round_rect.xml
@@ -17,6 +17,5 @@
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
     <solid android:color="#FFFFFF" />
-    <corners android:bottomLeftRadius="@dimen/bg_pill_radius"
-             android:bottomRightRadius="@dimen/bg_pill_radius" />
+    <corners android:radius="@dimen/bg_round_rect_radius" />
 </shape>
\ No newline at end of file
diff --git a/res/layout/notification.xml b/res/layout/notification.xml
index d828c4a..48c7b48 100644
--- a/res/layout/notification.xml
+++ b/res/layout/notification.xml
@@ -20,7 +20,7 @@
     android:layout_width="@dimen/bg_pill_width"
     android:layout_height="wrap_content"
     android:elevation="@dimen/deep_shortcuts_elevation"
-    android:background="@drawable/bg_white_pill">
+    android:background="@drawable/bg_white_round_rect">
 
     <RelativeLayout
         android:layout_width="match_parent"
@@ -28,27 +28,10 @@
         android:orientation="vertical"
         android:clipChildren="false">
 
-        <TextView
-            android:id="@+id/header"
-            android:layout_width="match_parent"
-            android:layout_height="@dimen/notification_footer_collapsed_height"
-            android:gravity="center_vertical"
-            android:textAlignment="center"
-            android:text="@string/notifications_header"
-            android:elevation="@dimen/notification_elevation"
-            android:background="@drawable/bg_white_pill_top" />
-
-        <View
-            android:id="@+id/divider"
-            android:layout_width="match_parent"
-            android:layout_height="@dimen/notification_divider_height"
-            android:layout_below="@id/header" />
-
         <include layout="@layout/notification_main"
             android:id="@+id/main_view"
             android:layout_width="match_parent"
-            android:layout_height="@dimen/bg_pill_height"
-            android:layout_below="@id/divider" />
+            android:layout_height="@dimen/notification_main_height" />
 
         <include layout="@layout/notification_footer"
             android:id="@+id/footer"
diff --git a/res/layout/notification_footer.xml b/res/layout/notification_footer.xml
index ceea24a..c025819 100644
--- a/res/layout/notification_footer.xml
+++ b/res/layout/notification_footer.xml
@@ -20,7 +20,6 @@
     android:layout_height="match_parent"
     android:orientation="vertical"
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:background="@drawable/bg_white_pill_bottom"
     android:elevation="@dimen/notification_elevation"
     android:clipChildren="false" >
 
@@ -34,6 +33,7 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:orientation="horizontal"
+        android:gravity="end"
         android:padding="@dimen/notification_footer_icon_row_padding"
         android:clipToPadding="false"
         android:clipChildren="false"/>
diff --git a/res/layout/notification_main.xml b/res/layout/notification_main.xml
index 84827f1..d036fe5 100644
--- a/res/layout/notification_main.xml
+++ b/res/layout/notification_main.xml
@@ -21,20 +21,14 @@
     android:layout_height="match_parent"
     android:orientation="horizontal"
     android:focusable="true"
+    android:padding="@dimen/notification_padding"
     android:elevation="@dimen/notification_elevation" >
 
-    <View
-        android:id="@+id/popup_item_icon"
-        android:layout_width="@dimen/notification_icon_size"
-        android:layout_height="@dimen/notification_icon_size"
-        android:layout_marginStart="@dimen/notification_icon_margin_start"
-        android:layout_gravity="center_vertical" />
-
     <LinearLayout
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:orientation="vertical"
-        android:layout_marginStart="@dimen/notification_text_margin_start"
+        android:layout_weight="1"
         android:gravity="center_vertical">
         <TextView
             android:id="@+id/title"
@@ -56,5 +50,12 @@
             android:layout_height="wrap_content" />
     </LinearLayout>
 
+    <View
+        android:id="@+id/popup_item_icon"
+        android:layout_width="@dimen/notification_icon_size"
+        android:layout_height="@dimen/notification_icon_size"
+        android:layout_weight="0"
+        android:layout_gravity="center_vertical" />
+
 </com.android.launcher3.notification.NotificationMainView>
 
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 517bf9f..177e08e 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -175,19 +175,18 @@
 <!-- Icon badges (with notification counts) -->
     <dimen name="badge_size">24dp</dimen>
     <dimen name="badge_text_size">12dp</dimen>
-    <dimen name="badge_small_padding">1dp</dimen>
+    <dimen name="badge_small_padding">0dp</dimen>
     <dimen name="badge_large_padding">3dp</dimen>
     <dimen name="notification_icon_size">28dp</dimen>
     <dimen name="notification_footer_icon_size">24dp</dimen>
-    <!-- (icon_size - secondary_icon_size) / 2 -->
 
 <!-- Notifications -->
+    <dimen name="bg_round_rect_radius">12dp</dimen>
+    <dimen name="notification_padding">12dp</dimen>
+    <!-- (icon_size - footer_icon_size) / 2 -->
     <dimen name="notification_footer_icon_row_padding">2dp</dimen>
-    <dimen name="notification_icon_margin_start">8dp</dimen>
-    <dimen name="notification_text_margin_start">8dp</dimen>
-    <dimen name="notification_footer_height">36dp</dimen>
-    <!-- The height to use when there are no icons in the footer -->
-    <dimen name="notification_footer_collapsed_height">@dimen/bg_pill_radius</dimen>
+    <dimen name="notification_main_height">60dp</dimen>
+    <dimen name="notification_footer_height">@dimen/bg_pill_height</dimen>
     <dimen name="notification_elevation">2dp</dimen>
     <dimen name="notification_divider_height">0.5dp</dimen>
     <dimen name="swipe_helper_falsing_threshold">70dp</dimen>
diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java
index 43cf827..aa7f5ee 100644
--- a/src/com/android/launcher3/LauncherAnimUtils.java
+++ b/src/com/android/launcher3/LauncherAnimUtils.java
@@ -130,17 +130,4 @@
         return anim;
     }
 
-    public static ValueAnimator animateViewHeight(final View v, int fromHeight, int toHeight) {
-        ValueAnimator anim = ValueAnimator.ofInt(fromHeight, toHeight);
-        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator valueAnimator) {
-                int val = (Integer) valueAnimator.getAnimatedValue();
-                ViewGroup.LayoutParams layoutParams = v.getLayoutParams();
-                layoutParams.height = val;
-                v.setLayoutParams(layoutParams);
-            }
-        });
-        return anim;
-    }
 }
diff --git a/src/com/android/launcher3/anim/PillHeightRevealOutlineProvider.java b/src/com/android/launcher3/anim/PillHeightRevealOutlineProvider.java
new file mode 100644
index 0000000..be1e2d6
--- /dev/null
+++ b/src/com/android/launcher3/anim/PillHeightRevealOutlineProvider.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2017 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 com.android.launcher3.anim;
+
+import android.graphics.Rect;
+
+import com.android.launcher3.util.PillRevealOutlineProvider;
+
+/**
+ * Extension of {@link PillRevealOutlineProvider} which only changes the height of the pill.
+ * For now, we assume the height is added/removed from the bottom.
+ */
+public class PillHeightRevealOutlineProvider extends PillRevealOutlineProvider {
+
+    private final int mNewHeight;
+
+    public PillHeightRevealOutlineProvider(Rect pillRect, float radius, int newHeight) {
+        super(0, 0, pillRect, radius);
+        mOutline.set(pillRect);
+        mNewHeight = newHeight;
+    }
+
+    @Override
+    public void setProgress(float progress) {
+        mOutline.top = 0;
+        int heightDifference = mPillRect.height() - mNewHeight;
+        mOutline.bottom = (int) (mPillRect.bottom - heightDifference * (1 - progress));
+    }
+}
diff --git a/src/com/android/launcher3/anim/PropertyResetListener.java b/src/com/android/launcher3/anim/PropertyResetListener.java
new file mode 100644
index 0000000..eefb014
--- /dev/null
+++ b/src/com/android/launcher3/anim/PropertyResetListener.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2017 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 com.android.launcher3.anim;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.util.Property;
+
+/**
+ * An AnimatorListener that sets the given property to the given value at the end of the animation.
+ */
+public class PropertyResetListener<T, V> extends AnimatorListenerAdapter {
+
+    private Property<T, V> mPropertyToReset;
+    private V mResetToValue;
+
+    public PropertyResetListener(Property<T, V> propertyToReset, V resetToValue) {
+        mPropertyToReset = propertyToReset;
+        mResetToValue = resetToValue;
+    }
+
+    @Override
+    public void onAnimationEnd(Animator animation) {
+        mPropertyToReset.set((T) ((ObjectAnimator) animation).getTarget(), mResetToValue);
+    }
+}
diff --git a/src/com/android/launcher3/badge/BadgeRenderer.java b/src/com/android/launcher3/badge/BadgeRenderer.java
index 8bbc2af..864a65d 100644
--- a/src/com/android/launcher3/badge/BadgeRenderer.java
+++ b/src/com/android/launcher3/badge/BadgeRenderer.java
@@ -51,6 +51,7 @@
         mSmallIconDrawer = new IconDrawer(res.getDimensionPixelSize(R.dimen.badge_large_padding));
         mTextPaint.setTextAlign(Paint.Align.CENTER);
         mTextPaint.setTextSize(res.getDimensionPixelSize(R.dimen.badge_text_size));
+        mTextPaint.setFakeBoldText(true);
         // Measure the text height.
         Rect tempTextHeight = new Rect();
         mTextPaint.getTextBounds("0", 0, 1, tempTextHeight);
diff --git a/src/com/android/launcher3/notification/NotificationFooterLayout.java b/src/com/android/launcher3/notification/NotificationFooterLayout.java
index 9686ae0..62126ef 100644
--- a/src/com/android/launcher3/notification/NotificationFooterLayout.java
+++ b/src/com/android/launcher3/notification/NotificationFooterLayout.java
@@ -21,18 +21,21 @@
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.content.Context;
-import android.content.res.ColorStateList;
 import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
 import android.util.AttributeSet;
 import android.view.Gravity;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.PropertyListBuilder;
+import com.android.launcher3.anim.PropertyResetListener;
 import com.android.launcher3.graphics.IconPalette;
 import com.android.launcher3.popup.PopupContainerWithArrow;
 
@@ -50,16 +53,17 @@
         void onIconAnimationEnd(NotificationInfo animatedNotification);
     }
 
-    private static final int MAX_FOOTER_NOTIFICATIONS = 5;
+    private static final int MAX_FOOTER_NOTIFICATIONS = 4;
 
     private static final Rect sTempRect = new Rect();
 
     private final List<NotificationInfo> mNotifications = new ArrayList<>();
     private final List<NotificationInfo> mOverflowNotifications = new ArrayList<>();
+    private final boolean mRtl;
 
     LinearLayout.LayoutParams mIconLayoutParams;
     private LinearLayout mIconRow;
-    private int mBackgroundColor;
+    private final ColorDrawable mBackgroundColor;
     private int mTextColor;
     private TextView mOverflowView;
 
@@ -74,13 +78,17 @@
     public NotificationFooterLayout(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
 
+        mRtl = Utilities.isRtl(getResources());
+
         int size = getResources().getDimensionPixelSize(
                 R.dimen.notification_footer_icon_size);
-        int padding = getResources().getDimensionPixelSize(
-                R.dimen.deep_shortcut_drawable_padding);
+        int padding = getResources().getDimensionPixelSize(R.dimen.notification_padding);
         mIconLayoutParams = new LayoutParams(size, size);
-        mIconLayoutParams.setMarginStart(padding);
+        mIconLayoutParams.setMarginEnd(padding);
         mIconLayoutParams.gravity = Gravity.CENTER_VERTICAL;
+
+        mBackgroundColor = new ColorDrawable();
+        setBackground(mBackgroundColor);
     }
 
     @Override
@@ -90,12 +98,15 @@
     }
 
     public void applyColors(IconPalette iconPalette) {
-        mBackgroundColor = iconPalette.backgroundColor;
-        setBackgroundTintList(ColorStateList.valueOf(mBackgroundColor));
+        mBackgroundColor.setColor(iconPalette.backgroundColor);
         findViewById(R.id.divider).setBackgroundColor(iconPalette.secondaryColor);
         mTextColor = iconPalette.textColor;
     }
 
+    public int getBackgroundColor() {
+        return mBackgroundColor.getColor();
+    }
+
     /**
      * Keep track of the NotificationInfo, and then update the UI when
      * {@link #commitNotificationInfos()} is called.
@@ -124,18 +135,18 @@
             mOverflowView = new TextView(getContext());
             mOverflowView.setTextColor(mTextColor);
             updateOverflowText();
-            mIconRow.addView(mOverflowView, mIconLayoutParams);
+            mIconRow.addView(mOverflowView, 0, mIconLayoutParams);
         }
     }
 
     private void addNotificationIconForInfo(NotificationInfo info, boolean fromOverflow) {
         View icon = new View(getContext());
-        icon.setBackground(info.getIconForBackground(getContext(), mBackgroundColor));
+        icon.setBackground(info.getIconForBackground(getContext(), getBackgroundColor()));
         icon.setOnClickListener(info);
-        int addIndex = mIconRow.getChildCount();
+        int addIndex = 0;
         if (fromOverflow) {
             // Add the notification before the overflow view.
-            addIndex--;
+            addIndex = 1;
             icon.setAlpha(0);
             icon.animate().alpha(1);
         }
@@ -151,7 +162,7 @@
     public void animateFirstNotificationTo(Rect toBounds,
             final IconAnimationEndListener callback) {
         AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
-        final View firstNotification = mIconRow.getChildAt(0);
+        final View firstNotification = mIconRow.getChildAt(mIconRow.getChildCount() - 1);
 
         Rect fromBounds = sTempRect;
         firstNotification.getGlobalVisibleRect(fromBounds);
@@ -169,20 +180,19 @@
         animation.play(moveAndScaleIcon);
 
         // Shift all notifications (not the overflow) over to fill the gap.
-        int gapWidth = mIconLayoutParams.width + mIconLayoutParams.getMarginStart();
-        int numIcons = mIconRow.getChildCount()
-                - (mOverflowNotifications.isEmpty() ? 0 : 1);
-        for (int i = 1; i < numIcons; i++) {
+        int gapWidth = mIconLayoutParams.width + mIconLayoutParams.getMarginEnd();
+        if (mRtl) {
+            gapWidth = -gapWidth;
+        }
+        int numIcons = mIconRow.getChildCount() - 1;
+        // We have to set the translation X to 0 when the new main notification
+        // is removed from the footer.
+        PropertyResetListener<View, Float> propertyResetListener
+                = new PropertyResetListener<>(TRANSLATION_X, 0f);
+        for (int i = mOverflowNotifications.isEmpty() ? 0 : 1; i < numIcons; i++) {
             final View child = mIconRow.getChildAt(i);
-            Animator shiftChild = ObjectAnimator.ofFloat(child, TRANSLATION_X, -gapWidth);
-            shiftChild.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    // We have to set the translation X to 0 when the new main notification
-                    // is removed from the footer.
-                    child.setTranslationX(0);
-                }
-            });
+            Animator shiftChild = ObjectAnimator.ofFloat(child, TRANSLATION_X, gapWidth);
+            shiftChild.addListener(propertyResetListener);
             animation.play(shiftChild);
         }
         animation.start();
@@ -205,18 +215,18 @@
             }
         }
         if (mIconRow.getChildCount() == 0) {
-            // There are no more icons in the secondary view, so hide it.
+            // There are no more icons in the footer, so hide it.
             PopupContainerWithArrow popup = PopupContainerWithArrow.getOpen(
                     Launcher.getLauncher(getContext()));
-            int newHeight = getResources().getDimensionPixelSize(
-                    R.dimen.notification_footer_collapsed_height);
-            AnimatorSet collapseSecondary = LauncherAnimUtils.createAnimatorSet();
-            collapseSecondary.play(popup.animateTranslationYBy(getHeight() - newHeight, 0));
-            collapseSecondary.play(LauncherAnimUtils.animateViewHeight(
-                    this, getHeight(), newHeight));
-            collapseSecondary.setDuration(getResources().getInteger(
-                    R.integer.config_removeNotificationViewDuration));
-            collapseSecondary.start();
+            Animator collapseFooter = popup.reduceNotificationViewHeight(getHeight(),
+                    getResources().getInteger(R.integer.config_removeNotificationViewDuration));
+            collapseFooter.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    ((ViewGroup) getParent()).removeView(NotificationFooterLayout.this);
+                }
+            });
+            collapseFooter.start();
         }
     }
 
diff --git a/src/com/android/launcher3/notification/NotificationItemView.java b/src/com/android/launcher3/notification/NotificationItemView.java
index d58bef6..742c90a 100644
--- a/src/com/android/launcher3/notification/NotificationItemView.java
+++ b/src/com/android/launcher3/notification/NotificationItemView.java
@@ -18,29 +18,32 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
 import android.content.Context;
 import android.content.res.ColorStateList;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
+import android.graphics.Shader;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.animation.LinearInterpolator;
+import android.view.ViewGroup;
 import android.widget.FrameLayout;
-import android.widget.TextView;
 
 import com.android.launcher3.ItemInfo;
-import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.R;
 import com.android.launcher3.graphics.IconPalette;
 import com.android.launcher3.logging.UserEventDispatcher.LogContainerProvider;
 import com.android.launcher3.popup.PopupItemView;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.anim.PillHeightRevealOutlineProvider;
 
 import java.util.List;
 
-import static com.android.launcher3.LauncherAnimUtils.animateViewHeight;
-
 /**
  * A {@link FrameLayout} that contains a header, main view and a footer.
  * The main view contains the icon and text (title + subtext) of the first notification.
@@ -51,7 +54,9 @@
 
     private static final Rect sTempRect = new Rect();
 
-    private TextView mHeader;
+    private final Paint mBackgroundClipPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG |
+            Paint.FILTER_BITMAP_FLAG);
+
     private View mDivider;
     private NotificationMainView mMainView;
     private NotificationFooterLayout mFooter;
@@ -73,11 +78,65 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mHeader = (TextView) findViewById(R.id.header);
         mDivider = findViewById(R.id.divider);
         mMainView = (NotificationMainView) findViewById(R.id.main_view);
         mFooter = (NotificationFooterLayout) findViewById(R.id.footer);
         mSwipeHelper = new SwipeHelper(SwipeHelper.X, mMainView, getContext());
+        mSwipeHelper.setDisableHardwareLayers(true);
+    }
+
+    private void initializeBackgroundClipping(boolean force) {
+        if (force || mBackgroundClipPaint.getShader() == null) {
+            mBackgroundClipPaint.setXfermode(null);
+            mBackgroundClipPaint.setShader(null);
+            Bitmap backgroundBitmap = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(),
+                    Bitmap.Config.ALPHA_8);
+            Canvas canvas = new Canvas();
+            canvas.setBitmap(backgroundBitmap);
+            float roundRectRadius = getResources().getDimensionPixelSize(
+                    R.dimen.bg_round_rect_radius);
+            canvas.drawRoundRect(0, 0, getMeasuredWidth(), getMeasuredHeight(),
+                    roundRectRadius, roundRectRadius, mBackgroundClipPaint);
+            Shader backgroundClipShader = new BitmapShader(backgroundBitmap,
+                    Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
+            mBackgroundClipPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
+            mBackgroundClipPaint.setShader(backgroundClipShader);
+        }
+    }
+
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        initializeBackgroundClipping(false /* force */);
+        int saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), null,
+                Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
+        super.dispatchDraw(canvas);
+        canvas.drawPaint(mBackgroundClipPaint);
+        canvas.restoreToCount(saveCount);
+    }
+
+    public Animator animateHeightRemoval(int heightToRemove) {
+        final int newHeight = getHeight() - heightToRemove;
+        Animator heightAnimator = new PillHeightRevealOutlineProvider(mPillRect,
+                getBackgroundRadius(), newHeight).createRevealAnimator(this, true /* isReversed */);
+        heightAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (newHeight > 0) {
+                    measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY),
+                            MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY));
+                    initializeBackgroundClipping(true /* force */);
+                    invalidate();
+                } else {
+                    ((ViewGroup) getParent()).removeView(NotificationItemView.this);
+                }
+            }
+        });
+        return heightAnimator;
+    }
+
+    @Override
+    protected float getBackgroundRadius() {
+        return getResources().getDimensionPixelSize(R.dimen.bg_round_rect_radius);
     }
 
     @Override
@@ -103,7 +162,7 @@
     protected ColorStateList getAttachedArrowColor() {
         // This NotificationView itself has a different color that is only
         // revealed when dismissing notifications.
-        return mFooter.getBackgroundTintList();
+        return ColorStateList.valueOf(mFooter.getBackgroundColor());
     }
 
     public void applyNotificationInfos(final List<NotificationInfo> notificationInfos) {
@@ -122,8 +181,6 @@
 
     public void applyColors(IconPalette iconPalette) {
         setBackgroundTintList(ColorStateList.valueOf(iconPalette.secondaryColor));
-        mHeader.setBackgroundTintList(ColorStateList.valueOf(iconPalette.backgroundColor));
-        mHeader.setTextColor(ColorStateList.valueOf(iconPalette.textColor));
         mDivider.setBackgroundColor(iconPalette.secondaryColor);
         mMainView.applyColors(iconPalette);
         mFooter.applyColors(iconPalette);
@@ -154,27 +211,6 @@
         }
     }
 
-    public Animator createRemovalAnimation(int fullDuration) {
-        AnimatorSet animation = new AnimatorSet();
-        int mainHeight = mMainView.getMeasuredHeight();
-        Animator removeMainView = animateViewHeight(mMainView, mainHeight, 0);
-        removeMainView.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                // Remove the remaining views but take on their color instead of the darker one.
-                setBackgroundTintList(mHeader.getBackgroundTintList());
-                removeAllViews();
-            }
-        });
-        Animator removeRest = LauncherAnimUtils.animateViewHeight(this, getHeight() - mainHeight, 0);
-        removeMainView.setDuration(fullDuration / 2);
-        removeRest.setDuration(fullDuration / 2);
-        removeMainView.setInterpolator(new LinearInterpolator());
-        removeRest.setInterpolator(new LinearInterpolator());
-        animation.playSequentially(removeMainView, removeRest);
-        return animation;
-    }
-
     @Override
     public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target,
             LauncherLogProto.Target targetParent) {
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index d34727c..7811b96 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -34,7 +34,6 @@
 import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
-import android.support.annotation.Nullable;
 import android.util.AttributeSet;
 import android.view.Gravity;
 import android.view.LayoutInflater;
@@ -60,6 +59,7 @@
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.accessibility.ShortcutMenuAccessibilityDelegate;
 import com.android.launcher3.anim.PropertyListBuilder;
+import com.android.launcher3.anim.PropertyResetListener;
 import com.android.launcher3.badge.BadgeInfo;
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragLayer;
@@ -225,7 +225,7 @@
     }
 
     private void addDummyViews(BubbleTextView originalIcon,
-            PopupPopulator.Item[] itemsToPopulate, boolean secondaryNotificationViewHasIcons) {
+            PopupPopulator.Item[] itemsToPopulate, boolean notificationFooterHasIcons) {
         final Resources res = getResources();
         final int spacing = res.getDimensionPixelSize(R.dimen.deep_shortcuts_spacing);
         final LayoutInflater inflater = mLauncher.getLayoutInflater();
@@ -234,10 +234,9 @@
             final PopupItemView item = (PopupItemView) inflater.inflate(
                     itemsToPopulate[i].layoutId, this, false);
             if (itemsToPopulate[i] == PopupPopulator.Item.NOTIFICATION) {
-                int secondaryHeight = secondaryNotificationViewHasIcons ?
-                        res.getDimensionPixelSize(R.dimen.notification_footer_height) :
-                        res.getDimensionPixelSize(R.dimen.notification_footer_collapsed_height);
-                item.findViewById(R.id.footer).getLayoutParams().height = secondaryHeight;
+                int footerHeight = notificationFooterHasIcons ?
+                        res.getDimensionPixelSize(R.dimen.notification_footer_height) : 0;
+                item.findViewById(R.id.footer).getLayoutParams().height = footerHeight;
             }
             if (i < numItems - 1) {
                 ((LayoutParams) item.getLayoutParams()).bottomMargin = spacing;
@@ -575,7 +574,8 @@
     }
 
     public void trimNotifications(Map<PackageUserKey, BadgeInfo> updatedBadges) {
-        final NotificationItemView notificationView = (NotificationItemView) findViewById(R.id.notification_view);
+        final NotificationItemView notificationView =
+                (NotificationItemView) findViewById(R.id.notification_view);
         if (notificationView == null) {
             return;
         }
@@ -586,9 +586,8 @@
             final int duration = getResources().getInteger(
                     R.integer.config_removeNotificationViewDuration);
             final int spacing = getResources().getDimensionPixelSize(R.dimen.deep_shortcuts_spacing);
-            removeNotification.play(animateTranslationYBy(notificationView.getHeight() + spacing,
-                    duration));
-            Animator reduceHeight = notificationView.createRemovalAnimation(duration);
+            removeNotification.play(reduceNotificationViewHeight(
+                    notificationView.getHeight() + spacing, duration, notificationView));
             final View removeMarginView = mIsAboveIcon ? getItemViewAt(getItemCount() - 2)
                     : notificationView;
             if (removeMarginView != null) {
@@ -602,7 +601,6 @@
                 });
                 removeNotification.play(removeMargin);
             }
-            removeNotification.play(reduceHeight);
             Animator fade = ObjectAnimator.ofFloat(notificationView, ALPHA, 0)
                     .setDuration(duration);
             fade.addListener(new AnimatorListenerAdapter() {
@@ -636,16 +634,43 @@
                 mArrow, new PropertyListBuilder().scale(scale).build());
     }
     /**
-     * Animates the translationY of this container if it is open above the icon.
-     * If it is below the icon, the container already shifts up when the height
-     * of a child (e.g. NotificationView) changes, so the translation isn't necessary.
+     * Animates the height of the notification item and the translationY of other items accordingly.
      */
-    public @Nullable Animator animateTranslationYBy(int translationY, int duration) {
-        if (mIsAboveIcon) {
-            return ObjectAnimator.ofFloat(this, TRANSLATION_Y, getTranslationY() + translationY)
-                    .setDuration(duration);
+    public Animator reduceNotificationViewHeight(int heightToRemove, int duration,
+            NotificationItemView notificationItem) {
+        final int translateYBy = mIsAboveIcon ? heightToRemove : -heightToRemove;
+        AnimatorSet animatorSet = LauncherAnimUtils.createAnimatorSet();
+        animatorSet.play(notificationItem.animateHeightRemoval(heightToRemove));
+        PropertyResetListener<View, Float> resetTranslationYListener
+                = new PropertyResetListener<>(TRANSLATION_Y, 0f);
+        for (int i = 0; i < getItemCount(); i++) {
+            final PopupItemView itemView = getItemViewAt(i);
+            if (!mIsAboveIcon && itemView == notificationItem) {
+                // The notification view is already in the right place when container is below icon.
+                continue;
+            }
+            ValueAnimator translateItem = ObjectAnimator.ofFloat(itemView, TRANSLATION_Y,
+                    itemView.getTranslationY() + translateYBy).setDuration(duration);
+            translateItem.addListener(resetTranslationYListener);
+            animatorSet.play(translateItem);
         }
-        return null;
+        if (mIsAboveIcon) {
+            // All the items, including the notification item, translated down, but the
+            // container itself did not. This means the items would jump back to their
+            // original translation unless we update the container's translationY here.
+            animatorSet.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    setTranslationY(getTranslationY() + translateYBy);
+                }
+            });
+        }
+        return animatorSet;
+    }
+
+    public Animator reduceNotificationViewHeight(int heightToRemove, int duration) {
+        return reduceNotificationViewHeight(heightToRemove, duration,
+                (NotificationItemView) findViewById(R.id.notification_view));
     }
 
     @Override
diff --git a/src/com/android/launcher3/popup/PopupItemView.java b/src/com/android/launcher3/popup/PopupItemView.java
index 6af6e7d..b3d7155 100644
--- a/src/com/android/launcher3/popup/PopupItemView.java
+++ b/src/com/android/launcher3/popup/PopupItemView.java
@@ -42,7 +42,7 @@
 
     protected static final Point sTempPoint = new Point();
 
-    private final Rect mPillRect;
+    protected final Rect mPillRect;
     private float mOpenAnimationProgress;
 
     protected View mIconView;
@@ -147,6 +147,10 @@
         return sTempPoint;
     }
 
+    protected float getBackgroundRadius() {
+        return getResources().getDimensionPixelSize(R.dimen.bg_pill_radius);
+    }
+
     /**
      * Extension of {@link PillRevealOutlineProvider} which scales the icon based on the height.
      */
@@ -161,10 +165,9 @@
         private final boolean mPivotLeft;
         private final float mTranslateX;
 
-        public ZoomRevealOutlineProvider(int x, int y, Rect pillRect,
-                View translateView, View zoomView, boolean isContainerAboveIcon, boolean pivotLeft) {
-            super(x, y, pillRect, zoomView.getResources().getDimensionPixelSize(
-                    R.dimen.bg_pill_radius));
+        public ZoomRevealOutlineProvider(int x, int y, Rect pillRect, PopupItemView translateView,
+                View zoomView, boolean isContainerAboveIcon, boolean pivotLeft) {
+            super(x, y, pillRect, translateView.getBackgroundRadius());
             mTranslateView = translateView;
             mZoomView = zoomView;
             mFullHeight = pillRect.height();