Merge "Add shadow to Adaptive Icons Bug: 35920618" into ub-launcher3-master
diff --git a/protos/launcher_dump.proto b/protos/launcher_dump.proto
new file mode 100644
index 0000000..dc8fbda
--- /dev/null
+++ b/protos/launcher_dump.proto
@@ -0,0 +1,75 @@
+/*
+ * 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.
+ */
+syntax = "proto2";
+
+option java_package = "com.android.launcher3.model";
+option java_outer_classname = "LauncherDumpProto";
+
+package model;
+
+message DumpTarget {
+  enum Type {
+    NONE = 0;
+    ITEM = 1;
+    CONTAINER = 2;
+  }
+
+  optional Type type = 1;
+  optional int32 page_id = 2;
+  optional int32 grid_x = 3;
+  optional int32 grid_y = 4;
+
+  // For container types only
+  optional ContainerType container_type = 5;
+
+  // For item types only
+  optional ItemType item_type = 6;
+
+  optional string package_name = 7; // All ItemTypes except UNKNOWN type
+  optional string component = 8;   // All ItemTypes except UNKNOWN type
+  optional string item_id = 9; // For Pinned Shortcuts and appWidgetId
+
+  optional int32 span_x = 10 [default = 1];// Used for ItemType.WIDGET
+  optional int32 span_y = 11 [default = 1];// Used for ItemType.WIDGET
+  optional UserType user_type = 12;
+}
+
+// Used to define what type of item a Target would represent.
+enum ItemType {
+  UNKNOWN_ITEMTYPE = 0;  // Launcher specific items
+  APP_ICON = 1; // Regular app icons
+  WIDGET = 2;   // Elements from AppWidgetManager
+  SHORTCUT = 3; // ShortcutManager
+}
+
+// Used to define what type of container a Target would represent.
+enum ContainerType {
+  UNKNOWN_CONTAINERTYPE = 0;
+  WORKSPACE = 1;
+  HOTSEAT = 2;
+  FOLDER = 3;
+}
+
+// Used to define what type of control a Target would represent.
+enum UserType {
+  DEFAULT = 0;
+  WORK = 1;
+}
+
+// Main message;
+message LauncherImpression {
+  repeated DumpTarget targets = 1;
+}
diff --git a/res/drawable/bg_pill_focused.xml b/res/drawable/bg_pill_focused.xml
deleted file mode 100644
index 54075d9..0000000
--- a/res/drawable/bg_pill_focused.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 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.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_focused="true">
-        <shape android:shape="rectangle">
-            <stroke android:color="?android:attr/colorControlActivated" android:width="2dp" />
-            <corners android:radius="@dimen/bg_pill_radius" />
-        </shape>
-    </item>
-</selector>
\ No newline at end of file
diff --git a/res/drawable/bg_white_pill.xml b/res/drawable/bg_white_pill.xml
deleted file mode 100644
index f92f739..0000000
--- a/res/drawable/bg_white_pill.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 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:radius="@dimen/bg_pill_radius" />
-</shape>
\ No newline at end of file
diff --git a/res/drawable/horizontal_ellipsis.xml b/res/drawable/horizontal_ellipsis.xml
new file mode 100644
index 0000000..ad08529
--- /dev/null
+++ b/res/drawable/horizontal_ellipsis.xml
@@ -0,0 +1,27 @@
+<?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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="@dimen/horizontal_ellipsis_size"
+        android:height="@dimen/horizontal_ellipsis_size"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/textColorSecondary" >
+
+    <path
+        android:pathData="M6 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm12 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-6 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"
+        android:fillColor="@android:color/white" />
+</vector>
\ No newline at end of file
diff --git a/res/layout/deep_shortcut.xml b/res/layout/deep_shortcut.xml
index 6c1d4da..b2ed709 100644
--- a/res/layout/deep_shortcut.xml
+++ b/res/layout/deep_shortcut.xml
@@ -17,18 +17,16 @@
 <com.android.launcher3.shortcuts.DeepShortcutView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:launcher="http://schemas.android.com/apk/res-auto"
-    android:layout_width="@dimen/bg_pill_width"
-    android:layout_height="@dimen/bg_pill_height"
-    android:elevation="@dimen/deep_shortcuts_elevation"
-    android:background="@drawable/bg_white_pill" >
+    android:layout_width="@dimen/bg_popup_item_width"
+    android:layout_height="@dimen/bg_popup_item_height" >
 
     <com.android.launcher3.shortcuts.DeepShortcutTextView
         style="@style/BaseIcon"
         android:id="@+id/deep_shortcut"
-        android:background="@drawable/bg_pill_focused"
+        android:background="?android:attr/selectableItemBackground"
         android:gravity="start|center_vertical"
         android:textAlignment="viewStart"
-        android:paddingStart="@dimen/bg_pill_height"
+        android:paddingStart="@dimen/bg_popup_item_height"
         android:paddingEnd="@dimen/deep_shortcut_padding_end"
         android:drawableEnd="@drawable/deep_shortcuts_drag_handle"
         android:drawablePadding="@dimen/deep_shortcut_drawable_padding"
@@ -40,10 +38,18 @@
         android:elevation="@dimen/deep_shortcuts_elevation" />
 
     <View
-        android:id="@+id/popup_item_icon"
+        android:id="@+id/icon"
         android:layout_width="@dimen/deep_shortcut_icon_size"
         android:layout_height="@dimen/deep_shortcut_icon_size"
         android:layout_margin="@dimen/deep_shortcut_padding_start"
         android:layout_gravity="start" />
 
+    <View
+        android:id="@+id/divider"
+        android:layout_width="@dimen/deep_shortcuts_divider_width"
+        android:layout_height="@dimen/popup_item_divider_height"
+        android:layout_gravity="end|bottom"
+        android:visibility="gone"
+        android:background="?android:attr/listDivider" />
+
 </com.android.launcher3.shortcuts.DeepShortcutView>
diff --git a/res/layout/notification.xml b/res/layout/notification.xml
index 48c7b48..6922ad9 100644
--- a/res/layout/notification.xml
+++ b/res/layout/notification.xml
@@ -17,10 +17,11 @@
 <com.android.launcher3.notification.NotificationItemView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/notification_view"
-    android:layout_width="@dimen/bg_pill_width"
+    android:layout_width="@dimen/bg_popup_item_width"
     android:layout_height="wrap_content"
     android:elevation="@dimen/deep_shortcuts_elevation"
-    android:background="@drawable/bg_white_round_rect">
+    android:background="@drawable/bg_white_round_rect"
+    android:backgroundTint="@color/notification_color_beneath">
 
     <RelativeLayout
         android:layout_width="match_parent"
@@ -28,16 +29,49 @@
         android:orientation="vertical"
         android:clipChildren="false">
 
+        <com.android.launcher3.notification.NotificationHeaderView
+            android:id="@+id/header"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/notification_header_height"
+            android:orientation="horizontal"
+            android:paddingStart="@dimen/notification_padding"
+            android:background="@color/notification_header_background_color"
+            android:elevation="@dimen/notification_elevation">
+            <TextView
+                android:id="@+id/notification_count"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:gravity="start|center_vertical"
+                android:paddingEnd="@dimen/notification_header_padding_after_count"
+                android:textSize="@dimen/notification_main_text_size"
+                android:textColor="?android:attr/textColorPrimary" />
+            <TextView
+                android:id="@+id/notification_text"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:gravity="start|center_vertical"
+                android:textSize="@dimen/notification_main_text_size"
+                android:textColor="?android:attr/textColorSecondary" />
+        </com.android.launcher3.notification.NotificationHeaderView>
+
         <include layout="@layout/notification_main"
             android:id="@+id/main_view"
             android:layout_width="match_parent"
-            android:layout_height="@dimen/notification_main_height" />
+            android:layout_height="@dimen/notification_main_height"
+            android:layout_below="@id/header" />
+
+        <View
+            android:id="@+id/divider"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/popup_item_divider_height"
+            android:background="@color/divider_color"
+            android:layout_below="@id/main_view"/>
 
         <include layout="@layout/notification_footer"
             android:id="@+id/footer"
             android:layout_width="match_parent"
             android:layout_height="@dimen/notification_footer_height"
-            android:layout_below="@id/main_view" />
+            android:layout_below="@id/divider" />
 
     </RelativeLayout>
 
diff --git a/res/layout/notification_footer.xml b/res/layout/notification_footer.xml
index c025819..f1f5724 100644
--- a/res/layout/notification_footer.xml
+++ b/res/layout/notification_footer.xml
@@ -18,25 +18,29 @@
 <com.android.launcher3.notification.NotificationFooterLayout
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:orientation="vertical"
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:elevation="@dimen/notification_elevation"
-    android:clipChildren="false" >
-
-    <View
-        android:id="@+id/divider"
-        android:layout_width="match_parent"
-        android:layout_height="@dimen/notification_divider_height"/>
+    android:clipChildren="false"
+    android:layout_gravity="center_vertical"
+    android:background="@color/notification_background_color">
 
     <LinearLayout
         android:id="@+id/icon_row"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:orientation="horizontal"
-        android:gravity="end"
+        android:gravity="end|center_vertical"
         android:padding="@dimen/notification_footer_icon_row_padding"
         android:clipToPadding="false"
         android:clipChildren="false"/>
 
+    <View
+        android:id="@+id/overflow"
+        android:layout_width="@dimen/horizontal_ellipsis_size"
+        android:layout_height="@dimen/horizontal_ellipsis_size"
+        android:background="@drawable/horizontal_ellipsis"
+        android:layout_marginStart="@dimen/horizontal_ellipsis_offset"
+        android:layout_gravity="start|center_vertical" />
+
 </com.android.launcher3.notification.NotificationFooterLayout>
 
diff --git a/res/layout/notification_main.xml b/res/layout/notification_main.xml
index d036fe5..9c5847f 100644
--- a/res/layout/notification_main.xml
+++ b/res/layout/notification_main.xml
@@ -19,43 +19,46 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:orientation="horizontal"
     android:focusable="true"
-    android:padding="@dimen/notification_padding"
     android:elevation="@dimen/notification_elevation" >
 
     <LinearLayout
+        android:id="@+id/text_and_background"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:orientation="vertical"
-        android:layout_weight="1"
-        android:gravity="center_vertical">
+        android:gravity="center_vertical"
+        android:background="@color/notification_background_color"
+        android:paddingStart="@dimen/notification_padding"
+        android:paddingEnd="@dimen/notification_main_text_padding_end">
         <TextView
             android:id="@+id/title"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
             android:textAlignment="viewStart"
             android:fontFamily="sans-serif"
-            android:textSize="14sp"
-            android:textColor="?android:attr/textColorSecondary"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content" />
+            android:textSize="@dimen/notification_main_text_size"
+            android:textColor="?android:attr/textColorPrimary"
+            android:lines="1"
+            android:ellipsize="end" />
 
         <TextView
             android:id="@+id/text"
-            android:paddingEnd="4dp"
-            android:textSize="12sp"
-            android:textAlignment="viewStart"
-            android:fontFamily="sans-serif"
-            android:textColor="?android:attr/textColorTertiary"
             android:layout_width="match_parent"
-            android:layout_height="wrap_content" />
+            android:layout_height="wrap_content"
+            android:fontFamily="sans-serif"
+            android:textSize="@dimen/notification_main_text_size"
+            android:textColor="?android:attr/textColorSecondary"
+            android:lines="1"
+            android:ellipsize="end" />
     </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" />
+        android:layout_marginEnd="@dimen/notification_padding"
+        android:layout_gravity="center_vertical|end" />
 
 </com.android.launcher3.notification.NotificationMainView>
 
diff --git a/res/layout/shortcuts_item.xml b/res/layout/shortcuts_item.xml
new file mode 100644
index 0000000..8b20bcb
--- /dev/null
+++ b/res/layout/shortcuts_item.xml
@@ -0,0 +1,32 @@
+<?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.
+-->
+
+<com.android.launcher3.shortcuts.ShortcutsItemView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/shortcuts_view"
+    android:layout_width="@dimen/bg_popup_item_width"
+    android:layout_height="wrap_content"
+    android:elevation="@dimen/deep_shortcuts_elevation"
+    android:background="@drawable/bg_white_round_rect">
+
+    <LinearLayout
+        android:id="@+id/deep_shortcuts"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical">
+    </LinearLayout>
+
+</com.android.launcher3.shortcuts.ShortcutsItemView>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index a02df16..68f0092 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -36,4 +36,8 @@
     <color name="spring_loaded_highlighted_panel_border_color">#FFF</color>
 
     <color name="notification_icon_default_color">#757575</color> <!-- Gray 600 -->
+    <color name="notification_header_background_color">#F5F5F5</color> <!-- Gray 100 -->
+    <color name="notification_background_color">#FFF</color>
+    <color name="notification_color_beneath">#E0E0E0</color> <!-- Gray 300 -->
+    <color name="divider_color">@color/notification_color_beneath</color>
 </resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 177e08e..132ae07 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -150,10 +150,9 @@
 
 <!-- Deep shortcuts -->
     <dimen name="deep_shortcuts_elevation">9dp</dimen>
-    <dimen name="bg_pill_width">208dp</dimen>
-    <dimen name="bg_pill_height">48dp</dimen>
-    <dimen name="bg_pill_radius">24dp</dimen>
-    <dimen name="deep_shortcuts_spacing">4dp</dimen>
+    <dimen name="bg_popup_item_width">208dp</dimen>
+    <dimen name="bg_popup_item_height">48dp</dimen>
+    <dimen name="popup_items_spacing">4dp</dimen>
     <dimen name="pre_drag_view_scale">6dp</dimen>
     <!-- an icon with shortcuts must be dragged this far before the container is removed. -->
     <dimen name="deep_shortcuts_start_drag_threshold">16dp</dimen>
@@ -171,24 +170,35 @@
          deep_shortcut_padding_end + deep_shortcut_drag_handle_size / 2 - deep_shortcuts_arrow_width / 2
          also happens to equal 19dp-->
     <dimen name="deep_shortcuts_arrow_horizontal_offset">19dp</dimen>
