Merge "Initializing LauncherAppState only on the main thread" into ub-launcher3-master
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index f3b7827..23ea195 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -76,13 +76,6 @@
             android:process=":wallpaper_chooser">
         </service>
 
-        <service android:name="com.android.launcher3.notification.NotificationListener"
-                 android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
-            <intent-filter>
-                <action android:name="android.service.notification.NotificationListenerService" />
-            </intent-filter>
-        </service>
-
         <meta-data android:name="android.nfc.disable_beam_default"
                        android:value="true" />
 
diff --git a/protos/launcher_log.proto b/protos/launcher_log.proto
index 33041db..c42b142 100644
--- a/protos/launcher_log.proto
+++ b/protos/launcher_log.proto
@@ -64,6 +64,7 @@
   DEEPSHORTCUT = 5;
   SEARCHBOX = 6;
   EDITTEXT = 7;
+  NOTIFICATION = 8;
 }
 
 // Used to define what type of container a Target would represent.
diff --git a/res/drawable-hdpi/virtual_preload.9.png b/res/drawable-hdpi/virtual_preload.9.png
deleted file mode 100644
index 670088f..0000000
--- a/res/drawable-hdpi/virtual_preload.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/virtual_preload_folder.9.png b/res/drawable-hdpi/virtual_preload_folder.9.png
deleted file mode 100644
index 68e2afe..0000000
--- a/res/drawable-hdpi/virtual_preload_folder.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/widget_tile.png b/res/drawable-hdpi/widget_tile.png
deleted file mode 100644
index 572bf6f..0000000
--- a/res/drawable-hdpi/widget_tile.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/virtual_preload.9.png b/res/drawable-mdpi/virtual_preload.9.png
deleted file mode 100644
index c4a01fe..0000000
--- a/res/drawable-mdpi/virtual_preload.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/virtual_preload_folder.9.png b/res/drawable-mdpi/virtual_preload_folder.9.png
deleted file mode 100644
index 2f3e420..0000000
--- a/res/drawable-mdpi/virtual_preload_folder.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/widget_tile.png b/res/drawable-mdpi/widget_tile.png
deleted file mode 100644
index 9652ace..0000000
--- a/res/drawable-mdpi/widget_tile.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/quantum_panel_shape_dark.xml b/res/drawable-v26/mask_drawable_wrapper.xml
similarity index 67%
copy from res/drawable/quantum_panel_shape_dark.xml
copy to res/drawable-v26/mask_drawable_wrapper.xml
index b299eb8..36efd0f 100644
--- a/res/drawable/quantum_panel_shape_dark.xml
+++ b/res/drawable-v26/mask_drawable_wrapper.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-     Copyright (C) 2015 The Android Open Source Project
+     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.
@@ -14,9 +14,9 @@
      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="?attr/colorSecondary" />
-    <corners
-        android:radius="2dp" />
-</shape>
+<maskable-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:color="#FFE0E0E0"/>
+    <foreground>
+        <com.android.launcher3.graphics.FixedScaleDrawable />
+    </foreground>
+</maskable-icon>
diff --git a/res/drawable-xhdpi/virtual_preload.9.png b/res/drawable-xhdpi/virtual_preload.9.png
deleted file mode 100644
index 2afade1..0000000
--- a/res/drawable-xhdpi/virtual_preload.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/virtual_preload_folder.9.png b/res/drawable-xhdpi/virtual_preload_folder.9.png
deleted file mode 100644
index cb3fdca..0000000
--- a/res/drawable-xhdpi/virtual_preload_folder.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/widget_tile.png b/res/drawable-xhdpi/widget_tile.png
deleted file mode 100644
index be1748d..0000000
--- a/res/drawable-xhdpi/widget_tile.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/virtual_preload.9.png b/res/drawable-xxhdpi/virtual_preload.9.png
deleted file mode 100644
index 03c6e7f..0000000
--- a/res/drawable-xxhdpi/virtual_preload.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/virtual_preload_folder.9.png b/res/drawable-xxhdpi/virtual_preload_folder.9.png
deleted file mode 100644
index 052a72e..0000000
--- a/res/drawable-xxhdpi/virtual_preload_folder.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/widget_tile.png b/res/drawable-xxhdpi/widget_tile.png
deleted file mode 100644
index c6237db..0000000
--- a/res/drawable-xxhdpi/widget_tile.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/quantum_panel_dark.xml b/res/drawable/quantum_panel_dark.xml
deleted file mode 100644
index b113b37..0000000
--- a/res/drawable/quantum_panel_dark.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2015 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.
--->
-<inset xmlns:android="http://schemas.android.com/apk/res/android"
-       android:drawable="@drawable/quantum_panel_shape_dark"
-       android:insetBottom="@dimen/quantum_panel_outer_padding"
-       android:insetLeft="@dimen/quantum_panel_outer_padding"
-       android:insetRight="@dimen/quantum_panel_outer_padding"
-       android:insetTop="@dimen/quantum_panel_outer_padding" />
diff --git a/res/drawable/quantum_panel_shape_dark.xml b/res/drawable/round_rect_primary.xml
similarity index 88%
rename from res/drawable/quantum_panel_shape_dark.xml
rename to res/drawable/round_rect_primary.xml
index b299eb8..2c47e06 100644
--- a/res/drawable/quantum_panel_shape_dark.xml
+++ b/res/drawable/round_rect_primary.xml
@@ -16,7 +16,6 @@
 -->
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
-    <solid android:color="?attr/colorSecondary" />
-    <corners
-        android:radius="2dp" />
+    <solid android:color="?android:attr/colorPrimary" />
+    <corners android:radius="2dp" />
 </shape>
diff --git a/res/layout/add_item_confirmation_activity.xml b/res/layout/add_item_confirmation_activity.xml
index da079da..2aae5bc 100644
--- a/res/layout/add_item_confirmation_activity.xml
+++ b/res/layout/add_item_confirmation_activity.xml
@@ -22,10 +22,10 @@
     android:layout_height="match_parent">
 
     <TextView
-        android:paddingLeft="16dp"
-        android:paddingRight="16dp"
-        android:paddingTop="8dp"
-        android:paddingBottom="8dp"
+        android:paddingLeft="24dp"
+        android:paddingRight="24dp"
+        android:paddingTop="4dp"
+        android:paddingBottom="20dp"
         android:text="@string/add_item_request_drag_hint"
         android:layout_width="match_parent"
         android:layout_height="wrap_content" />
@@ -33,7 +33,7 @@
     <FrameLayout
         android:theme="@style/WidgetContainerTheme"
         android:layout_width="match_parent"
-        android:background="?android:attr/colorPrimary"
+        android:background="?android:attr/colorPrimaryDark"
         android:layout_height="wrap_content">
 
         <include
diff --git a/res/layout/all_apps.xml b/res/layout/all_apps.xml
index 1909f3b..5f5b38b 100644
--- a/res/layout/all_apps.xml
+++ b/res/layout/all_apps.xml
@@ -55,7 +55,7 @@
             android:focusable="true"
             android:paddingStart="@dimen/container_fastscroll_thumb_max_width"
             android:paddingEnd="@dimen/container_fastscroll_thumb_max_width"
-            android:theme="@style/CustomOverscroll.Light" />
+            android:theme="@style/AllAppsOverscroll" />
 
         <!-- Fast scroller popup -->
         <TextView
diff --git a/res/layout/appwidget_error.xml b/res/layout/appwidget_error.xml
index d6bd0c5..6a459c3 100644
--- a/res/layout/appwidget_error.xml
+++ b/res/layout/appwidget_error.xml
@@ -19,9 +19,10 @@
     android:layout_height="match_parent"
     android:gravity="center"
     android:elevation="2dp"
+    android:padding="4dp"
     android:theme="@style/WidgetContainerTheme"
-    android:background="@drawable/quantum_panel_dark"
+    android:background="@drawable/round_rect_primary"
     android:textAppearance="?android:attr/textAppearanceMediumInverse"
-    android:textColor="@color/widgets_view_item_text_color"
+    android:textColor="?android:attr/textColorSecondary"
     android:text="@string/gadget_error_text"
     />
diff --git a/res/layout/widget_cell.xml b/res/layout/widget_cell.xml
index c5a6753..b9b098c 100644
--- a/res/layout/widget_cell.xml
+++ b/res/layout/widget_cell.xml
@@ -20,7 +20,7 @@
     android:layout_weight="1"
     android:orientation="vertical"
     android:focusable="true"
-    android:background="?android:attr/colorPrimary"
+    android:background="?android:attr/colorPrimaryDark"
     android:gravity="center_horizontal">
 
     <LinearLayout
@@ -42,10 +42,9 @@
             android:fadingEdge="horizontal"
             android:fontFamily="sans-serif-condensed"
             android:gravity="start"
-            android:shadowColor="#B0000000"
-            android:shadowRadius="2.0"
             android:singleLine="true"
-            android:textColor="@color/widgets_view_item_text_color"
+            android:maxLines="1"
+            android:textColor="?android:attr/textColorSecondary"
             android:textSize="14sp" />
 
         <!-- The original dimensions of the widget (can't be the same text as above due to different
@@ -56,11 +55,10 @@
             android:layout_height="wrap_content"
             android:layout_marginStart="5dp"
             android:layout_marginLeft="5dp"
-            android:textColor="@color/widgets_view_item_text_color"
+            android:textColor="?android:attr/textColorSecondary"
             android:textSize="14sp"
             android:fontFamily="sans-serif-condensed"
-            android:shadowRadius="2.0"
-            android:shadowColor="#B0000000" />
+            android:alpha="0.8" />
     </LinearLayout>
 
     <!-- The image of the widget. This view does not support padding. Any placement adjustment
diff --git a/res/drawable/widgets_row_divider.xml b/res/layout/widget_list_divider.xml
similarity index 69%
rename from res/drawable/widgets_row_divider.xml
rename to res/layout/widget_list_divider.xml
index 2c3c7a2..68c9a45 100644
--- a/res/drawable/widgets_row_divider.xml
+++ b/res/layout/widget_list_divider.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2015 The Android Open Source Project
+<!-- 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.
@@ -13,7 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android" >
-    <size android:width="@dimen/widget_row_divider" />
-    <solid android:color="?attr/colorSecondary" />
-</shape>
+<View xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="@dimen/widget_row_divider"
+    android:layout_height="match_parent"
+    android:background="?android:attr/colorPrimary" />
\ No newline at end of file
diff --git a/res/layout/widgets_list_row_view.xml b/res/layout/widgets_list_row_view.xml
index 530e856..5c2e230 100644
--- a/res/layout/widgets_list_row_view.xml
+++ b/res/layout/widgets_list_row_view.xml
@@ -19,7 +19,6 @@
     android:id="@+id/widgets_cell_list_container"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:background="?android:attr/colorPrimary"
     android:orientation="vertical"
     android:focusable="true"
     android:descendantFocusability="afterDescendants">
@@ -30,7 +29,7 @@
         android:id="@+id/section"
         android:layout_width="match_parent"
         android:layout_height="@dimen/widget_section_height"
-        android:background="?attr/colorSecondary"
+        android:background="?android:attr/colorPrimary"
         android:drawablePadding="@dimen/widget_section_horizontal_padding"
         android:ellipsize="end"
         android:focusable="true"
@@ -51,9 +50,9 @@
 
     <HorizontalScrollView
         android:id="@+id/widgets_scroll_container"
-        android:theme="@style/CustomOverscroll.Dark"
-        android:layout_width="wrap_content"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
+        android:background="?android:attr/colorPrimaryDark"
         android:scrollbars="none">
         <LinearLayout
             android:id="@+id/widgets_cell_list"
@@ -62,7 +61,6 @@
             android:paddingStart="@dimen/widget_row_padding"
             android:paddingEnd="0dp"
             android:orientation="horizontal"
-            android:divider="@drawable/widgets_row_divider"
-            android:showDividers="middle"/>
+            android:showDividers="none"/>
     </HorizontalScrollView>
 </LinearLayout>
diff --git a/res/layout/widgets_view.xml b/res/layout/widgets_view.xml
index d193a5e..2f11c28 100644
--- a/res/layout/widgets_view.xml
+++ b/res/layout/widgets_view.xml
@@ -23,7 +23,7 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:descendantFocusability="afterDescendants"
-    launcher:revealBackground="@drawable/quantum_panel_shape_dark"
+    launcher:revealBackground="@drawable/round_rect_primary"
     android:theme="@style/WidgetContainerTheme">
 
     <View
@@ -45,7 +45,6 @@
 
         <com.android.launcher3.widget.WidgetsRecyclerView
             android:id="@+id/widgets_list_view"
-            android:theme="@style/CustomOverscroll.Dark"
             android:layout_width="match_parent"
             android:layout_height="match_parent" />
 
diff --git a/res/values-v25/styles.xml b/res/values-v26/styles.xml
similarity index 84%
rename from res/values-v25/styles.xml
rename to res/values-v26/styles.xml
index ed670a9..6877623 100644
--- a/res/values-v25/styles.xml
+++ b/res/values-v26/styles.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
 /*
-* Copyright (C) 2016 The Android Open Source Project
+* 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.
@@ -19,6 +19,6 @@
 <resources>
     <!-- Theme for the widget container. -->
     <style name="WidgetContainerTheme" parent="@android:style/Theme.DeviceDefault.Settings">
-        <item name="colorSecondary">?android:attr/colorSecondary</item>
+        <item name="android:colorEdgeEffect">?android:attr/textColorSecondary</item>
     </style>
 </resources>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 1e34c0f..18f409f 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -72,12 +72,6 @@
         <attr name="folderItems" format="reference" />
     </declare-styleable>
 
-    <declare-styleable name="PreloadIconDrawable">
-        <attr name="background" format="reference" />
-        <attr name="ringOutset" format="dimension" />
-        <attr name="indicatorSize" format="dimension" />
-    </declare-styleable>
-
     <declare-styleable name="InsettableFrameLayout_Layout">
         <attr name="layout_ignoreInsets" format="boolean" />
     </declare-styleable>
@@ -86,9 +80,6 @@
         <attr name="hideParentOnDisable" format="boolean" />
     </declare-styleable>
 
-    <!-- Fallback attr for pre-API 25 support -->
-    <attr name="colorSecondary" format="reference|color" />
-
     <declare-styleable name="InvariantDeviceProfile">
         <attr name="name" format="string" />
         <attr name="minWidthDps" format="float" />
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 19bf51a..d166a79 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -45,12 +45,5 @@
     <color name="spring_loaded_panel_color">#40FFFFFF</color>
     <color name="spring_loaded_highlighted_panel_border_color">#FFF</color>
 
-    <!-- Widgets view -->
-    <color name="widgets_view_item_text_color">#3B3B3B</color>
-
-    <!-- Used as a fallback since colorSecondary doesn't exist pre-API 25 -->
-    <color name="fallback_secondary_color">#FF37474F</color>
-
     <color name="notification_icon_default_color">#757575</color> <!-- Gray 600 -->
-    <color name="legacy_icon_background">#FFE0E0E0</color>
 </resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 2cf17ea..70f5b32 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -85,6 +85,11 @@
     <dimen name="widget_preview_label_vertical_padding">8dp</dimen>
     <dimen name="widget_preview_label_horizontal_padding">16dp</dimen>
 
+    <dimen name="widget_preview_shadow_blur">0.5dp</dimen>
+    <dimen name="widget_preview_key_shadow_distance">1dp</dimen>
+    <dimen name="widget_preview_corner_radius">2dp</dimen>
+    <dimen name="widget_preview_cell_divider_width">0.5dp</dimen>
+
     <dimen name="widget_section_height">56dp</dimen>
     <dimen name="widget_section_icon_size">40dp</dimen>
     <dimen name="widget_section_vertical_padding">8dp</dimen>
@@ -172,6 +177,8 @@
 <!-- Icon badges (with notification counts) -->
     <dimen name="badge_size">24dp</dimen>
     <dimen name="badge_text_size">12dp</dimen>
+    <dimen name="badge_small_padding">1dp</dimen>
+    <dimen name="badge_large_padding">3dp</dimen>
     <dimen name="notification_icon_size">28dp</dimen>
     <dimen name="notification_footer_icon_size">24dp</dimen>
     <!-- (icon_size - secondary_icon_size) / 2 -->
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 8b4a1db..879c7d8 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -46,20 +46,16 @@
 
     <!-- Theme for the widget container. Overridden on API 25. -->
     <style name="WidgetContainerTheme" parent="@android:style/Theme.DeviceDefault.Settings">
-        <item name="colorSecondary">@color/fallback_secondary_color</item>
+        <item name="android:colorEdgeEffect">@color/workspace_edge_effect_color</item>
+        <item name="android:textColorPrimary">?android:attr/textColorPrimaryInverse</item>
+        <item name="android:textColorSecondary">?android:attr/textColorSecondaryInverse</item>
     </style>
 
     <!-- Overscroll effect -->
-    <style name="CustomOverscroll" />
-
-    <style name="CustomOverscroll.Light" parent="@android:style/Theme.DeviceDefault.Light">
+    <style name="AllAppsOverscroll" parent="@android:style/Theme.DeviceDefault.Light">
         <item name="android:colorEdgeEffect">@color/folder_edge_effect_color</item>
     </style>
 
-    <style name="CustomOverscroll.Dark">
-        <item name="android:colorEdgeEffect">@color/workspace_edge_effect_color</item>
-    </style>
-
     <!-- Different icons -->
     <style name="Icon">
         <item name="android:layout_width">match_parent</item>
@@ -148,17 +144,4 @@
     </style>
 
     <style name="DropTargetButton" parent="DropTargetButtonBase" />
-
-    <!-- Virtual preloaders -->
-    <style name="PreloadIcon">
-        <item name="background">@drawable/virtual_preload</item>
-        <item name="indicatorSize">4dp</item>
-        <item name="ringOutset">4dp</item>
-    </style>
-
-    <style name="PreloadIcon.Folder">
-        <item name="background">@drawable/virtual_preload_folder</item>
-        <item name="indicatorSize">4dp</item>
-        <item name="ringOutset">4dp</item>
-    </style>
 </resources>
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index c834c6b..52a83dc 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -121,4 +121,6 @@
     public static AbstractFloatingView getTopOpenView(Launcher launcher) {
         return getOpenView(launcher, TYPE_FOLDER | TYPE_POPUP_CONTAINER_WITH_ARROW);
     }
+
+    public abstract int getLogContainerType();
 }
diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/AppInfo.java
index 6bec997..8bf49c2 100644
--- a/src/com/android/launcher3/AppInfo.java
+++ b/src/com/android/launcher3/AppInfo.java
@@ -91,17 +91,6 @@
         return super.dumpProperties() + " componentName=" + componentName;
     }
 
-    /**
-     * Helper method used for debugging.
-     */
-    public static void dumpApplicationInfoList(String tag, String label, ArrayList<AppInfo> list) {
-        Log.d(tag, label + " size=" + list.size());
-        for (AppInfo info: list) {
-            Log.d(tag, "   title=\"" + info.title + "\" iconBitmap=" + info.iconBitmap
-                    + " componentName=" + info.componentName.getPackageName());
-        }
-    }
-
     public ShortcutInfo makeShortcut() {
         return new ShortcutInfo(this);
     }
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 2efe31f..bbf2cb8 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -19,7 +19,6 @@
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
-import android.content.res.Resources.Theme;
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -27,7 +26,6 @@
 import android.graphics.Region;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
-import android.util.SparseArray;
 import android.util.TypedValue;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -45,6 +43,7 @@
 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 java.text.NumberFormat;
@@ -57,8 +56,6 @@
 public class BubbleTextView extends TextView
         implements BaseRecyclerViewFastScrollBar.FastScrollFocusableView, ItemInfoUpdateReceiver {
 
-    private static SparseArray<Theme> sPreloaderThemes = new SparseArray<Theme>(2);
-
     // Dimensions in DP
     private static final float AMBIENT_SHADOW_RADIUS = 2.5f;
     private static final float KEY_SHADOW_RADIUS = 1f;
@@ -170,7 +167,7 @@
             applyPromiseState(promiseStateChanged);
         }
 
-        applyBadgeState(info);
+        applyBadgeState(info, false /* animate */);
     }
 
     public void applyFromApplicationInfo(AppInfo info) {
@@ -182,7 +179,7 @@
         // Verify high res immediately
         verifyHighRes();
 
-        applyBadgeState(info);
+        applyBadgeState(info, false /* animate */);
     }
 
     public void applyFromPackageItemInfo(PackageItemInfo info) {
@@ -423,10 +420,6 @@
         super.onAttachedToWindow();
 
         if (mBackground != null) mBackground.setCallback(this);
-
-        if (mIcon instanceof PreloadIconDrawable) {
-            ((PreloadIconDrawable) mIcon).applyPreloaderTheme(getPreloaderTheme());
-        }
         mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
     }
 