+    <!-- popup_item_width - icon_size - padding_start - drawable_padding -->
+    <dimen name="deep_shortcuts_divider_width">158dp</dimen>
 
 <!-- Icon badges (with notification counts) -->
     <dimen name="badge_size">24dp</dimen>
     <dimen name="badge_text_size">12dp</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>
+    <dimen name="notification_icon_size">24dp</dimen>
+    <dimen name="notification_footer_icon_size">18dp</dimen>
 
 <!-- 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>
+    <!-- notification_padding + (icon_size - footer_icon_size) / 2 -->
+    <dimen name="notification_footer_icon_row_padding">15dp</dimen>
+    <dimen name="notification_header_padding_after_count">8dp</dimen>
+    <dimen name="notification_header_height">32dp</dimen>
     <dimen name="notification_main_height">60dp</dimen>
-    <dimen name="notification_footer_height">@dimen/bg_pill_height</dimen>
+    <dimen name="notification_footer_height">@dimen/bg_popup_item_height</dimen>
+    <dimen name="notification_header_text_size">12dp</dimen>
+    <dimen name="notification_main_text_size">14dp</dimen>
+    <!-- notification_icon_size + notification+padding + padding we want between icon and text -->
+    <dimen name="notification_main_text_padding_end">40dp</dimen>
     <dimen name="notification_elevation">2dp</dimen>
-    <dimen name="notification_divider_height">0.5dp</dimen>
+    <dimen name="horizontal_ellipsis_size">18dp</dimen>
+    <!-- arrow_horizontal_offset - (ellipsis_size - arrow_width) / 2 -->
+    <dimen name="horizontal_ellipsis_offset">15dp</dimen>
+    <dimen name="popup_item_divider_height">0.5dp</dimen>
     <dimen name="swipe_helper_falsing_threshold">70dp</dimen>
 
 <!-- Other -->
diff --git a/res/values/strings.xml b/res/values/strings.xml
index c1280c7..58bfb49 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -72,7 +72,10 @@
          The text must fit in the size of a small icon [CHAR_LIMIT=3] -->
     <string name="deep_notifications_overflow" translatable="false">+%1$d</string>
     <!-- Text to display as the header above notifications. [CHAR_LIMIT=30] -->
-    <string name="notifications_header" translatable="false">Notifications</string>
+    <plurals name="notifications_header" translatable="false">
+        <item quantity="one">Notification</item>
+        <item quantity="other">Notifications</item>
+    </plurals>
 
     <!-- Drag and drop -->
     <skip />
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index 52a83dc..bd12686 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -95,7 +95,7 @@
         return null;
     }
 
-    protected static void closeOpenContainer(Launcher launcher, @FloatingViewType int type) {
+    public static void closeOpenContainer(Launcher launcher, @FloatingViewType int type) {
         AbstractFloatingView view = getOpenView(launcher, type);
         if (view != null) {
             view.close(true);
diff --git a/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/AllAppsList.java
index 9cce9b1..5b42cad 100644
--- a/src/com/android/launcher3/AllAppsList.java
+++ b/src/com/android/launcher3/AllAppsList.java
@@ -208,16 +208,6 @@
     }
 
     /**
-     * Query the launcher apps service for whether the supplied package has
-     * MAIN/LAUNCHER activities in the supplied package.
-     */
-    static boolean packageHasActivities(Context context, String packageName,
-            UserHandle user) {
-        final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
-        return launcherApps.getActivityList(packageName, user).size() > 0;
-    }
-
-    /**
      * Returns whether <em>apps</em> contains <em>component</em>.
      */
     private static boolean findActivity(ArrayList<AppInfo> apps, ComponentName component,
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 107d700..3b8fb0a 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -40,11 +40,12 @@
 import com.android.launcher3.badge.BadgeInfo;
 import com.android.launcher3.badge.BadgeRenderer;
 import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.folder.FolderIconPreviewVerifier;
 import com.android.launcher3.graphics.DrawableFactory;
 import com.android.launcher3.graphics.HolographicOutlineHelper;
-import com.android.launcher3.graphics.IconPalette;
 import com.android.launcher3.graphics.PreloadIconDrawable;
 import com.android.launcher3.model.PackageItemInfo;
+import com.android.launcher3.popup.PopupContainerWithArrow;
 
 import java.text.NumberFormat;
 
@@ -502,15 +503,14 @@
         if (mIcon instanceof FastBitmapDrawable) {
             BadgeInfo badgeInfo = mLauncher.getPopupDataProvider().getBadgeInfoForItem(itemInfo);
             BadgeRenderer badgeRenderer = mLauncher.getDeviceProfile().mBadgeRenderer;
+            PopupContainerWithArrow popup = PopupContainerWithArrow.getOpen(mLauncher);
+            if (popup != null) {
+                popup.updateNotificationHeader(badgeInfo, itemInfo);
+            }
             ((FastBitmapDrawable) mIcon).applyIconBadge(badgeInfo, badgeRenderer, animate);
         }
     }
 
-    public IconPalette getIconPalette() {
-        return mIcon instanceof FastBitmapDrawable ? ((FastBitmapDrawable) mIcon).getIconPalette()
-                : null;
-    }
-
     /**
      * Sets the icon for this view based on the layout direction.
      */
@@ -548,7 +548,9 @@
                 applyFromApplicationInfo((AppInfo) info);
             } else if (info instanceof ShortcutInfo) {
                 applyFromShortcutInfo((ShortcutInfo) info);
-                if ((info.rank < FolderIcon.NUM_ITEMS_IN_PREVIEW) && (info.container >= 0)) {
+                FolderIconPreviewVerifier verifier =
+                        new FolderIconPreviewVerifier(mLauncher.getDeviceProfile().inv);
+                if (verifier.isItemInPreview(info.rank) && (info.container >= 0)) {
                     View folderIcon =
                             mLauncher.getWorkspace().getHomescreenIconByItemId(info.container);
                     if (folderIcon != null) {
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
index 8d69fe3..8a477d8 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -42,6 +42,7 @@
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.Thunk;
 
 /**
@@ -142,8 +143,8 @@
             mCurrentFilter = new ColorMatrix();
         }
 
-        DragView.setColorScale(getTextColor(), mSrcFilter);
-        DragView.setColorScale(targetColor, mDstFilter);
+        Themes.setColorScaleOnMatrix(getTextColor(), mSrcFilter);
+        Themes.setColorScaleOnMatrix(targetColor, mDstFilter);
         ValueAnimator anim1 = ValueAnimator.ofObject(
                 new FloatArrayEvaluator(mCurrentFilter.getArray()),
                 mSrcFilter.getArray(), mDstFilter.getArray());
diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java
index be3ba90..5a44f75 100644
--- a/src/com/android/launcher3/FastBitmapDrawable.java
+++ b/src/com/android/launcher3/FastBitmapDrawable.java
@@ -167,7 +167,7 @@
         }
     }
 
-    public IconPalette getIconPalette() {
+    protected IconPalette getIconPalette() {
         if (mIconPalette == null) {
             mIconPalette = IconPalette.fromDominantColor(Utilities
                     .findDominantColorByHue(mBitmap, 20));
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 845fdbf..384f202 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -128,6 +128,11 @@
     // our monitoring of the package manager provides all updates and we never
     // need to do a requery. This is only ever touched from the loader thread.
     private boolean mModelLoaded;
+    public boolean isModelLoaded() {
+        synchronized (mLock) {
+            return mModelLoaded && mLoaderTask == null;
+        }
+    }
 
     /**
      * Set of runnables to be called on the background thread after the workspace binding
@@ -884,6 +889,8 @@
                     Intent intent;
                     String targetPkg;
 
+                    FolderIconPreviewVerifier verifier =
+                            new FolderIconPreviewVerifier(mApp.getInvariantDeviceProfile());
                     while (!mStopped && c.moveToNext()) {
                         try {
                             if (c.user == null) {
@@ -982,7 +989,7 @@
                                             c.markDeleted("Unrestored app removed: " + targetPkg);
                                             continue;
                                         }
-                                    } else if (pmHelper.isAppOnSdcard(targetPkg)) {
+                                    } else if (pmHelper.isAppOnSdcard(targetPkg, c.user)) {
                                         // Package is present but not available.
                                         disabledState |= ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
                                         // Add the icon on the workspace anyway.
@@ -1008,7 +1015,7 @@
                                 }
 
                                 boolean useLowResIcon = !c.isOnWorkspaceOrHotseat() &&
-                                        c.getInt(rankIndex) >= FolderIcon.NUM_ITEMS_IN_PREVIEW;
+                                        !verifier.isItemInPreview(c.getInt(rankIndex));
 
                                 if (c.restoreFlag != 0) {
                                     // Already verified above that user is same as default user
@@ -1033,8 +1040,7 @@
                                         info.iconBitmap = LauncherIcons
                                                 .createShortcutIcon(pinnedShortcut, context);
                                         if (pmHelper.isAppSuspended(
-                                                info.getTargetComponent().getPackageName(),
-                                                info.user)) {
+                                                pinnedShortcut.getPackage(), info.user)) {
                                             info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SUSPENDED;
                                         }
                                         intent = info.intent;
@@ -1047,7 +1053,7 @@
                                     info = c.loadSimpleShortcut();
 
                                     // Shortcuts are only available on the primary profile
-                                    if (pmHelper.isAppSuspended(targetPkg)) {
+                                    if (pmHelper.isAppSuspended(targetPkg, c.user)) {
                                         disabledState |= ShortcutInfo.FLAG_DISABLED_SUSPENDED;
                                     }
 
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index e250b3f..e6d7181 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -47,6 +47,7 @@
 import android.os.UserManager;
 import android.text.TextUtils;
 import android.util.Log;
+import android.view.ViewGroup;
 
 import com.android.launcher3.AutoInstallsLayout.LayoutParserCallback;
 import com.android.launcher3.LauncherSettings.Favorites;
@@ -63,6 +64,8 @@
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.Thunk;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -85,6 +88,18 @@
 
     protected DatabaseHelper mOpenHelper;
 
+    /**
+     * $ adb shell dumpsys activity provider com.android.launcher3
+     */
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
+        if (appState == null || !appState.getModel().isModelLoaded()) {
+            return;
+        }
+        appState.getModel().dumpState("", fd, writer, args);
+    }
+
     @Override
     public boolean onCreate() {
         if (ProviderConfig.IS_DOGFOOD_BUILD) {
diff --git a/src/com/android/launcher3/UninstallDropTarget.java b/src/com/android/launcher3/UninstallDropTarget.java
index e68a5b0..0fac29f 100644
--- a/src/com/android/launcher3/UninstallDropTarget.java
+++ b/src/com/android/launcher3/UninstallDropTarget.java
@@ -5,6 +5,7 @@
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.LauncherActivityInfo;
+import android.content.pm.PackageManager;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.UserHandle;
@@ -139,9 +140,10 @@
             final Runnable checkIfUninstallWasSuccess = new Runnable() {
                 @Override
                 public void run() {
-                    String packageName = cn.getPackageName();
-                    boolean uninstallSuccessful = !AllAppsList.packageHasActivities(
-                            launcher, packageName, user);
+                    // We use MATCH_UNINSTALLED_PACKAGES as the app can be on SD card as well.
+                    boolean uninstallSuccessful = LauncherAppsCompat.getInstance(launcher)
+                            .getApplicationInfo(cn.getPackageName(),
+                                    PackageManager.MATCH_UNINSTALLED_PACKAGES, user) == null;
                     callback.onDragObjectRemoved(uninstallSuccessful);
                 }
             };
diff --git a/src/com/android/launcher3/badge/BadgeRenderer.java b/src/com/android/launcher3/badge/BadgeRenderer.java
index 864a65d..5896928 100644
--- a/src/com/android/launcher3/badge/BadgeRenderer.java
+++ b/src/com/android/launcher3/badge/BadgeRenderer.java
@@ -20,14 +20,15 @@
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.Rect;
-import android.graphics.RectF;
 import android.graphics.Shader;
 import android.support.annotation.Nullable;
 
 import com.android.launcher3.R;
 import com.android.launcher3.graphics.IconPalette;
+import com.android.launcher3.graphics.ShadowGenerator;
 
 /**
  * Contains parameters necessary to draw a badge for an icon (e.g. the size of the badge).
@@ -40,10 +41,12 @@
     private final int mTextHeight;
     private final IconDrawer mLargeIconDrawer;
     private final IconDrawer mSmallIconDrawer;
-    private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
     private final Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+    private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG
+            | Paint.FILTER_BITMAP_FLAG);
+    private final Bitmap mBackgroundWithShadow;
 
-    public BadgeRenderer(Context context) {
+    public BadgeRenderer(final Context context) {
         mContext = context;
         Resources res = context.getResources();
         mSize = res.getDimensionPixelSize(R.dimen.badge_size);
@@ -56,6 +59,8 @@
         Rect tempTextHeight = new Rect();
         mTextPaint.getTextBounds("0", 0, 1, tempTextHeight);
         mTextHeight = tempTextHeight.height();
+
+        mBackgroundWithShadow = ShadowGenerator.createCircleWithShadow(Color.WHITE, mSize);
     }
 
     /**
@@ -68,13 +73,15 @@
      */
     public void draw(Canvas canvas, IconPalette palette, @Nullable BadgeInfo badgeInfo,
             Rect iconBounds, float badgeScale) {
-        mBackgroundPaint.setColor(palette.backgroundColor);
         mTextPaint.setColor(palette.textColor);
         canvas.save(Canvas.MATRIX_SAVE_FLAG);
         // We draw the badge relative to its center.
         canvas.translate(iconBounds.right - mSize / 2, iconBounds.top + mSize / 2);
         canvas.scale(badgeScale, badgeScale);
-        canvas.drawCircle(0, 0, mSize / 2, mBackgroundPaint);
+        mBackgroundPaint.setColorFilter(palette.backgroundColorMatrixFilter);
+        int backgroundSize = mBackgroundWithShadow.getHeight(); // Same as width.
+        canvas.drawBitmap(mBackgroundWithShadow, -backgroundSize / 2, -backgroundSize / 2,
+                mBackgroundPaint);
         IconDrawer iconDrawer = badgeInfo != null && badgeInfo.isIconLarge()
                 ? mLargeIconDrawer : mSmallIconDrawer;
         Shader icon = badgeInfo == null ? null : badgeInfo.getNotificationIconForBadge(
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompat.java b/src/com/android/launcher3/compat/LauncherAppsCompat.java
index 44a3686..2eb5b02 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompat.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompat.java
@@ -73,7 +73,8 @@
             UserHandle user);
     public abstract void startActivityForProfile(ComponentName component, UserHandle user,
             Rect sourceBounds, Bundle opts);
-    public abstract ApplicationInfo getApplicationInfo(String packageName, UserHandle user);
+    public abstract ApplicationInfo getApplicationInfo(
+            String packageName, int flags, UserHandle user);
     public abstract void showAppDetailsForProfile(ComponentName component, UserHandle user);
     public abstract void addOnAppsChangedCallback(OnAppsChangedCallbackCompat listener);
     public abstract void removeOnAppsChangedCallback(OnAppsChangedCallbackCompat listener);
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVL.java b/src/com/android/launcher3/compat/LauncherAppsCompatVL.java
index 776f593..4590173 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompatVL.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompatVL.java
@@ -27,6 +27,7 @@
 import android.content.pm.ShortcutInfo;
 import android.graphics.Rect;
 import android.os.Bundle;
+import android.os.Process;
 import android.os.UserHandle;
 
 import com.android.launcher3.compat.ShortcutConfigActivityInfo.ShortcutConfigActivityInfoVL;
@@ -66,9 +67,29 @@
     }
 
     @Override
-    public ApplicationInfo getApplicationInfo(String packageName, UserHandle user) {
-        List<LauncherActivityInfo> activityList = mLauncherApps.getActivityList(packageName, user);
-        return activityList.size() > 0 ? activityList.get(0).getApplicationInfo() : null;
+    public ApplicationInfo getApplicationInfo(String packageName, int flags, UserHandle user) {
+        final boolean isPrimaryUser = Process.myUserHandle().equals(user);
+        if (!isPrimaryUser && (flags == 0)) {
+            // We are looking for an installed app on a secondary profile. Prior to O, the only
+            // entry point for work profiles is through the LauncherActivity.
+            List<LauncherActivityInfo> activityList =
+                    mLauncherApps.getActivityList(packageName, user);
+            return activityList.size() > 0 ? activityList.get(0).getApplicationInfo() : null;
+        }
+        try {
+            ApplicationInfo info =
+                    mContext.getPackageManager().getApplicationInfo(packageName, flags);
+            // There is no way to check if the app is installed for managed profile. But for
+            // primary profile, we can still have this check.
+            if (isPrimaryUser && ((info.flags & ApplicationInfo.FLAG_INSTALLED) == 0)
+                    || !info.enabled) {
+                return null;
+            }
+            return info;
+        } catch (PackageManager.NameNotFoundException e) {
+            // Package not found
+            return null;
+        }
     }
 
     @Override
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVO.java b/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
index 377907a..2743379 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
@@ -35,8 +35,10 @@
     }
 
     @Override
-    public ApplicationInfo getApplicationInfo(String packageName, UserHandle user) {
-        return mLauncherApps.getApplicationInfo(packageName, 0, user);
+    public ApplicationInfo getApplicationInfo(String packageName, int flags, UserHandle user) {
+        ApplicationInfo info = mLauncherApps.getApplicationInfo(packageName, flags, user);
+        return info == null || (info.flags & ApplicationInfo.FLAG_INSTALLED) == 0 || !info.enabled
+                ? null : info;
     }
 
     @Override
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index e4c9be4..7806c98 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -24,7 +24,6 @@
 import android.annotation.SuppressLint;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
-import android.graphics.Color;
 import android.graphics.ColorMatrix;
 import android.graphics.ColorMatrixColorFilter;
 import android.graphics.Paint;
@@ -36,6 +35,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.R;
+import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.Thunk;
 
 import java.util.Arrays;
@@ -259,7 +259,7 @@
             m1.setSaturation(0);
 
             ColorMatrix m2 = new ColorMatrix();
-            setColorScale(color, m2);
+            Themes.setColorScaleOnMatrix(color, m2);
             m1.postConcat(m2);
 
             animateFilterTo(m1.getArray());
@@ -384,11 +384,6 @@
         }
     }
 
-    public static void setColorScale(int color, ColorMatrix target) {
-        target.setScale(Color.red(color) / 255f, Color.green(color) / 255f,
-                Color.blue(color) / 255f, Color.alpha(color) / 255f);
-    }
-
     public int getBlurSizeOutline() {
         return mBlurSizeOutline;
     }
diff --git a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
index 194a62f..503c2ec 100644
--- a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
+++ b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
@@ -121,6 +121,11 @@
     }
 
     @Override
+    public float getIconSize() {
+        return mIconSize;
+    }
+
+    @Override
     public int maxNumItems() {
         return MAX_NUM_ITEMS_IN_PREVIEW;
     }
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 3d2ffb4..763ab96 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -66,6 +66,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace.ItemOperator;
 import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
+import com.android.launcher3.anim.AnimationLayerSet;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.config.ProviderConfig;
 import com.android.launcher3.dragndrop.DragController;
@@ -510,6 +511,58 @@
         mState = STATE_SMALL;
     }
 
+    private AnimatorSet getOpeningAnimatorSet() {
+        AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
+
+        int width = getFolderWidth();
+        int height = getFolderHeight();
+
+        float transX = - 0.075f * (width / 2 - getPivotX());
+        float transY = - 0.075f * (height / 2 - getPivotY());
+        setTranslationX(transX);
+        setTranslationY(transY);
+        PropertyValuesHolder tx = PropertyValuesHolder.ofFloat(TRANSLATION_X, transX, 0);
+        PropertyValuesHolder ty = PropertyValuesHolder.ofFloat(TRANSLATION_Y, transY, 0);
+
+        Animator drift = ObjectAnimator.ofPropertyValuesHolder(this, tx, ty);
+        drift.setDuration(mMaterialExpandDuration);
+        drift.setStartDelay(mMaterialExpandStagger);
+        drift.setInterpolator(new LogDecelerateInterpolator(100, 0));
+
+        int rx = (int) Math.max(Math.max(width - getPivotX(), 0), getPivotX());
+        int ry = (int) Math.max(Math.max(height - getPivotY(), 0), getPivotY());
+        float radius = (float) Math.hypot(rx, ry);
+
+        Animator reveal = new CircleRevealOutlineProvider((int) getPivotX(),
+                (int) getPivotY(), 0, radius).createRevealAnimator(this);
+        reveal.setDuration(mMaterialExpandDuration);
+        reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
+
+        mContent.setAlpha(0f);
+        Animator iconsAlpha = ObjectAnimator.ofFloat(mContent, "alpha", 0f, 1f);
+        iconsAlpha.setDuration(mMaterialExpandDuration);
+        iconsAlpha.setStartDelay(mMaterialExpandStagger);
+        iconsAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
+
+        mFooter.setAlpha(0f);
+        Animator textAlpha = ObjectAnimator.ofFloat(mFooter, "alpha", 0f, 1f);
+        textAlpha.setDuration(mMaterialExpandDuration);
+        textAlpha.setStartDelay(mMaterialExpandStagger);
+        textAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
+
+        anim.play(drift);
+        anim.play(iconsAlpha);
+        anim.play(textAlpha);
+        anim.play(reveal);
+
+        AnimationLayerSet layerSet = new AnimationLayerSet();
+        layerSet.addView(mContent);
+        layerSet.addView(mFooter);
+        anim.addListener(layerSet);
+
+        return anim;
+    }
+
     /**
      * Opens the user folder described by the specified tag. The opening of the folder
      * is animated relative to the specified View. If the View is null, no animation
@@ -554,55 +607,10 @@
 
         mFolderIcon.growAndFadeOut();
 
-        AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
-        int width = getFolderWidth();
-        int height = getFolderHeight();
-
-        float transX = - 0.075f * (width / 2 - getPivotX());
-        float transY = - 0.075f * (height / 2 - getPivotY());
-        setTranslationX(transX);
-        setTranslationY(transY);
-        PropertyValuesHolder tx = PropertyValuesHolder.ofFloat(TRANSLATION_X, transX, 0);
-        PropertyValuesHolder ty = PropertyValuesHolder.ofFloat(TRANSLATION_Y, transY, 0);
-
-        Animator drift = ObjectAnimator.ofPropertyValuesHolder(this, tx, ty);
-        drift.setDuration(mMaterialExpandDuration);
-        drift.setStartDelay(mMaterialExpandStagger);
-        drift.setInterpolator(new LogDecelerateInterpolator(100, 0));
-
-        int rx = (int) Math.max(Math.max(width - getPivotX(), 0), getPivotX());
-        int ry = (int) Math.max(Math.max(height - getPivotY(), 0), getPivotY());
-        float radius = (float) Math.hypot(rx, ry);
-
-        Animator reveal = new CircleRevealOutlineProvider((int) getPivotX(),
-                (int) getPivotY(), 0, radius).createRevealAnimator(this);
-        reveal.setDuration(mMaterialExpandDuration);
-        reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
-
-        mContent.setAlpha(0f);
-        Animator iconsAlpha = ObjectAnimator.ofFloat(mContent, "alpha", 0f, 1f);
-        iconsAlpha.setDuration(mMaterialExpandDuration);
-        iconsAlpha.setStartDelay(mMaterialExpandStagger);
-        iconsAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
-
-        mFooter.setAlpha(0f);
-        Animator textAlpha = ObjectAnimator.ofFloat(mFooter, "alpha", 0f, 1f);
-        textAlpha.setDuration(mMaterialExpandDuration);
-        textAlpha.setStartDelay(mMaterialExpandStagger);
-        textAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
-
-        anim.play(drift);
-        anim.play(iconsAlpha);
-        anim.play(textAlpha);
-        anim.play(reveal);
-
-        mContent.setLayerType(LAYER_TYPE_HARDWARE, null);
-        mFooter.setLayerType(LAYER_TYPE_HARDWARE, null);
+        AnimatorSet anim = getOpeningAnimatorSet();
         onCompleteRunnable = new Runnable() {
             @Override
             public void run() {
-                mContent.setLayerType(LAYER_TYPE_NONE, null);
-                mFooter.setLayerType(LAYER_TYPE_NONE, null);
                 mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
             }
         };
@@ -715,12 +723,22 @@
         parent.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
     }
 
+    private AnimatorSet getClosingAnimatorSet() {
+        AnimatorSet animatorSet = LauncherAnimUtils.createAnimatorSet();
+        animatorSet.play(LauncherAnimUtils.ofViewAlphaAndScale(this, 0, 0.9f, 0.9f));
+
+        AnimationLayerSet layerSet = new AnimationLayerSet();
+        layerSet.addView(this);
+        animatorSet.addListener(layerSet);
+
+        return animatorSet;
+    }
+
     private void animateClosed() {
-        final ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(this, 0, 0.9f, 0.9f);
-        oa.addListener(new AnimatorListenerAdapter() {
+        AnimatorSet a = getClosingAnimatorSet();
+        a.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
-                setLayerType(LAYER_TYPE_NONE, null);
                 closeComplete(true);
             }
             @Override
@@ -732,9 +750,8 @@
                 mState = STATE_ANIMATING;
             }
         });
-        oa.setDuration(mExpandDuration);
-        setLayerType(LAYER_TYPE_HARDWARE, null);
-        oa.start();
+        a.setDuration(mExpandDuration);
+        a.start();
     }
 
     private void closeComplete(boolean wasAnimated) {
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 407f923..d84a9d2 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -1138,6 +1138,7 @@
             PreviewItemDrawingParams params);
         void init(int availableSpace, int intrinsicIconSize, boolean rtl);
         float scaleForItem(int index, int totalNumItems);
+        float getIconSize();
         int maxNumItems();
         boolean clipToBackground();
     }
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index f29fb9a..bc78324 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -331,6 +331,8 @@
         int position = 0;
         int newX, newY, rank;
 
+        FolderIconPreviewVerifier verifier = new FolderIconPreviewVerifier(
+                Launcher.getLauncher(getContext()).getDeviceProfile().inv);
         rank = 0;
         for (int i = 0; i < itemCount; i++) {
             View v = list.size() > i ? list.get(i) : null;
@@ -363,7 +365,7 @@
                 currentPage.addViewToCellLayout(
                         v, -1, mFolder.mLauncher.getViewIdForItem(info), lp, true);
 
-                if (rank < FolderIcon.NUM_ITEMS_IN_PREVIEW && v instanceof BubbleTextView) {
+                if (verifier.isItemInPreview(rank) && v instanceof BubbleTextView) {
                     ((BubbleTextView) v).verifyHighRes();
                 }
             }
diff --git a/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java b/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java
index 1f4e648..9c8c2ef 100644
--- a/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java
+++ b/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java
@@ -87,6 +87,11 @@
     }
 
     @Override
+    public float getIconSize() {
+        return mBaselineIconSize;
+    }
+
+    @Override
     public float scaleForItem(int index, int numItems) {
         // Scale is determined by the position of the icon in the preview.
         index = MAX_NUM_ITEMS_IN_PREVIEW - index - 1;
diff --git a/src/com/android/launcher3/graphics/IconPalette.java b/src/com/android/launcher3/graphics/IconPalette.java
index 23c6a12..cd7cf70 100644
--- a/src/com/android/launcher3/graphics/IconPalette.java
+++ b/src/com/android/launcher3/graphics/IconPalette.java
@@ -19,11 +19,12 @@
 import android.app.Notification;
 import android.content.Context;
 import android.graphics.Color;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
 import android.support.v4.graphics.ColorUtils;
 import android.util.Log;
 
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.util.Themes;
 
 /**
@@ -41,12 +42,16 @@
 
     public final int dominantColor;
     public final int backgroundColor;
+    public final ColorMatrixColorFilter backgroundColorMatrixFilter;
     public final int textColor;
     public final int secondaryColor;
 
     private IconPalette(int color) {
         dominantColor = color;
         backgroundColor = getMutedColor(dominantColor);
+        ColorMatrix backgroundColorMatrix = new ColorMatrix();
+        Themes.setColorScaleOnMatrix(backgroundColor, backgroundColorMatrix);
+        backgroundColorMatrixFilter = new ColorMatrixColorFilter(backgroundColorMatrix);
         textColor = getTextColorForBackground(backgroundColor);
         secondaryColor = getLowContrastColor(backgroundColor);
     }
diff --git a/src/com/android/launcher3/graphics/ShadowGenerator.java b/src/com/android/launcher3/graphics/ShadowGenerator.java
index 31276ec..6c603c9 100644
--- a/src/com/android/launcher3/graphics/ShadowGenerator.java
+++ b/src/com/android/launcher3/graphics/ShadowGenerator.java
@@ -83,6 +83,38 @@
         return result;
     }
 
+    public static Bitmap createCircleWithShadow(int circleColor, int diameter) {
+
+        float shadowRadius = diameter * 1f / 32;
+        float shadowYOffset = diameter * 1f / 16;
+
+        int radius = diameter / 2;
+
+        Canvas canvas = new Canvas();
+        Paint blurPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+        blurPaint.setMaskFilter(new BlurMaskFilter(shadowRadius, Blur.NORMAL));
+
+        int center = Math.round(radius + shadowRadius + shadowYOffset);
+        int size = center * 2;
+        Bitmap result = Bitmap.createBitmap(size, size, Config.ARGB_8888);
+        canvas.setBitmap(result);
+
+        // Draw ambient shadow, center aligned within size
+        blurPaint.setAlpha(AMBIENT_SHADOW_ALPHA);
+        canvas.drawCircle(center, center, radius, blurPaint);
+
+        // Draw key shadow, bottom aligned within size
+        blurPaint.setAlpha(KEY_SHADOW_ALPHA);
+        canvas.drawCircle(center, center + shadowYOffset, radius, blurPaint);
+
+        // Draw the circle
+        Paint drawPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+        drawPaint.setColor(circleColor);
+        canvas.drawCircle(center, center, radius, drawPaint);
+
+        return result;
+    }
+
     public static ShadowGenerator getInstance(Context context) {
         Preconditions.assertNonUiThread();
         synchronized (LOCK) {
diff --git a/src/com/android/launcher3/logging/DumpTargetWrapper.java b/src/com/android/launcher3/logging/DumpTargetWrapper.java
new file mode 100644
index 0000000..2646a22
--- /dev/null
+++ b/src/com/android/launcher3/logging/DumpTargetWrapper.java
@@ -0,0 +1,149 @@
+/*
+ * 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.logging;
+
+import android.os.Process;
+import android.text.TextUtils;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.model.nano.LauncherDumpProto;
+import com.android.launcher3.model.nano.LauncherDumpProto.ContainerType;
+import com.android.launcher3.model.nano.LauncherDumpProto.DumpTarget;
+import com.android.launcher3.model.nano.LauncherDumpProto.ItemType;
+import com.android.launcher3.model.nano.LauncherDumpProto.UserType;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class can be used when proto definition doesn't support nesting.
+ */
+public class DumpTargetWrapper {
+    DumpTarget node;
+    ArrayList<DumpTargetWrapper> children;
+
+    public DumpTargetWrapper() {
+        children = new ArrayList<>();
+    }
+
+    public DumpTargetWrapper(DumpTarget t) {
+        this();
+        node = t;
+    }
+
+    public DumpTargetWrapper(int containerType, int id) {
+        this();
+        node = newContainerTarget(containerType, id);
+    }
+
+    public DumpTargetWrapper(ItemInfo info) {
+        this();
+        node = newItemTarget(info);
+    }
+
+    public DumpTarget getDumpTarget() {
+        return node;
+    }
+
+    public void add(DumpTargetWrapper child) {
+        children.add(child);
+    }
+
+    public List<DumpTarget> getFlattenedList() {
+        ArrayList<DumpTarget> list = new ArrayList<>();
+        list.add(node);
+        if (!children.isEmpty()) {
+            for(DumpTargetWrapper t: children) {
+                list.addAll(t.getFlattenedList());
+            }
+            list.add(node); // add a delimiter empty object
+        }
+        return list;
+    }
+    public DumpTarget newItemTarget(ItemInfo info) {
+        DumpTarget dt = new DumpTarget();
+        dt.type = DumpTarget.Type.ITEM;
+
+        switch (info.itemType) {
+            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
+                dt.itemType = ItemType.APP_ICON;
+                break;
+            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+                dt.itemType = ItemType.UNKNOWN_ITEMTYPE;
+                break;
+            case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
+                dt.itemType = ItemType.WIDGET;
+                break;
+            case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
+                dt.itemType = ItemType.SHORTCUT;
+                break;
+        }
+        return dt;
+    }
+
+    public DumpTarget newContainerTarget(int type, int id) {
+        DumpTarget dt = new DumpTarget();
+        dt.type = DumpTarget.Type.CONTAINER;
+        dt.containerType = type;
+        dt.pageId = id;
+        return dt;
+    }
+
+    public static String getDumpTargetStr(DumpTarget t) {
+        if (t == null){
+            return "";
+        }
+        switch (t.type) {
+            case LauncherDumpProto.DumpTarget.Type.ITEM:
+                return getItemStr(t);
+            case LauncherDumpProto.DumpTarget.Type.CONTAINER:
+                String str = LoggerUtils.getFieldName(t.containerType, ContainerType.class);
+                if (t.containerType == ContainerType.WORKSPACE) {
+                    str += " id=" + t.pageId;
+                } else if (t.containerType == ContainerType.FOLDER) {
+                    str += " grid(" + t.gridX + "," + t.gridY+ ")";
+                }
+                return str;
+            default:
+                return "UNKNOWN TARGET TYPE";
+        }
+    }
+
+    private static String getItemStr(DumpTarget t) {
+        String typeStr = LoggerUtils.getFieldName(t.itemType, ItemType.class);
+        if (!TextUtils.isEmpty(t.packageName)) {
+            typeStr += ", package=" + t.packageName;
+        }
+        if (!TextUtils.isEmpty(t.component)) {
+            typeStr += ", component=" + t.component;
+        }
+        return typeStr + ", grid(" + t.gridX + "," + t.gridY + "), span(" + t.spanX + "," + t.spanY
+                + "), pageIdx=" + t.pageId + " user=" + t.userType;
+    }
+
+    public DumpTarget writeToDumpTarget(ItemInfo info) {
+        node.component = info.getTargetComponent() == null? "":
+                info.getTargetComponent().flattenToString();
+        node.packageName = info.getIntent() == null? "": info.getIntent().getPackage();
+        node.gridX = info.cellX;
+        node.gridY = info.cellY;
+        node.spanX = info.spanX;
+        node.spanY = info.spanY;
+        node.userType = (info.user.equals(Process.myUserHandle()))? UserType.DEFAULT : UserType.WORK;
+        return node;
+    }
+}
diff --git a/src/com/android/launcher3/logging/LoggerUtils.java b/src/com/android/launcher3/logging/LoggerUtils.java
index c13e8b3..499fdc7 100644
--- a/src/com/android/launcher3/logging/LoggerUtils.java
+++ b/src/com/android/launcher3/logging/LoggerUtils.java
@@ -1,5 +1,21 @@
+/*
+ * Copyright (C) 2016 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.logging;
 
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.SparseArray;
 import android.view.View;
@@ -27,7 +43,7 @@
     private static final ArrayMap<Class, SparseArray<String>> sNameCache = new ArrayMap<>();
     private static final String UNKNOWN = "UNKNOWN";
 
-    private static String getFieldName(int value, Class c) {
+    public static String getFieldName(int value, Class c) {
         SparseArray<String> cache;
         synchronized (sNameCache) {
             cache = sNameCache.get(c);
@@ -68,8 +84,13 @@
             case Target.Type.CONTROL:
                 return getFieldName(t.controlType, ControlType.class);
             case Target.Type.CONTAINER:
-                return getFieldName(t.containerType, ContainerType.class)
-                        + " id=" + t.pageIndex;
+                String str = getFieldName(t.containerType, ContainerType.class);
+                if (t.containerType == ContainerType.WORKSPACE) {
+                    str += " id=" + t.pageIndex;
+                } else if (t.containerType == ContainerType.FOLDER) {
+                    str += " grid(" + t.gridX + "," + t.gridY+ ")";
+                }
+                return str;
             default:
                 return "UNKNOWN TARGET TYPE";
         }
@@ -86,10 +107,8 @@
         if (t.intentHash != 0) {
             typeStr += ", intentHash=" + t.intentHash;
         }
-        if (t.spanX != 0) {
-            typeStr += ", spanX=" + t.spanX;
-        }
-        return typeStr + ", grid=(" + t.gridX + "," + t.gridY + "), id=" + t.pageIndex;
+        return typeStr + ", grid(" + t.gridX + "," + t.gridY + "), span(" + t.spanX + "," + t.spanY
+                + "), pageIdx=" + t.pageIndex;
     }
 
     public static Target newItemTarget(View v) {
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 6b64087..0e73ca6 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -24,19 +24,27 @@
 import com.android.launcher3.FolderInfo;
 import com.android.launcher3.InstallShortcutReceiver;
 import com.android.launcher3.ItemInfo;
-import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.logging.LoggerUtils;
+import com.android.launcher3.logging.DumpTargetWrapper;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
 import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.model.nano.LauncherDumpProto;
+import com.android.launcher3.model.nano.LauncherDumpProto.ContainerType;
+import com.android.launcher3.model.nano.LauncherDumpProto.DumpTarget;
+import com.android.launcher3.model.nano.LauncherDumpProto.ItemType;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.LongArrayMap;
 import com.android.launcher3.util.MultiHashMap;
+import com.google.protobuf.nano.MessageNano;
 
 import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -102,21 +110,31 @@
         deepShortcutMap.clear();
     }
 
-    // TODO: current dump is very cryptic and hard to understand. Make it more legible.
-    public synchronized void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
-        writer.println(prefix + "Data Model:");
-        for (int i = 0; i < workspaceScreens.size(); i++) {
-            writer.println(prefix + "\tIndex of workspaceScreens:" + workspaceScreens.get(i).toString());
+     public synchronized void dump(String prefix, FileDescriptor fd, PrintWriter writer,
+             String[] args) {
+        if (args.length > 0 && TextUtils.equals(args[0], "--proto")) {
+            dumpProto(prefix, fd, writer, args);
+            return;
         }
+        writer.println(prefix + "Data Model:");
+        writer.print(prefix + " ---- workspace screens: ");
+        for (int i = 0; i < workspaceScreens.size(); i++) {
+            writer.print(" " + workspaceScreens.get(i).toString());
+        }
+        writer.println();
+        writer.println(prefix + " ---- workspace items ");
         for (int i = 0; i < workspaceItems.size(); i++) {
             writer.println(prefix + '\t' + workspaceItems.get(i).toString());
         }
+        writer.println(prefix + " ---- appwidget items ");
         for (int i = 0; i < appWidgets.size(); i++) {
             writer.println(prefix + '\t' + appWidgets.get(i).toString());
         }
+        writer.println(prefix + " ---- folder items ");
         for (int i = 0; i< folders.size(); i++) {
             writer.println(prefix + '\t' + folders.valueAt(i).toString());
         }
+        writer.println(prefix + " ---- items id map ");
         for (int i = 0; i< itemsIdMap.size(); i++) {
             writer.println(prefix + '\t' + itemsIdMap.valueAt(i).toString());
         }
@@ -133,6 +151,88 @@
         }
     }
 
+    private synchronized void dumpProto(String prefix, FileDescriptor fd, PrintWriter writer,
+            String[] args) {
+
+        // Add top parent nodes. (L1)
+        DumpTargetWrapper hotseat = new DumpTargetWrapper(ContainerType.HOTSEAT, 0);
+        LongArrayMap<DumpTargetWrapper> workspaces = new LongArrayMap<>();
+        for (int i = 0; i < workspaceScreens.size(); i++) {
+            workspaces.put(new Long(workspaceScreens.get(i)),
+                    new DumpTargetWrapper(ContainerType.WORKSPACE, i));
+        }
+        DumpTargetWrapper dtw;
+        // Add non leaf / non top nodes (L2)
+        for (int i = 0; i < folders.size(); i++) {
+            FolderInfo fInfo = folders.valueAt(i);
+            dtw = new DumpTargetWrapper(ContainerType.FOLDER, folders.size());
+            dtw.writeToDumpTarget(fInfo);
+            for(ShortcutInfo sInfo: fInfo.contents) {
+                DumpTargetWrapper child = new DumpTargetWrapper(sInfo);
+                child.writeToDumpTarget(sInfo);
+                dtw.add(child);
+            }
+            if (fInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+                hotseat.add(dtw);
+            } else if (fInfo.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+                workspaces.get(new Long(fInfo.screenId)).add(dtw);
+            }
+        }
+        // Add leaf nodes (L3): *Info
+        for (int i = 0; i < workspaceItems.size(); i++) {
+            ItemInfo info = workspaceItems.get(i);
+            if (info instanceof FolderInfo) {
+                continue;
+            }
+            dtw = new DumpTargetWrapper(info);
+            dtw.writeToDumpTarget(info);
+            if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+                hotseat.add(dtw);
+            } else if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+                workspaces.get(new Long(info.screenId)).add(dtw);
+            }
+        }
+        for (int i = 0; i < appWidgets.size(); i++) {
+            ItemInfo info = appWidgets.get(i);
+            dtw = new DumpTargetWrapper(info);
+            dtw.writeToDumpTarget(info);
+            if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+                hotseat.add(dtw);
+            } else if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+                workspaces.get(new Long(info.screenId)).add(dtw);
+            }
+        }
+
+
+        // Traverse target wrapper
+        ArrayList<DumpTarget> targetList = new ArrayList<>();
+        targetList.addAll(hotseat.getFlattenedList());
+        for (int i = 0; i < workspaces.size(); i++) {
+            targetList.addAll(workspaces.valueAt(i).getFlattenedList());
+        }
+
+        if (args.length > 1 && TextUtils.equals(args[1], "--debug")) {
+            for (int i = 0; i < targetList.size(); i++) {
+                writer.println(prefix + DumpTargetWrapper.getDumpTargetStr(targetList.get(i)));
+            }
+            return;
+        } else {
+            LauncherDumpProto.LauncherImpression proto = new LauncherDumpProto.LauncherImpression();
+            proto.targets = new DumpTarget[targetList.size()];
+            for (int i = 0; i < targetList.size(); i++) {
+                proto.targets[i] = targetList.get(i);
+            }
+            FileOutputStream fos = new FileOutputStream(fd);
+            try {
+
+                fos.write(MessageNano.toByteArray(proto));
+                Log.d(TAG, MessageNano.toByteArray(proto).length + "Bytes");
+            } catch (IOException e) {
+                Log.e(TAG, "Exception writing dumpsys --proto", e);
+            }
+        }
+    }
+
     public synchronized void removeItem(Context context, ItemInfo... items) {
         removeItem(context, Arrays.asList(items));
     }
diff --git a/src/com/android/launcher3/model/SdCardAvailableReceiver.java b/src/com/android/launcher3/model/SdCardAvailableReceiver.java
index 7c98362..278669b 100644
--- a/src/com/android/launcher3/model/SdCardAvailableReceiver.java
+++ b/src/com/android/launcher3/model/SdCardAvailableReceiver.java
@@ -63,7 +63,7 @@
 
             for (String pkg : new HashSet<>(entry.getValue())) {
                 if (!launcherApps.isPackageEnabledForProfile(pkg, user)) {
-                    if (pmHelper.isAppOnSdcard(pkg)) {
+                    if (pmHelper.isAppOnSdcard(pkg, user)) {
                         packagesUnavailable.add(pkg);
                     } else {
                         packagesRemoved.add(pkg);
diff --git a/src/com/android/launcher3/notification/NotificationFooterLayout.java b/src/com/android/launcher3/notification/NotificationFooterLayout.java
index 62126ef..2e80341 100644
--- a/src/com/android/launcher3/notification/NotificationFooterLayout.java
+++ b/src/com/android/launcher3/notification/NotificationFooterLayout.java
@@ -21,14 +21,15 @@
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.content.Context;
+import android.content.res.Resources;
 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.FrameLayout;
 import android.widget.LinearLayout;
-import android.widget.TextView;
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAnimUtils;
@@ -36,7 +37,6 @@
 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;
 
 import java.util.ArrayList;
@@ -44,16 +44,16 @@
 import java.util.List;
 
 /**
- * A {@link LinearLayout} that contains only icons of notifications.
- * If there are more than {@link #MAX_FOOTER_NOTIFICATIONS} icons, we add a "+x" overflow.
+ * A {@link FrameLayout} that contains only icons of notifications.
+ * If there are more than {@link #MAX_FOOTER_NOTIFICATIONS} icons, we add a "..." overflow.
  */
-public class NotificationFooterLayout extends LinearLayout {
+public class NotificationFooterLayout extends FrameLayout {
 
     public interface IconAnimationEndListener {
         void onIconAnimationEnd(NotificationInfo animatedNotification);
     }
 
-    private static final int MAX_FOOTER_NOTIFICATIONS = 4;
+    private static final int MAX_FOOTER_NOTIFICATIONS = 5;
 
     private static final Rect sTempRect = new Rect();
 
@@ -61,11 +61,10 @@
     private final List<NotificationInfo> mOverflowNotifications = new ArrayList<>();
     private final boolean mRtl;
 
-    LinearLayout.LayoutParams mIconLayoutParams;
+    FrameLayout.LayoutParams mIconLayoutParams;
+    private View mOverflowEllipsis;
     private LinearLayout mIconRow;
-    private final ColorDrawable mBackgroundColor;
-    private int mTextColor;
-    private TextView mOverflowView;
+    private int mBackgroundColor;
 
     public NotificationFooterLayout(Context context) {
         this(context, null, 0);
@@ -78,33 +77,29 @@
     public NotificationFooterLayout(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
 
-        mRtl = Utilities.isRtl(getResources());
+        Resources res = getResources();
+        mRtl = Utilities.isRtl(res);
 
-        int size = getResources().getDimensionPixelSize(
-                R.dimen.notification_footer_icon_size);
-        int padding = getResources().getDimensionPixelSize(R.dimen.notification_padding);
-        mIconLayoutParams = new LayoutParams(size, size);
-        mIconLayoutParams.setMarginEnd(padding);
+        int iconSize = res.getDimensionPixelSize(R.dimen.notification_footer_icon_size);
+        mIconLayoutParams = new LayoutParams(iconSize, iconSize);
         mIconLayoutParams.gravity = Gravity.CENTER_VERTICAL;
-
-        mBackgroundColor = new ColorDrawable();
-        setBackground(mBackgroundColor);
+        // Compute margin start for each icon such that the icons between the first one
+        // and the ellipsis are evenly spaced out.
+        int paddingEnd = res.getDimensionPixelSize(R.dimen.notification_footer_icon_row_padding);
+        int ellipsisSpace = res.getDimensionPixelSize(R.dimen.horizontal_ellipsis_offset)
+                + res.getDimensionPixelSize(R.dimen.horizontal_ellipsis_size);
+        int footerWidth = res.getDimensionPixelSize(R.dimen.bg_popup_item_width);
+        int availableIconRowSpace = footerWidth - paddingEnd - ellipsisSpace
+                - iconSize * MAX_FOOTER_NOTIFICATIONS;
+        mIconLayoutParams.setMarginStart(availableIconRowSpace / MAX_FOOTER_NOTIFICATIONS);
     }
 
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
+        mOverflowEllipsis = findViewById(R.id.overflow);
         mIconRow = (LinearLayout) findViewById(R.id.icon_row);
-    }
-
-    public void applyColors(IconPalette iconPalette) {
-        mBackgroundColor.setColor(iconPalette.backgroundColor);
-        findViewById(R.id.divider).setBackgroundColor(iconPalette.secondaryColor);
-        mTextColor = iconPalette.textColor;
-    }
-
-    public int getBackgroundColor() {
-        return mBackgroundColor.getColor();
+        mBackgroundColor = ((ColorDrawable) getBackground()).getColor();
     }
 
     /**
@@ -128,35 +123,26 @@
 
         for (int i = 0; i < mNotifications.size(); i++) {
             NotificationInfo info = mNotifications.get(i);
-            addNotificationIconForInfo(info, false /* fromOverflow */);
+            addNotificationIconForInfo(info);
         }
-
-        if (!mOverflowNotifications.isEmpty()) {
-            mOverflowView = new TextView(getContext());
-            mOverflowView.setTextColor(mTextColor);
-            updateOverflowText();
-            mIconRow.addView(mOverflowView, 0, mIconLayoutParams);
-        }
+        updateOverflowEllipsisVisibility();
     }
 
-    private void addNotificationIconForInfo(NotificationInfo info, boolean fromOverflow) {
+    private void updateOverflowEllipsisVisibility() {
+        mOverflowEllipsis.setVisibility(mOverflowNotifications.isEmpty() ? GONE : VISIBLE);
+    }
+
+    /**
+     * Creates an icon for the given NotificationInfo, and adds it to the icon row.
+     * @return the icon view that was added
+     */
+    private View addNotificationIconForInfo(NotificationInfo info) {
         View icon = new View(getContext());
-        icon.setBackground(info.getIconForBackground(getContext(), getBackgroundColor()));
+        icon.setBackground(info.getIconForBackground(getContext(), mBackgroundColor));
         icon.setOnClickListener(info);
-        int addIndex = 0;
-        if (fromOverflow) {
-            // Add the notification before the overflow view.
-            addIndex = 1;
-            icon.setAlpha(0);
-            icon.animate().alpha(1);
-        }
         icon.setTag(info);
-        mIconRow.addView(icon, addIndex, mIconLayoutParams);
-    }
-
-    private void updateOverflowText() {
-        mOverflowView.setText(getResources().getString(R.string.deep_notifications_overflow,
-                mOverflowNotifications.size()));
+        mIconRow.addView(icon, 0, mIconLayoutParams);
+        return icon;
     }
 
     public void animateFirstNotificationTo(Rect toBounds,
@@ -180,16 +166,22 @@
         animation.play(moveAndScaleIcon);
 
         // Shift all notifications (not the overflow) over to fill the gap.
-        int gapWidth = mIconLayoutParams.width + mIconLayoutParams.getMarginEnd();
+        int gapWidth = mIconLayoutParams.width + mIconLayoutParams.getMarginStart();
         if (mRtl) {
             gapWidth = -gapWidth;
         }
-        int numIcons = mIconRow.getChildCount() - 1;
-        // We have to set the translation X to 0 when the new main notification
+        if (!mOverflowNotifications.isEmpty()) {
+            NotificationInfo notification = mOverflowNotifications.remove(0);
+            mNotifications.add(notification);
+            View iconFromOverflow = addNotificationIconForInfo(notification);
+            animation.play(ObjectAnimator.ofFloat(iconFromOverflow, ALPHA, 0, 1));
+        }
+        int numIcons = mIconRow.getChildCount() - 1; // All children besides the one leaving.
+        // We have to reset 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++) {
+        for (int i = 0; i < numIcons; i++) {
             final View child = mIconRow.getChildAt(i);
             Animator shiftChild = ObjectAnimator.ofFloat(child, TRANSLATION_X, gapWidth);
             shiftChild.addListener(propertyResetListener);
@@ -201,19 +193,7 @@
     private void removeViewFromIconRow(View child) {
         mIconRow.removeView(child);
         mNotifications.remove((NotificationInfo) child.getTag());
-        if (!mOverflowNotifications.isEmpty()) {
-            NotificationInfo notification = mOverflowNotifications.remove(0);
-            mNotifications.add(notification);
-            addNotificationIconForInfo(notification, true /* fromOverflow */);
-        }
-        if (mOverflowView != null) {
-            if (mOverflowNotifications.isEmpty()) {
-                mIconRow.removeView(mOverflowView);
-                mOverflowView = null;
-            } else {
-                updateOverflowText();
-            }
-        }
+        updateOverflowEllipsisVisibility();
         if (mIconRow.getChildCount() == 0) {
             // There are no more icons in the footer, so hide it.
             PopupContainerWithArrow popup = PopupContainerWithArrow.getOpen(
@@ -240,16 +220,11 @@
                 overflowIterator.remove();
             }
         }
-        TextView overflowView = null;
         for (int i = mIconRow.getChildCount() - 1; i >= 0; i--) {
             View child = mIconRow.getChildAt(i);
-            if (child instanceof TextView) {
-                overflowView = (TextView) child;
-            } else {
-                NotificationInfo childInfo = (NotificationInfo) child.getTag();
-                if (!notifications.contains(childInfo.notificationKey)) {
-                    removeViewFromIconRow(child);
-                }
+            NotificationInfo childInfo = (NotificationInfo) child.getTag();
+            if (!notifications.contains(childInfo.notificationKey)) {
+                removeViewFromIconRow(child);
             }
         }
     }
diff --git a/src/com/android/launcher3/notification/NotificationHeaderView.java b/src/com/android/launcher3/notification/NotificationHeaderView.java
new file mode 100644
index 0000000..e70b489
--- /dev/null
+++ b/src/com/android/launcher3/notification/NotificationHeaderView.java
@@ -0,0 +1,59 @@
+/*
+ * 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.notification;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.launcher3.R;
+
+/**
+ * A {@link LinearLayout} that contains two text views: one for the notification count
+ * and one just to say "Notification" or "Notifications"
+ */
+public class NotificationHeaderView extends LinearLayout {
+
+    private TextView mNotificationCount;
+    private TextView mNotificationText;
+
+    public NotificationHeaderView(Context context) {
+        this(context, null, 0);
+    }
+
+    public NotificationHeaderView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public NotificationHeaderView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mNotificationCount = (TextView) findViewById(R.id.notification_count);
+        mNotificationText = (TextView) findViewById(R.id.notification_text);
+    }
+
+    public void update(int notificationCount) {
+        mNotificationCount.setText(String.valueOf(notificationCount));
+        mNotificationText.setText(getResources().getQuantityString(
+                R.plurals.notifications_header, notificationCount));
+    }
+}
diff --git a/src/com/android/launcher3/notification/NotificationItemView.java b/src/com/android/launcher3/notification/NotificationItemView.java
index 742c90a..a340742 100644
--- a/src/com/android/launcher3/notification/NotificationItemView.java
+++ b/src/com/android/launcher3/notification/NotificationItemView.java
@@ -19,15 +19,7 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 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;
@@ -36,11 +28,10 @@
 
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.R;
-import com.android.launcher3.graphics.IconPalette;
+import com.android.launcher3.anim.PillHeightRevealOutlineProvider;
 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;
 
@@ -54,10 +45,7 @@
 
     private static final Rect sTempRect = new Rect();
 
-    private final Paint mBackgroundClipPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG |
-            Paint.FILTER_BITMAP_FLAG);
-
-    private View mDivider;
+    private NotificationHeaderView mHeader;
     private NotificationMainView mMainView;
     private NotificationFooterLayout mFooter;
     private SwipeHelper mSwipeHelper;
@@ -78,42 +66,13 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mDivider = findViewById(R.id.divider);
+        mHeader = (NotificationHeaderView) findViewById(R.id.header);
         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,
@@ -134,9 +93,8 @@
         return heightAnimator;
     }
 
-    @Override
-    protected float getBackgroundRadius() {
-        return getResources().getDimensionPixelSize(R.dimen.bg_round_rect_radius);
+    public void updateHeader(int notificationCount) {
+        mHeader.update(notificationCount);
     }
 
     @Override
@@ -158,13 +116,6 @@
         return mSwipeHelper.onTouchEvent(ev) || super.onTouchEvent(ev);
     }
 
-    @Override
-    protected ColorStateList getAttachedArrowColor() {
-        // This NotificationView itself has a different color that is only
-        // revealed when dismissing notifications.
-        return ColorStateList.valueOf(mFooter.getBackgroundColor());
-    }
-
     public void applyNotificationInfos(final List<NotificationInfo> notificationInfos) {
         if (notificationInfos.isEmpty()) {
             return;
@@ -179,13 +130,6 @@
         mFooter.commitNotificationInfos();
     }
 
-    public void applyColors(IconPalette iconPalette) {
-        setBackgroundTintList(ColorStateList.valueOf(iconPalette.secondaryColor));
-        mDivider.setBackgroundColor(iconPalette.secondaryColor);
-        mMainView.applyColors(iconPalette);
-        mFooter.applyColors(iconPalette);
-    }
-
     public void trimNotifications(final List<String> notificationKeys) {
         boolean dismissedMainNotification = !notificationKeys.contains(
                 mMainView.getNotificationInfo().notificationKey);
diff --git a/src/com/android/launcher3/notification/NotificationListener.java b/src/com/android/launcher3/notification/NotificationListener.java
index a627d23..d9f7d76 100644
--- a/src/com/android/launcher3/notification/NotificationListener.java
+++ b/src/com/android/launcher3/notification/NotificationListener.java
@@ -26,6 +26,7 @@
 import android.support.v4.util.Pair;
 
 import com.android.launcher3.LauncherModel;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.PackageUserKey;
 
@@ -55,6 +56,8 @@
     private final Handler mWorkerHandler;
     private final Handler mUiHandler;
 
+    private Ranking mTempRanking = new Ranking();
+
     private Handler.Callback mWorkerCallback = new Handler.Callback() {
         @Override
         public boolean handleMessage(Message message) {
@@ -166,7 +169,7 @@
         NotificationPostedMsg(StatusBarNotification sbn) {
             packageUserKey = PackageUserKey.fromNotification(sbn);
             notificationKey = sbn.getKey();
-            shouldBeFilteredOut = shouldBeFilteredOut(sbn.getNotification());
+            shouldBeFilteredOut = shouldBeFilteredOut(sbn);
         }
     }
 
@@ -190,14 +193,14 @@
      * Filter out notifications that don't have an intent
      * or are headers for grouped notifications.
      *
-     * TODO: use the system concept of a badged notification instead
+     * @see #shouldBeFilteredOut(StatusBarNotification)
      */
     private List<StatusBarNotification> filterNotifications(
             StatusBarNotification[] notifications) {
         if (notifications == null) return null;
         Set<Integer> removedNotifications = new HashSet<>();
         for (int i = 0; i < notifications.length; i++) {
-            if (shouldBeFilteredOut(notifications[i].getNotification())) {
+            if (shouldBeFilteredOut(notifications[i])) {
                 removedNotifications.add(i);
             }
         }
@@ -211,7 +214,14 @@
         return filteredNotifications;
     }
 
-    private boolean shouldBeFilteredOut(Notification notification) {
+    private boolean shouldBeFilteredOut(StatusBarNotification sbn) {
+        if (Utilities.isAtLeastO()) {
+            getCurrentRanking().getRanking(sbn.getKey(), mTempRanking);
+            if (!mTempRanking.canShowBadge()) {
+                return true;
+            }
+        }
+        Notification notification = sbn.getNotification();
         boolean isGroupHeader = (notification.flags & Notification.FLAG_GROUP_SUMMARY) != 0;
         return (notification.contentIntent == null || isGroupHeader);
     }
diff --git a/src/com/android/launcher3/notification/NotificationMainView.java b/src/com/android/launcher3/notification/NotificationMainView.java
index b342525..bb2dac0 100644
--- a/src/com/android/launcher3/notification/NotificationMainView.java
+++ b/src/com/android/launcher3/notification/NotificationMainView.java
@@ -16,38 +16,35 @@
 
 package com.android.launcher3.notification;
 
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ArgbEvaluator;
 import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
 import android.content.Context;
+import android.content.res.ColorStateList;
 import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.RippleDrawable;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.View;
-import android.widget.LinearLayout;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
 import android.widget.TextView;
 
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.R;
-import com.android.launcher3.graphics.IconPalette;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.util.Themes;
 
 /**
- * A {@link LinearLayout} that contains a single notification, e.g. icon + title + text.
+ * A {@link android.widget.FrameLayout} that contains a single notification,
+ * e.g. icon + title + text.
  */
-public class NotificationMainView extends LinearLayout implements SwipeHelper.Callback {
-
-    private final ArgbEvaluator mArgbEvaluator = new ArgbEvaluator();
+public class NotificationMainView extends FrameLayout implements SwipeHelper.Callback {
 
     private NotificationInfo mNotificationInfo;
+    private ViewGroup mTextAndBackground;
+    private int mBackgroundColor;
     private TextView mTitleView;
     private TextView mTextView;
-    private IconPalette mIconPalette;
-    private ColorDrawable mColorBackground;
 
     public NotificationMainView(Context context) {
         this(context, null, 0);
@@ -65,14 +62,15 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
 
-        mTitleView = (TextView) findViewById(R.id.title);
-        mTextView = (TextView) findViewById(R.id.text);
-    }
-
-    public void applyColors(IconPalette iconPalette) {
-        mColorBackground = new ColorDrawable(iconPalette.backgroundColor);
-        setBackground(mColorBackground);
-        mIconPalette = iconPalette;
+        mTextAndBackground = (ViewGroup) findViewById(R.id.text_and_background);
+        ColorDrawable colorBackground = (ColorDrawable) mTextAndBackground.getBackground();
+        mBackgroundColor = colorBackground.getColor();
+        RippleDrawable rippleBackground = new RippleDrawable(ColorStateList.valueOf(
+                Themes.getAttrColor(getContext(), android.R.attr.colorControlHighlight)),
+                colorBackground, null);
+        mTextAndBackground.setBackground(rippleBackground);
+        mTitleView = (TextView) mTextAndBackground.findViewById(R.id.title);
+        mTextView = (TextView) mTextAndBackground.findViewById(R.id.text);
     }
 
     public void applyNotificationInfo(NotificationInfo mainNotification, View iconView) {
@@ -84,30 +82,18 @@
      */
     public void applyNotificationInfo(NotificationInfo mainNotification, View iconView,
            boolean animate) {
-        if (animate) {
-            mTitleView.setAlpha(0);
-            mTextView.setAlpha(0);
-            mColorBackground.setColor(mIconPalette.secondaryColor);
-        }
         mNotificationInfo = mainNotification;
         mTitleView.setText(mNotificationInfo.title);
         mTextView.setText(mNotificationInfo.text);
-        iconView.setBackground(mNotificationInfo.getIconForBackground(
-                getContext(), mIconPalette.backgroundColor));
+        iconView.setBackground(mNotificationInfo.getIconForBackground(getContext(),
+                mBackgroundColor));
         setOnClickListener(mNotificationInfo);
         setTranslationX(0);
         // Add a dummy ItemInfo so that logging populates the correct container and item types
         // instead of DEFAULT_CONTAINERTYPE and DEFAULT_ITEMTYPE, respectively.
         setTag(new ItemInfo());
         if (animate) {
-            AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
-            Animator textFade = ObjectAnimator.ofFloat(mTextView, View.ALPHA, 1);
-            Animator titleFade = ObjectAnimator.ofFloat(mTitleView, View.ALPHA, 1);
-            ValueAnimator colorChange = ObjectAnimator.ofObject(mColorBackground, "color",
-                    mArgbEvaluator, mIconPalette.secondaryColor, mIconPalette.backgroundColor);
-            animation.playTogether(textFade, titleFade, colorChange);
-            animation.setDuration(150);
-            animation.start();
+            ObjectAnimator.ofFloat(mTextAndBackground, ALPHA, 0, 1).setDuration(150).start();
         }
     }
 
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 7811b96..b8d38f5 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -27,7 +27,6 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Color;
-import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.drawable.ShapeDrawable;
@@ -64,15 +63,13 @@
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.dragndrop.DragView;
-import com.android.launcher3.graphics.IconPalette;
 import com.android.launcher3.graphics.TriangleShape;
 import com.android.launcher3.notification.NotificationItemView;
 import com.android.launcher3.shortcuts.DeepShortcutView;
-import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
+import com.android.launcher3.shortcuts.ShortcutsItemView;
 import com.android.launcher3.util.PackageUserKey;
 
-import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
@@ -84,18 +81,17 @@
  * A container for shortcuts to deep links within apps.
  */
 @TargetApi(Build.VERSION_CODES.N)
-public class PopupContainerWithArrow extends AbstractFloatingView
-        implements View.OnLongClickListener, View.OnTouchListener, DragSource,
+public class PopupContainerWithArrow extends AbstractFloatingView implements DragSource,
         DragController.DragListener {
 
-    private final Point mIconShift = new Point();
-    private final Point mIconLastTouchPos = new Point();
-
     protected final Launcher mLauncher;
     private final int mStartDragThreshold;
     private LauncherAccessibilityDelegate mAccessibilityDelegate;
     private final boolean mIsRtl;
 
+    public ShortcutsItemView mShortcutsItemView;
+    private NotificationItemView mNotificationItemView;
+
     protected BubbleTextView mOriginalIcon;
     private final Rect mTempRect = new Rect();
     private PointF mInterceptTouchDown = new PointF();
@@ -177,6 +173,8 @@
         boolean reverseOrder = mIsAboveIcon;
         if (reverseOrder) {
             removeAllViews();
+            mNotificationItemView = null;
+            mShortcutsItemView = null;
             itemsToPopulate = PopupPopulator.reverseItems(itemsToPopulate);
             addDummyViews(originalIcon, itemsToPopulate, notificationKeys.length > 1);
 
@@ -184,32 +182,20 @@
             orientAboutIcon(originalIcon, arrowHeight + arrowVerticalOffset);
         }
 
-        List<DeepShortcutView> shortcutViews = new ArrayList<>();
-        NotificationItemView notificationView = null;
-        for (int i = 0; i < getChildCount(); i++) {
-            View item = getChildAt(i);
-            switch (itemsToPopulate[i]) {
-                case SHORTCUT:
-                    if (reverseOrder) {
-                        shortcutViews.add(0, (DeepShortcutView) item);
-                    } else {
-                        shortcutViews.add((DeepShortcutView) item);
-                    }
-                    break;
-                case NOTIFICATION:
-                    notificationView = (NotificationItemView) item;
-                    IconPalette iconPalette = originalIcon.getIconPalette();
-                    notificationView.applyColors(iconPalette);
-                    break;
-            }
+        ItemInfo originalItemInfo = (ItemInfo) originalIcon.getTag();
+        List<DeepShortcutView> shortcutViews = mShortcutsItemView == null
+                ? Collections.EMPTY_LIST
+                : mShortcutsItemView.getDeepShortcutViews(reverseOrder);
+        if (mNotificationItemView != null) {
+            BadgeInfo badgeInfo = mLauncher.getPopupDataProvider()
+                    .getBadgeInfoForItem(originalItemInfo);
+            updateNotificationHeader(badgeInfo);
         }
 
         // Add the arrow.
         mArrow = addArrowView(arrowHorizontalOffset, arrowVerticalOffset, arrowWidth, arrowHeight);
         mArrow.setPivotX(arrowWidth / 2);
         mArrow.setPivotY(mIsAboveIcon ? 0 : arrowHeight);
-        PopupItemView firstItem = getItemViewAt(mIsAboveIcon ? getItemCount() - 1 : 0);
-        mArrow.setBackgroundTintList(firstItem.getAttachedArrowColor());
 
         animateOpen();
 
@@ -220,29 +206,47 @@
         // Load the shortcuts on a background thread and update the container as it animates.
         final Looper workerLooper = LauncherModel.getWorkerLooper();
         new Handler(workerLooper).postAtFrontOfQueue(PopupPopulator.createUpdateRunnable(
-                mLauncher, (ItemInfo) originalIcon.getTag(), new Handler(Looper.getMainLooper()),
-                this, shortcutIds, shortcutViews, notificationKeys, notificationView));
+                mLauncher, originalItemInfo, new Handler(Looper.getMainLooper()),
+                this, shortcutIds, shortcutViews, notificationKeys, mNotificationItemView));
     }
 
     private void addDummyViews(BubbleTextView originalIcon,
-            PopupPopulator.Item[] itemsToPopulate, boolean notificationFooterHasIcons) {
+            PopupPopulator.Item[] itemTypesToPopulate, boolean notificationFooterHasIcons) {
         final Resources res = getResources();
-        final int spacing = res.getDimensionPixelSize(R.dimen.deep_shortcuts_spacing);
+        final int spacing = res.getDimensionPixelSize(R.dimen.popup_items_spacing);
         final LayoutInflater inflater = mLauncher.getLayoutInflater();
-        int numItems = itemsToPopulate.length;
+        int numItems = itemTypesToPopulate.length;
         for (int i = 0; i < numItems; i++) {
-            final PopupItemView item = (PopupItemView) inflater.inflate(
-                    itemsToPopulate[i].layoutId, this, false);
-            if (itemsToPopulate[i] == PopupPopulator.Item.NOTIFICATION) {
+            PopupPopulator.Item itemTypeToPopulate = itemTypesToPopulate[i];
+            final View item = inflater.inflate(itemTypeToPopulate.layoutId, this, false);
+
+            if (itemTypeToPopulate == PopupPopulator.Item.NOTIFICATION) {
+                mNotificationItemView = (NotificationItemView) item;
                 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;
-            }
+
+            boolean itemIsFollowedByDifferentType = i < numItems - 1
+                    && itemTypesToPopulate[i + 1] != itemTypeToPopulate;
+
             item.setAccessibilityDelegate(mAccessibilityDelegate);
-            addView(item);
+            if (itemTypeToPopulate == PopupPopulator.Item.SHORTCUT) {
+                if (mShortcutsItemView == null) {
+                    mShortcutsItemView = (ShortcutsItemView) inflater.inflate(
+                            R.layout.shortcuts_item, this, false);
+                    addView(mShortcutsItemView);
+                }
+                mShortcutsItemView.addDeepShortcutView((DeepShortcutView) item);
+                if (itemIsFollowedByDifferentType) {
+                    ((LayoutParams) mShortcutsItemView.getLayoutParams()).bottomMargin = spacing;
+                }
+            } else {
+                addView(item);
+                if (itemIsFollowedByDifferentType) {
+                    ((LayoutParams) item.getLayoutParams()).bottomMargin = spacing;
+                }
+            }
         }
         // TODO: update this, since not all items are shortcuts
         setContentDescription(getContext().getString(R.string.shortcuts_menu_description,
@@ -534,49 +538,26 @@
         return true;
     }
 
-    @Override
-    public boolean onTouch(View v, MotionEvent ev) {
-        // Touched a shortcut, update where it was touched so we can drag from there on long click.
-        switch (ev.getAction()) {
-            case MotionEvent.ACTION_DOWN:
-            case MotionEvent.ACTION_MOVE:
-                mIconLastTouchPos.set((int) ev.getX(), (int) ev.getY());
-                break;
+    /**
+     * Updates the notification header to reflect the badge info. Since this can be called
+     * for any badge info (not necessarily the one associated with this app), we first
+     * check that the ItemInfo matches the one of this popup.
+     */
+    public void updateNotificationHeader(BadgeInfo badgeInfo, ItemInfo originalItemInfo) {
+        if (originalItemInfo != mOriginalIcon.getTag()) {
+            return;
         }
-        return false;
+        updateNotificationHeader(badgeInfo);
     }
 
-    public boolean onLongClick(View v) {
-        // Return early if this is not initiated from a touch or not the correct view
-        if (!v.isInTouchMode() || !(v.getParent() instanceof DeepShortcutView)) return false;
-        // Return early if global dragging is not enabled
-        if (!mLauncher.isDraggingEnabled()) return false;
-        // Return early if an item is already being dragged (e.g. when long-pressing two shortcuts)
-        if (mLauncher.getDragController().isDragging()) return false;
-
-        // Long clicked on a shortcut.
-        mDeferContainerRemoval = true;
-        DeepShortcutView sv = (DeepShortcutView) v.getParent();
-        sv.setWillDrawIcon(false);
-
-        // Move the icon to align with the center-top of the touch point
-        mIconShift.x = mIconLastTouchPos.x - sv.getIconCenter().x;
-        mIconShift.y = mIconLastTouchPos.y - mLauncher.getDeviceProfile().iconSizePx;
-
-        DragView dv = mLauncher.getWorkspace().beginDragShared(
-                sv.getBubbleText(), this, sv.getFinalInfo(),
-                new ShortcutDragPreviewProvider(sv.getIconView(), mIconShift), new DragOptions());
-        dv.animateShift(-mIconShift.x, -mIconShift.y);
-
-        // TODO: support dragging from within folder without having to close it
-        AbstractFloatingView.closeOpenContainer(mLauncher, AbstractFloatingView.TYPE_FOLDER);
-        return false;
+    private void updateNotificationHeader(BadgeInfo badgeInfo) {
+        if (mNotificationItemView != null && badgeInfo != null) {
+            mNotificationItemView.updateHeader(badgeInfo.getNotificationCount());
+        }
     }
 
     public void trimNotifications(Map<PackageUserKey, BadgeInfo> updatedBadges) {
-        final NotificationItemView notificationView =
-                (NotificationItemView) findViewById(R.id.notification_view);
-        if (notificationView == null) {
+        if (mNotificationItemView == null) {
             return;
         }
         ItemInfo originalInfo = (ItemInfo) mOriginalIcon.getTag();
@@ -585,11 +566,11 @@
             AnimatorSet removeNotification = LauncherAnimUtils.createAnimatorSet();
             final int duration = getResources().getInteger(
                     R.integer.config_removeNotificationViewDuration);
-            final int spacing = getResources().getDimensionPixelSize(R.dimen.deep_shortcuts_spacing);
+            final int spacing = getResources().getDimensionPixelSize(R.dimen.popup_items_spacing);
             removeNotification.play(reduceNotificationViewHeight(
-                    notificationView.getHeight() + spacing, duration, notificationView));
+                    mNotificationItemView.getHeight() + spacing, duration, mNotificationItemView));
             final View removeMarginView = mIsAboveIcon ? getItemViewAt(getItemCount() - 2)
-                    : notificationView;
+                    : mNotificationItemView;
             if (removeMarginView != null) {
                 ValueAnimator removeMargin = ValueAnimator.ofFloat(1, 0).setDuration(duration);
                 removeMargin.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@@ -601,18 +582,16 @@
                 });
                 removeNotification.play(removeMargin);
             }
-            Animator fade = ObjectAnimator.ofFloat(notificationView, ALPHA, 0)
+            Animator fade = ObjectAnimator.ofFloat(mNotificationItemView, ALPHA, 0)
                     .setDuration(duration);
             fade.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
-                    removeView(notificationView);
+                    removeView(mNotificationItemView);
                     if (getItemCount() == 0) {
                         close(false);
                         return;
                     }
-                    View firstItem = getItemViewAt(mIsAboveIcon ? getItemCount() - 1 : 0);
-                    mArrow.setBackgroundTintList(firstItem.getBackgroundTintList());
                 }
             });
             removeNotification.play(fade);
@@ -626,7 +605,7 @@
             removeNotification.start();
             return;
         }
-        notificationView.trimNotifications(badgeInfo.getNotificationKeys());
+        mNotificationItemView.trimNotifications(badgeInfo.getNotificationKeys());
     }
 
     private ObjectAnimator createArrowScaleAnim(float scale) {
@@ -669,8 +648,7 @@
     }
 
     public Animator reduceNotificationViewHeight(int heightToRemove, int duration) {
-        return reduceNotificationViewHeight(heightToRemove, duration,
-                (NotificationItemView) findViewById(R.id.notification_view));
+        return reduceNotificationViewHeight(heightToRemove, duration, mNotificationItemView);
     }
 
     @Override
@@ -702,6 +680,7 @@
     public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
         // Either the original icon or one of the shortcuts was dragged.
         // Hide the container, but don't remove it yet because that interferes with touch events.
+        mDeferContainerRemoval = true;
         animateClose();
     }
 
@@ -723,7 +702,6 @@
     @Override
     public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
         target.itemType = ItemType.DEEPSHORTCUT;
-        target.rank = info.rank;
         targetParent.containerType = ContainerType.DEEPSHORTCUTS;
     }
 
@@ -765,38 +743,17 @@
         for (int i = firstOpenItemIndex; i < firstOpenItemIndex + numOpenShortcuts; i++) {
             final PopupItemView view = getItemViewAt(i);
             Animator anim;
-            if (view.willDrawIcon()) {
-                anim = view.createCloseAnimation(mIsAboveIcon, mIsLeftAligned, duration);
-                int animationIndex = mIsAboveIcon ? i - firstOpenItemIndex
-                        : numOpenShortcuts - i - 1;
-                anim.setStartDelay(stagger * animationIndex);
+            anim = view.createCloseAnimation(mIsAboveIcon, mIsLeftAligned, duration);
+            int animationIndex = mIsAboveIcon ? i - firstOpenItemIndex
+                    : numOpenShortcuts - i - 1;
+            anim.setStartDelay(stagger * animationIndex);
 
-                Animator fadeAnim = ObjectAnimator.ofFloat(view, View.ALPHA, 0);
-                // Don't start fading until the arrow is gone.
-                fadeAnim.setStartDelay(stagger * animationIndex + arrowScaleDuration);
-                fadeAnim.setDuration(duration - arrowScaleDuration);
-                fadeAnim.setInterpolator(fadeInterpolator);
-                shortcutAnims.play(fadeAnim);
-            } else {
-                // The view is being dragged. Animate it such that it collapses with the drag view
-                anim = view.collapseToIcon();
-                anim.setDuration(DragView.VIEW_ZOOM_DURATION);
-
-                // Scale and translate the view to follow the drag view.
-                Point iconCenter = view.getIconCenter();
-                view.setPivotX(iconCenter.x);
-                view.setPivotY(iconCenter.y);
-
-                float scale = ((float) mLauncher.getDeviceProfile().iconSizePx) / view.getHeight();
-                Animator anim2 = LauncherAnimUtils.ofPropertyValuesHolder(view,
-                        new PropertyListBuilder()
-                                .scale(scale)
-                                .translationX(mIconShift.x)
-                                .translationY(mIconShift.y)
-                                .build())
-                        .setDuration(DragView.VIEW_ZOOM_DURATION);
-                shortcutAnims.play(anim2);
-            }
+            Animator fadeAnim = ObjectAnimator.ofFloat(view, View.ALPHA, 0);
+            // Don't start fading until the arrow is gone.
+            fadeAnim.setStartDelay(stagger * animationIndex + arrowScaleDuration);
+            fadeAnim.setDuration(duration - arrowScaleDuration);
+            fadeAnim.setInterpolator(fadeInterpolator);
+            shortcutAnims.play(fadeAnim);
             anim.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
diff --git a/src/com/android/launcher3/popup/PopupItemView.java b/src/com/android/launcher3/popup/PopupItemView.java
index b3d7155..b839d99 100644
--- a/src/com/android/launcher3/popup/PopupItemView.java
+++ b/src/com/android/launcher3/popup/PopupItemView.java
@@ -20,9 +20,15 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 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.Point;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
+import android.graphics.Shader;
 import android.util.AttributeSet;
 import android.view.View;
 import android.widget.FrameLayout;
@@ -31,7 +37,6 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.PillRevealOutlineProvider;
-import com.android.launcher3.util.PillWidthRevealOutlineProvider;
 
 /**
  * An abstract {@link FrameLayout} that supports animating an item's content
@@ -47,6 +52,9 @@
 
     protected View mIconView;
 
+    private final Paint mBackgroundClipPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG |
+            Paint.FILTER_BITMAP_FLAG);
+
     public PopupItemView(Context context) {
         this(context, null, 0);
     }
@@ -73,12 +81,31 @@
         mPillRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
     }
 
-    protected ColorStateList getAttachedArrowColor() {
-        return getBackgroundTintList();
+    protected 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);
+            canvas.drawRoundRect(0, 0, getMeasuredWidth(), getMeasuredHeight(),
+                    getBackgroundRadius(), getBackgroundRadius(), mBackgroundClipPaint);
+            Shader backgroundClipShader = new BitmapShader(backgroundBitmap,
+                    Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
+            mBackgroundClipPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
+            mBackgroundClipPaint.setShader(backgroundClipShader);
+        }
     }
 
-    public boolean willDrawIcon() {
-        return true;
+    @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);
     }
 
     /**
@@ -126,17 +153,6 @@
     }
 
     /**
-     * Creates an animator which clips the container to form a circle around the icon.
-     */
-    public Animator collapseToIcon() {
-        int halfHeight = getMeasuredHeight() / 2;
-        int iconCenterX = getIconCenter().x;
-        return new PillWidthRevealOutlineProvider(mPillRect,
-                iconCenterX - halfHeight, iconCenterX + halfHeight)
-                        .createRevealAnimator(this, true);
-    }
-
-    /**
      * Returns the position of the center of the icon relative to the container.
      */
     public Point getIconCenter() {
@@ -148,7 +164,7 @@
     }
 
     protected float getBackgroundRadius() {
-        return getResources().getDimensionPixelSize(R.dimen.bg_pill_radius);
+        return getResources().getDimensionPixelSize(R.dimen.bg_round_rect_radius);
     }
 
     /**
@@ -182,8 +198,10 @@
         public void setProgress(float progress) {
             super.setProgress(progress);
 
-            mZoomView.setScaleX(progress);
-            mZoomView.setScaleY(progress);
+            if (mZoomView != null) {
+                mZoomView.setScaleX(progress);
+                mZoomView.setScaleY(progress);
+            }
 
             float height = mOutline.height();
             mTranslateView.setTranslationY(mTranslateYMultiplier * (mFullHeight - height));
diff --git a/src/com/android/launcher3/popup/PopupPopulator.java b/src/com/android/launcher3/popup/PopupPopulator.java
index d2814ee..39c2db2 100644
--- a/src/com/android/launcher3/popup/PopupPopulator.java
+++ b/src/com/android/launcher3/popup/PopupPopulator.java
@@ -196,7 +196,8 @@
 
         @Override
         public void run() {
-            mShortcutChild.applyShortcutInfo(mShortcutChildInfo, mDetail, mContainer);
+            mShortcutChild.applyShortcutInfo(mShortcutChildInfo, mDetail,
+                    mContainer.mShortcutsItemView);
         }
     }
 
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutView.java b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
index 2f07c9a..47a023e 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutView.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
@@ -17,26 +17,30 @@
 package com.android.launcher3.shortcuts;
 
 import android.content.Context;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.view.View;
+import android.widget.FrameLayout;
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutInfo;
-import com.android.launcher3.popup.PopupContainerWithArrow;
-import com.android.launcher3.popup.PopupItemView;
+import com.android.launcher3.Utilities;
 
 /**
  * A {@link android.widget.FrameLayout} that contains a {@link DeepShortcutView}.
  * This lets us animate the DeepShortcutView (icon and text) separately from the background.
  */
-public class DeepShortcutView extends PopupItemView {
+public class DeepShortcutView extends FrameLayout {
+
+    private static final Point sTempPoint = new Point();
 
     private final Rect mPillRect;
 
     private DeepShortcutTextView mBubbleText;
+    private View mIconView;
 
     private ShortcutInfo mInfo;
     private ShortcutInfoCompat mDetail;
@@ -59,6 +63,7 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         mBubbleText = (DeepShortcutTextView) findViewById(R.id.deep_shortcut);
+        mIconView = findViewById(R.id.icon);
     }
 
     public DeepShortcutTextView getBubbleText() {
@@ -73,6 +78,17 @@
         return mIconView.getVisibility() == View.VISIBLE;
     }
 
+    /**
+     * Returns the position of the center of the icon relative to the container.
+     */
+    public Point getIconCenter() {
+        sTempPoint.y = sTempPoint.x = getMeasuredHeight() / 2;
+        if (Utilities.isRtl(getResources())) {
+            sTempPoint.x = getMeasuredWidth() - sTempPoint.x;
+        }
+        return sTempPoint;
+    }
+
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
@@ -81,7 +97,7 @@
 
     /** package private **/
     public void applyShortcutInfo(ShortcutInfo info, ShortcutInfoCompat detail,
-            PopupContainerWithArrow container) {
+            ShortcutsItemView container) {
         mInfo = info;
         mDetail = detail;
         mBubbleText.applyFromShortcutInfo(info);
diff --git a/src/com/android/launcher3/shortcuts/ShortcutsItemView.java b/src/com/android/launcher3/shortcuts/ShortcutsItemView.java
new file mode 100644
index 0000000..349c4c9
--- /dev/null
+++ b/src/com/android/launcher3/shortcuts/ShortcutsItemView.java
@@ -0,0 +1,180 @@
+/*
+ * 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.shortcuts;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.content.Context;
+import android.graphics.Point;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAnimUtils;
+import com.android.launcher3.R;
+import com.android.launcher3.anim.PropertyListBuilder;
+import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.logging.UserEventDispatcher.LogContainerProvider;
+import com.android.launcher3.popup.PopupContainerWithArrow;
+import com.android.launcher3.popup.PopupItemView;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A {@link PopupItemView} that contains all of the {@link DeepShortcutView}s for an app.
+ */
+public class ShortcutsItemView extends PopupItemView implements View.OnLongClickListener,
+        View.OnTouchListener, LogContainerProvider {
+
+    private Launcher mLauncher;
+    private LinearLayout mDeepShortcutsLayout;
+    private final Point mIconShift = new Point();
+    private final Point mIconLastTouchPos = new Point();
+
+    public ShortcutsItemView(Context context) {
+        this(context, null, 0);
+    }
+
+    public ShortcutsItemView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public ShortcutsItemView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        mLauncher = Launcher.getLauncher(context);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mDeepShortcutsLayout = (LinearLayout) findViewById(R.id.deep_shortcuts);
+    }
+
+    @Override
+    public boolean onTouch(View v, MotionEvent ev) {
+        // Touched a shortcut, update where it was touched so we can drag from there on long click.
+        switch (ev.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+            case MotionEvent.ACTION_MOVE:
+                mIconLastTouchPos.set((int) ev.getX(), (int) ev.getY());
+                break;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onLongClick(View v) {
+        // Return early if this is not initiated from a touch or not the correct view
+        if (!v.isInTouchMode() || !(v.getParent() instanceof DeepShortcutView)) return false;
+        // Return early if global dragging is not enabled
+        if (!mLauncher.isDraggingEnabled()) return false;
+        // Return early if an item is already being dragged (e.g. when long-pressing two shortcuts)
+        if (mLauncher.getDragController().isDragging()) return false;
+
+        // Long clicked on a shortcut.
+        DeepShortcutView sv = (DeepShortcutView) v.getParent();
+        sv.setWillDrawIcon(false);
+
+        // Move the icon to align with the center-top of the touch point
+        mIconShift.x = mIconLastTouchPos.x - sv.getIconCenter().x;
+        mIconShift.y = mIconLastTouchPos.y - mLauncher.getDeviceProfile().iconSizePx;
+
+        DragView dv = mLauncher.getWorkspace().beginDragShared(sv.getBubbleText(),
+                (PopupContainerWithArrow) getParent(), sv.getFinalInfo(),
+                new ShortcutDragPreviewProvider(sv.getIconView(), mIconShift), new DragOptions());
+        dv.animateShift(-mIconShift.x, -mIconShift.y);
+
+        // TODO: support dragging from within folder without having to close it
+        AbstractFloatingView.closeOpenContainer(mLauncher, AbstractFloatingView.TYPE_FOLDER);
+        return false;
+    }
+
+    public void addDeepShortcutView(DeepShortcutView deepShortcutView) {
+        if (getNumDeepShortcuts() > 0) {
+            getDeepShortcutAt(getNumDeepShortcuts() - 1).findViewById(R.id.divider)
+                    .setVisibility(VISIBLE);
+        }
+        mDeepShortcutsLayout.addView(deepShortcutView);
+    }
+
+    private DeepShortcutView getDeepShortcutAt(int index) {
+        return (DeepShortcutView) mDeepShortcutsLayout.getChildAt(index);
+    }
+
+    private int getNumDeepShortcuts() {
+        return mDeepShortcutsLayout.getChildCount();
+    }
+
+    public List<DeepShortcutView> getDeepShortcutViews(boolean reverseOrder) {
+        int numDeepShortcuts = getNumDeepShortcuts();
+        List<DeepShortcutView> deepShortcutViews = new ArrayList<>(numDeepShortcuts);
+        for (int i = 0; i < numDeepShortcuts; i++) {
+            DeepShortcutView deepShortcut = getDeepShortcutAt(i);
+            if (reverseOrder) {
+                deepShortcutViews.add(0, deepShortcut);
+            } else {
+                deepShortcutViews.add(deepShortcut);
+            }
+        }
+        return deepShortcutViews;
+    }
+
+    @Override
+    public Animator createOpenAnimation(boolean isContainerAboveIcon, boolean pivotLeft) {
+        AnimatorSet openAnimation = LauncherAnimUtils.createAnimatorSet();
+        openAnimation.play(super.createOpenAnimation(isContainerAboveIcon, pivotLeft));
+        for (int i = 0; i < getNumDeepShortcuts(); i++) {
+            View deepShortcutIcon = getDeepShortcutAt(i).getIconView();
+            deepShortcutIcon.setScaleX(0);
+            deepShortcutIcon.setScaleY(0);
+            openAnimation.play(LauncherAnimUtils.ofPropertyValuesHolder(
+                    deepShortcutIcon, new PropertyListBuilder().scale(1).build()));
+        }
+        return openAnimation;
+    }
+
+    @Override
+    public Animator createCloseAnimation(boolean isContainerAboveIcon, boolean pivotLeft,
+            long duration) {
+        AnimatorSet closeAnimation = LauncherAnimUtils.createAnimatorSet();
+        closeAnimation.play(super.createCloseAnimation(isContainerAboveIcon, pivotLeft, duration));
+        for (int i = 0; i < getNumDeepShortcuts(); i++) {
+            View deepShortcutIcon = getDeepShortcutAt(i).getIconView();
+            deepShortcutIcon.setScaleX(1);
+            deepShortcutIcon.setScaleY(1);
+            closeAnimation.play(LauncherAnimUtils.ofPropertyValuesHolder(
+                    deepShortcutIcon, new PropertyListBuilder().scale(0).build()));
+        }
+        return closeAnimation;
+    }
+
+    @Override
+    public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target,
+            LauncherLogProto.Target targetParent) {
+        target.itemType = LauncherLogProto.ItemType.DEEPSHORTCUT;
+        target.rank = info.rank;
+        targetParent.containerType = LauncherLogProto.ContainerType.DEEPSHORTCUTS;
+    }
+}
diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index bfa932b..7629f78 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -40,81 +40,55 @@
  */
 public class PackageManagerHelper {
 
-    private static final int FLAG_SUSPENDED = 1<<30;
-
     private final Context mContext;
     private final PackageManager mPm;
+    private final LauncherAppsCompat mLauncherApps;
 
     public PackageManagerHelper(Context context) {
         mContext = context;
         mPm = context.getPackageManager();
+        mLauncherApps = LauncherAppsCompat.getInstance(context);
     }
 
     /**
      * Returns true if the app can possibly be on the SDCard. This is just a workaround and doesn't
      * guarantee that the app is on SD card.
      */
-    public boolean isAppOnSdcard(String packageName) {
-        return isAppEnabled(packageName, PackageManager.GET_UNINSTALLED_PACKAGES);
+    public boolean isAppOnSdcard(String packageName, UserHandle user) {
+        ApplicationInfo info = mLauncherApps.getApplicationInfo(
+                packageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, user);
+        return info != null && (info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
     }
 
-    public boolean isAppEnabled(String packageName) {
-        return isAppEnabled(packageName, 0);
-    }
-
-    public boolean isAppEnabled(String packageName, int flags) {
-        try {
-            ApplicationInfo info = mPm.getApplicationInfo(packageName, flags);
-            return info != null && info.enabled;
-        } catch (PackageManager.NameNotFoundException e) {
-            return false;
-        }
-    }
-
-  /**
-   * Returns whether a package is suspended for the current user as per
-   * {@link android.app.admin.DevicePolicyManager#isPackageSuspended}.
-   */
-    public boolean isAppSuspended(String packageName) {
-        try {
-            ApplicationInfo info = mPm.getApplicationInfo(packageName, 0);
-            return info != null && isAppSuspended(info);
-        } catch (PackageManager.NameNotFoundException e) {
-            return false;
-        }
-    }
-
-  /**
-   * Returns whether the target app is suspended for a given user as per
-   * {@link android.app.admin.DevicePolicyManager#isPackageSuspended}.
-   */
+    /**
+     * Returns whether the target app is suspended for a given user as per
+     * {@link android.app.admin.DevicePolicyManager#isPackageSuspended}.
+     */
     public boolean isAppSuspended(String packageName, UserHandle user) {
-        ApplicationInfo info =
-                LauncherAppsCompat.getInstance(mContext).getApplicationInfo(packageName, user);
+        ApplicationInfo info = mLauncherApps.getApplicationInfo(packageName, 0, user);
         return info != null && isAppSuspended(info);
     }
 
     public boolean isSafeMode() {
-        return mPm.isSafeMode();
+        return mContext.getPackageManager().isSafeMode();
     }
 
     public Intent getAppLaunchIntent(String pkg, UserHandle user) {
-        List<LauncherActivityInfo> activities = LauncherAppsCompat.getInstance(mContext)
-                .getActivityList(pkg, user);
+        List<LauncherActivityInfo> activities = mLauncherApps.getActivityList(pkg, user);
         return activities.isEmpty() ? null :
                 AppInfo.makeLaunchIntent(mContext, activities.get(0), user);
     }
 
-  /**
-   * Returns whether an application is suspended as per
-   * {@link android.app.admin.DevicePolicyManager#isPackageSuspended}.
-   */
+    /**
+     * Returns whether an application is suspended as per
+     * {@link android.app.admin.DevicePolicyManager#isPackageSuspended}.
+     */
     public static boolean isAppSuspended(ApplicationInfo info) {
         // The value of FLAG_SUSPENDED was reused by a hidden constant
         // ApplicationInfo.FLAG_PRIVILEGED prior to N, so only check for suspended flag on N
         // or later.
         if (Utilities.ATLEAST_NOUGAT) {
-            return (info.flags & FLAG_SUSPENDED) != 0;
+            return (info.flags & ApplicationInfo.FLAG_SUSPENDED) != 0;
         } else {
             return false;
         }
diff --git a/src/com/android/launcher3/util/PillWidthRevealOutlineProvider.java b/src/com/android/launcher3/util/PillWidthRevealOutlineProvider.java
deleted file mode 100644
index 89dda3b..0000000
--- a/src/com/android/launcher3/util/PillWidthRevealOutlineProvider.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2016 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.util;
-
-import android.graphics.Rect;
-
-/**
- * Extension of {@link PillRevealOutlineProvider} which only changes the width of the pill.
- */
-public class PillWidthRevealOutlineProvider extends PillRevealOutlineProvider {
-
-    private final int mStartLeft;
-    private final int mStartRight;
-
-    public PillWidthRevealOutlineProvider(Rect pillRect, int left, int right) {
-        super(0, 0, pillRect);
-        mOutline.set(pillRect);
-        mStartLeft = left;
-        mStartRight = right;
-    }
-
-    @Override
-    public void setProgress(float progress) {
-        mOutline.left = (int) (progress * mPillRect.left + (1 - progress) * mStartLeft);
-        mOutline.right = (int) (progress * mPillRect.right + (1 - progress) * mStartRight);
-    }
-}
diff --git a/src/com/android/launcher3/util/Themes.java b/src/com/android/launcher3/util/Themes.java
index acd589e..d863339 100644
--- a/src/com/android/launcher3/util/Themes.java
+++ b/src/com/android/launcher3/util/Themes.java
@@ -18,6 +18,8 @@
 
 import android.content.Context;
 import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.ColorMatrix;
 import android.view.ContextThemeWrapper;
 
 /**
@@ -49,4 +51,21 @@
         ta.recycle();
         return (int) (255 * alpha + 0.5f);
     }
+
+    /**
+     * Scales a color matrix such that, when applied to color R G B A, it produces R' G' B' A' where
+     * R' = r * R
+     * G' = g * G
+     * B' = b * B
+     * A' = a * A
+     *
+     * The matrix will, for instance, turn white into r g b a, and black will remain black.
+     *
+     * @param color The color r g b a
+     * @param target The ColorMatrix to scale
+     */
+    public static void setColorScaleOnMatrix(int color, ColorMatrix target) {
+        target.setScale(Color.red(color) / 255f, Color.green(color) / 255f,
+                Color.blue(color) / 255f, Color.alpha(color) / 255f);
+    }
 }