@@ -495,7 +488,8 @@
                 if (mIcon instanceof PreloadIconDrawable) {
                     preloadDrawable = (PreloadIconDrawable) mIcon;
                 } else {
-                    preloadDrawable = new PreloadIconDrawable(mIcon, getPreloaderTheme());
+                    preloadDrawable = DrawableFactory.get(getContext())
+                            .newPendingIcon(info.iconBitmap, getContext());
                     setIcon(preloadDrawable);
                 }
 
@@ -507,11 +501,11 @@
         }
     }
 
-    public void applyBadgeState(ItemInfo itemInfo) {
+    public void applyBadgeState(ItemInfo itemInfo, boolean animate) {
         if (mIcon instanceof FastBitmapDrawable) {
             BadgeInfo badgeInfo = mLauncher.getPopupDataProvider().getBadgeInfoForItem(itemInfo);
             BadgeRenderer badgeRenderer = mLauncher.getDeviceProfile().mBadgeRenderer;
-            ((FastBitmapDrawable) mIcon).applyIconBadge(badgeInfo, badgeRenderer);
+            ((FastBitmapDrawable) mIcon).applyIconBadge(badgeInfo, badgeRenderer, animate);
         }
     }
 
@@ -520,20 +514,6 @@
                 : null;
     }
 
-    private Theme getPreloaderTheme() {
-        Object tag = getTag();
-        int style = ((tag != null) && (tag instanceof ShortcutInfo) &&
-                (((ShortcutInfo) tag).container >= 0)) ? R.style.PreloadIcon_Folder
-                        : R.style.PreloadIcon;
-        Theme theme = sPreloaderThemes.get(style);
-        if (theme == null) {
-            theme = getResources().newTheme();
-            theme.applyStyle(style, true);
-            sPreloaderThemes.put(style, theme);
-        }
-        return theme;
-    }
-
     /**
      * Sets the icon for this view based on the layout direction.
      */
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index da12e67..0df8e5a 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -951,7 +951,7 @@
                 return true;
             }
 
-            ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
+            ValueAnimator va = LauncherAnimUtils.ofFloat(0f, 1f);
             va.setDuration(duration);
             mReorderAnimators.put(lp, va);
 
@@ -2047,7 +2047,7 @@
             if (noMovement) {
                 return;
             }
-            ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
+            ValueAnimator va = LauncherAnimUtils.ofFloat(0f, 1f);
             a = va;
 
             // Animations are disabled in power save mode, causing the repeated animation to jump
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 34ce923..c9e3d4f 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -196,9 +196,7 @@
         hotseatBarBottomPaddingPx = 0;
         hotseatLandGutterPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_gutter_width);
 
-        int badgeSize = res.getDimensionPixelSize(R.dimen.badge_size);
-        int badgeTextSize = res.getDimensionPixelSize(R.dimen.badge_text_size);
-        mBadgeRenderer = new BadgeRenderer(badgeSize, badgeTextSize);
+        mBadgeRenderer = new BadgeRenderer(context);
 
         // Determine sizes.
         widthPx = width;
diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java
index df19547..1f74c88 100644
--- a/src/com/android/launcher3/FastBitmapDrawable.java
+++ b/src/com/android/launcher3/FastBitmapDrawable.java
@@ -30,7 +30,7 @@
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffColorFilter;
 import android.graphics.drawable.Drawable;
-import android.util.Log;
+import android.util.Property;
 import android.util.SparseArray;
 import android.view.animation.DecelerateInterpolator;
 
@@ -107,6 +107,21 @@
     private BadgeInfo mBadgeInfo;
     private BadgeRenderer mBadgeRenderer;
     private IconPalette mIconPalette;
+    private float mBadgeScale;
+
+    private static final Property<FastBitmapDrawable, Float> BADGE_SCALE_PROPERTY
+            = new Property<FastBitmapDrawable, Float>(Float.TYPE, "badgeScale") {
+        @Override
+        public Float get(FastBitmapDrawable fastBitmapDrawable) {
+            return fastBitmapDrawable.mBadgeScale;
+        }
+
+        @Override
+        public void set(FastBitmapDrawable fastBitmapDrawable, Float value) {
+            fastBitmapDrawable.mBadgeScale = value;
+            fastBitmapDrawable.invalidateSelf();
+        }
+    };
 
     // The saturation and brightness are values that are mapped to REDUCED_FILTER_VALUE_SPACE and
     // as a result, can be used to compose the key for the cached ColorMatrixColorFilters
@@ -120,17 +135,25 @@
 
     public FastBitmapDrawable(Bitmap b) {
         mBitmap = b;
-        setBounds(0, 0, b.getWidth(), b.getHeight());
+        setFilterBitmap(true);
     }
 
-    public void applyIconBadge(BadgeInfo badgeInfo, BadgeRenderer badgeRenderer) {
+    public void applyIconBadge(final BadgeInfo badgeInfo, BadgeRenderer badgeRenderer,
+            boolean animate) {
         boolean wasBadged = mBadgeInfo != null;
         boolean isBadged = badgeInfo != null;
+        float newBadgeScale = isBadged ? 1f : 0;
         mBadgeInfo = badgeInfo;
         mBadgeRenderer = badgeRenderer;
         if (wasBadged || isBadged) {
             mIconPalette = getIconPalette();
-            invalidateSelf();
+            // Animate when a badge is first added or when it is removed.
+            if (animate && (wasBadged ^ isBadged) && isVisible()) {
+                ObjectAnimator.ofFloat(this, BADGE_SCALE_PROPERTY, newBadgeScale).start();
+            } else {
+                mBadgeScale = newBadgeScale;
+                invalidateSelf();
+            }
         }
     }
 
@@ -154,7 +177,7 @@
 
     protected void drawBadgeIfNecessary(Canvas canvas) {
         if (hasBadge()) {
-            mBadgeRenderer.draw(canvas, mIconPalette, mBadgeInfo, getBounds());
+            mBadgeRenderer.draw(canvas, mIconPalette, mBadgeInfo, getBounds(), mBadgeScale);
         }
     }
 
@@ -167,7 +190,7 @@
     }
 
     private boolean hasBadge() {
-        return mBadgeInfo != null && mBadgeInfo.getNotificationCount() != 0;
+        return (mBadgeInfo != null && mBadgeInfo.getNotificationCount() > 0) || mBadgeScale > 0;
     }
 
     @Override
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index 5b487b6..6e9820c 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -45,6 +45,7 @@
 import android.support.annotation.NonNull;
 import android.text.TextUtils;
 import android.util.Log;
+import android.view.ContextThemeWrapper;
 
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.UserManagerCompat;
@@ -132,9 +133,9 @@
         mWorkerHandler = new Handler(LauncherModel.getWorkerLooper());
 
         mActivityBgColor = context.getResources().getColor(R.color.quantum_panel_bg_color);
-        TypedArray ta = context.obtainStyledAttributes(new int[]{R.attr.colorSecondary});
-        mPackageBgColor = ta.getColor(0, 0);
-        ta.recycle();
+        mPackageBgColor = Utilities.getAttrColor(
+                new ContextThemeWrapper(context, R.style.WidgetContainerTheme),
+                android.R.attr.colorPrimary);
         mLowResOptions = new BitmapFactory.Options();
         // Always prefer RGB_565 config for low res. If the bitmap has transparency, it will
         // automatically be loaded as ALPHA_8888.
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index 96d6b89..1bab774 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -163,8 +163,8 @@
         if (info != null) {
             if (!info.isLauncherActivity()) {
                 // Since its a custom shortcut, verify that it is safe to launch.
-                if (!PackageManagerHelper.hasPermissionForActivity(
-                        context, info.launchIntent, null)) {
+                if (!new PackageManagerHelper(context).hasPermissionForActivity(
+                        info.launchIntent, null)) {
                     // Target cannot be launched, or requires some special permission to launch
                     Log.e(TAG, "Ignoring malicious intent " + info.launchIntent.toUri(0));
                     return;
diff --git a/src/com/android/launcher3/InterruptibleInOutAnimator.java b/src/com/android/launcher3/InterruptibleInOutAnimator.java
index 29df38b..8501e24 100644
--- a/src/com/android/launcher3/InterruptibleInOutAnimator.java
+++ b/src/com/android/launcher3/InterruptibleInOutAnimator.java
@@ -48,7 +48,7 @@
     @Thunk int mDirection = STOPPED;
 
     public InterruptibleInOutAnimator(View view, long duration, float fromValue, float toValue) {
-        mAnimator = LauncherAnimUtils.ofFloat(view, fromValue, toValue).setDuration(duration);
+        mAnimator = LauncherAnimUtils.ofFloat(fromValue, toValue).setDuration(duration);
         mOriginalDuration = duration;
         mOriginalFromValue = fromValue;
         mOriginalToValue = toValue;
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 11db9a0..a160c41 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -111,12 +111,14 @@
 import com.android.launcher3.popup.PopupContainerWithArrow;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
 import com.android.launcher3.util.ActivityResultInfo;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.launcher3.util.LogConfig;
 import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.PackageUserKey;
@@ -172,10 +174,6 @@
      */
     protected static final int REQUEST_LAST = 100;
 
-    // To turn on these properties, type
-    // adb shell setprop logTap.tag.PROPERTY_NAME [VERBOSE | SUPPRESS]
-    static final String DUMP_STATE_PROPERTY = "launcher_dump_state";
-
     // The Intent extra that defines whether to ignore the launch animation
     static final String INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION =
             "com.android.launcher3.intent.extra.shortcut.INGORE_LAUNCH_ANIMATION";
@@ -1464,8 +1462,8 @@
         if (info == null) {
             info = InstallShortcutReceiver.fromShortcutIntent(this, data);
 
-            if (info == null || !PackageManagerHelper.hasPermissionForActivity(
-                    this, info.intent, args.getPendingIntent().getComponent().getPackageName())) {
+            if (info == null || !new PackageManagerHelper(this).hasPermissionForActivity(
+                    info.intent, args.getPendingIntent().getComponent().getPackageName())) {
                 // The app is trying to add a shortcut without sufficient permissions
                 Log.e(TAG, "Ignoring malicious intent " + info.intent.toUri(0));
                 return;
@@ -2201,25 +2199,7 @@
 
     @Override
     public boolean dispatchKeyEvent(KeyEvent event) {
-        if (event.getAction() == KeyEvent.ACTION_DOWN) {
-            switch (event.getKeyCode()) {
-                case KeyEvent.KEYCODE_HOME:
-                    return true;
-                case KeyEvent.KEYCODE_VOLUME_DOWN:
-                    if (Utilities.isPropertyEnabled(DUMP_STATE_PROPERTY)) {
-                        dumpState();
-                        return true;
-                    }
-                    break;
-            }
-        } else if (event.getAction() == KeyEvent.ACTION_UP) {
-            switch (event.getKeyCode()) {
-                case KeyEvent.KEYCODE_HOME:
-                    return true;
-            }
-        }
-
-        return super.dispatchKeyEvent(event);
+        return (event.getKeyCode() == KeyEvent.KEYCODE_HOME) || super.dispatchKeyEvent(event);
     }
 
     @Override
@@ -2293,7 +2273,11 @@
 
         if (v instanceof CellLayout) {
             if (mWorkspace.isInOverviewMode()) {
-                mWorkspace.snapToPageFromOverView(mWorkspace.indexOfChild(v));
+                int page = mWorkspace.indexOfChild(v);
+                getUserEventDispatcher().logActionOnContainer(LauncherLogProto.Action.Type.TOUCH,
+                        LauncherLogProto.Action.Direction.NONE,
+                        LauncherLogProto.ContainerType.OVERVIEW, page);
+                mWorkspace.snapToPageFromOverView(page);
                 showWorkspace(true);
             }
             return;
@@ -3968,50 +3952,48 @@
     }
 
     /**
-     * Prints out out state for debugging.
+     * $ adb shell dumpsys activity com.android.launcher3.Launcher [--all]
      */
-    public void dumpState() {
-        Log.d(TAG, "BEGIN launcher3 dump state for launcher " + this);
-        Log.d(TAG, "mWorkspaceLoading=" + mWorkspaceLoading);
-        Log.d(TAG, "mPendingRequestArgs=" + mPendingRequestArgs);
-        Log.d(TAG, "mPendingActivityResult=" + mPendingActivityResult);
-        mModel.dumpState();
-        // TODO(hyunyoungs): add mWidgetsView.dumpState(); or mWidgetsModel.dumpState();
-
-        Log.d(TAG, "END launcher3 dump state");
-    }
-
     @Override
     public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
         super.dump(prefix, fd, writer, args);
-        // Dump workspace
-        writer.println(prefix + "Workspace Items");
-        for (int i = mWorkspace.numCustomPages(); i < mWorkspace.getPageCount(); i++) {
-            writer.println(prefix + "  Homescreen " + i);
 
-            ViewGroup layout = ((CellLayout) mWorkspace.getPageAt(i)).getShortcutsAndWidgets();
+        if (args.length > 0 && TextUtils.equals(args[0], "--all")) {
+            writer.println(prefix + "Workspace Items");
+            for (int i = mWorkspace.numCustomPages(); i < mWorkspace.getPageCount(); i++) {
+                writer.println(prefix + "  Homescreen " + i);
+
+                ViewGroup layout = ((CellLayout) mWorkspace.getPageAt(i)).getShortcutsAndWidgets();
+                for (int j = 0; j < layout.getChildCount(); j++) {
+                    Object tag = layout.getChildAt(j).getTag();
+                    if (tag != null) {
+                        writer.println(prefix + "    " + tag.toString());
+                    }
+                }
+            }
+
+            writer.println(prefix + "  Hotseat");
+            ViewGroup layout = mHotseat.getLayout().getShortcutsAndWidgets();
             for (int j = 0; j < layout.getChildCount(); j++) {
                 Object tag = layout.getChildAt(j).getTag();
                 if (tag != null) {
                     writer.println(prefix + "    " + tag.toString());
                 }
             }
-        }
 
-        writer.println(prefix + "  Hotseat");
-        ViewGroup layout = mHotseat.getLayout().getShortcutsAndWidgets();
-        for (int j = 0; j < layout.getChildCount(); j++) {
-            Object tag = layout.getChildAt(j).getTag();
-            if (tag != null) {
-                writer.println(prefix + "    " + tag.toString());
+            try {
+                FileLog.flushAll(writer);
+            } catch (Exception e) {
+                // Ignore
             }
         }
 
-        try {
-            FileLog.flushAll(writer);
-        } catch (Exception e) {
-            // Ignore
-        }
+        writer.println(prefix + "Misc:");
+        writer.print(prefix + "\tmWorkspaceLoading=" + mWorkspaceLoading);
+        writer.print(" mPendingRequestArgs=" + mPendingRequestArgs);
+        writer.println(" mPendingActivityResult=" + mPendingActivityResult);
+
+        mModel.dumpState(prefix, fd, writer, args);
 
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.dump(prefix, fd, writer, args);
diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java
index 9ea277c..43cf827 100644
--- a/src/com/android/launcher3/LauncherAnimUtils.java
+++ b/src/com/android/launcher3/LauncherAnimUtils.java
@@ -94,7 +94,7 @@
         return anim;
     }
 
-    public static ValueAnimator ofFloat(View target, float... values) {
+    public static ValueAnimator ofFloat(float... values) {
         ValueAnimator anim = new ValueAnimator();
         anim.setFloatValues(values);
         cancelOnDestroyActivity(anim);
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 79f9792..9ad8433 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -81,6 +81,8 @@
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.ViewOnDrawExecutor;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
 import java.net.URISyntaxException;
 import java.util.ArrayList;
@@ -1095,12 +1097,11 @@
             if (LauncherAppState.PROFILE_STARTUP) {
                 Trace.beginSection("Loading Workspace");
             }
-            final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
 
             final Context context = mContext;
             final ContentResolver contentResolver = context.getContentResolver();
-            final PackageManager manager = context.getPackageManager();
-            final boolean isSafeMode = manager.isSafeMode();
+            final PackageManagerHelper pmHelper = new PackageManagerHelper(context);
+            final boolean isSafeMode = pmHelper.isSafeMode();
             final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
             final DeepShortcutManager shortcutManager = DeepShortcutManager.getInstance(context);
             final boolean isSdCardReady = Utilities.isBootCompleted();
@@ -1187,7 +1188,6 @@
                     }
 
                     ShortcutInfo info;
-                    String intentDescription;
                     LauncherAppWidgetInfo appWidgetInfo;
                     Intent intent;
                     String targetPkg;
@@ -1249,7 +1249,7 @@
                                         if (c.hasRestoreFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
                                             // We allow auto install apps to have their intent
                                             // updated after an install.
-                                            intent = manager.getLaunchIntentForPackage(targetPkg);
+                                            intent = pmHelper.getAppLaunchIntent(targetPkg, c.user);
                                             if (intent != null) {
                                                 c.restoreFlag = 0;
                                                 c.updater().put(
@@ -1290,8 +1290,7 @@
                                             c.markDeleted("Unrestored app removed: " + targetPkg);
                                             continue;
                                         }
-                                    } else if (PackageManagerHelper.isAppOnSdcard(
-                                            manager, targetPkg)) {
+                                    } else if (pmHelper.isAppOnSdcard(targetPkg)) {
                                         // Package is present but not available.
                                         disabledState |= ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
                                         // Add the icon on the workspace anyway.
@@ -1351,7 +1350,7 @@
                                     info = c.loadSimpleShortcut();
 
                                     // Shortcuts are only available on the primary profile
-                                    if (PackageManagerHelper.isAppSuspended(manager, targetPkg)) {
+                                    if (pmHelper.isAppSuspended(targetPkg)) {
                                         disabledState |= ShortcutInfo.FLAG_DISABLED_SUSPENDED;
                                     }
 
@@ -2098,15 +2097,6 @@
             }
             bindDeepShortcuts();
         }
-
-        public void dumpState() {
-            synchronized (sBgDataModel) {
-                Log.d(TAG, "mLoaderTask.mContext=" + mContext);
-                Log.d(TAG, "mLoaderTask.mStopped=" + mStopped);
-                Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished);
-                Log.d(TAG, "mItems size=" + sBgDataModel.workspaceItems.size());
-            }
-        }
     }
 
     public void bindDeepShortcuts() {
@@ -2267,17 +2257,15 @@
                 && (provider.provider.getPackageName() != null);
     }
 
-    public void dumpState() {
-        Log.d(TAG, "mCallbacks=" + mCallbacks);
-        AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.data", mBgAllAppsList.data);
-        AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.added", mBgAllAppsList.added);
-        AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.removed", mBgAllAppsList.removed);
-        AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.modified", mBgAllAppsList.modified);
-        if (mLoaderTask != null) {
-            mLoaderTask.dumpState();
-        } else {
-            Log.d(TAG, "mLoaderTask=null");
+    public void dumpState(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+        if (args.length > 0 && TextUtils.equals(args[0], "--all")) {
+            writer.println(prefix + "All apps list: size=" + mBgAllAppsList.data.size());
+            for (AppInfo info : mBgAllAppsList.data) {
+                writer.println(prefix + "   title=\"" + info.title + "\" iconBitmap=" + info.iconBitmap
+                        + " componentName=" + info.componentName.getPackageName());
+            }
         }
+        sBgDataModel.dump(prefix, fd, writer, args);
     }
 
     public Callbacks getCallback() {
diff --git a/src/com/android/launcher3/PendingAppWidgetHostView.java b/src/com/android/launcher3/PendingAppWidgetHostView.java
index 3256df6..815fd10 100644
--- a/src/com/android/launcher3/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/PendingAppWidgetHostView.java
@@ -17,7 +17,6 @@
 package com.android.launcher3;
 
 import android.content.Context;
-import android.content.res.Resources.Theme;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
@@ -33,8 +32,8 @@
 import android.view.View;
 import android.view.View.OnClickListener;
 
-import com.android.launcher3.graphics.DrawableFactory;
 import com.android.launcher3.IconCache.ItemInfoUpdateReceiver;
+import com.android.launcher3.graphics.DrawableFactory;
 import com.android.launcher3.model.PackageItemInfo;
 
 public class PendingAppWidgetHostView extends LauncherAppWidgetHostView
@@ -42,8 +41,6 @@
     private static final float SETUP_ICON_SIZE_FACTOR = 2f / 5;
     private static final float MIN_SATUNATION = 0.7f;
 
-    private static Theme sPreloaderTheme;
-
     private final Rect mRect = new Rect();
     private View mDefaultView;
     private OnClickListener mClickListener;
@@ -72,10 +69,10 @@
         mDisabledForSafeMode = disabledForSafeMode;
 
         mPaint = new TextPaint();
-        mPaint.setColor(0xFFFFFFFF);
+        mPaint.setColor(Utilities.getAttrColor(getContext(), android.R.attr.textColorPrimary));
         mPaint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX,
                 mLauncher.getDeviceProfile().iconTextSizePx, getResources().getDisplayMetrics()));
-        setBackgroundResource(R.drawable.quantum_panel_dark);
+        setBackgroundResource(R.drawable.round_rect_primary);
         setWillNotDraw(false);
 
         setElevation(getResources().getDimension(R.dimen.pending_widget_elevation));
@@ -149,13 +146,8 @@
 
                 updateSettingColor();
             } else {
-                if (sPreloaderTheme == null) {
-                    sPreloaderTheme = getResources().newTheme();
-                    sPreloaderTheme.applyStyle(R.style.PreloadIcon, true);
-                }
-
-                FastBitmapDrawable drawable = drawableFactory.newIcon(mIcon, mInfo);
-                mCenterDrawable = new PreloadIconDrawable(drawable, sPreloaderTheme);
+                mCenterDrawable = DrawableFactory.get(getContext())
+                        .newPendingIcon(mIcon, getContext());
                 mCenterDrawable.setCallback(this);
                 mSettingIconDrawable = null;
                 applyState();
@@ -226,13 +218,10 @@
         int availableHeight = getHeight() - paddingTop - paddingBottom - 2 * minPadding;
 
         if (mSettingIconDrawable == null) {
-            int outset = (mCenterDrawable instanceof PreloadIconDrawable) ?
-                    ((PreloadIconDrawable) mCenterDrawable).getOutset() : 0;
-            int maxSize = grid.iconSizePx + 2 * outset;
+            int maxSize = grid.iconSizePx;
             int size = Math.min(maxSize, Math.min(availableWidth, availableHeight));
 
             mRect.set(0, 0, size, size);
-            mRect.inset(outset, outset);
             mRect.offsetTo((getWidth() - mRect.width()) / 2, (getHeight() - mRect.height()) / 2);
             mCenterDrawable.setBounds(mRect);
         } else  {
diff --git a/src/com/android/launcher3/PreloadIconDrawable.java b/src/com/android/launcher3/PreloadIconDrawable.java
deleted file mode 100644
index 973e688..0000000
--- a/src/com/android/launcher3/PreloadIconDrawable.java
+++ /dev/null
@@ -1,252 +0,0 @@
-package com.android.launcher3;
-
-import android.animation.ObjectAnimator;
-import android.content.res.Resources.Theme;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ColorFilter;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.Drawable;
-
-public class PreloadIconDrawable extends Drawable {
-
-    private static final float ANIMATION_PROGRESS_STOPPED = -1.0f;
-    private static final float ANIMATION_PROGRESS_STARTED = 0f;
-    private static final float ANIMATION_PROGRESS_COMPLETED = 1.0f;
-
-    private static final float MIN_SATURATION = 0.2f;
-    private static final float MIN_LIGHTNESS = 0.6f;
-
-    private static final float ICON_SCALE_FACTOR = 0.5f;
-    private static final int DEFAULT_COLOR = 0xFF009688;
-
-    private static final Rect sTempRect = new Rect();
-
-    private final RectF mIndicatorRect = new RectF();
-    private boolean mIndicatorRectDirty;
-
-    private final Paint mPaint;
-    public final Drawable mIcon;
-
-    private Drawable mBgDrawable;
-    private int mRingOutset;
-
-    private int mIndicatorColor = 0;
-
-    /**
-     * Indicates the progress of the preloader [0-100]. If it goes above 100, only the icon
-     * is shown with no progress bar.
-     */
-    private int mProgress = 0;
-
-    private float mAnimationProgress = ANIMATION_PROGRESS_STOPPED;
-    private ObjectAnimator mAnimator;
-
-    public PreloadIconDrawable(Drawable icon, Theme theme) {
-        mIcon = icon;
-
-        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-        mPaint.setStyle(Paint.Style.STROKE);
-        mPaint.setStrokeCap(Paint.Cap.ROUND);
-
-        setBounds(icon.getBounds());
-        applyPreloaderTheme(theme);
-        onLevelChange(0);
-    }
-
-    public void applyPreloaderTheme(Theme t) {
-        TypedArray ta = t.obtainStyledAttributes(R.styleable.PreloadIconDrawable);
-        mBgDrawable = ta.getDrawable(R.styleable.PreloadIconDrawable_background);
-        mBgDrawable.setFilterBitmap(true);
-        mPaint.setStrokeWidth(ta.getDimension(R.styleable.PreloadIconDrawable_indicatorSize, 0));
-        mRingOutset = ta.getDimensionPixelSize(R.styleable.PreloadIconDrawable_ringOutset, 0);
-        ta.recycle();
-        onBoundsChange(getBounds());
-        invalidateSelf();
-    }
-
-    @Override
-    protected void onBoundsChange(Rect bounds) {
-        mIcon.setBounds(bounds);
-        if (mBgDrawable != null) {
-            sTempRect.set(bounds);
-            sTempRect.inset(-mRingOutset, -mRingOutset);
-            mBgDrawable.setBounds(sTempRect);
-        }
-        mIndicatorRectDirty = true;
-    }
-
-    public int getOutset() {
-        return mRingOutset;
-    }
-
-    /**
-     * The size of the indicator is same as the content region of the {@link #mBgDrawable} minus
-     * half the stroke size to accommodate the indicator.
-     */
-    private void initIndicatorRect() {
-        Drawable d = mBgDrawable;
-        Rect bounds = d.getBounds();
-
-        d.getPadding(sTempRect);
-        // Amount by which padding has to be scaled
-        float paddingScaleX = ((float) bounds.width()) / d.getIntrinsicWidth();
-        float paddingScaleY = ((float) bounds.height()) / d.getIntrinsicHeight();
-        mIndicatorRect.set(
-                bounds.left + sTempRect.left * paddingScaleX,
-                bounds.top + sTempRect.top * paddingScaleY,
-                bounds.right - sTempRect.right * paddingScaleX,
-                bounds.bottom - sTempRect.bottom * paddingScaleY);
-
-        float inset = mPaint.getStrokeWidth() / 2;
-        mIndicatorRect.inset(inset, inset);
-        mIndicatorRectDirty = false;
-    }
-
-    @Override
-    public void draw(Canvas canvas) {
-        final Rect r = new Rect(getBounds());
-        if (canvas.getClipBounds(sTempRect) && !Rect.intersects(sTempRect, r)) {
-            // The draw region has been clipped.
-            return;
-        }
-        if (mIndicatorRectDirty) {
-            initIndicatorRect();
-        }
-        final float iconScale;
-
-        if ((mAnimationProgress >= ANIMATION_PROGRESS_STARTED)
-                && (mAnimationProgress < ANIMATION_PROGRESS_COMPLETED)) {
-            mPaint.setAlpha((int) ((1 - mAnimationProgress) * 255));
-            mBgDrawable.setAlpha(mPaint.getAlpha());
-            mBgDrawable.draw(canvas);
-            canvas.drawOval(mIndicatorRect, mPaint);
-
-            iconScale = ICON_SCALE_FACTOR + (1 - ICON_SCALE_FACTOR) * mAnimationProgress;
-        } else if (mAnimationProgress == ANIMATION_PROGRESS_STOPPED) {
-            mPaint.setAlpha(255);
-            iconScale = ICON_SCALE_FACTOR;
-            mBgDrawable.setAlpha(255);
-            mBgDrawable.draw(canvas);
-
-            if (mProgress >= 100) {
-                canvas.drawOval(mIndicatorRect, mPaint);
-            } else if (mProgress > 0) {
-                canvas.drawArc(mIndicatorRect, -90, mProgress * 3.6f, false, mPaint);
-            }
-        } else {
-            iconScale = 1;
-        }
-
-        canvas.save();
-        canvas.scale(iconScale, iconScale, r.exactCenterX(), r.exactCenterY());
-        mIcon.draw(canvas);
-        canvas.restore();
-    }
-
-    @Override
-    public int getOpacity() {
-        return PixelFormat.TRANSLUCENT;
-    }
-
-    @Override
-    public void setAlpha(int alpha) {
-        mIcon.setAlpha(alpha);
-    }
-
-    @Override
-    public void setColorFilter(ColorFilter cf) {
-        mIcon.setColorFilter(cf);
-    }
-
-    @Override
-    protected boolean onLevelChange(int level) {
-        mProgress = level;
-
-        // Stop Animation
-        if (mAnimator != null) {
-            mAnimator.cancel();
-            mAnimator = null;
-        }
-        mAnimationProgress = ANIMATION_PROGRESS_STOPPED;
-        if (level > 0) {
-            // Set the paint color only when the level changes, so that the dominant color
-            // is only calculated when needed.
-            mPaint.setColor(getIndicatorColor());
-        }
-        if (mIcon instanceof FastBitmapDrawable) {
-            ((FastBitmapDrawable) mIcon).setIsDisabled(level < 100);
-        }
-
-        invalidateSelf();
-        return true;
-    }
-
-    /**
-     * Runs the finish animation if it is has not been run after last level change.
-     */
-    public void maybePerformFinishedAnimation() {
-        if (mAnimationProgress > ANIMATION_PROGRESS_STOPPED) {
-            return;
-        }
-        if (mAnimator != null) {
-            mAnimator.cancel();
-        }
-        setAnimationProgress(ANIMATION_PROGRESS_STARTED);
-        mAnimator = ObjectAnimator.ofFloat(this, "animationProgress",
-                ANIMATION_PROGRESS_STARTED, ANIMATION_PROGRESS_COMPLETED);
-        mAnimator.start();
-    }
-
-    public void setAnimationProgress(float progress) {
-        if (progress != mAnimationProgress) {
-            mAnimationProgress = progress;
-            invalidateSelf();
-        }
-    }
-
-    public float getAnimationProgress() {
-        return mAnimationProgress;
-    }
-
-    public boolean hasNotCompleted() {
-        return mAnimationProgress < ANIMATION_PROGRESS_COMPLETED;
-    }
-
-    @Override
-    public int getIntrinsicHeight() {
-        return mIcon.getIntrinsicHeight();
-    }
-
-    @Override
-    public int getIntrinsicWidth() {
-        return mIcon.getIntrinsicWidth();
-    }
-
-    private int getIndicatorColor() {
-        if (mIndicatorColor != 0) {
-            return mIndicatorColor;
-        }
-        if (!(mIcon instanceof FastBitmapDrawable)) {
-            mIndicatorColor = DEFAULT_COLOR;
-            return mIndicatorColor;
-        }
-        mIndicatorColor = Utilities.findDominantColorByHue(
-                ((FastBitmapDrawable) mIcon).getBitmap(), 20);
-
-        // Make sure that the dominant color has enough saturation to be visible properly.
-        float[] hsv = new float[3];
-        Color.colorToHSV(mIndicatorColor, hsv);
-        if (hsv[1] < MIN_SATURATION) {
-            mIndicatorColor = DEFAULT_COLOR;
-            return mIndicatorColor;
-        }
-        hsv[2] = Math.max(MIN_LIGHTNESS, hsv[2]);
-        mIndicatorColor = Color.HSVToColor(hsv);
-        return mIndicatorColor;
-    }
-}
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 267cb2a..78774f3 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -60,6 +60,7 @@
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.util.Collection;
+import java.util.HashSet;
 import java.util.Locale;
 import java.util.Set;
 import java.util.concurrent.Executor;
@@ -608,7 +609,11 @@
     }
 
     public static int getColorAccent(Context context) {
-        TypedArray ta = context.obtainStyledAttributes(new int[]{android.R.attr.colorAccent});
+        return getAttrColor(context, android.R.attr.colorAccent);
+    }
+
+    public static int getAttrColor(Context context, int attr) {
+        TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
         int colorAccent = ta.getColor(0, 0);
         ta.recycle();
         return colorAccent;
@@ -648,4 +653,14 @@
             throw new RuntimeException(e);
         }
     }
+
+    /**
+     * Returns a HashSet with a single element. We use this instead of Collections.singleton()
+     * because HashSet ensures all operations, such as remove, are supported.
+     */
+    public static <T> HashSet<T> singletonHashSet(T elem) {
+        HashSet<T> hashSet = new HashSet<>(1);
+        hashSet.add(elem);
+        return hashSet;
+    }
 }
diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java
index 091ae99..3512210 100644
--- a/src/com/android/launcher3/WidgetPreviewLoader.java
+++ b/src/com/android/launcher3/WidgetPreviewLoader.java
@@ -18,9 +18,8 @@
 import android.graphics.ColorMatrixColorFilter;
 import android.graphics.Paint;
 import android.graphics.PorterDuff;
-import android.graphics.Rect;
+import android.graphics.PorterDuffXfermode;
 import android.graphics.RectF;
-import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.AsyncTask;
 import android.os.Handler;
@@ -31,6 +30,7 @@
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.ShortcutConfigActivityInfo;
 import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.graphics.ShadowGenerator;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.Preconditions;
@@ -52,8 +52,6 @@
     private static final String TAG = "WidgetPreviewLoader";
     private static final boolean DEBUG = false;
 
-    private static final float WIDGET_PREVIEW_ICON_PADDING_PERCENTAGE = 0.25f;
-
     private final HashMap<String, long[]> mPackageVersions = new HashMap<>();
 
     /**
@@ -104,7 +102,7 @@
      * sizes (landscape vs portrait).
      */
     private static class CacheDb extends SQLiteCacheHelper {
-        private static final int DB_VERSION = 5;
+        private static final int DB_VERSION = 6;
 
         private static final String TABLE_NAME = "shortcut_and_widget_previews";
         private static final String COLUMN_COMPONENT = "componentName";
@@ -299,7 +297,7 @@
         Drawable drawable = null;
         if (info.previewImage != 0) {
             try {
-                drawable = info.loadPreviewImage(launcher.getApplicationContext(), 0);
+                drawable = info.loadPreviewImage(mContext, 0);
             } catch (OutOfMemoryError e) {
                 Log.w(TAG, "Error loading widget preview for: " + info.provider, e);
                 // During OutOfMemoryError, the previous heap stack is not affected. Catching
@@ -321,17 +319,14 @@
         int previewWidth;
         int previewHeight;
 
-        Bitmap tileBitmap = null;
-
         if (widgetPreviewExists) {
             previewWidth = drawable.getIntrinsicWidth();
             previewHeight = drawable.getIntrinsicHeight();
         } else {
-            // Generate a preview image if we couldn't load one
-            tileBitmap = ((BitmapDrawable) mContext.getResources().getDrawable(
-                    R.drawable.widget_tile)).getBitmap();
-            previewWidth = tileBitmap.getWidth() * spanX;
-            previewHeight = tileBitmap.getHeight() * spanY;
+            DeviceProfile dp = launcher.getDeviceProfile();
+            int tileSize = Math.min(dp.cellWidthPx, dp.cellHeightPx);
+            previewWidth = tileSize * spanX;
+            previewHeight = tileSize * spanY;
         }
 
         // Scale to fit width only - let the widget preview be clipped in the
@@ -371,45 +366,61 @@
             drawable.setBounds(x, 0, x + previewWidth, previewHeight);
             drawable.draw(c);
         } else {
-            final Paint p = new Paint();
-            p.setFilterBitmap(true);
-            int appIconSize = launcher.getDeviceProfile().iconSizePx;
+            Resources res = mContext.getResources();
+            float shadowBlur = res.getDimension(R.dimen.widget_preview_shadow_blur);
+            float keyShadowDistance = res.getDimension(R.dimen.widget_preview_key_shadow_distance);
+            float corner = res.getDimension(R.dimen.widget_preview_corner_radius);
 
-            // draw the spanX x spanY tiles
-            final Rect src = new Rect(0, 0, tileBitmap.getWidth(), tileBitmap.getHeight());
+            RectF boxRect = new RectF(shadowBlur, shadowBlur,
+                    previewWidth - shadowBlur, previewHeight - shadowBlur - keyShadowDistance);
 
-            float tileW = scale * tileBitmap.getWidth();
-            float tileH = scale * tileBitmap.getHeight();
-            final RectF dst = new RectF(0, 0, tileW, tileH);
+            final Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
+            p.setColor(0xFFFFFFFF);
 
-            float tx = x;
-            for (int i = 0; i < spanX; i++, tx += tileW) {
-                float ty = 0;
-                for (int j = 0; j < spanY; j++, ty += tileH) {
-                    dst.offsetTo(tx, ty);
-                    c.drawBitmap(tileBitmap, src, dst, p);
-                }
+            // Key shadow
+            p.setShadowLayer(shadowBlur, 0, keyShadowDistance,
+                    ShadowGenerator.KEY_SHADOW_ALPHA << 24);
+            c.drawRoundRect(boxRect, corner, corner, p);
+
+            // Ambient shadow
+            p.setShadowLayer(shadowBlur, 0, 0, ShadowGenerator.AMBIENT_SHADOW_ALPHA << 24);
+            c.drawRoundRect(boxRect, corner, corner, p);
+
+            // Draw horizontal and vertical lines to represent individual columns.
+            p.clearShadowLayer();
+            p.setStyle(Paint.Style.STROKE);
+            p.setStrokeWidth(res.getDimension(R.dimen.widget_preview_cell_divider_width));
+            p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
+
+            float t = boxRect.left;
+            float tileSize = boxRect.width() / spanX;
+            for (int i = 1; i < spanX; i++) {
+                t += tileSize;
+                c.drawLine(t, 0, t, previewHeight, p);
             }
 
-            // Draw the icon in the top left corner
-            // TODO: use top right for RTL
-            int minOffset = (int) (appIconSize * WIDGET_PREVIEW_ICON_PADDING_PERCENTAGE);
-            int smallestSide = Math.min(previewWidth, previewHeight);
-            float iconScale = Math.min((float) smallestSide / (appIconSize + 2 * minOffset), scale);
+            t = boxRect.top;
+            tileSize = boxRect.height() / spanY;
+            for (int i = 1; i < spanY; i++) {
+                t += tileSize;
+                c.drawLine(0, t, previewWidth, t, p);
+            }
 
+            // Draw icon in the center.
             try {
                 Drawable icon = info.getIcon(launcher, mIconCache);
                 if (icon != null) {
+                    int appIconSize = launcher.getDeviceProfile().iconSizePx;
+                    int iconSize = (int) Math.min(appIconSize * scale,
+                            Math.min(boxRect.width(), boxRect.height()));
+
                     icon = mutateOnMainThread(icon);
-                    int hoffset = (int) ((tileW - appIconSize * iconScale) / 2) + x;
-                    int yoffset = (int) ((tileH - appIconSize * iconScale) / 2);
-                    icon.setBounds(hoffset, yoffset,
-                            hoffset + (int) (appIconSize * iconScale),
-                            yoffset + (int) (appIconSize * iconScale));
+                    int hoffset = (previewWidth - iconSize) / 2;
+                    int yoffset = (previewHeight - iconSize) / 2;
+                    icon.setBounds(hoffset, yoffset, hoffset + iconSize, yoffset + iconSize);
                     icon.draw(c);
                 }
-            } catch (Resources.NotFoundException e) {
-            }
+            } catch (Resources.NotFoundException e) { }
             c.setBitmap(null);
         }
         return preview;
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index d2008a6..3aa8825 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -61,6 +61,7 @@
 import com.android.launcher3.accessibility.OverviewScreenAccessibilityDelegate;
 import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
 import com.android.launcher3.anim.AnimationLayerSet;
+import com.android.launcher3.badge.FolderBadgeInfo;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.config.ProviderConfig;
@@ -72,6 +73,7 @@
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.graphics.DragPreviewProvider;
+import com.android.launcher3.graphics.PreloadIconDrawable;
 import com.android.launcher3.popup.PopupContainerWithArrow;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
@@ -3984,19 +3986,39 @@
 
     public void updateIconBadges(final Set<PackageUserKey> updatedBadges) {
         final PackageUserKey packageUserKey = new PackageUserKey(null, null);
+        final HashSet<Long> folderIds = new HashSet<>();
         mapOverItems(MAP_RECURSE, new ItemOperator() {
             @Override
             public boolean evaluate(ItemInfo info, View v) {
-                if (info instanceof ShortcutInfo && v instanceof BubbleTextView) {
-                    packageUserKey.updateFromItemInfo(info);
+                if (info instanceof ShortcutInfo && v instanceof BubbleTextView
+                        && packageUserKey.updateFromItemInfo(info)) {
                     if (updatedBadges.contains(packageUserKey)) {
-                        ((BubbleTextView) v).applyBadgeState(info);
+                        ((BubbleTextView) v).applyBadgeState(info, true /* animate */);
+                        folderIds.add(info.container);
                     }
                 }
                 // process all the shortcuts
                 return false;
             }
         });
+
+        // Update folder icons
+        mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
+            @Override
+            public boolean evaluate(ItemInfo info, View v) {
+                if (info instanceof FolderInfo && folderIds.contains(info.id)
+                        && v instanceof FolderIcon) {
+                    FolderBadgeInfo folderBadgeInfo = new FolderBadgeInfo();
+                    for (ShortcutInfo si : ((FolderInfo) info).contents) {
+                        folderBadgeInfo.addBadgeInfo(mLauncher.getPopupDataProvider()
+                                .getBadgeInfoForItem(si));
+                    }
+                    ((FolderIcon) v).setBadgeInfo(folderBadgeInfo);
+                }
+                // process all the shortcuts
+                return false;
+            }
+        });
     }
 
     public void removeAbandonedPromise(String packageName, UserHandle user) {
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index ec1fa34..54d0bbe 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -478,8 +478,7 @@
     public void updateIconBadges(Set<PackageUserKey> updatedBadges) {
         final PackageUserKey packageUserKey = new PackageUserKey(null, null);
         for (AlphabeticalAppsList.AdapterItem app : mApps.getAdapterItems()) {
-            if (app.appInfo != null) {
-                packageUserKey.updateFromItemInfo(app.appInfo);
+            if (app.appInfo != null && packageUserKey.updateFromItemInfo(app.appInfo)) {
                 if (updatedBadges.contains(packageUserKey)) {
                     mAdapter.notifyItemChanged(app.position);
                 }
diff --git a/src/com/android/launcher3/anim/AnimationLayerSet.java b/src/com/android/launcher3/anim/AnimationLayerSet.java
index 42706ff..d2f5e78 100644
--- a/src/com/android/launcher3/anim/AnimationLayerSet.java
+++ b/src/com/android/launcher3/anim/AnimationLayerSet.java
@@ -20,23 +20,29 @@
 import android.animation.AnimatorListenerAdapter;
 import android.view.View;
 
-import java.util.HashSet;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
 
 /**
  * Helper class to automatically build view hardware layers for the duration of an animation.
  */
 public class AnimationLayerSet extends AnimatorListenerAdapter {
 
-    private final HashSet<View> mViews = new HashSet<>();
+    private final HashMap<View, Integer> mViewsToLayerTypeMap = new HashMap<>();
 
     public void addView(View v) {
-        mViews.add(v);
+        mViewsToLayerTypeMap.put(v, v.getLayerType());
     }
 
     @Override
     public void onAnimationStart(Animator animation) {
         // Enable all necessary layers
-        for (View v : mViews) {
+        Iterator<Map.Entry<View, Integer>> itr = mViewsToLayerTypeMap.entrySet().iterator();
+        while (itr.hasNext()) {
+            Map.Entry<View, Integer> entry = itr.next();
+            View v = entry.getKey();
+            entry.setValue(v.getLayerType());
             v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
             if (v.isAttachedToWindow() && v.getVisibility() == View.VISIBLE) {
                 v.buildLayer();
@@ -46,8 +52,10 @@
 
     @Override
     public void onAnimationEnd(Animator animation) {
-        for (View v : mViews) {
-            v.setLayerType(View.LAYER_TYPE_NONE, null);
+        Iterator<Map.Entry<View, Integer>> itr = mViewsToLayerTypeMap.entrySet().iterator();
+        while (itr.hasNext()) {
+            Map.Entry<View, Integer> entry = itr.next();
+            entry.getKey().setLayerType(entry.getValue(), null);
         }
     }
 }
diff --git a/src/com/android/launcher3/badge/BadgeInfo.java b/src/com/android/launcher3/badge/BadgeInfo.java
index 77355c7..532396c 100644
--- a/src/com/android/launcher3/badge/BadgeInfo.java
+++ b/src/com/android/launcher3/badge/BadgeInfo.java
@@ -16,6 +16,14 @@
 
 package com.android.launcher3.badge;
 
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Shader;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.Nullable;
+
 import com.android.launcher3.notification.NotificationInfo;
 import com.android.launcher3.util.PackageUserKey;
 
@@ -29,12 +37,22 @@
 
     /** Used to link this BadgeInfo to icons on the workspace and all apps */
     private PackageUserKey mPackageUserKey;
+
     /**
      * The keys of the notifications that this badge represents. These keys can later be
      * used to retrieve {@link NotificationInfo}'s.
      */
     private List<String> mNotificationKeys;
 
+    /** This will only be initialized if the badge should display the notification icon. */
+    private NotificationInfo mNotificationInfo;
+
+    /**
+     * When retrieving the notification icon, we draw it into this shader, which can be clipped
+     * as necessary when drawn in a badge.
+     */
+    private Shader mNotificationIcon;
+
     public BadgeInfo(PackageUserKey packageUserKey) {
         mPackageUserKey = packageUserKey;
         mNotificationKeys = new ArrayList<>();
@@ -65,15 +83,55 @@
         return mNotificationKeys.size();
     }
 
+    public void setNotificationToShow(@Nullable NotificationInfo notificationInfo) {
+        mNotificationInfo = notificationInfo;
+        mNotificationIcon = null;
+    }
+
+    public boolean hasNotificationToShow() {
+        return mNotificationInfo != null;
+    }
+
+    /**
+     * Returns a shader to set on a Paint that will draw the notification icon in a badge.
+     *
+     * The shader is cached until {@link #setNotificationToShow(NotificationInfo)} is called.
+     */
+    public @Nullable Shader getNotificationIconForBadge(Context context, int badgeColor,
+            int badgeSize, int badgePadding) {
+        if (mNotificationInfo == null) {
+            return null;
+        }
+        if (mNotificationIcon == null) {
+            Drawable icon = mNotificationInfo.getIconForBackground(context, badgeColor)
+                    .getConstantState().newDrawable();
+            int iconSize = badgeSize - badgePadding * 2;
+            icon.setBounds(0, 0, iconSize, iconSize);
+            Bitmap iconBitmap = Bitmap.createBitmap(badgeSize, badgeSize, Bitmap.Config.ARGB_8888);
+            Canvas canvas = new Canvas(iconBitmap);
+            canvas.translate(badgePadding, badgePadding);
+            icon.draw(canvas);
+            mNotificationIcon = new BitmapShader(iconBitmap, Shader.TileMode.CLAMP,
+                    Shader.TileMode.CLAMP);
+        }
+        return mNotificationIcon;
+    }
+
+    public boolean isIconLarge() {
+        return mNotificationInfo != null && mNotificationInfo.isIconLarge();
+    }
+
     /**
      * Whether newBadge represents the same PackageUserKey as this badge, and icons with
      * this badge should be invalidated. So, for instance, if a badge has 3 notifications
      * and one of those notifications is updated, this method should return false because
      * the badge still says "3" and the contents of those notifications are only retrieved
-     * upon long-click. This method always returns true when adding or removing notifications.
+     * upon long-click. This method always returns true when adding or removing notifications,
+     * or if the badge has a notification icon to show.
      */
     public boolean shouldBeInvalidated(BadgeInfo newBadge) {
         return mPackageUserKey.equals(newBadge.mPackageUserKey)
-                && getNotificationCount() != newBadge.getNotificationCount();
+                && (getNotificationCount() != newBadge.getNotificationCount()
+                    || hasNotificationToShow());
     }
 }
diff --git a/src/com/android/launcher3/badge/BadgeRenderer.java b/src/com/android/launcher3/badge/BadgeRenderer.java
index 787ee72..8bbc2af 100644
--- a/src/com/android/launcher3/badge/BadgeRenderer.java
+++ b/src/com/android/launcher3/badge/BadgeRenderer.java
@@ -16,11 +16,17 @@
 
 package com.android.launcher3.badge;
 
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
 import android.graphics.Canvas;
 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;
 
 /**
@@ -29,42 +35,81 @@
  */
 public class BadgeRenderer {
 
-    public int size;
-    public int textSize;
-
-    private final RectF mBackgroundRect = new RectF();
+    private final Context mContext;
+    private final int mSize;
+    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 int mTextHeight;
 
-    public BadgeRenderer(int size, int textSize) {
-        this.size = size;
-        this.textSize = textSize;
+    public BadgeRenderer(Context context) {
+        mContext = context;
+        Resources res = context.getResources();
+        mSize = res.getDimensionPixelSize(R.dimen.badge_size);
+        mLargeIconDrawer = new IconDrawer(res.getDimensionPixelSize(R.dimen.badge_small_padding));
+        mSmallIconDrawer = new IconDrawer(res.getDimensionPixelSize(R.dimen.badge_large_padding));
         mTextPaint.setTextAlign(Paint.Align.CENTER);
-        mTextPaint.setTextSize(textSize);
+        mTextPaint.setTextSize(res.getDimensionPixelSize(R.dimen.badge_text_size));
         // Measure the text height.
-        Rect temp = new Rect();
-        mTextPaint.getTextBounds("0", 0, 1, temp);
-        mTextHeight = temp.height();
+        Rect tempTextHeight = new Rect();
+        mTextPaint.getTextBounds("0", 0, 1, tempTextHeight);
+        mTextHeight = tempTextHeight.height();
     }
 
     /**
      * Draw a circle in the top right corner of the given bounds, and draw
      * {@link BadgeInfo#getNotificationCount()} on top of the circle.
      * @param palette The colors (based on the icon) to use for the badge.
-     * @param badgeInfo Contains data to draw on the badge.
+     * @param badgeInfo Contains data to draw on the badge. Could be null if we are animating out.
      * @param iconBounds The bounds of the icon being badged.
+     * @param badgeScale The progress of the animation, from 0 to 1.
      */
-    public void draw(Canvas canvas, IconPalette palette, BadgeInfo badgeInfo, Rect iconBounds) {
+    public void draw(Canvas canvas, IconPalette palette, @Nullable BadgeInfo badgeInfo,
+            Rect iconBounds, float badgeScale) {
         mBackgroundPaint.setColor(palette.backgroundColor);
         mTextPaint.setColor(palette.textColor);
-        mBackgroundRect.set(iconBounds.right - size, iconBounds.top, iconBounds.right,
-                iconBounds.top + size);
-        canvas.drawOval(mBackgroundRect, mBackgroundPaint);
-        String notificationCount = String.valueOf(badgeInfo.getNotificationCount());
-        canvas.drawText(notificationCount,
-                mBackgroundRect.centerX(),
-                mBackgroundRect.centerY() + mTextHeight / 2,
-                mTextPaint);
+        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);
+        IconDrawer iconDrawer = badgeInfo != null && badgeInfo.isIconLarge()
+                ? mLargeIconDrawer : mSmallIconDrawer;
+        Shader icon = badgeInfo == null ? null : badgeInfo.getNotificationIconForBadge(
+                mContext, palette.backgroundColor, mSize, iconDrawer.mPadding);
+        if (icon != null) {
+            // Draw the notification icon with padding.
+            iconDrawer.drawIcon(icon, canvas);
+        } else {
+            // Draw the notification count.
+            String notificationCount = badgeInfo == null ? "0"
+                    : String.valueOf(badgeInfo.getNotificationCount());
+            canvas.drawText(notificationCount, 0, mTextHeight / 2, mTextPaint);
+        }
+        canvas.restore();
+    }
+
+    /** Draws the notification icon with padding of a given size. */
+    private class IconDrawer {
+
+        private final int mPadding;
+        private final Bitmap mCircleClipBitmap;
+        private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG |
+                Paint.FILTER_BITMAP_FLAG);
+
+        public IconDrawer(int padding) {
+            mPadding = padding;
+            mCircleClipBitmap = Bitmap.createBitmap(mSize, mSize, Bitmap.Config.ALPHA_8);
+            Canvas canvas = new Canvas();
+            canvas.setBitmap(mCircleClipBitmap);
+            canvas.drawCircle(mSize / 2, mSize / 2, mSize / 2 - padding, mPaint);
+        }
+
+        public void drawIcon(Shader icon, Canvas canvas) {
+            mPaint.setShader(icon);
+            canvas.drawBitmap(mCircleClipBitmap, -mSize / 2, -mSize / 2, mPaint);
+            mPaint.setShader(null);
+        }
     }
 }
diff --git a/src/com/android/launcher3/badge/FolderBadgeInfo.java b/src/com/android/launcher3/badge/FolderBadgeInfo.java
new file mode 100644
index 0000000..4d1e5c2
--- /dev/null
+++ b/src/com/android/launcher3/badge/FolderBadgeInfo.java
@@ -0,0 +1,60 @@
+/*
+ * 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.badge;
+
+import com.android.launcher3.Utilities;
+
+import static com.android.launcher3.Utilities.boundToRange;
+
+/**
+ * Subclass of BadgeInfo that only contains the badge count,
+ * which is the sum of all the Folder's items' counts.
+ */
+public class FolderBadgeInfo extends BadgeInfo {
+
+    private static final int MIN_COUNT = 0;
+    private static final int MAX_COUNT = 999;
+
+    private int mTotalNotificationCount;
+
+    public FolderBadgeInfo() {
+        super(null);
+    }
+
+    public void addBadgeInfo(BadgeInfo badgeToAdd) {
+        if (badgeToAdd == null) {
+            return;
+        }
+        mTotalNotificationCount += badgeToAdd.getNotificationCount();
+        mTotalNotificationCount = Utilities.boundToRange(
+                mTotalNotificationCount, MIN_COUNT, MAX_COUNT);
+    }
+
+    public void subtractBadgeInfo(BadgeInfo badgeToSubtract) {
+        if (badgeToSubtract == null) {
+            return;
+        }
+        mTotalNotificationCount -= badgeToSubtract.getNotificationCount();
+        mTotalNotificationCount = Utilities.boundToRange(
+                mTotalNotificationCount, MIN_COUNT, MAX_COUNT);
+    }
+
+    @Override
+    public int getNotificationCount() {
+        return mTotalNotificationCount;
+    }
+}
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index e31c8ff..7410ae6 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -582,8 +582,8 @@
             }
         }
         final View dropTargetAsView = dropTarget instanceof View ? (View) dropTarget : null;
-        mLauncher.getUserEventDispatcher().logDragNDrop(mDragObject, dropTargetAsView);
         if (!mIsInPreDrag) {
+            mLauncher.getUserEventDispatcher().logDragNDrop(mDragObject, dropTargetAsView);
             mDragObject.dragSource.onDropCompleted(
                     dropTargetAsView, mDragObject, flingAnimation != null, accepted);
         }
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 1be40f7..23a2577 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -57,6 +57,8 @@
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
+import com.android.launcher3.logging.LoggerUtils;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.TouchController;
 
@@ -192,6 +194,8 @@
                         return true;
                     }
                 } else {
+                    mLauncher.getUserEventDispatcher().logActionTapOutside(
+                            LoggerUtils.newContainerTarget(topView.getLogContainerType()));
                     topView.close(true);
 
                     // We let touches on the original icon go through so that users can launch
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 3a98ae7..e4c9be4 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -99,7 +99,7 @@
         setScaleY(initialScale);
 
         // Animate the view into the correct position
-        mAnim = LauncherAnimUtils.ofFloat(this, 0f, 1f);
+        mAnim = LauncherAnimUtils.ofFloat(0f, 1f);
         mAnim.setDuration(VIEW_ZOOM_DURATION);
         mAnim.addUpdateListener(new AnimatorUpdateListener() {
             @Override
@@ -237,7 +237,7 @@
     }
 
     public void crossFade(int duration) {
-        ValueAnimator va = LauncherAnimUtils.ofFloat(this, 0f, 1f);
+        ValueAnimator va = LauncherAnimUtils.ofFloat(0f, 1f);
         va.setDuration(duration);
         va.setInterpolator(new DecelerateInterpolator(1.5f));
         va.addUpdateListener(new AnimatorUpdateListener() {
diff --git a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
index 6ee02f9..840fcf5 100644
--- a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
+++ b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
@@ -1,16 +1,17 @@
 package com.android.launcher3.folder;
 
-import android.graphics.Path;
-import android.graphics.Point;
+import android.view.View;
 
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
+
+import java.util.ArrayList;
+import java.util.List;
 
 public class ClippedFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule {
 
     static final int MAX_NUM_ITEMS_IN_PREVIEW = 4;
     private static final int MIN_NUM_ITEMS_IN_PREVIEW = 2;
+    private static final int MAX_NUM_ITEMS_PER_ROW = 2;
 
     final float MIN_SCALE = 0.48f;
     final float MAX_SCALE = 0.58f;
@@ -38,7 +39,7 @@
     public FolderIcon.PreviewItemDrawingParams computePreviewItemDrawingParams(int index,
             int curNumItems, FolderIcon.PreviewItemDrawingParams params) {
 
-        float totalScale = scaleForNumItems(curNumItems);
+        float totalScale = scaleForItem(index, curNumItems);
         float transX;
         float transY;
         float overlayAlpha = 0;
@@ -94,7 +95,7 @@
                 MIN_NUM_ITEMS_IN_PREVIEW) / (MAX_NUM_ITEMS_IN_PREVIEW - MIN_NUM_ITEMS_IN_PREVIEW));
         double theta = theta0 + index * (2 * Math.PI / curNumItems) * direction;
 
-        float halfIconSize = (mIconSize * scaleForNumItems(curNumItems)) / 2;
+        float halfIconSize = (mIconSize * scaleForItem(index, curNumItems)) / 2;
 
         // Map the location along the circle, and offset the coordinates to represent the center
         // of the icon, and to be based from the top / left of the preview area. The y component
@@ -104,7 +105,9 @@
 
     }
 
-    private float scaleForNumItems(int numItems) {
+    @Override
+    public float scaleForItem(int index, int numItems) {
+        // Scale is determined by the number of items in the preview.
         float scale = 1f;
         if (numItems <= 2) {
             scale = MAX_SCALE;
@@ -118,7 +121,7 @@
     }
 
     @Override
-    public int numItems() {
+    public int maxNumItems() {
         return MAX_NUM_ITEMS_IN_PREVIEW;
     }
 
@@ -127,4 +130,23 @@
         return true;
     }
 
+    @Override
+    public List<View> getItemsToDisplay(Folder folder) {
+        List<View> items = new ArrayList<>(folder.getItemsInReadingOrder());
+        int numItems = items.size();
+        if (FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION && numItems > MAX_NUM_ITEMS_IN_PREVIEW) {
+            // We match the icons in the preview with the layout of the opened folder (b/27944225),
+            // but we still need to figure out how we want to handle updating the preview when the
+            // upper left quadrant changes.
+            int appsPerRow = folder.mContent.getPageAt(0).getCountX();
+            int appsToDelete = appsPerRow - MAX_NUM_ITEMS_PER_ROW;
+
+            // We only display the upper left quadrant.
+            while (appsToDelete > 0) {
+                items.remove(MAX_NUM_ITEMS_PER_ROW);
+                appsToDelete--;
+            }
+        }
+        return items.subList(0, Math.min(numItems, MAX_NUM_ITEMS_IN_PREVIEW));
+    }
 }
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 876c3c4..eacea4a 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -74,6 +74,7 @@
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.pageindicators.PageIndicatorDots;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.CircleRevealOutlineProvider;
@@ -1532,4 +1533,9 @@
     public static Folder getOpen(Launcher launcher) {
         return getOpenView(launcher, TYPE_FOLDER);
     }
+
+    @Override
+    public int getLogContainerType() {
+        return ContainerType.FOLDER;
+    }
 }
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 973245b..96d5675 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -33,6 +33,7 @@
 import android.os.Parcelable;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
+import android.util.Property;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
@@ -53,25 +54,27 @@
 import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.FolderInfo;
 import com.android.launcher3.FolderInfo.FolderListener;
-import com.android.launcher3.IconCache;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.OnAlarmListener;
-import com.android.launcher3.PreloadIconDrawable;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.SimpleOnStylusPressListener;
 import com.android.launcher3.StylusEventHelper;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
+import com.android.launcher3.badge.BadgeRenderer;
+import com.android.launcher3.badge.FolderBadgeInfo;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.graphics.IconPalette;
 import com.android.launcher3.util.Thunk;
 
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * An icon that can appear on in the workspace representing an {@link Folder}.
@@ -126,6 +129,24 @@
 
     private Alarm mOpenAlarm = new Alarm();
 
+    private FolderBadgeInfo mBadgeInfo = new FolderBadgeInfo();
+    private BadgeRenderer mBadgeRenderer;
+    private float mBadgeScale;
+
+    private static final Property<FolderIcon, Float> BADGE_SCALE_PROPERTY
+            = new Property<FolderIcon, Float>(Float.TYPE, "badgeScale") {
+        @Override
+        public Float get(FolderIcon folderIcon) {
+            return folderIcon.mBadgeScale;
+        }
+
+        @Override
+        public void set(FolderIcon folderIcon, Float value) {
+            folderIcon.mBadgeScale = value;
+            folderIcon.invalidate();
+        }
+    };
+
     public FolderIcon(Context context, AttributeSet attrs) {
         super(context, attrs);
         init();
@@ -174,6 +195,7 @@
         icon.setOnClickListener(launcher);
         icon.mInfo = folderInfo;
         icon.mLauncher = launcher;
+        icon.mBadgeRenderer = launcher.getDeviceProfile().mBadgeRenderer;
         icon.setContentDescription(launcher.getString(R.string.folder_name_format, folderInfo.title));
         Folder folder = Folder.fromXml(launcher);
         folder.setDragController(launcher.getDragController());
@@ -245,7 +267,7 @@
     };
 
     public Drawable prepareCreate(final View destView) {
-        Drawable animateDrawable = getTopDrawable((TextView) destView);
+        Drawable animateDrawable = ((TextView) destView).getCompoundDrawables()[1];
         computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(),
                 destView.getMeasuredWidth());
         return animateDrawable;
@@ -270,7 +292,7 @@
     }
 
     public void performDestroyAnimation(final View finalView, Runnable onCompleteRunnable) {
-        Drawable animateDrawable = getTopDrawable((TextView) finalView);
+        Drawable animateDrawable = ((TextView) finalView).getCompoundDrawables()[1];
         computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(),
                 finalView.getMeasuredWidth());
 
@@ -322,7 +344,7 @@
             to.offset(center[0] - animateView.getMeasuredWidth() / 2,
                       center[1] - animateView.getMeasuredHeight() / 2);
 
-            float finalAlpha = index < mPreviewLayoutRule.numItems() ? 0.5f : 0f;
+            float finalAlpha = index < mPreviewLayoutRule.maxNumItems() ? 0.5f : 0f;
 
             float finalScale = scale * scaleRelativeToDragLayer;
             dragLayer.animateView(animateView, from, to, finalAlpha,
@@ -381,6 +403,28 @@
         computePreviewDrawingParams(d.getIntrinsicWidth(), getMeasuredWidth());
     }
 
+    public void setBadgeInfo(FolderBadgeInfo badgeInfo) {
+        updateBadgeScale(mBadgeInfo.getNotificationCount(), badgeInfo.getNotificationCount());
+        mBadgeInfo = badgeInfo;
+    }
+
+    /**
+     * Sets mBadgeScale to 1 or 0, animating if oldCount or newCount is 0
+     * (the badge is being added or removed).
+     */
+    private void updateBadgeScale(int oldCount, int newCount) {
+        boolean wasBadged = oldCount > 0;
+        boolean isBadged = newCount > 0;
+        float newBadgeScale = isBadged ? 1f : 0f;
+        // Animate when a badge is first added or when it is removed.
+        if ((wasBadged ^ isBadged) && isShown()) {
+            ObjectAnimator.ofFloat(this, BADGE_SCALE_PROPERTY, newBadgeScale).start();
+        } else {
+            mBadgeScale = newBadgeScale;
+            invalidate();
+        }
+    }
+
     static class PreviewItemDrawingParams {
         PreviewItemDrawingParams(float transX, float transY, float scale, float overlayAlpha) {
             this.transX = transX;
@@ -415,8 +459,8 @@
     }
 
     private float getLocalCenterForIndex(int index, int curNumItems, int[] center) {
-        mTmpParams = computePreviewItemDrawingParams(Math.min(mPreviewLayoutRule.numItems(), index),
-                curNumItems, mTmpParams);
+        mTmpParams = computePreviewItemDrawingParams(
+                Math.min(mPreviewLayoutRule.maxNumItems(), index), curNumItems, mTmpParams);
 
         mTmpParams.transX += mBackground.basePreviewOffsetX;
         mTmpParams.transY += mBackground.basePreviewOffsetY;
@@ -539,6 +583,14 @@
             return basePreviewOffsetY - (getScaledRadius() - getRadius());
         }
 
+        /**
+         * Returns the progress of the scale animation, where 0 means the scale is at 1f
+         * and 1 means the scale is at ACCEPT_SCALE_FACTOR.
+         */
+        float getScaleProgress() {
+            return (mScale - 1f) / (ACCEPT_SCALE_FACTOR - 1f);
+        }
+
         void invalidate() {
             int radius = getScaledRadius();
             mClipPath.reset();
@@ -661,7 +713,7 @@
                 mScaleAnimator.cancel();
             }
 
-            mScaleAnimator = LauncherAnimUtils.ofFloat(null, 0f, 1.0f);
+            mScaleAnimator = LauncherAnimUtils.ofFloat(0f, 1.0f);
 
             mScaleAnimator.addUpdateListener(new AnimatorUpdateListener() {
                 @Override
@@ -769,11 +821,16 @@
         if (mPreviewLayoutRule.clipToBackground() && !mBackground.drawingDelegated()) {
             mBackground.drawBackgroundStroke(canvas, mBgPaint);
         }
-    }
 
-    private Drawable getTopDrawable(TextView v) {
-        Drawable d = v.getCompoundDrawables()[1];
-        return (d instanceof PreloadIconDrawable) ? ((PreloadIconDrawable) d).mIcon : d;
+        int offsetX = mBackground.getOffsetX();
+        int offsetY = mBackground.getOffsetY();
+        int previewSize = (int) (mBackground.previewSize * mBackground.mScale);
+        Rect bounds = new Rect(offsetX, offsetY, offsetX + previewSize, offsetY + previewSize);
+        if ((mBadgeInfo != null && mBadgeInfo.getNotificationCount() > 0) || mBadgeScale > 0) {
+            // If we are animating to the accepting state, animate the badge out.
+            float badgeScale = Math.max(0, mBadgeScale - mBackground.getScaleProgress());
+            mBadgeRenderer.draw(canvas, IconPalette.FOLDER_ICON_PALETTE, mBadgeInfo, bounds, badgeScale);
+        }
     }
 
     class FolderPreviewItemAnim {
@@ -807,7 +864,7 @@
             final float transX0 = mTmpParams.transX;
             final float transY0 = mTmpParams.transY;
 
-            mValueAnimator = LauncherAnimUtils.ofFloat(FolderIcon.this, 0f, 1.0f);
+            mValueAnimator = LauncherAnimUtils.ofFloat(0f, 1.0f);
             mValueAnimator.addUpdateListener(new AnimatorUpdateListener(){
                 public void onAnimationUpdate(ValueAnimator animation) {
                     float progress = animation.getAnimatedFraction();
@@ -877,8 +934,8 @@
     }
 
     private void updateItemDrawingParams(boolean animate) {
-        ArrayList<View> items = mFolder.getItemsInReadingOrder();
-        int nItemsInPreview = Math.min(items.size(), mPreviewLayoutRule.numItems());
+        List<View> items = mPreviewLayoutRule.getItemsToDisplay(mFolder);
+        int nItemsInPreview = items.size();
 
         int prevNumItems = mDrawingParams.size();
 
@@ -892,7 +949,7 @@
 
         for (int i = 0; i < mDrawingParams.size(); i++) {
             PreviewItemDrawingParams p = mDrawingParams.get(i);
-            p.drawable = getTopDrawable((TextView) items.get(i));
+            p.drawable = ((TextView) items.get(i)).getCompoundDrawables()[1];
 
             if (!animate || FeatureFlags.LAUNCHER3_LEGACY_FOLDER_ICON) {
                 computePreviewItemDrawingParams(i, nItemsInPreview, p);
@@ -923,16 +980,27 @@
         requestLayout();
     }
 
+    @Override
     public void onAdd(ShortcutInfo item) {
+        int oldCount = mBadgeInfo.getNotificationCount();
+        mBadgeInfo.addBadgeInfo(mLauncher.getPopupDataProvider().getBadgeInfoForItem(item));
+        int newCount = mBadgeInfo.getNotificationCount();
+        updateBadgeScale(oldCount, newCount);
         invalidate();
         requestLayout();
     }
 
+    @Override
     public void onRemove(ShortcutInfo item) {
+        int oldCount = mBadgeInfo.getNotificationCount();
+        mBadgeInfo.subtractBadgeInfo(mLauncher.getPopupDataProvider().getBadgeInfoForItem(item));
+        int newCount = mBadgeInfo.getNotificationCount();
+        updateBadgeScale(oldCount, newCount);
         invalidate();
         requestLayout();
     }
 
+    @Override
     public void onTitleChanged(CharSequence title) {
         mFolderName.setText(title);
         setContentDescription(getContext().getString(R.string.folder_name_format, title));
@@ -1044,10 +1112,10 @@
     public interface PreviewLayoutRule {
         PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems,
             PreviewItemDrawingParams params);
-
         void init(int availableSpace, int intrinsicIconSize, boolean rtl);
-
-        int numItems();
+        float scaleForItem(int index, int totalNumItems);
+        int maxNumItems();
         boolean clipToBackground();
+        List<View> getItemsToDisplay(Folder folder);
     }
 }
diff --git a/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java b/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java
index 7fb02e3..297203a 100644
--- a/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java
+++ b/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java
@@ -16,10 +16,12 @@
 
 package com.android.launcher3.folder;
 
-import android.graphics.Path;
+import android.view.View;
 
 import com.android.launcher3.folder.FolderIcon.PreviewItemDrawingParams;
 
+import java.util.List;
+
 public class StackFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule {
 
     static final int MAX_NUM_ITEMS_IN_PREVIEW = 3;
@@ -54,10 +56,10 @@
     @Override
     public PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems,
             PreviewItemDrawingParams params) {
+        float scale = scaleForItem(index, curNumItems);
 
         index = MAX_NUM_ITEMS_IN_PREVIEW - index - 1;
         float r = (index * 1.0f) / (MAX_NUM_ITEMS_IN_PREVIEW - 1);
-        float scale = (1 - PERSPECTIVE_SCALE_FACTOR * (1 - r));
 
         float offset = (1 - r) * mMaxPerspectiveShift;
         float scaledSize = scale * mBaselineIconSize;
@@ -80,12 +82,26 @@
     }
 
     @Override
-    public int numItems() {
+    public int maxNumItems() {
         return MAX_NUM_ITEMS_IN_PREVIEW;
     }
 
     @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;
+        float r = (index * 1.0f) / (MAX_NUM_ITEMS_IN_PREVIEW - 1);
+        return (1 - PERSPECTIVE_SCALE_FACTOR * (1 - r));
+    }
+
+    @Override
     public boolean clipToBackground() {
         return false;
     }
+
+    @Override
+    public List<View> getItemsToDisplay(Folder folder) {
+        List<View> items = folder.getItemsInReadingOrder();
+        return items.subList(0, Math.min(items.size(), MAX_NUM_ITEMS_IN_PREVIEW));
+    }
 }
diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java
index 1a470ff..bb136f7 100644
--- a/src/com/android/launcher3/graphics/DragPreviewProvider.java
+++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java
@@ -27,7 +27,6 @@
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppWidgetHostView;
-import com.android.launcher3.PreloadIconDrawable;
 import com.android.launcher3.R;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.config.ProviderConfig;
@@ -185,10 +184,6 @@
         } else {
             bounds.offsetTo(0, 0);
         }
-        if (d instanceof PreloadIconDrawable) {
-            int inset = -((PreloadIconDrawable) d).getOutset();
-            bounds.inset(inset, inset);
-        }
         return bounds;
     }
 
diff --git a/src/com/android/launcher3/graphics/DrawableFactory.java b/src/com/android/launcher3/graphics/DrawableFactory.java
index 4d4d508..2493447 100644
--- a/src/com/android/launcher3/graphics/DrawableFactory.java
+++ b/src/com/android/launcher3/graphics/DrawableFactory.java
@@ -21,12 +21,14 @@
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.Path;
 import android.graphics.Rect;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Process;
 import android.os.UserHandle;
 import android.support.annotation.UiThread;
+import android.util.Log;
 
 import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.ItemInfo;
@@ -41,9 +43,13 @@
  */
 public class DrawableFactory {
 
+    private static final String TAG = "DrawableFactory";
+
     private static DrawableFactory sInstance;
     private static final Object LOCK = new Object();
 
+    private Path mPreloadProgressPath;
+
     public static DrawableFactory get(Context context) {
         synchronized (LOCK) {
             if (sInstance == null) {
@@ -61,9 +67,38 @@
      * Returns a FastBitmapDrawable with the icon.
      */
     public FastBitmapDrawable newIcon(Bitmap icon, ItemInfo info) {
-        FastBitmapDrawable d = new FastBitmapDrawable(icon);
-        d.setFilterBitmap(true);
-        return d;
+        return new FastBitmapDrawable(icon);
+    }
+
+    /**
+     * Returns a FastBitmapDrawable with the icon.
+     */
+    public PreloadIconDrawable newPendingIcon(Bitmap icon, Context context) {
+        if (mPreloadProgressPath == null) {
+            mPreloadProgressPath = getPreloadProgressPath(context);
+        }
+        return new PreloadIconDrawable(icon, mPreloadProgressPath);
+    }
+
+
+    protected Path getPreloadProgressPath(Context context) {
+        if (Utilities.isAtLeastO()) {
+            try {
+                // Try to load the path from Mask Icon
+                Drawable maskIcon = context.getDrawable(R.drawable.mask_drawable_wrapper);
+                maskIcon.setBounds(0, 0,
+                        PreloadIconDrawable.PATH_SIZE, PreloadIconDrawable.PATH_SIZE);
+                return (Path) maskIcon.getClass().getMethod("getIconMask").invoke(maskIcon);
+            } catch (Exception e) {
+                Log.e(TAG, "Error loading mask icon", e);
+            }
+        }
+
+        // Create a circle static from top center and going clockwise.
+        Path p = new Path();
+        p.moveTo(PreloadIconDrawable.PATH_SIZE / 2, 0);
+        p.addArc(0, 0, PreloadIconDrawable.PATH_SIZE, PreloadIconDrawable.PATH_SIZE, -90, 360);
+        return p;
     }
 
     public AllAppsBackgroundDrawable getAllAppsBackground(Context context) {
diff --git a/src/com/android/launcher3/graphics/FixedScaleDrawable.java b/src/com/android/launcher3/graphics/FixedScaleDrawable.java
new file mode 100644
index 0000000..4be4bd5
--- /dev/null
+++ b/src/com/android/launcher3/graphics/FixedScaleDrawable.java
@@ -0,0 +1,41 @@
+package com.android.launcher3.graphics;
+
+import android.annotation.TargetApi;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.graphics.Canvas;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.DrawableWrapper;
+import android.os.Build;
+import android.util.AttributeSet;
+
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * Extension of {@link DrawableWrapper} which scales the child drawables by a fixed amount.
+ */
+@TargetApi(Build.VERSION_CODES.N)
+public class FixedScaleDrawable extends DrawableWrapper {
+
+    // TODO b/33553066 use the constant defined in MaskableIconDrawable
+    private static final float LEGACY_ICON_SCALE = .7f * .6667f;
+
+    public FixedScaleDrawable() {
+        super(new ColorDrawable());
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
+        canvas.scale(LEGACY_ICON_SCALE, LEGACY_ICON_SCALE,
+                getBounds().exactCenterX(), getBounds().exactCenterY());
+        super.draw(canvas);
+        canvas.restoreToCount(saveCount);
+    }
+
+    @Override
+    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) { }
+
+    @Override
+    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) { }
+}
diff --git a/src/com/android/launcher3/graphics/HolographicOutlineHelper.java b/src/com/android/launcher3/graphics/HolographicOutlineHelper.java
index 9c39721..c9873d9 100644
--- a/src/com/android/launcher3/graphics/HolographicOutlineHelper.java
+++ b/src/com/android/launcher3/graphics/HolographicOutlineHelper.java
@@ -86,33 +86,26 @@
      * bitmap.
      */
     public void applyExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas) {
-        applyExpensiveOutlineWithBlur(srcDst, srcDstCanvas, true);
-    }
-
-    public void applyExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas,
-            boolean clipAlpha) {
         if (ProviderConfig.IS_DOGFOOD_BUILD && srcDst.getConfig() != Bitmap.Config.ALPHA_8) {
             throw new RuntimeException("Outline blue is only supported on alpha bitmaps");
         }
 
         // We start by removing most of the alpha channel so as to ignore shadows, and
         // other types of partial transparency when defining the shape of the object
-        if (clipAlpha) {
-            byte[] pixels = new byte[srcDst.getWidth() * srcDst.getHeight()];
-            ByteBuffer buffer = ByteBuffer.wrap(pixels);
-            buffer.rewind();
-            srcDst.copyPixelsToBuffer(buffer);
+        byte[] pixels = new byte[srcDst.getWidth() * srcDst.getHeight()];
+        ByteBuffer buffer = ByteBuffer.wrap(pixels);
+        buffer.rewind();
+        srcDst.copyPixelsToBuffer(buffer);
 
-            for (int i = 0; i < pixels.length; i++) {
-                if ((pixels[i] & 0xFF) < 188) {
-                    pixels[i] = 0;
-                }
+        for (int i = 0; i < pixels.length; i++) {
+            if ((pixels[i] & 0xFF) < 188) {
+                pixels[i] = 0;
             }
-
-            buffer.rewind();
-            srcDst.copyPixelsFromBuffer(buffer);
         }
 
+        buffer.rewind();
+        srcDst.copyPixelsFromBuffer(buffer);
+
         // calculate the outer blur first
         mBlurPaint.setMaskFilter(mMediumOuterBlurMaskFilter);
         int[] outerBlurOffset = new int[2];
diff --git a/src/com/android/launcher3/graphics/IconPalette.java b/src/com/android/launcher3/graphics/IconPalette.java
index 27aeaba..7cb69b3 100644
--- a/src/com/android/launcher3/graphics/IconPalette.java
+++ b/src/com/android/launcher3/graphics/IconPalette.java
@@ -32,16 +32,44 @@
     private static final boolean DEBUG = false;
     private static final String TAG = "IconPalette";
 
-    public int backgroundColor;
-    public int textColor;
-    public int secondaryColor;
+    public static final IconPalette FOLDER_ICON_PALETTE = new IconPalette(Color.WHITE);
+
+    private static final float MIN_PRELOAD_COLOR_SATURATION = 0.2f;
+    private static final float MIN_PRELOAD_COLOR_LIGHTNESS = 0.6f;
+    private static final int DEFAULT_PRELOAD_COLOR = 0xFF009688;
+
+    public final int dominantColor;
+    public final int backgroundColor;
+    public final int textColor;
+    public final int secondaryColor;
+
+    private IconPalette(int color) {
+        dominantColor = color;
+        backgroundColor = getMutedColor(dominantColor);
+        textColor = getTextColorForBackground(backgroundColor);
+        secondaryColor = getLowContrastColor(backgroundColor);
+    }
+
+    /**
+     * Returns a color suitable for the progress bar color of preload icon.
+     */
+    public int getPreloadProgressColor() {
+        int result = dominantColor;
+
+        // Make sure that the dominant color has enough saturation to be visible properly.
+        float[] hsv = new float[3];
+        Color.colorToHSV(result, hsv);
+        if (hsv[1] < MIN_PRELOAD_COLOR_SATURATION) {
+            result = DEFAULT_PRELOAD_COLOR;
+        } else {
+            hsv[2] = Math.max(MIN_PRELOAD_COLOR_LIGHTNESS, hsv[2]);
+            result = Color.HSVToColor(hsv);
+        }
+        return result;
+    }
 
     public static IconPalette fromDominantColor(int dominantColor) {
-        IconPalette palette = new IconPalette();
-        palette.backgroundColor = getMutedColor(dominantColor);
-        palette.textColor = getTextColorForBackground(palette.backgroundColor);
-        palette.secondaryColor = getLowContrastColor(palette.backgroundColor);
-        return palette;
+        return new IconPalette(dominantColor);
     }
 
     /**
diff --git a/src/com/android/launcher3/graphics/LauncherIcons.java b/src/com/android/launcher3/graphics/LauncherIcons.java
index 472b913..3fffb5b 100644
--- a/src/com/android/launcher3/graphics/LauncherIcons.java
+++ b/src/com/android/launcher3/graphics/LauncherIcons.java
@@ -29,13 +29,10 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.PaintDrawable;
-import android.graphics.drawable.ScaleDrawable;
 import android.os.Process;
 import android.os.UserHandle;
-import android.view.Gravity;
 
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.IconCache;
@@ -52,8 +49,6 @@
  * Helper methods for generating various launcher icons
  */
 public class LauncherIcons {
-    // TODO b/33553066 use the constant defined in MaskableIconDrawable
-    private static final float LEGACY_ICON_SCALE = .7f * .6667f;
 
     private static final Rect sOldBounds = new Rect();
     private static final Canvas sCanvas = new Canvas();
@@ -236,17 +231,16 @@
         if (!(ProviderConfig.IS_DOGFOOD_BUILD && Utilities.isAtLeastO())) {
             return drawable;
         }
-        int color = context.getResources().getColor(R.color.legacy_icon_background);
-        ColorDrawable colorDrawable = new ColorDrawable(color);
-        ScaleDrawable scaleDrawable = new ScaleDrawable(drawable,
-                Gravity.CENTER, LEGACY_ICON_SCALE, LEGACY_ICON_SCALE);
-        scaleDrawable.setLevel(1);
+
         try {
             Class clazz = Class.forName("android.graphics.drawable.MaskableIconDrawable");
-            if (!clazz.isAssignableFrom(drawable.getClass())){
+            if (!clazz.isAssignableFrom(drawable.getClass())) {
+                Drawable maskWrapper =
+                        context.getDrawable(R.drawable.mask_drawable_wrapper).mutate();
+                ((FixedScaleDrawable) clazz.getMethod("getForeground").invoke(maskWrapper))
+                        .setDrawable(drawable);
 
-                return (Drawable) clazz.getConstructor(Drawable.class, Drawable.class)
-                        .newInstance(colorDrawable, scaleDrawable);
+                return maskWrapper;
             }
         } catch (Exception e) {
             return drawable;
diff --git a/src/com/android/launcher3/graphics/PreloadIconDrawable.java b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
new file mode 100644
index 0000000..bc07ce1
--- /dev/null
+++ b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
@@ -0,0 +1,289 @@
+/*
+ * 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.graphics;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PathMeasure;
+import android.graphics.Rect;
+import android.util.Property;
+import android.util.SparseArray;
+import android.view.animation.LinearInterpolator;
+
+import com.android.launcher3.FastBitmapDrawable;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Extension of {@link FastBitmapDrawable} which shows a progress bar around the icon.
+ */
+public class PreloadIconDrawable extends FastBitmapDrawable {
+
+    private static final Property<PreloadIconDrawable, Float> INTERNAL_STATE =
+            new Property<PreloadIconDrawable, Float>(Float.TYPE, "internalStateProgress") {
+                @Override
+                public Float get(PreloadIconDrawable object) {
+                    return object.mInternalStateProgress;
+                }
+
+                @Override
+                public void set(PreloadIconDrawable object, Float value) {
+                    object.setInternalProgress(value);
+                }
+            };
+
+    public static final int PATH_SIZE = 100;
+
+    private static final float PROGRESS_WIDTH = 7;
+    private static final float PROGRESS_GAP = 2;
+    private static final int MAX_PAINT_ALPHA = 255;
+
+    private static final long DURATION_SCALE = 500;
+
+    // The smaller the number, the faster the animation would be.
+    // Duration = COMPLETE_ANIM_FRACTION * DURATION_SCALE
+    private static final float COMPLETE_ANIM_FRACTION = 0.3f;
+
+    private static final int COLOR_TRACK = 0x77EEEEEE;
+    private static final int COLOR_SHADOW = 0x55000000;
+
+    private static final float SMALL_SCALE = 0.75f;
+
+    private static final SparseArray<WeakReference<Bitmap>> sShadowCache = new SparseArray<>();
+
+    private final Matrix mTmpMatrix = new Matrix();
+    private final PathMeasure mPathMeasure = new PathMeasure();
+
+    // Path in [0, 100] bounds.
+    private final Path mProgressPath;
+
+    private final Path mScaledTrackPath;
+    private final Path mScaledProgressPath;
+    private final Paint mProgressPaint;
+
+    private Bitmap mShadowBitmap;
+    private int mIndicatorColor = 0;
+
+    private int mTrackAlpha;
+    private float mTrackLength;
+    private float mIconScale;
+
+    private boolean mRanFinishAnimation;
+
+    // Progress of the internal state. [0, 1] indicates the fraction of completed progress,
+    // [1, (1 + COMPLETE_ANIM_FRACTION)] indicates the progress of zoom animation.
+    private float mInternalStateProgress;
+
+    private ObjectAnimator mCurrentAnim;
+
+    /**
+     * @param progressPath fixed path in the bounds [0, 0, 100, 100] representing a progress bar.
+     */
+    public PreloadIconDrawable(Bitmap b, Path progressPath) {
+        super(b);
+        mProgressPath = progressPath;
+        mScaledTrackPath = new Path();
+        mScaledProgressPath = new Path();
+
+        mProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+        mProgressPaint.setStyle(Paint.Style.STROKE);
+        mProgressPaint.setStrokeCap(Paint.Cap.ROUND);
+
+        setInternalProgress(0);
+    }
+
+    @Override
+    protected void onBoundsChange(Rect bounds) {
+        super.onBoundsChange(bounds);
+        mTmpMatrix.setScale(
+                (bounds.width() - PROGRESS_WIDTH - 2 * PROGRESS_GAP) / PATH_SIZE,
+                (bounds.height() - PROGRESS_WIDTH - 2 * PROGRESS_GAP) / PATH_SIZE);
+        mTmpMatrix.postTranslate(
+                bounds.left + PROGRESS_WIDTH / 2 + PROGRESS_GAP,
+                bounds.top + PROGRESS_WIDTH / 2 + PROGRESS_GAP);
+
+        mProgressPath.transform(mTmpMatrix, mScaledTrackPath);
+        float scale = bounds.width() / PATH_SIZE;
+        mProgressPaint.setStrokeWidth(PROGRESS_WIDTH * scale);
+
+        mShadowBitmap = getShadowBitmap(bounds.width(), bounds.height(),
+                (PROGRESS_GAP ) * scale);
+        mPathMeasure.setPath(mScaledTrackPath, true);
+        mTrackLength = mPathMeasure.getLength();
+
+        setInternalProgress(mInternalStateProgress);
+    }
+
+    private Bitmap getShadowBitmap(int width, int height, float shadowRadius) {
+        int key = (width << 16) | height;
+        WeakReference<Bitmap> shadowRef = sShadowCache.get(key);
+        Bitmap shadow = shadowRef != null ? shadowRef.get() : null;
+        if (shadow != null) {
+            return shadow;
+        }
+        shadow = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        Canvas c = new Canvas(shadow);
+        mProgressPaint.setShadowLayer(shadowRadius, 0, 0, COLOR_SHADOW);
+        mProgressPaint.setColor(COLOR_TRACK);
+        mProgressPaint.setAlpha(MAX_PAINT_ALPHA);
+        c.drawPath(mScaledTrackPath, mProgressPaint);
+        mProgressPaint.clearShadowLayer();
+        c.setBitmap(null);
+
+        sShadowCache.put(key, new WeakReference<>(shadow));
+        return shadow;
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        if (mRanFinishAnimation) {
+            super.draw(canvas);
+            return;
+        }
+
+        // Draw track.
+        mProgressPaint.setColor(mIndicatorColor);
+        mProgressPaint.setAlpha(mTrackAlpha);
+        if (mShadowBitmap != null) {
+            canvas.drawBitmap(mShadowBitmap, getBounds().left, getBounds().top, mProgressPaint);
+        }
+        canvas.drawPath(mScaledProgressPath, mProgressPaint);
+
+        int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
+        Rect bounds = getBounds();
+
+        canvas.scale(mIconScale, mIconScale, bounds.exactCenterX(), bounds.exactCenterY());
+        drawInternal(canvas);
+        canvas.restoreToCount(saveCount);
+    }
+
+    /**
+     * Updates the install progress based on the level
+     */
+    @Override
+    protected boolean onLevelChange(int level) {
+        // Run the animation if we have already been bound.
+        updateInternalState(level * 0.01f,  getBounds().width() > 0, false);
+        return true;
+    }
+
+    /**
+     * Runs the finish animation if it is has not been run after last call to
+     * {@link #onLevelChange}
+     */
+    public void maybePerformFinishedAnimation() {
+        // If the drawable was recently initialized, skip the progress animation.
+        if (mInternalStateProgress == 0) {
+            mInternalStateProgress = 1;
+        }
+        updateInternalState(1 + COMPLETE_ANIM_FRACTION, true, true);
+    }
+
+    public boolean hasNotCompleted() {
+        return !mRanFinishAnimation;
+    }
+
+    private void updateInternalState(float finalProgress, boolean shouldAnimate, boolean isFinish) {
+        if (mCurrentAnim != null) {
+            mCurrentAnim.cancel();
+            mCurrentAnim = null;
+        }
+
+        if (Float.compare(finalProgress, mInternalStateProgress) == 0) {
+            return;
+        }
+        if (!shouldAnimate || mRanFinishAnimation) {
+            setInternalProgress(finalProgress);
+        } else {
+            mCurrentAnim = ObjectAnimator.ofFloat(this, INTERNAL_STATE, finalProgress);
+            mCurrentAnim.setDuration(
+                    (long) ((finalProgress - mInternalStateProgress) * DURATION_SCALE));
+            mCurrentAnim.setInterpolator(new LinearInterpolator());
+            if (isFinish) {
+                mCurrentAnim.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        mRanFinishAnimation = true;
+                    }
+                });
+            }
+            mCurrentAnim.start();
+        }
+
+    }
+
+    /**
+     * Sets the internal progress and updates the UI accordingly
+     *   for progress <= 0:
+     *     - icon in the small scale and disabled state
+     *     - progress track is visible
+     *     - progress bar is not visible
+     *   for 0 < progress < 1
+     *     - icon in the small scale and disabled state
+     *     - progress track is visible
+     *     - progress bar is visible with dominant color. Progress bar is drawn as a fraction of
+     *       {@link #mScaledTrackPath}.
+     *       @see PathMeasure#getSegment(float, float, Path, boolean)
+     *   for 1 <= progress < (1 + COMPLETE_ANIM_FRACTION)
+     *     - we calculate fraction of progress in the above range
+     *     - progress track is drawn with alpha based on fraction
+     *     - progress bar is drawn at 100% with alpha based on fraction
+     *     - icon is scaled up based on fraction and is drawn in enabled state
+     *   for progress >= (1 + COMPLETE_ANIM_FRACTION)
+     *     - only icon is drawn in normal state
+     */
+    private void setInternalProgress(float progress) {
+        mInternalStateProgress = progress;
+        if (progress <= 0) {
+            mIconScale = SMALL_SCALE;
+            mScaledTrackPath.reset();
+            mTrackAlpha = MAX_PAINT_ALPHA;
+            setIsDisabled(true);
+        } else if (mIndicatorColor == 0) {
+            // Update the indicator color
+            mIndicatorColor = getIconPalette().getPreloadProgressColor();
+        }
+
+        if (progress < 1 && progress > 0) {
+            mPathMeasure.getSegment(0, progress * mTrackLength, mScaledProgressPath, true);
+            mIconScale = SMALL_SCALE;
+            mTrackAlpha = MAX_PAINT_ALPHA;
+            setIsDisabled(true);
+        } else if (progress >= 1) {
+            setIsDisabled(false);
+            mScaledTrackPath.set(mScaledProgressPath);
+            float fraction = (progress - 1) / COMPLETE_ANIM_FRACTION;
+
+            if (fraction >= 1) {
+                // Animation has completed
+                mIconScale = 1;
+                mTrackAlpha = 0;
+            } else {
+                mTrackAlpha = Math.round((1 - fraction) * MAX_PAINT_ALPHA);
+                mIconScale = SMALL_SCALE + (1 - SMALL_SCALE) * fraction;
+            }
+        }
+        invalidateSelf();
+    }
+}
diff --git a/src/com/android/launcher3/graphics/ShadowGenerator.java b/src/com/android/launcher3/graphics/ShadowGenerator.java
index 8aea5a0..31276ec 100644
--- a/src/com/android/launcher3/graphics/ShadowGenerator.java
+++ b/src/com/android/launcher3/graphics/ShadowGenerator.java
@@ -39,9 +39,9 @@
 
     // Percent of actual icon size
     private static final float KEY_SHADOW_DISTANCE = 1f/48;
-    private static final int KEY_SHADOW_ALPHA = 61;
+    public static final int KEY_SHADOW_ALPHA = 61;
 
-    private static final int AMBIENT_SHADOW_ALPHA = 30;
+    public static final int AMBIENT_SHADOW_ALPHA = 30;
 
     private static final Object LOCK = new Object();
     // Singleton object guarded by {@link #LOCK}
diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java
index 8ded6b8..5f45c61 100644
--- a/src/com/android/launcher3/logging/UserEventDispatcher.java
+++ b/src/com/android/launcher3/logging/UserEventDispatcher.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.logging;
 
+import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.os.SystemClock;
@@ -27,11 +28,13 @@
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.LauncherEvent;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.LogConfig;
 
 import java.util.List;
 import java.util.Locale;
@@ -56,7 +59,7 @@
 
     private static final String TAG = "UserEvent";
     private static final boolean IS_VERBOSE =
-            ProviderConfig.IS_DOGFOOD_BUILD && Utilities.isPropertyEnabled(TAG);
+            ProviderConfig.IS_DOGFOOD_BUILD && Utilities.isPropertyEnabled(LogConfig.USEREVENT);
 
     /**
      * Implemented by containers to provide a container source for a given child.
@@ -111,7 +114,7 @@
     // intentHash                       required
     // --------------------------------------------------------------
 
-    protected LauncherEvent createLauncherEvent(View v, Intent intent) {
+    protected LauncherEvent createLauncherEvent(View v, int intentHashCode, ComponentName cn) {
         LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.TAP),
                 newItemTarget(v), newTarget(Target.Type.CONTAINER));
 
@@ -119,8 +122,7 @@
         int idx = 0;
         if (fillInLogContainerData(event, v)) {
             ItemInfo itemInfo = (ItemInfo) v.getTag();
-            event.srcTarget[idx].intentHash = intent.hashCode();
-            ComponentName cn = intent.getComponent();
+            event.srcTarget[idx].intentHash = intentHashCode;
             if (cn != null) {
                 event.srcTarget[idx].packageNameHash = cn.getPackageName().hashCode();
                 event.srcTarget[idx].componentHash = cn.hashCode();
@@ -145,13 +147,22 @@
     }
 
     public void logAppLaunch(View v, Intent intent) {
-        LauncherEvent ev = createLauncherEvent(v, intent);
+        LauncherEvent ev = createLauncherEvent(v, intent.hashCode(), intent.getComponent());
         if (ev == null) {
             return;
         }
         dispatchUserEvent(ev, intent);
     }
 
+    public void logNotificationLaunch(View v, PendingIntent intent) {
+        ComponentName dummyComponent = new ComponentName(intent.getCreatorPackage(), "--dummy--");
+        LauncherEvent ev = createLauncherEvent(v, intent.hashCode(), dummyComponent);
+        if (ev == null) {
+            return;
+        }
+        dispatchUserEvent(ev, null);
+    }
+
     public void logActionCommand(int command, int containerType) {
         logActionCommand(command, containerType, 0);
     }
@@ -186,6 +197,13 @@
         dispatchUserEvent(event, null);
     }
 
+    public void logActionTapOutside(Target target) {
+        LauncherEvent event = newLauncherEvent(newTouchAction(Action.Type.TOUCH),
+                target);
+        event.action.isOutside = true;
+        dispatchUserEvent(event, null);
+    }
+
     public void logActionOnContainer(int action, int dir, int containerType) {
         logActionOnContainer(action, dir, containerType, 0);
     }
@@ -198,9 +216,17 @@
         dispatchUserEvent(event, null);
     }
 
+    public void logActionOnItem(int action, int dir, int itemType) {
+        Target itemTarget = newTarget(Target.Type.ITEM);
+        itemTarget.itemType = itemType;
+        LauncherEvent event = newLauncherEvent(newTouchAction(action), itemTarget);
+        event.action.dir = dir;
+        dispatchUserEvent(event, null);
+    }
+
     public void logDeepShortcutsOpen(View icon) {
         LogContainerProvider provider = getLaunchProviderRecursive(icon);
-        if (icon == null && !(icon.getTag() instanceof ItemInfo)) {
+        if (icon == null || !(icon.getTag() instanceof ItemInfo)) {
             return;
         }
         ItemInfo info = (ItemInfo) icon.getTag();
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 2ac33ea..6b64087 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -17,6 +17,7 @@
 
 import android.content.Context;
 import android.os.UserHandle;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.MutableInt;
 
@@ -35,6 +36,8 @@
 import com.android.launcher3.util.LongArrayMap;
 import com.android.launcher3.util.MultiHashMap;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -99,6 +102,37 @@
         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());
+        }
+        for (int i = 0; i < workspaceItems.size(); i++) {
+            writer.println(prefix + '\t' + workspaceItems.get(i).toString());
+        }
+        for (int i = 0; i < appWidgets.size(); i++) {
+            writer.println(prefix + '\t' + appWidgets.get(i).toString());
+        }
+        for (int i = 0; i< folders.size(); i++) {
+            writer.println(prefix + '\t' + folders.valueAt(i).toString());
+        }
+        for (int i = 0; i< itemsIdMap.size(); i++) {
+            writer.println(prefix + '\t' + itemsIdMap.valueAt(i).toString());
+        }
+
+        if (args.length > 0 && TextUtils.equals(args[0], "--all")) {
+            writer.println(prefix + "shortcuts");
+            for (ArrayList<String> map : deepShortcutMap.values()) {
+                writer.print(prefix + "  ");
+                for (String str : map) {
+                    writer.print(str.toString() + ", ");
+                }
+                writer.println();
+            }
+        }
+    }
+
     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 d710de8..7c98362 100644
--- a/src/com/android/launcher3/model/SdCardAvailableReceiver.java
+++ b/src/com/android/launcher3/model/SdCardAvailableReceiver.java
@@ -54,7 +54,7 @@
     @Override
     public void onReceive(Context context, Intent intent) {
         final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
-        final PackageManager manager = context.getPackageManager();
+        final PackageManagerHelper pmHelper = new PackageManagerHelper(context);
         for (Entry<UserHandle, ArrayList<String>> entry : mPackages.entrySet()) {
             UserHandle user = entry.getKey();
 
@@ -63,7 +63,7 @@
 
             for (String pkg : new HashSet<>(entry.getValue())) {
                 if (!launcherApps.isPackageEnabledForProfile(pkg, user)) {
-                    if (PackageManagerHelper.isAppOnSdcard(manager, pkg)) {
+                    if (pmHelper.isAppOnSdcard(pkg)) {
                         packagesUnavailable.add(pkg);
                     } else {
                         packagesRemoved.add(pkg);
diff --git a/src/com/android/launcher3/notification/NotificationInfo.java b/src/com/android/launcher3/notification/NotificationInfo.java
index af5e817..77a18c7 100644
--- a/src/com/android/launcher3/notification/NotificationInfo.java
+++ b/src/com/android/launcher3/notification/NotificationInfo.java
@@ -16,11 +16,13 @@
 
 package com.android.launcher3.notification;
 
+import android.app.ActivityOptions;
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
+import android.os.Bundle;
 import android.service.notification.StatusBarNotification;
 import android.view.View;
 
@@ -38,6 +40,11 @@
  */
 public class NotificationInfo implements View.OnClickListener {
 
+    // TODO: use Notification constants directly.
+    public static final int BADGE_ICON_NONE = 0;
+    public static final int BADGE_ICON_SMALL = 1;
+    public static final int BADGE_ICON_LARGE = 2;
+
     public final PackageUserKey packageUserKey;
     public final String notificationKey;
     public final CharSequence title;
@@ -46,9 +53,10 @@
     public final boolean autoCancel;
     public final boolean dismissable;
 
+    private final int mBadgeIcon;
     private final Drawable mIconDrawable;
-    private boolean mShouldTintIcon;
     private int mIconColor;
+    private boolean mIsIconLarge;
 
     /**
      * Extracts the data that we need from the StatusBarNotification.
@@ -59,17 +67,20 @@
         Notification notification = statusBarNotification.getNotification();
         title = notification.extras.getCharSequence(Notification.EXTRA_TITLE);
         text = notification.extras.getCharSequence(Notification.EXTRA_TEXT);
+        mBadgeIcon = BADGE_ICON_LARGE; // TODO: get from the Notification
         // Load the icon. Since it is backed by ashmem, we won't copy the entire bitmap
         // into our process as long as we don't touch it and it exists in systemui.
-        Icon icon = notification.getLargeIcon();
+        Icon icon = mBadgeIcon == BADGE_ICON_SMALL ? null : notification.getLargeIcon();
         if (icon == null) {
+            // Use the small icon.
             icon = notification.getSmallIcon();
             mIconDrawable = icon.loadDrawable(context);
             mIconColor = statusBarNotification.getNotification().color;
-            mShouldTintIcon = true;
+            mIsIconLarge = false;
         } else {
+            // Use the large icon.
             mIconDrawable = icon.loadDrawable(context);
-            mShouldTintIcon = false;
+            mIsIconLarge = true;
         }
         intent = notification.contentIntent;
         autoCancel = (notification.flags & Notification.FLAG_AUTO_CANCEL) != 0;
@@ -79,8 +90,11 @@
     @Override
     public void onClick(View view) {
         final Launcher launcher = Launcher.getLauncher(view.getContext());
+        Bundle activityOptions = ActivityOptions.makeClipRevealAnimation(
+                view, 0, 0, view.getWidth(), view.getHeight()).toBundle();
         try {
-            intent.send();
+            intent.send(null, 0, null, null, null, null, activityOptions);
+            launcher.getUserEventDispatcher().logNotificationLaunch(view, intent);
         } catch (PendingIntent.CanceledException e) {
             e.printStackTrace();
         }
@@ -91,7 +105,8 @@
     }
 
     public Drawable getIconForBackground(Context context, int background) {
-        if (!mShouldTintIcon) {
+        if (mIsIconLarge) {
+            // Only small icons should be tinted.
             return mIconDrawable;
         }
         mIconColor = IconPalette.resolveContrastColor(context, mIconColor, background);
@@ -100,7 +115,17 @@
         // get it set and invalidated properly.
         icon.setTintList(null);
         icon.setTint(mIconColor);
-        mShouldTintIcon = false;
         return icon;
     }
+
+    public boolean isIconLarge() {
+        return mIsIconLarge;
+    }
+
+    public boolean shouldShowIconInBadge() {
+        // If the icon we're using for this notification matches what the Notification
+        // specified should show in the badge, then return true.
+        return mIsIconLarge && mBadgeIcon == BADGE_ICON_LARGE
+                || !mIsIconLarge && mBadgeIcon == BADGE_ICON_SMALL;
+    }
 }
diff --git a/src/com/android/launcher3/notification/NotificationItemView.java b/src/com/android/launcher3/notification/NotificationItemView.java
index 422722d..639447d 100644
--- a/src/com/android/launcher3/notification/NotificationItemView.java
+++ b/src/com/android/launcher3/notification/NotificationItemView.java
@@ -29,15 +29,16 @@
 import android.widget.FrameLayout;
 import android.widget.TextView;
 
+import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.R;
 import com.android.launcher3.graphics.IconPalette;
+import com.android.launcher3.logging.UserEventDispatcher.LogContainerProvider;
 import com.android.launcher3.popup.PopupItemView;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
 
 import java.util.ArrayList;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Set;
 
 import static com.android.launcher3.LauncherAnimUtils.animateViewHeight;
 
@@ -47,7 +48,7 @@
  * The footer contains: A list of just the icons of all the notifications past the first one.
  * @see NotificationFooterLayout
  */
-public class NotificationItemView extends PopupItemView {
+public class NotificationItemView extends PopupItemView implements LogContainerProvider {
 
     private static final Rect sTempRect = new Rect();
 
@@ -57,7 +58,6 @@
     private NotificationFooterLayout mFooter;
     private SwipeHelper mSwipeHelper;
     private boolean mAnimatingNextIcon;
-    private IconPalette mIconPalette;
 
     public NotificationItemView(Context context) {
         this(context, null, 0);
@@ -114,7 +114,6 @@
     }
 
     public void applyColors(IconPalette iconPalette) {
-        mIconPalette = iconPalette;
         setBackgroundTintList(ColorStateList.valueOf(iconPalette.secondaryColor));
         mHeader.setBackgroundTintList(ColorStateList.valueOf(iconPalette.backgroundColor));
         mHeader.setTextColor(ColorStateList.valueOf(iconPalette.textColor));
@@ -174,4 +173,11 @@
         animation.playSequentially(removeMainView, removeRest);
         return animation;
     }
+
+    @Override
+    public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target,
+            LauncherLogProto.Target targetParent) {
+        target.itemType = LauncherLogProto.ItemType.NOTIFICATION;
+        targetParent.containerType = LauncherLogProto.ContainerType.DEEPSHORTCUTS;
+    }
 }
diff --git a/src/com/android/launcher3/notification/NotificationListener.java b/src/com/android/launcher3/notification/NotificationListener.java
index 3f9a584..206bb31 100644
--- a/src/com/android/launcher3/notification/NotificationListener.java
+++ b/src/com/android/launcher3/notification/NotificationListener.java
@@ -24,7 +24,6 @@
 import android.service.notification.StatusBarNotification;
 import android.support.annotation.Nullable;
 import android.support.v4.util.Pair;
-import android.util.Log;
 
 import com.android.launcher3.LauncherModel;
 import com.android.launcher3.config.FeatureFlags;
diff --git a/src/com/android/launcher3/notification/NotificationMainView.java b/src/com/android/launcher3/notification/NotificationMainView.java
index 76a84b7..6677c2f 100644
--- a/src/com/android/launcher3/notification/NotificationMainView.java
+++ b/src/com/android/launcher3/notification/NotificationMainView.java
@@ -20,18 +20,19 @@
 import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
 import android.content.Context;
-import android.support.annotation.Nullable;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.View;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.LauncherViewPropertyAnimator;
 import com.android.launcher3.R;
 import com.android.launcher3.graphics.IconPalette;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
 
 /**
  * A {@link LinearLayout} that contains a single notification, e.g. icon + title + text.
@@ -89,6 +90,9 @@
                 getContext(), mIconPalette.backgroundColor));
         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 = new LauncherViewPropertyAnimator(mTextView).alpha(1);
@@ -135,8 +139,13 @@
 
     @Override
     public void onChildDismissed(View v) {
-        Launcher.getLauncher(getContext()).getPopupDataProvider().cancelNotification(
+        Launcher launcher = Launcher.getLauncher(getContext());
+        launcher.getPopupDataProvider().cancelNotification(
                 mNotificationInfo.notificationKey);
+        launcher.getUserEventDispatcher().logActionOnItem(
+                LauncherLogProto.Action.Touch.SWIPE,
+                LauncherLogProto.Action.Direction.RIGHT, // Assume all swipes are right for logging.
+                LauncherLogProto.ItemType.NOTIFICATION);
     }
 
     @Override
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index af1bd9b..7fda8b5 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -696,7 +696,7 @@
     @Override
     public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
         target.itemType = ItemType.DEEPSHORTCUT;
-        // TODO: add target.rank
+        target.rank = info.rank;
         targetParent.containerType = ContainerType.DEEPSHORTCUTS;
     }
 
@@ -825,4 +825,9 @@
     public static PopupContainerWithArrow getOpen(Launcher launcher) {
         return getOpenView(launcher, TYPE_POPUP_CONTAINER_WITH_ARROW);
     }
+
+    @Override
+    public int getLogContainerType() {
+        return ContainerType.DEEPSHORTCUTS;
+    }
 }
diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java
index c773079..c754fda 100644
--- a/src/com/android/launcher3/popup/PopupDataProvider.java
+++ b/src/com/android/launcher3/popup/PopupDataProvider.java
@@ -22,7 +22,9 @@
 
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.badge.BadgeInfo;
+import com.android.launcher3.notification.NotificationInfo;
 import com.android.launcher3.notification.NotificationListener;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.util.ComponentKey;
@@ -31,8 +33,10 @@
 
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Provides data for the popup menu that appears after long-clicking on apps.
@@ -55,15 +59,18 @@
 
     @Override
     public void onNotificationPosted(PackageUserKey postedPackageUserKey, String notificationKey) {
-        BadgeInfo oldBadgeInfo = mPackageUserToBadgeInfos.get(postedPackageUserKey);
-        if (oldBadgeInfo == null) {
+        BadgeInfo badgeInfo = mPackageUserToBadgeInfos.get(postedPackageUserKey);
+        boolean notificationWasAdded; // As opposed to updated.
+        if (badgeInfo == null) {
             BadgeInfo newBadgeInfo = new BadgeInfo(postedPackageUserKey);
             newBadgeInfo.addNotificationKeyIfNotExists(notificationKey);
             mPackageUserToBadgeInfos.put(postedPackageUserKey, newBadgeInfo);
-            mLauncher.updateIconBadges(Collections.singleton(postedPackageUserKey));
-        } else if (oldBadgeInfo.addNotificationKeyIfNotExists(notificationKey)) {
-            mLauncher.updateIconBadges(Collections.singleton(postedPackageUserKey));
+            notificationWasAdded = true;
+        } else {
+            notificationWasAdded = badgeInfo.addNotificationKeyIfNotExists(notificationKey);
         }
+        updateLauncherIconBadges(Utilities.singletonHashSet(postedPackageUserKey),
+                notificationWasAdded);
     }
 
     @Override
@@ -73,7 +80,7 @@
             if (oldBadgeInfo.getNotificationCount() == 0) {
                 mPackageUserToBadgeInfos.remove(removedPackageUserKey);
             }
-            mLauncher.updateIconBadges(Collections.singleton(removedPackageUserKey));
+            updateLauncherIconBadges(Utilities.singletonHashSet(removedPackageUserKey));
 
             PopupContainerWithArrow openContainer = PopupContainerWithArrow.getOpen(mLauncher);
             if (openContainer != null) {
@@ -112,7 +119,7 @@
         }
 
         if (!updatedBadges.isEmpty()) {
-            mLauncher.updateIconBadges(updatedBadges.keySet());
+            updateLauncherIconBadges(updatedBadges.keySet());
         }
 
         PopupContainerWithArrow openContainer = PopupContainerWithArrow.getOpen(mLauncher);
@@ -121,6 +128,58 @@
         }
     }
 
+    private void updateLauncherIconBadges(Set<PackageUserKey> updatedBadges) {
+        updateLauncherIconBadges(updatedBadges, true);
+    }
+
+    /**
+     * Updates the icons on launcher (workspace, folders, all apps) to refresh their badges.
+     * @param updatedBadges The packages whose badges should be refreshed (either a notification was
+     *                      added or removed, or the badge should show the notification icon).
+     * @param addedOrRemoved An optional parameter that will allow us to only refresh badges that
+     *                       updated (not added/removed) that have icons. If a badge updated
+     *                       but it doesn't have an icon, then the badge number doesn't change.
+     */
+    private void updateLauncherIconBadges(Set<PackageUserKey> updatedBadges,
+            boolean addedOrRemoved) {
+        Iterator<PackageUserKey> iterator = updatedBadges.iterator();
+        while (iterator.hasNext()) {
+            BadgeInfo badgeInfo = mPackageUserToBadgeInfos.get(iterator.next());
+            if (badgeInfo != null && !updateBadgeIcon(badgeInfo) && !addedOrRemoved) {
+                // The notification icon isn't used, and the badge wasn't added or removed
+                // so there is no update to be made.
+                iterator.remove();
+            }
+        }
+        if (!updatedBadges.isEmpty()) {
+            mLauncher.updateIconBadges(updatedBadges);
+        }
+    }
+
+    /**
+     * Determines whether the badge should show a notification icon rather than a number,
+     * and sets that icon on the BadgeInfo if so.
+     * @param badgeInfo The badge to update with an icon (null if it shouldn't show one).
+     * @return Whether the badge icon potentially changed (true unless it stayed null).
+     */
+    private boolean updateBadgeIcon(BadgeInfo badgeInfo) {
+        boolean hadNotificationToShow = badgeInfo.hasNotificationToShow();
+        NotificationInfo notificationInfo = null;
+        NotificationListener notificationListener = NotificationListener.getInstance();
+        if (notificationListener != null && badgeInfo.getNotificationKeys().size() == 1) {
+            StatusBarNotification[] activeNotifications = notificationListener
+                    .getActiveNotifications(new String[] {badgeInfo.getNotificationKeys().get(0)});
+            if (activeNotifications.length == 1) {
+                notificationInfo = new NotificationInfo(mLauncher, activeNotifications[0]);
+                if (!notificationInfo.shouldShowIconInBadge()) {
+                    notificationInfo = null;
+                }
+            }
+        }
+        badgeInfo.setNotificationToShow(notificationInfo);
+        return hadNotificationToShow || badgeInfo.hasNotificationToShow();
+    }
+
     public void setDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMapCopy) {
         mDeepShortcutMap = deepShortcutMapCopy;
         if (LOGD) Log.d(TAG, "bindDeepShortcutMap: " + mDeepShortcutMap);
@@ -148,7 +207,7 @@
     }
 
     public String[] getNotificationKeysForItem(ItemInfo info) {
-        BadgeInfo badgeInfo = mPackageUserToBadgeInfos.get(PackageUserKey.fromItemInfo(info));
+        BadgeInfo badgeInfo = getBadgeInfoForItem(info);
         if (badgeInfo == null) { return new String[0]; }
         List<String> notificationKeys = badgeInfo.getNotificationKeys();
         return notificationKeys.toArray(new String[notificationKeys.size()]);
diff --git a/src/com/android/launcher3/popup/PopupPopulator.java b/src/com/android/launcher3/popup/PopupPopulator.java
index f990fa2..d2814ee 100644
--- a/src/com/android/launcher3/popup/PopupPopulator.java
+++ b/src/com/android/launcher3/popup/PopupPopulator.java
@@ -171,6 +171,7 @@
                     // Use unbadged icon for the menu.
                     si.iconBitmap = LauncherIcons.createShortcutIcon(
                             shortcut, launcher, false /* badged */);
+                    si.rank = i;
                     uiHandler.post(new UpdateShortcutChild(container, shortcutViews.get(i),
                             si, shortcut));
                 }
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index a200a85..dc85aba 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -27,6 +27,7 @@
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.logging.FileLog;
+import com.android.launcher3.util.LogConfig;
 
 import java.io.InvalidObjectException;
 
@@ -44,13 +45,6 @@
     private static final String INFO_COLUMN_NAME = "name";
     private static final String INFO_COLUMN_DEFAULT_VALUE = "dflt_value";
 
-    /**
-     * When enabled all icons are kept on the home screen, even if they don't have an active
-     * session. To enable use:
-     *      adb shell setprop log.tag.launcher_keep_all_icons VERBOSE
-     */
-    private static final String KEEP_ALL_ICONS = "launcher_keep_all_icons";
-
     public static boolean performRestore(DatabaseHelper helper) {
         SQLiteDatabase db = helper.getWritableDatabase();
         db.beginTransaction();
@@ -85,7 +79,7 @@
         }
 
         // Mark all items as restored.
-        boolean keepAllIcons = Utilities.isPropertyEnabled(KEEP_ALL_ICONS);
+        boolean keepAllIcons = Utilities.isPropertyEnabled(LogConfig.KEEP_ALL_ICONS);
         ContentValues values = new ContentValues();
         values.put(Favorites.RESTORED, ShortcutInfo.FLAG_RESTORED_ICON
                 | (keepAllIcons ? ShortcutInfo.FLAG_RESTORE_STARTED : 0));
diff --git a/src/com/android/launcher3/util/LogConfig.java b/src/com/android/launcher3/util/LogConfig.java
new file mode 100644
index 0000000..4acdb5c
--- /dev/null
+++ b/src/com/android/launcher3/util/LogConfig.java
@@ -0,0 +1,31 @@
+package com.android.launcher3.util;
+
+/**
+ * This is a utility class that keeps track of all the tag that can be enabled to debug
+ * a behavior in runtime.
+ *
+ * To use any of the strings defined in this class, execute the following command.
+ *
+ * $ adb shell setprop log.tag.TAGNAME VERBOSE
+ */
+
+public class LogConfig {
+    // These are list of strings that can be used to replace TAGNAME.
+
+    /**
+     * After this tag is turned on, whenever there is n user event, debug information is
+     * printed out to logcat.
+     */
+    public static final String USEREVENT = "UserEvent";
+
+    /**
+     * When turned on, all icons are kept on the home screen, even if they don't have an active
+     * session.
+     */
+    public static final String KEEP_ALL_ICONS = "KeepAllIcons";
+
+    /**
+     * When turned on, icon cache is only fetched from memory and not disk.
+     */
+    public static final String MEMORY_ONLY_ICON_CACHE = "MemoryOnlyIconCache";
+}
diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index 33a9fc6..e89fc0c 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -20,14 +20,20 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
 import android.net.Uri;
 import android.os.Build;
+import android.os.UserHandle;
 import android.text.TextUtils;
 
+import com.android.launcher3.AppInfo;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.LauncherAppsCompat;
+
+import java.util.List;
 
 /**
  * Utility methods using package manager
@@ -36,36 +42,55 @@
 
     private static final int FLAG_SUSPENDED = 1<<30;
 
+    private final Context mContext;
+    private final PackageManager mPm;
+
+    public PackageManagerHelper(Context context) {
+        mContext = context;
+        mPm = context.getPackageManager();
+    }
+
     /**
      * 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 static boolean isAppOnSdcard(PackageManager pm, String packageName) {
-        return isAppEnabled(pm, packageName, PackageManager.GET_UNINSTALLED_PACKAGES);
+    public boolean isAppOnSdcard(String packageName) {
+        return isAppEnabled(packageName, PackageManager.GET_UNINSTALLED_PACKAGES);
     }
 
-    public static boolean isAppEnabled(PackageManager pm, String packageName) {
-        return isAppEnabled(pm, packageName, 0);
+    public boolean isAppEnabled(String packageName) {
+        return isAppEnabled(packageName, 0);
     }
 
-    public static boolean isAppEnabled(PackageManager pm, String packageName, int flags) {
+    public boolean isAppEnabled(String packageName, int flags) {
         try {
-            ApplicationInfo info = pm.getApplicationInfo(packageName, flags);
+            ApplicationInfo info = mPm.getApplicationInfo(packageName, flags);
             return info != null && info.enabled;
         } catch (PackageManager.NameNotFoundException e) {
             return false;
         }
     }
 
-    public static boolean isAppSuspended(PackageManager pm, String packageName) {
+    public boolean isAppSuspended(String packageName) {
         try {
-            ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
+            ApplicationInfo info = mPm.getApplicationInfo(packageName, 0);
             return info != null && isAppSuspended(info);
         } catch (PackageManager.NameNotFoundException e) {
             return false;
         }
     }
 
+    public boolean isSafeMode() {
+        return mPm.isSafeMode();
+    }
+
+    public Intent getAppLaunchIntent(String pkg, UserHandle user) {
+        List<LauncherActivityInfo> activities = LauncherAppsCompat.getInstance(mContext)
+                .getActivityList(pkg, user);
+        return activities.isEmpty() ? null :
+                AppInfo.makeLaunchIntent(mContext, activities.get(0), user);
+    }
+
     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
@@ -82,10 +107,8 @@
      * {@param intent}. If {@param srcPackage} is null, then the activity should not need
      * any permissions
      */
-    public static boolean hasPermissionForActivity(Context context, Intent intent,
-            String srcPackage) {
-        PackageManager pm = context.getPackageManager();
-        ResolveInfo target = pm.resolveActivity(intent, 0);
+    public boolean hasPermissionForActivity(Intent intent, String srcPackage) {
+        ResolveInfo target = mPm.resolveActivity(intent, 0);
         if (target == null) {
             // Not a valid target
             return false;
@@ -100,7 +123,7 @@
         }
 
         // Source does not have sufficient permissions.
-        if(pm.checkPermission(target.activityInfo.permission, srcPackage) !=
+        if(mPm.checkPermission(target.activityInfo.permission, srcPackage) !=
                 PackageManager.PERMISSION_GRANTED) {
             return false;
         }
@@ -120,7 +143,7 @@
         // app-op is only enabled for apps running in compatibility mode, simply block such apps.
 
         try {
-            return pm.getApplicationInfo(srcPackage, 0).targetSdkVersion >= Build.VERSION_CODES.M;
+            return mPm.getApplicationInfo(srcPackage, 0).targetSdkVersion >= Build.VERSION_CODES.M;
         } catch (NameNotFoundException e) { }
 
         return false;
diff --git a/src/com/android/launcher3/util/PackageUserKey.java b/src/com/android/launcher3/util/PackageUserKey.java
index d08b0e9..3fb2401 100644
--- a/src/com/android/launcher3/util/PackageUserKey.java
+++ b/src/com/android/launcher3/util/PackageUserKey.java
@@ -4,6 +4,7 @@
 import android.service.notification.StatusBarNotification;
 
 import com.android.launcher3.ItemInfo;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
 
 import java.util.Arrays;
 
@@ -32,9 +33,16 @@
         mHashCode = Arrays.hashCode(new Object[] {packageName, user});
     }
 
-    /** This should only be called to avoid new object creations in a loop. */
-    public void updateFromItemInfo(ItemInfo info) {
-        update(info.getTargetComponent().getPackageName(), info.user);
+    /**
+     * This should only be called to avoid new object creations in a loop.
+     * @return Whether this PackageUserKey was successfully updated - it shouldn't be used if not.
+     */
+    public boolean updateFromItemInfo(ItemInfo info) {
+        if (DeepShortcutManager.supportsShortcuts(info)) {
+            update(info.getTargetComponent().getPackageName(), info.user);
+            return true;
+        }
+        return false;
     }
 
     @Override
diff --git a/src/com/android/launcher3/util/SQLiteCacheHelper.java b/src/com/android/launcher3/util/SQLiteCacheHelper.java
index 9aabfeb..1ff6293 100644
--- a/src/com/android/launcher3/util/SQLiteCacheHelper.java
+++ b/src/com/android/launcher3/util/SQLiteCacheHelper.java
@@ -20,7 +20,7 @@
     private static final String TAG = "SQLiteCacheHelper";
 
     private static final boolean NO_ICON_CACHE = ProviderConfig.IS_DOGFOOD_BUILD &&
-            Utilities.isPropertyEnabled("MEMORY_ONLY_ICON_CACHE");
+            Utilities.isPropertyEnabled(LogConfig.MEMORY_ONLY_ICON_CACHE);
 
     private final String mTableName;
     private final MySQLiteOpenHelper mOpenHelper;
diff --git a/src/com/android/launcher3/widget/PendingItemDragHelper.java b/src/com/android/launcher3/widget/PendingItemDragHelper.java
index a4698c2..c723f9e 100644
--- a/src/com/android/launcher3/widget/PendingItemDragHelper.java
+++ b/src/com/android/launcher3/widget/PendingItemDragHelper.java
@@ -178,12 +178,8 @@
         dst.offset((w - scaledWidth) / 2, (h - scaledHeight) / 2);
 
         canvas.drawBitmap(mPreviewBitmap, src, dst, null);
-
-        // Don't clip alpha values for the drag outline if we're using the default widget preview
-        boolean clipAlpha = !(mAddInfo instanceof PendingAddWidgetInfo &&
-                (((PendingAddWidgetInfo) mAddInfo).previewImage == 0));
         HolographicOutlineHelper.getInstance(mView.getContext())
-                .applyExpensiveOutlineWithBlur(b, canvas, clipAlpha);
+                .applyExpensiveOutlineWithBlur(b, canvas);
         canvas.setBitmap(null);
 
         return b;
diff --git a/src/com/android/launcher3/widget/WidgetsListAdapter.java b/src/com/android/launcher3/widget/WidgetsListAdapter.java
index 60e6e2a..38210fc 100644
--- a/src/com/android/launcher3/widget/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/WidgetsListAdapter.java
@@ -113,20 +113,27 @@
 
         // Add more views.
         // if there are too many, hide them.
-        int diff = infoList.size() - row.getChildCount();
+        int expectedChildCount = infoList.size() + Math.max(0, infoList.size() - 1);
+        int childCount = row.getChildCount();
 
-        if (diff > 0) {
-            for (int i = 0; i < diff; i++) {
-                WidgetCell widget = (WidgetCell) mLayoutInflater.inflate(
-                        R.layout.widget_cell, row, false);
+        if (expectedChildCount > childCount) {
+            for (int i = childCount ; i < expectedChildCount; i++) {
+                if ((i & 1) == 1) {
+                    // Add a divider for odd index
+                    mLayoutInflater.inflate(R.layout.widget_list_divider, row);
+                } else {
+                    // Add cell for even index
+                    WidgetCell widget = (WidgetCell) mLayoutInflater.inflate(
+                            R.layout.widget_cell, row, false);
 
-                // set up touch.
-                widget.setOnClickListener(mIconClickListener);
-                widget.setOnLongClickListener(mIconLongClickListener);
-                row.addView(widget);
+                    // set up touch.
+                    widget.setOnClickListener(mIconClickListener);
+                    widget.setOnLongClickListener(mIconLongClickListener);
+                    row.addView(widget);
+                }
             }
-        } else if (diff < 0) {
-            for (int i=infoList.size() ; i < row.getChildCount(); i++) {
+        } else if (expectedChildCount < childCount) {
+            for (int i = expectedChildCount ; i < childCount; i++) {
                 row.getChildAt(i).setVisibility(View.GONE);
             }
         }
@@ -136,10 +143,14 @@
 
         // Bind the view in the widget horizontal tray region.
         for (int i=0; i < infoList.size(); i++) {
-            WidgetCell widget = (WidgetCell) row.getChildAt(i);
+            WidgetCell widget = (WidgetCell) row.getChildAt(2*i);
             widget.applyFromCellItem(infoList.get(i), mWidgetPreviewLoader);
             widget.ensurePreview();
             widget.setVisibility(View.VISIBLE);
+
+            if (i > 0) {
+                row.getChildAt(2*i - 1).setVisibility(View.VISIBLE);
+            }
         }
     }
 
@@ -151,11 +162,10 @@
 
         ViewGroup container = (ViewGroup) mLayoutInflater.inflate(
                 R.layout.widgets_list_row_view, parent, false);
-        LinearLayout cellList = (LinearLayout) container.findViewById(R.id.widgets_cell_list);
 
         // if the end padding is 0, then container view (horizontal scroll view) doesn't respect
         // the end of the linear layout width + the start padding and doesn't allow scrolling.
-        cellList.setPaddingRelative(mIndent, 0, 1, 0);
+        container.findViewById(R.id.widgets_cell_list).setPaddingRelative(mIndent, 0, 1, 0);
 
         return new WidgetsRowViewHolder(container);
     }
@@ -163,7 +173,7 @@
     @Override
     public void onViewRecycled(WidgetsRowViewHolder holder) {
         int total = holder.cellContainer.getChildCount();
-        for (int i = 0; i < total; i++) {
+        for (int i = 0; i < total; i+=2) {
             WidgetCell widget = (WidgetCell) holder.cellContainer.getChildAt(i);
             widget.clear();
         }
diff --git a/src_config/com/android/launcher3/config/FeatureFlags.java b/src_config/com/android/launcher3/config/FeatureFlags.java
index ffb86e4..5c29366 100644
--- a/src_config/com/android/launcher3/config/FeatureFlags.java
+++ b/src_config/com/android/launcher3/config/FeatureFlags.java
@@ -28,6 +28,7 @@
     public static boolean LAUNCHER3_USE_SYSTEM_DRAG_DRIVER = true;
     public static boolean LAUNCHER3_DISABLE_PINCH_TO_OVERVIEW = false;
     public static boolean LAUNCHER3_ALL_APPS_PULL_UP = true;
+    public static boolean LAUNCHER3_NEW_FOLDER_ANIMATION = false;
 
     // Feature flag to enable moving the QSB on the 0th screen of the workspace.
     public static final boolean QSB_ON_FIRST_SCREEN = true;