Add logging for workspace rearrangement in overview mode
am: 0d4899026c

Change-Id: Ie6569ecafdd63981e167a910f852a8133697ae81
diff --git a/Android.mk b/Android.mk
index 202ff44..28beff2 100644
--- a/Android.mk
+++ b/Android.mk
@@ -35,7 +35,7 @@
 
 LOCAL_RESOURCE_DIR := \
     $(LOCAL_PATH)/res \
-    frameworks/support/v7/recyclerview/res
+    prebuilts/sdk/current/support/v7/recyclerview/res \
 
 LOCAL_PROGUARD_FLAG_FILES := proguard.flags
 
diff --git a/build.gradle b/build.gradle
index e103d79..4629caa 100644
--- a/build.gradle
+++ b/build.gradle
@@ -3,7 +3,7 @@
         mavenCentral()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:2.1.3'
+        classpath 'com.android.tools.build:gradle:2.2.0'
         classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.0'
     }
 }
@@ -45,6 +45,7 @@
 
         androidTest {
             java.srcDirs = ['tests/src']
+            res.srcDirs = ['tests/res']
             manifest.srcFile "tests/AndroidManifest.xml"
         }
 
@@ -65,6 +66,9 @@
     compile 'com.google.protobuf.nano:protobuf-javanano:3.0.0-alpha-2'
 
     testCompile 'junit:junit:4.12'
+    androidTestCompile "org.mockito:mockito-core:1.+"
+    androidTestCompile 'com.google.dexmaker:dexmaker:1.2'
+    androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2'
     androidTestCompile 'com.android.support.test:runner:0.5'
     androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
     androidTestCompile 'com.android.support:support-annotations:23.2.0'
@@ -80,7 +84,7 @@
                 task.builtins {
                     remove java
                     javanano {
-                        option 'ignore_services=false'
+                        option "java_package=launcher_log.proto|com.android.launcher3.userevent.nano"
                     }
                 }
             }
diff --git a/proguard.flags b/proguard.flags
index ad6f727..f1a3eaf 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -86,7 +86,8 @@
 # next row when focus is on the last item of last row when using a RecyclerView
 # Keep optimized and shrunk proguard to prevent issues like this when using
 # support jar.
--keep,allowoptimization,allowshrinking class android.support.** {
-  *;
-}
+#-keep,allowoptimization,allowshrinking class android.support.** {
+#  *;
+#}
+-keep class android.support.v7.widget.RecyclerView { *; }
 
diff --git a/protos/launcher_log.proto b/protos/launcher_log.proto
index 448cf64..6b27559 100644
--- a/protos/launcher_log.proto
+++ b/protos/launcher_log.proto
@@ -15,7 +15,7 @@
  */
 syntax = "proto2";
 
-option java_package = "com.android.launcher3.userevent.nano";
+option java_package = "com.android.launcher3.userevent";
 option java_outer_classname = "LauncherLogProto";
 
 package userevent;
diff --git a/res/drawable-hdpi/ic_all_apps_bg_hand.png b/res/drawable-hdpi/ic_all_apps_bg_hand.png
index dff2f54..437fd37 100644
--- a/res/drawable-hdpi/ic_all_apps_bg_hand.png
+++ b/res/drawable-hdpi/ic_all_apps_bg_hand.png
Binary files differ
diff --git a/res/drawable-hdpi/quantum_panel_bitmap.9.png b/res/drawable-hdpi/quantum_panel_bitmap.9.png
deleted file mode 100644
index d2aee73..0000000
--- a/res/drawable-hdpi/quantum_panel_bitmap.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/quantum_panel_dark_bitmap.9.png b/res/drawable-hdpi/quantum_panel_dark_bitmap.9.png
deleted file mode 100644
index 78345b8..0000000
--- a/res/drawable-hdpi/quantum_panel_dark_bitmap.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_all_apps_bg_hand.png b/res/drawable-mdpi/ic_all_apps_bg_hand.png
index 0d1d7bb..0a00241 100644
--- a/res/drawable-mdpi/ic_all_apps_bg_hand.png
+++ b/res/drawable-mdpi/ic_all_apps_bg_hand.png
Binary files differ
diff --git a/res/drawable-mdpi/quantum_panel_bitmap.9.png b/res/drawable-mdpi/quantum_panel_bitmap.9.png
deleted file mode 100644
index 9325d49..0000000
--- a/res/drawable-mdpi/quantum_panel_bitmap.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/quantum_panel_dark_bitmap.9.png b/res/drawable-mdpi/quantum_panel_dark_bitmap.9.png
deleted file mode 100644
index bf74fa0..0000000
--- a/res/drawable-mdpi/quantum_panel_dark_bitmap.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-v21/quantum_panel.xml b/res/drawable-v21/quantum_panel.xml
deleted file mode 100644
index d1c0783..0000000
--- a/res/drawable-v21/quantum_panel.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"
-    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-v21/quantum_panel_dark.xml b/res/drawable-v21/quantum_panel_dark.xml
deleted file mode 100644
index 405ad51..0000000
--- a/res/drawable-v21/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-xhdpi/ic_all_apps_bg_hand.png b/res/drawable-xhdpi/ic_all_apps_bg_hand.png
index e727d37..1acb378 100644
--- a/res/drawable-xhdpi/ic_all_apps_bg_hand.png
+++ b/res/drawable-xhdpi/ic_all_apps_bg_hand.png
Binary files differ
diff --git a/res/drawable-xhdpi/quantum_panel_bitmap.9.png b/res/drawable-xhdpi/quantum_panel_bitmap.9.png
deleted file mode 100644
index b89e8b4..0000000
--- a/res/drawable-xhdpi/quantum_panel_bitmap.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/quantum_panel_dark_bitmap.9.png b/res/drawable-xhdpi/quantum_panel_dark_bitmap.9.png
deleted file mode 100644
index 1d17136..0000000
--- a/res/drawable-xhdpi/quantum_panel_dark_bitmap.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_all_apps_bg_hand.png b/res/drawable-xxhdpi/ic_all_apps_bg_hand.png
index fffcc6b..09c6c8d 100644
--- a/res/drawable-xxhdpi/ic_all_apps_bg_hand.png
+++ b/res/drawable-xxhdpi/ic_all_apps_bg_hand.png
Binary files differ
diff --git a/res/drawable-xxhdpi/quantum_panel_bitmap.9.png b/res/drawable-xxhdpi/quantum_panel_bitmap.9.png
deleted file mode 100644
index 1dd1f6d..0000000
--- a/res/drawable-xxhdpi/quantum_panel_bitmap.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/quantum_panel_dark_bitmap.9.png b/res/drawable-xxhdpi/quantum_panel_dark_bitmap.9.png
deleted file mode 100644
index 48d584b..0000000
--- a/res/drawable-xxhdpi/quantum_panel_dark_bitmap.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_all_apps_bg_hand.png b/res/drawable-xxxhdpi/ic_all_apps_bg_hand.png
index 4d065d8..49c004d 100644
--- a/res/drawable-xxxhdpi/ic_all_apps_bg_hand.png
+++ b/res/drawable-xxxhdpi/ic_all_apps_bg_hand.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/quantum_panel_bitmap.9.png b/res/drawable-xxxhdpi/quantum_panel_bitmap.9.png
deleted file mode 100644
index 915177d..0000000
--- a/res/drawable-xxxhdpi/quantum_panel_bitmap.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxxhdpi/quantum_panel_dark_bitmap.9.png b/res/drawable-xxxhdpi/quantum_panel_dark_bitmap.9.png
deleted file mode 100644
index 27b8466..0000000
--- a/res/drawable-xxxhdpi/quantum_panel_dark_bitmap.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/quantum_panel.xml b/res/drawable/quantum_panel.xml
index 1f4fb71..fda1003 100644
--- a/res/drawable/quantum_panel.xml
+++ b/res/drawable/quantum_panel.xml
@@ -14,5 +14,9 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<nine-patch xmlns:android="http://schemas.android.com/apk/res/android"
-    android:src="@drawable/quantum_panel_bitmap" />
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+       android:drawable="@drawable/quantum_panel_shape"
+       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_dark.xml b/res/drawable/quantum_panel_dark.xml
index 6642e78..b113b37 100644
--- a/res/drawable/quantum_panel_dark.xml
+++ b/res/drawable/quantum_panel_dark.xml
@@ -14,5 +14,9 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<nine-patch xmlns:android="http://schemas.android.com/apk/res/android"
-    android:src="@drawable/quantum_panel_dark_bitmap" />
+<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/layout-port/launcher.xml b/res/layout-port/launcher.xml
index a2e2f9b..dd981dd 100644
--- a/res/layout-port/launcher.xml
+++ b/res/layout-port/launcher.xml
@@ -77,8 +77,7 @@
             android:id="@+id/apps_view"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
-            android:visibility="invisible"
-            launcher:layout_ignoreInsets="true" />
+            android:visibility="invisible" />
     </com.android.launcher3.dragndrop.DragLayer>
 
 </com.android.launcher3.LauncherRootView>
diff --git a/res/layout-sw720dp/launcher.xml b/res/layout-sw720dp/launcher.xml
index 12c01b7..06cb550 100644
--- a/res/layout-sw720dp/launcher.xml
+++ b/res/layout-sw720dp/launcher.xml
@@ -76,8 +76,7 @@
             android:id="@+id/apps_view"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
-            android:visibility="invisible"
-           launcher:layout_ignoreInsets="true" />
+            android:visibility="invisible" />
     </com.android.launcher3.dragndrop.DragLayer>
 
 </com.android.launcher3.LauncherRootView>
diff --git a/res/layout/all_apps.xml b/res/layout/all_apps.xml
index 4909eb3..1909f3b 100644
--- a/res/layout/all_apps.xml
+++ b/res/layout/all_apps.xml
@@ -46,21 +46,30 @@
         <!-- DO NOT CHANGE THE ID -->
         <com.android.launcher3.allapps.AllAppsRecyclerView
             android:id="@+id/apps_list_view"
+            android:layout_below="@+id/search_container"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:layout_gravity="center_horizontal|top"
-            android:layout_marginTop="@dimen/all_apps_search_bar_height"
             android:clipToPadding="false"
             android:descendantFocusability="afterDescendants"
             android:focusable="true"
+            android:paddingStart="@dimen/container_fastscroll_thumb_max_width"
+            android:paddingEnd="@dimen/container_fastscroll_thumb_max_width"
             android:theme="@style/CustomOverscroll.Light" />
 
+        <!-- Fast scroller popup -->
+        <TextView
+            style="@style/FastScrollerPopup"
+            android:layout_below="@+id/search_container"
+            android:id="@+id/fast_scroller_popup"
+            android:layout_alignParentEnd="true"
+            android:layout_marginEnd="@dimen/container_fastscroll_popup_margin" />
+
         <FrameLayout
             android:id="@+id/search_container"
             android:layout_width="match_parent"
             android:layout_height="@dimen/all_apps_search_bar_height"
             android:layout_gravity="center|top"
-            android:paddingTop="@dimen/all_apps_search_bar_margin_top"
             android:gravity="center|bottom"
             android:orientation="horizontal"
             android:saveEnabled="false">
@@ -68,8 +77,9 @@
             <com.android.launcher3.ExtendedEditText
                 android:id="@+id/search_box_input"
                 android:layout_width="match_parent"
-                android:layout_height="match_parent"
+                android:layout_height="@dimen/all_apps_search_bar_field_height"
                 android:background="@android:color/transparent"
+                android:layout_gravity="bottom"
                 android:focusableInTouchMode="true"
                 android:gravity="center"
                 android:imeOptions="actionSearch|flagNoExtractUi"
diff --git a/res/layout/app_widget_resize_frame.xml b/res/layout/app_widget_resize_frame.xml
new file mode 100644
index 0000000..91a1e45
--- /dev/null
+++ b/res/layout/app_widget_resize_frame.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<com.android.launcher3.AppWidgetResizeFrame
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@drawable/widget_resize_shadow"
+    android:foreground="@drawable/widget_resize_frame"
+    android:padding="0dp" >
+
+    <!-- Left -->
+    <ImageView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:src="@drawable/ic_widget_resize_handle"
+        android:layout_gravity="left|center_vertical"
+        android:layout_marginLeft="@dimen/widget_handle_margin" />
+
+    <!-- Top -->
+    <ImageView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:src="@drawable/ic_widget_resize_handle"
+        android:layout_gravity="top|center_horizontal"
+        android:layout_marginTop="@dimen/widget_handle_margin" />
+
+    <!-- Right -->
+    <ImageView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:src="@drawable/ic_widget_resize_handle"
+        android:layout_gravity="right|center_vertical"
+        android:layout_marginRight="@dimen/widget_handle_margin" />
+
+    <!-- Bottom -->
+    <ImageView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:src="@drawable/ic_widget_resize_handle"
+        android:layout_gravity="bottom|center_horizontal"
+        android:layout_marginBottom="@dimen/widget_handle_margin" />
+
+</com.android.launcher3.AppWidgetResizeFrame>
\ No newline at end of file
diff --git a/res/layout/appwidget_error.xml b/res/layout/appwidget_error.xml
index 708ece4..d6bd0c5 100644
--- a/res/layout/appwidget_error.xml
+++ b/res/layout/appwidget_error.xml
@@ -19,6 +19,7 @@
     android:layout_height="match_parent"
     android:gravity="center"
     android:elevation="2dp"
+    android:theme="@style/WidgetContainerTheme"
     android:background="@drawable/quantum_panel_dark"
     android:textAppearance="?android:attr/textAppearanceMediumInverse"
     android:textColor="@color/widgets_view_item_text_color"
diff --git a/res/layout/overview_panel.xml b/res/layout/overview_panel.xml
index 9ba3f09..2091721 100644
--- a/res/layout/overview_panel.xml
+++ b/res/layout/overview_panel.xml
@@ -15,11 +15,13 @@
      limitations under the License.
 -->
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+      xmlns:launcher="http://schemas.android.com/apk/res-auto"
+      launcher:layout_ignoreInsets="true"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:layout_gravity="center_horizontal|bottom"
       android:gravity="top"
-      android:orientation="horizontal" >
+      android:orientation="horizontal">
 
     <TextView
         android:id="@+id/wallpaper_button"
diff --git a/res/layout/qsb_blocker_view.xml b/res/layout/qsb_blocker_view.xml
index 58a148e..453eebe 100644
--- a/res/layout/qsb_blocker_view.xml
+++ b/res/layout/qsb_blocker_view.xml
@@ -14,7 +14,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<com.android.launcher3.QsbBlockerView
+<com.android.launcher3.qsb.QsbBlockerView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent" />
\ No newline at end of file
diff --git a/res/layout/qsb_container.xml b/res/layout/qsb_container.xml
index b75e3b5..6fa843d 100644
--- a/res/layout/qsb_container.xml
+++ b/res/layout/qsb_container.xml
@@ -14,7 +14,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<com.android.launcher3.QsbContainerView
+<com.android.launcher3.qsb.QsbContainerView
         xmlns:android="http://schemas.android.com/apk/res/android"
         android:orientation="vertical"
         android:layout_width="match_parent"
@@ -23,8 +23,8 @@
         android:padding="0dp" >
 
     <fragment
-        android:name="com.android.launcher3.QsbContainerView$QsbFragment"
+        android:name="com.android.launcher3.qsb.QsbContainerView$QsbFragment"
         android:layout_width="match_parent"
         android:tag="qsb_view"
         android:layout_height="match_parent"/>
-</com.android.launcher3.QsbContainerView>
\ No newline at end of file
+</com.android.launcher3.qsb.QsbContainerView>
\ No newline at end of file
diff --git a/res/layout/widgets_view.xml b/res/layout/widgets_view.xml
index c4431be..d193a5e 100644
--- a/res/layout/widgets_view.xml
+++ b/res/layout/widgets_view.xml
@@ -49,6 +49,14 @@
             android:layout_width="match_parent"
             android:layout_height="match_parent" />
 
+        <!-- Fast scroller popup -->
+        <TextView
+            style="@style/FastScrollerPopup"
+            android:layout_below="@+id/search_container"
+            android:id="@+id/fast_scroller_popup"
+            android:layout_gravity="top|end"
+            android:layout_marginEnd="@dimen/container_fastscroll_popup_margin" />
+
         <ProgressBar
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 9152ea5..dd427e3 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Afgelaaide program in veiligmodus gedeaktiveer"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Legstukke gedeaktiveer in Veiligmodus"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Kortpad is nie beskikbaar nie"</string>
+    <string name="home_screen" msgid="806512411299847073">"Tuisskerm"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Gepasmaakte handelinge"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Raak en hou om \'n legstuk op te tel."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Dubbeltik en hou om \'n legstuk op te tel of gebruik gepasmaakte handelinge."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index c20dc12..02d5cc1 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"የወረደው መተግበሪያ ደህንነቱ በተጠበቀ ሁኔታ ውስጥ ተሰናክሏል"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"ምግብሮች በደህንነቱ የተጠበቀ ሁኔታ ተሰናክለዋል"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"አቋራጭ አይገኝም"</string>
+    <string name="home_screen" msgid="806512411299847073">"መነሻ ገጽ"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"ብጁ እርምጃዎች"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"ፍርግም ለማንሳት ይንኩ እና ይያዙት"</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"አንድ ንዑስ ፕሮግራም ለመምረጥ ወይም ብጁ እርምጃዎችን ለመጠቀም ሁለቴ መታ አድርገው ይያዙ።"</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index c80d162..437e974 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"تم تعطيل التطبيق الذي تم تنزيله في الوضع الآمن"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"الأدوات معطلة في الوضع الآمن"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"الاختصار غير متاح"</string>
+    <string name="home_screen" msgid="806512411299847073">"الشاشة الرئيسية"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"الإجراءات المخصّصة"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"المس مع الاستمرار لاختيار إحدى الأدوات."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"انقر نقرًا مزدوجًا مع الاستمرار لاختيار أداة أو استخدم الإجراءات المخصصة."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-az-rAZ/strings.xml b/res/values-az-rAZ/strings.xml
index 1a4f31b..fe0ef4f 100644
--- a/res/values-az-rAZ/strings.xml
+++ b/res/values-az-rAZ/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Güvənli rejimdə icazə verilməyən tətbiq endirildi"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Vidcetlər Güvənli rejimdə deaktiv edilib"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Qısayol əlçatan deyil"</string>
+    <string name="home_screen" msgid="806512411299847073">"Əsas ekran"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Fərdi əməliyyatlar"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Vidceti götürmək üçün toxunub saxlayın."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Vidceti götürmək üçün &amp; iki dəfə toxunub saxlayın və ya fərdi fəaliyyətləri istifadə edin."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index a9344f9..bd7b874 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Preuzeta aplikacija je onemogućena u Bezbednom režimu"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Vidžeti su onemogućeni u Bezbednom režimu"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Prečica nije dostupna"</string>
+    <string name="home_screen" msgid="806512411299847073">"Početni ekran"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Prilagođene radnje"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Dodirnite i zadržite da biste izabrali vidžet."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Dvaput dodirnite i zadržite da biste izabrali vidžet ili koristite prilagođene radnje."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d×%2$d"</string>
diff --git a/res/values-be-rBY/strings.xml b/res/values-be-rBY/strings.xml
index f5dba2d..b15dc60 100644
--- a/res/values-be-rBY/strings.xml
+++ b/res/values-be-rBY/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Спампаваная праграма адключана ў Бяспечным рэжыме"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Віджэты адключаны ў Бяспечным рэжыме"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Ярлык недаступны"</string>
+    <string name="home_screen" msgid="806512411299847073">"Галоўны экран"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Спецыяльныя дзеянні"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Дакраніцеся і ўтрымлiвайце віджэт, каб выбр. яго."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Дакраніцеся двойчы і ўтрымлівайце, каб выбраць віджэт або выкарыстоўваць карыстальніцкія дзеянні."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 2c6d3d4..13d1641 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Изтегленото приложение е деактивирано в безопасния режим"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Приспособленията са деактивирани в безопасния режим"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Няма достъп до прекия път"</string>
+    <string name="home_screen" msgid="806512411299847073">"Начален екран"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Персонализирани действия"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Докоснете и задръжте за избор на приспособление."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Докоснете двукратно и задръжте за избор на приспособление или използвайте персонализирани действия."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-bn-rBD/strings.xml b/res/values-bn-rBD/strings.xml
index d5108e9..60cdd47 100644
--- a/res/values-bn-rBD/strings.xml
+++ b/res/values-bn-rBD/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"ডাউনলোড করা অ্যাপ্লিকেশান নিরাপদ মোডে অক্ষম রয়েছে"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"সুরক্ষিত মোডে উইজেট নিষ্ক্রিয় থাকে"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"শর্টকাটগুলি অনুপলব্ধ"</string>
+    <string name="home_screen" msgid="806512411299847073">"হোম স্ক্রীন"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"কাস্টম অ্যাকশন"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"একটি উইজেট তুলতে তা স্পর্শ করে ধরে রাখুন৷"</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"কোনো উইজেট বেছে নিতে দুবার-আলতো চেপে ধরে থাকুন অথবা কাস্টম ক্রিয়াগুলি ব্যবহার করুন৷"</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-bs-rBA/strings.xml b/res/values-bs-rBA/strings.xml
index 579b95a..705ca04 100644
--- a/res/values-bs-rBA/strings.xml
+++ b/res/values-bs-rBA/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Preuzeta aplikacija je onemogućena u sigurnom načinu rada"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Vidžeti su onemogućeni u sigurnom načinu rada."</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Prečica nije dostupna"</string>
+    <string name="home_screen" msgid="806512411299847073">"Početni ekran"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Prilagođene akcije"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Dodirnite &amp; i držite da biste uzeli dodatak."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Dvaput dodirnite &amp; i držite da biste uzeli vidžet ili koristite prilagođene radnje."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 4ba0498..c12ec8d 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"L\'aplicació que has baixat està desactivada al mode segur."</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"En Mode segur, els widgets estan desactivats."</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"La drecera no està disponible"</string>
+    <string name="home_screen" msgid="806512411299847073">"Pantalla d\'inici"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Accions personalitzades"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Mantén premut un widget per triar-lo."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Fes doble toc i mantén premut per seleccionar un widget o per utilitzar les accions personalitzades."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 373920c..8eacd0a 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Stažená aplikace je v nouzovém režimu zakázána"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"V nouzovém režimu jsou widgety zakázány."</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Zkratka není k dispozici"</string>
+    <string name="home_screen" msgid="806512411299847073">"Plocha"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Vlastní akce"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Widget vyberete dotykem a podržením."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Dvojitým klepnutím a podržením vyberte widget, případně použijte vlastní akce."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 9304140..220bd4b 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Downloadet app er deaktiveret i sikker tilstand"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets er deaktiveret i Beskyttet tilstand"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Genvejen er ikke tilgængelig"</string>
+    <string name="home_screen" msgid="806512411299847073">"Startskærm"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Brugerdefinerede handlinger"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Tryk på en widget, og hold den nede for at vælge."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Tryk to gange, og hold fingeren nede for at vælge en widget eller bruge tilpassede handlinger."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index e461d46..990c901 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Heruntergeladene App im abgesicherten Modus deaktiviert"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets im abgesicherten Modus deaktiviert"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Verknüpfung nicht verfügbar"</string>
+    <string name="home_screen" msgid="806512411299847073">"Startbildschirm"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Benutzerdefinierte Aktionen"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Zum Hinzufügen Widget berühren und halten"</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Zum Hinzufügen auf Widget doppeltippen und gedrückt halten oder benutzerdefinierte Aktionen verwenden."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 119232d..c82b7ee 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Η λήψη εφαρμογών απενεργοποήθηκε στην Ασφαλή λειτουργία"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Τα γραφικά στοιχεία απενεργοποιήθηκαν στην ασφαλή λειτουργία"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Η συντόμευση δεν είναι διαθέσιμη"</string>
+    <string name="home_screen" msgid="806512411299847073">"Αρχική οθόνη"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Προσαρμοσμένες ενέργειες"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Αγγίξτε παρατεταμένα για να πάρετε ένα γραφ.στοιχ."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Πατήστε δύο φορές παρατεταμένα για επιλογή γραφικού στοιχείου ή χρήση προσαρμοσμένων ενεργειών."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index 44daaa8..baae4b0 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Downloaded app disabled in Safe mode"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets disabled in Safe mode"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Shortcut isn\'t available"</string>
+    <string name="home_screen" msgid="806512411299847073">"Home screen"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Customised actions"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Touch &amp; hold to pick up a widget."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Double-tap &amp; hold to pick up a widget or use customised actions."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index 44daaa8..baae4b0 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Downloaded app disabled in Safe mode"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets disabled in Safe mode"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Shortcut isn\'t available"</string>
+    <string name="home_screen" msgid="806512411299847073">"Home screen"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Customised actions"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Touch &amp; hold to pick up a widget."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Double-tap &amp; hold to pick up a widget or use customised actions."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index 44daaa8..baae4b0 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Downloaded app disabled in Safe mode"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets disabled in Safe mode"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Shortcut isn\'t available"</string>
+    <string name="home_screen" msgid="806512411299847073">"Home screen"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Customised actions"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Touch &amp; hold to pick up a widget."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Double-tap &amp; hold to pick up a widget or use customised actions."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 4bd6c88..250f784 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Aplicación descargada inhabilitada en modo seguro"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets inhabilitados en modo seguro"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"El acceso directo no está disponible"</string>
+    <string name="home_screen" msgid="806512411299847073">"Pantalla principal"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Acciones personalizadas"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Mantén presionado el widget que desees elegir."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Presiona dos veces y mantén presionado para elegir un widget o usa una acción personalizada."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index a4fc620..1fdf3ef 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Aplicación descargada inhabilitada en modo seguro"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets inhabilitados en modo seguro"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Acceso directo no disponible"</string>
+    <string name="home_screen" msgid="806512411299847073">"Pantalla de inicio"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Acciones personalizadas"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Mantén pulsado el widget que quieras seleccionar."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Toca dos veces y mantén pulsado el widget que quieras seleccionar o utiliza acciones personalizadas."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-et-rEE/strings.xml b/res/values-et-rEE/strings.xml
index feb63ca..a0ee1e9 100644
--- a/res/values-et-rEE/strings.xml
+++ b/res/values-et-rEE/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Allalaetud rakendus on turvarežiimis keelatud"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Turvarežiimis on vidinad keelatud"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Otsetee pole saadaval"</string>
+    <string name="home_screen" msgid="806512411299847073">"Avaekraan"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Kohandatud toimingud"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Vidina valimiseks vajutage ja hoidke seda all."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Topeltpuudutage ja hoidke vidina valimiseks või kohandatud toimingute kasutamiseks."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-eu-rES/strings.xml b/res/values-eu-rES/strings.xml
index 76ef5c6..b2aed2e 100644
--- a/res/values-eu-rES/strings.xml
+++ b/res/values-eu-rES/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Deskargatutako aplikazioa modu seguruan desgaitu da"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgetak desgaitu egin dira modu seguruan"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Lasterbideak ez daude erabilgarri"</string>
+    <string name="home_screen" msgid="806512411299847073">"Hasierako pantaila"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Ekintza pertsonalizatuak"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Eduki sakatuta widgeta aukeratzeko."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Sakatu birritan eta eduki sakatuta widgeta aukeratzeko edo ekintza pertsonalizatuak erabiltzeko."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 95675a7..88a4fb5 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"برنامه بارگیری شده در حالت ایمن غیرفعال شد"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"ابزارک‌ها در حالت ایمن غیرفعال هستند"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"میان‌بر دردسترس نیست"</string>
+    <string name="home_screen" msgid="806512411299847073">"صفحه اصلی"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"عملکردهای سفارشی"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"برای انتخاب ابزارک لمس کنید و نگه دارید."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"برای انتخاب یک ابزارک، دو ضربه سریع بزنید و نگه‌دارید یا از اقدامات سفارشی استفاده کنید."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 40e6d96..0aa01b9 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Ladattu sovellus poistettiin käytöstä suojatussa tilassa"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgetit poistettu käytöstä vikasietotilassa"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Pikakuvake ei ole käytettävissä."</string>
+    <string name="home_screen" msgid="806512411299847073">"Aloitusnäyttö"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Muokatut toiminnot"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Valitse widget painamalla sitä pitkään."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Valitse widget tai käytä muokattuja toimintoja kaksoisnapauttamalla ja painamalla kohdetta pitkään."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index 01d6b27..e261b74 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"L\'application téléchargée est désactivée en mode sécurisé."</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets désactivés en mode sans échec"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Le raccourci n\'est pas disponible"</string>
+    <string name="home_screen" msgid="806512411299847073">"Écran d\'accueil"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Actions personnalisées"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Maintenez un doigt sur le widget pour l\'ajouter."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Touchez 2x un widget et maintenez doigt dessus pour l’ajouter ou utiliser des actions personnalisées"</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index f42748c..591dff7 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"L\'application téléchargée est désactivée en mode sécurisé."</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Les widgets sont désactivés en mode sécurisé."</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Raccourci non disponible"</string>
+    <string name="home_screen" msgid="806512411299847073">"Écran d\'accueil"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Actions personnalisées"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"App. de manière prolongée pour sélectionner widget."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Appuyez 2 fois et maintenez la pression pour sélectionner widget ou utilisez actions personnalisées."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d x %2$d"</string>
diff --git a/res/values-gl-rES/strings.xml b/res/values-gl-rES/strings.xml
index c03ecdc..766cddb 100644
--- a/res/values-gl-rES/strings.xml
+++ b/res/values-gl-rES/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"A aplicación que descargaches está desactivada no modo seguro"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Os widgets están desactivados no modo seguro"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"O atallo non está dispoñible"</string>
+    <string name="home_screen" msgid="806512411299847073">"Pantalla de inicio"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Accións personalizadas"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Mantén premido un widget para seleccionalo."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Toca dúas veces e mantén premido para seleccionar un widget ou utiliza accións personalizadas."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-gu-rIN/strings.xml b/res/values-gu-rIN/strings.xml
index 39a176c..c14816a 100644
--- a/res/values-gu-rIN/strings.xml
+++ b/res/values-gu-rIN/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"સુરક્ષિત મોડમાં ડાઉનલોડ કરેલ ઍપ્લિકેશન અક્ષમ કરી"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"સુરક્ષિત મોડમાં વિજેટ્સ અક્ષમ કર્યા"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"શૉર્ટકટ ઉપલબ્ધ નથી"</string>
+    <string name="home_screen" msgid="806512411299847073">"હોમ સ્ક્રીન"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"કસ્ટમ ક્રિયાઓ"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"વિજેટ ચૂંટવા માટે ટચ કરો અને પકડી રાખો."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"વિજેટ ચૂંટવા અથવા કસ્ટમ ક્રિયાઓનો ઉપયોગ કરવા માટે બે વાર ટેપ કરો અને પકડી રાખો."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index f534e1e..3d1fecb 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"डाउनलोड किए गए ऐप्स सुरक्षित मोड में अक्षम है"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"विजेट सुरक्षित मोड में अक्षम हैं"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"शॉर्टकट उपलब्ध नहीं है"</string>
+    <string name="home_screen" msgid="806512411299847073">"होम स्क्रीन"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"कस्टम कार्रवाई"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"विजेट को चुनने के लिए स्‍पर्श करके रखें."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"कोई विजेट चुनने के लिए डबल टैप करके रखें या कस्‍टम कार्रवाइयां चुनें."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index b4fb50e..ce370f9 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Preuzeta aplikacija onemogućena je u Sigurnom načinu rada"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgeti su onemogućeni u Sigurnom načinu rada"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Prečac nije dostupan"</string>
+    <string name="home_screen" msgid="806512411299847073">"Početni zaslon"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Prilagođene radnje"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Dodirnite i držite kako biste podigli widget."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Dodirnite dvaput i držite kako biste podigli widget ili pokušajte prilagođenim radnjama."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 279749b..c2003b6 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"A letöltött alkalmazás Csökkentett módban ki van kapcsolva"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"A modulok ki vannak kapcsolva Csökkentett módban"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"A gyorsparancs nem áll rendelkezésre"</string>
+    <string name="home_screen" msgid="806512411299847073">"Kezdőképernyő"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Egyéni műveletek"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Modul felvételéhez érintse meg, és tartsa lenyomva"</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Modul mozgatásához koppintson rá duplán és tartsa lenyomva, vagy használjon egyéni műveleteket."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-hy-rAM/strings.xml b/res/values-hy-rAM/strings.xml
index c7401a9..7884895 100644
--- a/res/values-hy-rAM/strings.xml
+++ b/res/values-hy-rAM/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Ներբեռնված ծրագիրն անջատված է Անվտանգ ռեժիմում"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Վիջեթներն անջատված են անվտանգ ռեժիմում"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Դյուրանցումն անհասանելի է"</string>
+    <string name="home_screen" msgid="806512411299847073">"Հիմնական էկրան"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Հատուկ գործողություններ"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Հպեք և պահեք՝ վիջեթն ընտրելու համար:"</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Կրկնակի հպեք և պահեք՝ վիջեթ ավելացնելու համար կամ օգտվեք հարմարեցրած գործողություններից:"</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 00e8d63..16c3752 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Aplikasi yang diunduh dinonaktifkan dalam mode Aman"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widget dinonaktifkan dalam mode Aman"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Pintasan tidak tersedia"</string>
+    <string name="home_screen" msgid="806512411299847073">"Layar utama"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Tindakan khusus"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Sentuh lama untuk memilih widget."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Ketuk dua kalip &amp; tahan untuk mengambil widget atau menggunakan tindakan khusus."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-is-rIS/strings.xml b/res/values-is-rIS/strings.xml
index 7586ae3..f39628d 100644
--- a/res/values-is-rIS/strings.xml
+++ b/res/values-is-rIS/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Sótt forrit er óvirkt í öryggisstillingu"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Græjur eru óvirkar í öruggri stillingu"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Flýtileið er ekki tiltæk"</string>
+    <string name="home_screen" msgid="806512411299847073">"Heimaskjár"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Sérsniðnar aðgerðir"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Haltu fingri á græju til að grípa hana."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Ýttu tvisvar og haltu fingri á græju til að grípa hana eða notaðu sérsniðnar aðgerðir."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index b821222..e4e1242 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"L\'app scaricata è stata disattivata in modalità provvisoria"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widget disabilitati in modalità provvisoria"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"La scorciatoia non è disponibile"</string>
+    <string name="home_screen" msgid="806512411299847073">"Schermata Home"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Azioni personalizzate"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Tocca e tieni premuto per scegliere un widget."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Tocca due volte e tieni premuto per scegliere un widget o per utilizzare azioni personalizzate."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index ffea51f..c04bf12 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"אפליקציה שהורדת הושבתה במצב בטוח"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"ווידג\'טים מושבתים במצב בטוח"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"קיצור הדרך אינו זמין"</string>
+    <string name="home_screen" msgid="806512411299847073">"מסך דף הבית"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"פעולות מותאמות אישית"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"גע נגיעה רציפה בווידג\'ט כדי לבחור בו."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"הקש פעמיים וגע נגיעה רציפה בווידג\'ט כדי לבחור בו, או השתמש בפעולות מותאמות אישית."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 8729aba..ea9d381 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"ダウンロードしたアプリは、セーフモードでは無効です"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"セーフモードではウィジェットは無効です"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"ショートカットは使用できません"</string>
+    <string name="home_screen" msgid="806512411299847073">"ホーム画面"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"カスタム操作"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"ウィジェットを追加するには押し続けます。"</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"ダブルタップ後に押し続けてウィジェットを選択するか、カスタム操作を使用してください。"</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$dx%2$d"</string>
diff --git a/res/values-ka-rGE/strings.xml b/res/values-ka-rGE/strings.xml
index 1a200d7..7c41389 100644
--- a/res/values-ka-rGE/strings.xml
+++ b/res/values-ka-rGE/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"უსაფრთხო რეჟიმში ჩამოტვირთული აპი გაუქმებულია"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"უსაფრთხო რეჟიმში ვიჯეტი გამორთულია"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"მალსახმობი მიუწვდომელია"</string>
+    <string name="home_screen" msgid="806512411299847073">"მთავარი ეკრანი"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"მორგებული ქმედებები"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"შეეხეთ და დააყოვნეთ ვიჯეტის ასარჩევად."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"ორმაგად შეეხეთ და გეჭიროთ ვიჯეტის ასარჩევად ან მორგებული მოქმედებების გამოსაყენებლად."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-kk-rKZ/strings.xml b/res/values-kk-rKZ/strings.xml
index 009fa4a..7762cd7 100644
--- a/res/values-kk-rKZ/strings.xml
+++ b/res/values-kk-rKZ/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Жүктелген қолданба қауіпсіз режимде өшірілген"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Қауіпсіз режимде виджеттер өшіріледі"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Таңбаша қолжетімді емес"</string>
+    <string name="home_screen" msgid="806512411299847073">"Негізгі экран"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Арнаулы әрекеттер"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Виджетті таңдау үшін түртіп, мықтап ұстаңыз."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Виджетті таңдау немесе арнаулы әрекеттерді таңдау үшін екі рет түртіп, ұстап тұрыңыз."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-km-rKH/strings.xml b/res/values-km-rKH/strings.xml
index 864c979..862a652 100644
--- a/res/values-km-rKH/strings.xml
+++ b/res/values-km-rKH/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"បាន​បិទ​កម្មវិធី​ដែល​បាន​ទាញ​យក​ក្នុង​របៀប​សុវត្ថិភាព"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"បាន​បិទ​ធាតុ​ក្រាហ្វិក​ក្នុង​របៀប​សុវត្ថិភាព"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"ផ្លូវកាត់មិនអាចប្រើបានទេ"</string>
+    <string name="home_screen" msgid="806512411299847073">"អេក្រង់ដើម"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"សកម្មភាព​ផ្ទាល់ខ្លួន"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"ប៉ះ &amp; សង្កត់ ដើម្បី​ជ្រើស​ធាតុ​ក្រាហ្វិក។"</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"ប៉ះពីរដង ហើយចុចឲ្យជាប់ដើម្បីជ្រើសយកធាតុក្រាហ្វិក ឬប្រើសកម្មភាពផ្ទាល់ខ្លួន។"</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-kn-rIN/strings.xml b/res/values-kn-rIN/strings.xml
index 2c50567..55fe36c 100644
--- a/res/values-kn-rIN/strings.xml
+++ b/res/values-kn-rIN/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"ಡೌನ್‌ಲೋಡ್ ಮಾಡಲಾದ ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ಸುರಕ್ಷಿತ ಮೋಡ್‌ನಲ್ಲಿ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"ಸುರಕ್ಷಿತ ಮೋಡ್‌ನಲ್ಲಿ ವಿಜೆಟ್‌ಗಳನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"ಶಾರ್ಟ್‌ಕಟ್ ಲಭ್ಯವಿಲ್ಲ"</string>
+    <string name="home_screen" msgid="806512411299847073">"ಮುಖಪುಟದ ಪರದೆ"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"ಕಸ್ಟಮ್ ಕ್ರಿಯೆಗಳು"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"ವಿಜೆಟ್ ಅನ್ನು ಆರಿಸಿಕೊಳ್ಳಲು ಸ್ಪರ್ಶಿಸಿ &amp; ಹಿಡಿದುಕೊಳ್ಳಿ."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"ಡಬಲ್ ಟ್ಯಾಪ್ ಮಾಡಿ ಮತ್ತು ವಿಜೆಟ್ ಆರಿಸಿಕೊಳ್ಳಲು ಹೋಲ್ಡ್ ಮಾಡಿ ಅಥವಾ ಕಸ್ಟಮ್ ಕ್ರಿಯೆಗಳನ್ನು ಬಳಸಿ"</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 1ef438f..bcc1699 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"다운로드한 앱은 안전 모드에서 사용할 수 없습니다."</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"안전 모드에서 위젯 사용 중지됨"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"바로가기를 사용할 수 없음"</string>
+    <string name="home_screen" msgid="806512411299847073">"메인 스크린"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"맞춤 작업"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"위젯을 선택하려면 길게 터치하세요."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"위젯을 선택하려면 두 번 탭한 다음 길게 터치하거나 맞춤 액션을 사용합니다."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d×%2$d"</string>
diff --git a/res/values-ky-rKG/strings.xml b/res/values-ky-rKG/strings.xml
index 71e02d0..75700ce 100644
--- a/res/values-ky-rKG/strings.xml
+++ b/res/values-ky-rKG/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Жүктөп алынган колдонмо Коопсуз режиминде иштен чыгарылды"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Виджеттер Коопсуз режимде өчүрүлгөн"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Кыска жол жок"</string>
+    <string name="home_screen" msgid="806512411299847073">"Башкы экран"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Ыңгайлаштырылган аракеттер"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Виджетти тандаш үчүн, басып туруңуз"</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Виджет тандоо үчүн эки жолу таптап, кармап туруңуз же ыңгайлаштырылган аракеттерди колдонуңуз."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-lo-rLA/strings.xml b/res/values-lo-rLA/strings.xml
index e231102..31b7db4 100644
--- a/res/values-lo-rLA/strings.xml
+++ b/res/values-lo-rLA/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"ແອັບຯ​ທີ່​ດາວ​ໂຫລດ​ແລ້ວ​ຖືກ​ປິດ​ການ​ນຳ​ໃຊ້​ໃນ Safe mode"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"​ວິດ​ເຈັດ​ຖືກ​ປິດ​ໃນ Safe mode"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"ບໍ່ສາມາດໃຊ້ທາງລັດໄດ້"</string>
+    <string name="home_screen" msgid="806512411299847073">"ໜ້າຈໍຫຼັກ"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"ຄຳສັ່ງແບບກຳນົດເອງ"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"ສຳພັດຄ້າງໄວ້ ເພື່ອຈັບວິດເຈັດ."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"ແຕະ​ຄ້າງ​ໄວ້ ເພື່ອ​ເລືອກວິດ​ເຈັດ ຫຼື ໃຊ້​ການ​ດຳ​ເນີນ​ການ​ກຳ​ນົດ​ເອງ."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 13ebaa3..1564d45 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Atsisiųsta programa išjungta Saugos režimu"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Valdikliai išjungti Saugiame režime"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Sparčiojo klavišo negalima naudoti"</string>
+    <string name="home_screen" msgid="806512411299847073">"Pagrindinis ekranas"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Tinkinti veiksmai"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Palieskite ir laikykite, kad pasirinkt. valdiklį."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Dukart palieskite ir laikykite, kad pasirinktumėte valdiklį ar naudotumėte tinkintus veiksmus."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 924b399..6458d0b 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Lejupielādētā lietotne ir atspējota drošajā režīmā."</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Logrīki atspējoti drošajā režīmā"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Saīsne nav pieejama."</string>
+    <string name="home_screen" msgid="806512411299847073">"Sākuma ekrāns"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Pielāgotās darbības"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Lai izvēlētos logrīku, pieskarieties un turiet to."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Lai atlasītu logrīku, veiciet dubultskārienu uz tā un turiet to vai arī veiciet pielāgotas darbības."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-mk-rMK/strings.xml b/res/values-mk-rMK/strings.xml
index 7e44707..83da8a1 100644
--- a/res/values-mk-rMK/strings.xml
+++ b/res/values-mk-rMK/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Преземената апликација е оневозможена во безбеден режим"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Додатоците се оневозможени во безбеден режим"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Кратенката не е достапна"</string>
+    <string name="home_screen" msgid="806512411299847073">"Почетен екран"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Приспособени дејства"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Допри и задржи за да се избере виџетот."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Допрете двапати и задржете за да изберете додаток или да користите приспособени дејства."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-ml-rIN/strings.xml b/res/values-ml-rIN/strings.xml
index 941d1ac..95a558a 100644
--- a/res/values-ml-rIN/strings.xml
+++ b/res/values-ml-rIN/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"ഡൗൺലോഡുചെയ്‌ത അപ്ലിക്കേഷൻ സുരക്ഷാ മോഡിൽ പ്രവർത്തനരഹിതമാക്കി"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"സുരക്ഷിത മോഡിൽ വിജറ്റുകൾ പ്രവർത്തനരഹിതമാക്കി"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"കുറുക്കുവഴി ലഭ്യമല്ല"</string>
+    <string name="home_screen" msgid="806512411299847073">"ഹോം സ്‌ക്രീൻ"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"ഇഷ്‌ടാനുസൃത പ്രവർത്തനങ്ങൾ"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"ഒരു വിജറ്റ് ചേർക്കുന്നതിന് അത് സ്‌പർശിച്ച് പിടിക്കുക."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"വിജറ്റ് തിരഞ്ഞെടുക്കാനോ ഇഷ്ടാനുസൃത പ്രവർത്തനങ്ങൾ ഉപയോഗിക്കാനോ രണ്ടുതവണ ടാപ്പുചെയ്ത് പിടിക്കുക."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-mn-rMN/strings.xml b/res/values-mn-rMN/strings.xml
index 45e1856..d9efe5d 100644
--- a/res/values-mn-rMN/strings.xml
+++ b/res/values-mn-rMN/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Татаж авсан апп-г Аюулгүй горим дотроос идэвхгүйжүүлсэн"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Safe горимд виджетүүдийг идэвхгүйжүүлсэн"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Товчлол алга"</string>
+    <string name="home_screen" msgid="806512411299847073">"Үндсэн нүүр"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Захиалгат үйлдэл"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Виджетийг авах бол хүрээд барина уу."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Жижиг хэрэгсэл авах болон тохируулсан үйлдлийг ашиглахын тулд 2 удаа товшоод барина уу."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-mr-rIN/strings.xml b/res/values-mr-rIN/strings.xml
index 1ec443c..5938b8e 100644
--- a/res/values-mr-rIN/strings.xml
+++ b/res/values-mr-rIN/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"डाउनलोड केलेला अ‍ॅप सुरक्षित मोड मध्‍ये अक्षम केला"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"विजेट सुरक्षित मोडमध्ये अक्षम झाले"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"शॉर्टकट उपलब्ध नाही"</string>
+    <string name="home_screen" msgid="806512411299847073">"मुख्यपृष्ठ"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"सानुकूल क्रिया"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"विजेट निवडण्यासाठी स्पर्श करा आणि धरून ठेवा."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"एक विजेट निवडण्यासाठी दोनदा टॅप करा आणि धरून ठेवा किंवा सानुकूल क्रिया वापरा."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-ms-rMY/strings.xml b/res/values-ms-rMY/strings.xml
index 215315a..63de9cb 100644
--- a/res/values-ms-rMY/strings.xml
+++ b/res/values-ms-rMY/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Apl yang dimuat turun dilumpuhkan dalam mod Selamat"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widget dilumpuhkan dalam mod Selamat"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Pintasan tidak tersedia"</string>
+    <string name="home_screen" msgid="806512411299847073">"Skrin utama"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Tindakan tersuai"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Sentuh &amp; tahan untuk mengambil widget."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Ketik dua kali &amp; tahan untuk mengambil widget atau menggunakan tindakan tersuai"</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-my-rMM/strings.xml b/res/values-my-rMM/strings.xml
index bc4e484..c65f3ab 100644
--- a/res/values-my-rMM/strings.xml
+++ b/res/values-my-rMM/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"ဒေါင်းလုဒ် အက်ပ်ကို လုံခြုံရေး မုဒ်ထဲမှာ ပိတ်ထား"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"လုံခြုံရေး မုဒ်ထဲမှာ ဝီဂျက်များကို ပိတ်ထား"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"ဖြတ်လမ်း မရနိုင်ပါ"</string>
+    <string name="home_screen" msgid="806512411299847073">"ပင်မစာမျက်နှာ"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"စိတ်ကြိုက် လုပ်ဆောင်ချက်များ"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"ဝဒ်ဂျက်တစ်ခုကို ကောက်ယူရန် ဖိနှိပ်ထားပါ"</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"ဝစ်ဂျက်တစ်ခုကိုရယူရန် သို့မဟုတ် စိတ်ကြိုက်လုပ်ဆောင်မှုများကို အသုံးပြုရန် နှစ်ချက်တို့ပြီး ကိုင်ထားပါ။"</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 449094d..38f027a 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"En nedlastet app er deaktivert i sikker modus"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Moduler er deaktivert i sikker modus"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Snarveien er ikke tilgjengelig"</string>
+    <string name="home_screen" msgid="806512411299847073">"Startskjerm"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Tilpassede handlinger"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Trykk og hold inne for å plukke opp en modul."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Dobbelttrykk og hold inne for å velge en modul eller bruke tilpassede handlinger."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-ne-rNP/strings.xml b/res/values-ne-rNP/strings.xml
index 8accfcc..330352f 100644
--- a/res/values-ne-rNP/strings.xml
+++ b/res/values-ne-rNP/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"सुरक्षित मोडमा डाउनलोड गरेको अनुप्रयोग अक्षम गरिएको छ"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"सुरक्षित मोडमा विगेटहरू अक्षम गरियो"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"सर्टकट उपलब्ध छैन"</string>
+    <string name="home_screen" msgid="806512411299847073">"गृह स्क्रिन"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"आफू अनुकूलका कारबाहीहरू"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"एउटा विजेटलाई टिप्नको लागि टच गरेर होल्ड गर्नुहोस्।"</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"विजेटलाई छान्न वा अनुकूलन कार्यहरू प्रयोग गर्न डबल ट्याप गरी होल्ड गर्नुहोस्।"</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 0134ae1..58f63e6 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Gedownloade app uitgeschakeld in veilige modus"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets uitgeschakeld in Veilige modus"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Snelkoppeling is niet beschikbaar"</string>
+    <string name="home_screen" msgid="806512411299847073">"Startscherm"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Aangepaste acties"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Blijf aanraken om een widget toe te voegen."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Dubbeltik en blijf aanraken om een widget toe te voegen of aangepaste acties te gebruiken."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-pa-rIN/strings.xml b/res/values-pa-rIN/strings.xml
index 3be9bec..38769aa 100644
--- a/res/values-pa-rIN/strings.xml
+++ b/res/values-pa-rIN/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"ਡਾਊਨਲੋਡ ਕੀਤਾ ਐਪ ਸੁਰੱਖਿਅਤ ਮੋਡ ਵਿੱਚ ਅਸਮਰਥਿਤ"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"ਵਿਜਿਟ ਸੁਰੱਖਿਅਤ ਮੋਡ ਵਿੱਚ ਅਸਮਰਥਿਤ"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"ਸ਼ਾਰਟਕੱਟ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string>
+    <string name="home_screen" msgid="806512411299847073">"ਮੁੱਖ ਸਕ੍ਰੀਨ"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"ਵਿਸ਼ੇਸ਼-ਵਿਉਂਤਬੱਧ ਕਾਰਵਾਈਆਂ"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"ਇੱਕ ਵਿਜੇਟ ਚੁਣਨ ਲਈ ਛੋਹਵੋT &amp; ਹੋਲਡ ਕਰੋ।"</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"ਡਬਲ-ਟੈਪ &amp; ਇੱਕ ਵਿਜੇਟ ਚੁਣਨ ਲਈ ਹੋਲਡ ਕਰੋ ਅਤੇ ਕਸਟਮ ਕਿਰਿਆਵਾਂ ਵਰਤੋ।"</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index feb2a36..1e2fc47 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Pobrana aplikacja została wyłączona w trybie awaryjnym"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widżety są wyłączone w trybie bezpiecznym"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Skrót nie jest dostępny"</string>
+    <string name="home_screen" msgid="806512411299847073">"Ekran główny"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Działania niestandardowe"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Aby dodać widżet, kliknij go i przytrzymaj."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Kliknij dwukrotnie i przytrzymaj, by wybrać widżet lub użyć działań niestandardowych."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 6d684fa..000c7ad 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Aplicação transferida desativada no Modo de segurança"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets desativados no Modo de segurança"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"O atalho não está disponível"</string>
+    <string name="home_screen" msgid="806512411299847073">"Ecrã principal"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Ações personalizadas"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Prima sem soltar para escolher um widget."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Toque duas vezes sem soltar para escolher um widget ou utilize ações personalizadas."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 82a756f..9969997 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"App transferido por download desativado no modo de segurança"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets desativados no modo de segurança"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"O atalho não está disponível"</string>
+    <string name="home_screen" msgid="806512411299847073">"Tela inicial"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Ações personalizadas"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Toque e pressione para selecionar um widget."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Toque duas vezes e segure para selecionar um widget ou usar ações personalizadas."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 5905bcf..6faf57f 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Aplicația descărcată este dezactivată în modul de siguranță"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgeturile sunt dezactivate în modul de siguranță"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Comanda rapidă nu este disponibilă"</string>
+    <string name="home_screen" msgid="806512411299847073">"Ecran de pornire"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Acțiuni personalizate"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Atingeți lung un widget pentru a-l alege."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Atingeți de două ori și mențineți apăsat ca să alegeți un widget sau folosiți acțiuni personalizate."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index ad30c97..7bc1819 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Скачанное приложение отключено в безопасном режиме"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Виджеты отключены в безопасном режиме"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Ярлык недоступен"</string>
+    <string name="home_screen" msgid="806512411299847073">"Главный экран"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Пользовательские действия"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Чтобы выбрать виджет, нажмите на значок и удерживайте его."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Чтобы выбрать виджет, нажмите на него дважды и не отпускайте или выполните предложенные действия."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d x %2$d"</string>
diff --git a/res/values-si-rLK/strings.xml b/res/values-si-rLK/strings.xml
index 1e0ed28..df4b411 100644
--- a/res/values-si-rLK/strings.xml
+++ b/res/values-si-rLK/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"ආරක්ෂිත ආකාරය තුළ බාගන්න ලද යෙදුම් අබල කරන්න"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"සුරක්ෂිත ආකාරය තුළ විජටය අබල කරන ලදි"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"කෙටි මග ලබා ගත නොහැකිය"</string>
+    <string name="home_screen" msgid="806512411299847073">"මුල් පිටු තිරය"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"අභිරුචි ක්‍රියා"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"විජට් එක ස්පර්ශ කර අහුලා ගැනීමට අල්ලාගෙන සිටින්න."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"විජට් එකක් අහුලා ගැනීමට හෝ අභිරුචි ක්‍රියා කිරීමට ඩබල් ටැප් කර අල්ලා ගෙන සිටින්න."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 7210d79..d0a2e59 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Stiahnutá aplikácia je v núdzovom režime zakázaná"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Miniaplikácie sú v núdzovom režime zakázané"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Skratky nie sú k dispozícii"</string>
+    <string name="home_screen" msgid="806512411299847073">"Plocha"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Vlastné akcie"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Miniaplikáciu pridáte stlačením a podržaním."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Miniaplikáciu pridáte dvojitým klepnutím a pridržaním alebo pomocou vlastných akcií."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index b55cb0d..0682f8d 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Prenesena aplikacija je onemogočena v Varnem načinu"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Pripomočki so onemogočeni v varnem načinu"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Bližnjica ni na voljo"</string>
+    <string name="home_screen" msgid="806512411299847073">"Začetni zaslon"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Dejanja po meri"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Za izbiro pripomočka se ga dotaknite in pridržite."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Če želite izbrati pripomoček ali uporabiti dejanja po meri, se ga dvakrat dotaknite in ga pridržite."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-sq-rAL/strings.xml b/res/values-sq-rAL/strings.xml
index c4fbaf3..701eaf8 100644
--- a/res/values-sq-rAL/strings.xml
+++ b/res/values-sq-rAL/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Aplikacioni i shkarkuar është i çaktivizuar në modalitetin e sigurt"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Miniaplikacionet janë të çaktivizuara në modalitetin e sigurt"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Shkurtorja nuk është e disponueshme"</string>
+    <string name="home_screen" msgid="806512411299847073">"Ekrani bazë"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Veprimet e personalizuara"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Prek dhe mbaj shtypur për të zgjedhur një miniaplikacion."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Prek dy herë dhe mbaj shtypur për të zgjedhur një miniaplikacion ose për të përdorur veprimet e personalizuara."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 67a63cf..a396f18 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Преузета апликација је онемогућена у Безбедном режиму"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Виџети су онемогућени у Безбедном режиму"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Пречица није доступна"</string>
+    <string name="home_screen" msgid="806512411299847073">"Почетни екран"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Прилагођене радње"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Додирните и задржите да бисте изабрали виџет."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Двапут додирните и задржите да бисте изабрали виџет или користите прилагођене радње."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d×%2$d"</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index dfcc1b0..907de79 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Den hämtade appen inaktiverades i säkert läge"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets är inaktiverade i felsäkert läge"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Genvägen är inte tillgänglig"</string>
+    <string name="home_screen" msgid="806512411299847073">"Startskärm"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Anpassade åtgärder"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Tryck länge om du vill flytta en widget."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Tryck två gånger och håll kvar om du vill ta upp en widget eller använda anpassade åtgärder."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 03c8f06..20654e0 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Programu iliyopakuliwa imezimwa katika Hali Salama"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Wijeti zimezimwa katika hali ya Usalama"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Hakuna njia ya mkato"</string>
+    <string name="home_screen" msgid="806512411299847073">"Skrini ya kwanza"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Vitendo maalum"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Gusa na ushikilie ili kuteua wijeti."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Gonga mara mbili na ushikilie ile uchague wijeti au utumie vitendo maalum."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-sw600dp/dimens.xml b/res/values-sw600dp/dimens.xml
index 2838088..ead666c 100644
--- a/res/values-sw600dp/dimens.xml
+++ b/res/values-sw600dp/dimens.xml
@@ -16,8 +16,6 @@
 
 <resources>
 <!-- All Apps -->
-    <dimen name="all_apps_grid_view_start_margin">0dp</dimen>
-    <dimen name="all_apps_grid_section_text_size">26sp</dimen>
     <dimen name="all_apps_background_canvas_width">850dp</dimen>
     <dimen name="all_apps_background_canvas_height">525dp</dimen>
 
diff --git a/res/values-ta-rIN/strings.xml b/res/values-ta-rIN/strings.xml
index 7db0ae4..fd51b71 100644
--- a/res/values-ta-rIN/strings.xml
+++ b/res/values-ta-rIN/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"இறக்கிய பயன்பாடு பாதுகாப்பு முறையில் முடக்கப்பட்டது"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"பாதுகாப்புப் பயன்முறையில் விட்ஜெட்கள் முடக்கப்பட்டுள்ளன"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"குறுக்குவழி இல்லை"</string>
+    <string name="home_screen" msgid="806512411299847073">"முகப்புத் திரை"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"தனிப்பயன் செயல்கள்"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"விட்ஜெட்டைத் தேர்வுசெய்ய தொட்டுப் பிடிக்கவும்."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"விட்ஜெட்டைத் தேர்ந்தெடுக்க இருமுறை தட்டிப் பிடிக்கவும் அல்லது தனிப்பயன் செயல்களைப் பயன்படுத்தவும்."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-te-rIN/strings.xml b/res/values-te-rIN/strings.xml
index 4e8b86f..ba19ddc 100644
--- a/res/values-te-rIN/strings.xml
+++ b/res/values-te-rIN/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"డౌన్‌లోడ్ చేసిన అనువర్తనం సురక్షిత మోడ్‌లో నిలిపివేయబడింది"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"సురక్షిత మోడ్‌లో విడ్జెట్‌లు నిలిపివేయబడ్డాయి"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"సత్వరమార్గం అందుబాటులో లేదు"</string>
+    <string name="home_screen" msgid="806512411299847073">"హోమ్ స్క్రీన్"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"అనుకూల చర్యలు"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"విడ్జెట్‌ను ఎంచుకోవడానికి తాకి &amp; నొక్కి పెట్టండి."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"విడ్జెట్‌ను ఎంచుకోవడానికి లేదా అనుకూల చర్యలను ఉపయోగించడానికి రెండుసార్లు నొక్కి, ఉంచండి."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 90dec72..4bcda7a 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"แอปที่ดาวน์โหลดถูกปิดในโหมดปลอดภัย"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"มีการปิดใช้งานวิดเจ็ตในเซฟโหมด"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"ทางลัดไม่พร้อมใช้งาน"</string>
+    <string name="home_screen" msgid="806512411299847073">"หน้าจอหลัก"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"การทำงานที่กำหนดเอง"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"แตะค้างเพื่อรับวิดเจ็ต"</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"แตะ 2 ครั้งค้างไว้เพื่อเลือกวิดเจ็ตหรือใช้การกระทำที่กำหนดเอง"</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index b944ebc..55e02e9 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Naka-disable ang na-download na app sa Safe mode"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Naka-disable ang mga widget sa Safe mode"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Hindi available ang shortcut"</string>
+    <string name="home_screen" msgid="806512411299847073">"Home screen"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Mga custom na pagkilos"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Pindutin nang matagal upang kumuha ng widget."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"I-double tap nang matagal upang pumili ng widget o gumamit ng mga custom na pagkilos."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index e95ee2d..f30b559 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"İndirilen uygulama Güvenli modda devre dışı bırakıldı"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Güvenli modda widget\'lar devre dışı"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Kısayol kullanılamıyor"</string>
+    <string name="home_screen" msgid="806512411299847073">"Ana ekran"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Özel işlemler"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Widget seçmek için dokunun ve basılı tutun."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Bir widget\'ı seçmek veya özel işlemleri kullanmak için iki kez hafifçe dokunun ve basılı tutun."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 1f73f04..750b6a0 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Завантажений додаток вимкнено в безпечному режимі"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"У безпечному режимі віджети вимкнено"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Ярлик недоступний"</string>
+    <string name="home_screen" msgid="806512411299847073">"Головний екран"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Спеціальні дії"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Натисніть і утримуйте, щоб вибрати віджет."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Двічі натисніть і утримуйте, щоб вибрати віджет, або виконайте іншу дію."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-ur-rPK/strings.xml b/res/values-ur-rPK/strings.xml
index e4c637d..b285de0 100644
--- a/res/values-ur-rPK/strings.xml
+++ b/res/values-ur-rPK/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"ڈاؤن لوڈ کردہ ایپ کو محفوظ وضع میں غیر فعال کر دیا گیا"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"ویجیٹس کو محفوظ وضع میں غیر فعال کر دیا گیا"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"شارٹ کٹ دستیاب نہیں ہے"</string>
+    <string name="home_screen" msgid="806512411299847073">"ہوم اسکرین"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"حسب ضرورت کارروائیاں"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"کوئی ویجیٹ منتخب کرنے کیلئے ٹچ کریں اور پکڑے رہیں۔"</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"کوئی ویجٹ منتخب کرنے یا حسب ضرورت کاروائیاں استعمال کرنے کیلئے دو بار تھپتھپائیں اور پکڑے رکھیں۔"</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-uz-rUZ/strings.xml b/res/values-uz-rUZ/strings.xml
index 0e541d3..77b7367 100644
--- a/res/values-uz-rUZ/strings.xml
+++ b/res/values-uz-rUZ/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Yuklab olingan ilova xavfsiz rejimda o‘chirib qo‘yildi"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Xavfsiz rejimda vidjetlar o‘chirib qo‘yilgan"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Tezkor tugmadan foydalanib bo‘lmaydi"</string>
+    <string name="home_screen" msgid="806512411299847073">"Bosh ekran"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Maxsus amallar"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Vidjetni tanlash uchun bosib turing."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Ikki marta bosib va bosib turgan holatda vidjetni tanlang yoki maxsus amaldan foydalaning."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 2bf5ad6..2d953e6 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Ứng dụng đã tải xuống bị tắt ở chế độ An toàn"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Tiện ích con bị vô hiệu hóa ở chế độ an toàn"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Lối tắt không khả dụng"</string>
+    <string name="home_screen" msgid="806512411299847073">"Màn hình chính"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Tác vụ tùy chỉnh"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Chạm và giữ để chọn tiện ích con."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Nhấn đúp và giữ để chọn tiện ích hoặc sử dụng tác vụ tùy chỉnh."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index e3ed354..7967812 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"安全模式下不允许使用下载的此应用"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"安全模式下不允许使用小部件"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"无法使用快捷方式"</string>
+    <string name="home_screen" msgid="806512411299847073">"主屏幕"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"自定义操作"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"触摸并按住小部件即可选择。"</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"点按两次并按住小部件即可选择小部件,您也可以使用自定义操作。"</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index ad0f99a..236fc42 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"在安全模式中無法使用「已下載的應用程式」功能"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"在安全模式中無法使用小工具"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"沒有可用的捷徑"</string>
+    <string name="home_screen" msgid="806512411299847073">"主畫面"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"自訂操作"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"輕觸並按住小工具即可選取。"</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"連扲兩下,然後扲住,就可以新增小工具,或者執行自訂操作。"</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 24f391d..8d4c744 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"在安全模式中無法使用「已下載的應用程式」功能"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"在安全模式下無法使用小工具"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"目前無法使用捷徑"</string>
+    <string name="home_screen" msgid="806512411299847073">"主螢幕"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"自訂動作"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"輕觸並按住小工具即可選取。"</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"輕觸兩下並按住小工具即可選取,您也可以使用自訂動作。"</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 10fbef4..88d4295 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"Uhlelo lokusebenza olulandiwe lukhutshaziwe kumodi ephephile"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"Amawijethi akhutshaziwe kwimodi yokuphepha"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Isinqamuleli asitholakali"</string>
+    <string name="home_screen" msgid="806512411299847073">"Isikrini sasekhaya"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Izenzo zangokwezifiso"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Thinta uphinde ubambe ukuze uphakamise iwijethi."</string>
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Thepha kabili bese uyabamba ukuze uthathe iwijethi noma sebenzisa izenzo ezingokwezifiso."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 3423835..32bccb8 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -27,6 +27,7 @@
             <enum name="all_apps" value="1" />
             <enum name="folder" value="2" />
             <enum name="widget_section" value="3" />
+            <enum name="shortcut_popup" value="4" />
         </attr>
         <attr name="deferShadowGeneration" format="boolean" />
         <attr name="customShadows" format="boolean" />
diff --git a/res/values/config.xml b/res/values/config.xml
index a942f02..5b3ee46 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -91,6 +91,9 @@
     <!-- View ID used by cell layout to jail its content -->
     <item type="id" name="cell_layout_jail_id" />
 
+    <!-- View ID used by PreviewImageView to cache its instance -->
+    <item type="id" name="preview_image_id" />
+
 <!-- Deep shortcuts -->
     <integer name="config_deepShortcutOpenDuration">220</integer>
     <integer name="config_deepShortcutArrowOpenDuration">80</integer>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index eff9d21..188f98f 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -33,8 +33,6 @@
     <dimen name="dynamic_grid_workspace_page_spacing">8dp</dimen>
     <!-- Minimum space between workspace and hotseat in spring loaded mode -->
     <dimen name="dynamic_grid_min_spring_loaded_space">8dp</dimen>
-    <dimen name="dynamic_grid_container_land_left_padding">118dp</dimen>
-    <dimen name="dynamic_grid_container_land_right_padding">66dp</dimen>
 
 <!-- Drop target bar -->
     <dimen name="dynamic_grid_drop_target_size">48dp</dimen>
@@ -54,6 +52,7 @@
 
     <dimen name="container_fastscroll_thumb_min_width">5dp</dimen>
     <dimen name="container_fastscroll_thumb_max_width">9dp</dimen>
+    <dimen name="container_fastscroll_popup_margin">18dp</dimen>
     <dimen name="container_fastscroll_thumb_height">72dp</dimen>
     <dimen name="container_fastscroll_thumb_touch_inset">-24dp</dimen>
     <dimen name="container_fastscroll_popup_size">72dp</dimen>
@@ -61,11 +60,8 @@
 
 <!-- All Apps -->
     <dimen name="all_apps_button_scale_down">0dp</dimen>
-    <dimen name="all_apps_grid_view_start_margin">0dp</dimen>
-    <dimen name="all_apps_grid_section_y_offset">8dp</dimen>
-    <dimen name="all_apps_grid_section_text_size">24sp</dimen>
+    <dimen name="all_apps_search_bar_field_height">48dp</dimen>
     <dimen name="all_apps_search_bar_height">60dp</dimen>
-    <dimen name="all_apps_search_bar_margin_top">12dp</dimen>
     <dimen name="all_apps_search_bar_icon_margin_right">4dp</dimen>
     <dimen name="all_apps_search_bar_icon_margin_top">1dp</dimen>
     <dimen name="all_apps_list_bottom_padding">8dp</dimen>
@@ -165,7 +161,7 @@
     <dimen name="bg_pill_height">48dp</dimen>
     <dimen name="bg_pill_radius">24dp</dimen>
     <dimen name="deep_shortcuts_spacing">4dp</dimen>
-    <dimen name="deferred_drag_view_scale">6dp</dimen>
+    <dimen name="pre_drag_view_scale">6dp</dimen>
     <!-- an icon with shortcuts must be dragged this far before the container is removed. -->
     <dimen name="deep_shortcuts_start_drag_threshold">16dp</dimen>
     <dimen name="deep_shortcut_icon_size">36dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 60a37e5..a9c970d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -37,6 +37,10 @@
     <string name="safemode_widget_error">Widgets disabled in Safe mode</string>
     <!-- Message shown when a shortcut is not available. It could have been temporarily disabled and may start working again after some time. -->
     <string name="shortcut_not_available">Shortcut isn\'t available</string>
+    <!-- User visible name for the launcher/home screen. [CHAR_LIMIT=30] -->
+    <string name="home_screen">Home screen</string>
+    <!-- Label for showing custom action list of a shortcut or widget. [CHAR_LIMIT=30] -->
+    <string name="custom_actions">Custom actions</string>
 
     <!-- Widgets -->
     <!-- Message to tell the user to press and hold on a widget to add it [CHAR_LIMIT=50] -->
diff --git a/res/values/styles.xml b/res/values/styles.xml
index cd06b75..4e70f43 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -30,6 +30,20 @@
 
     <style name="Theme" parent="@style/LauncherTheme"></style>
 
+    <style name="FastScrollerPopup" >
+        <item name="android:background">@drawable/container_fastscroll_popup_bg</item>
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:minWidth">@dimen/container_fastscroll_popup_size</item>
+        <item name="android:layout_height">@dimen/container_fastscroll_popup_size</item>
+        <item name="android:textSize">@dimen/container_fastscroll_popup_text_size</item>
+        <item name="android:gravity">center</item>
+        <item name="android:alpha">0</item>
+        <item name="android:elevation">3dp</item>
+        <item name="android:saveEnabled">false</item>
+        <item name="android:textColor">@android:color/white</item>
+        <item name="android:includeFontPadding">false</item>
+    </style>
+
     <!-- 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>
@@ -74,7 +88,6 @@
         <item name="android:background">@null</item>
         <item name="android:textColor">@color/quantum_panel_text_color</item>
         <item name="android:shadowRadius">0</item>
-        <item name="android:textSize">@dimen/folder_child_text_size</item>
         <item name="android:gravity">center_horizontal</item>
         <item name="android:includeFontPadding">false</item>
         <item name="customShadows">false</item>
@@ -95,6 +108,7 @@
         <item name="android:shadowRadius">0</item>
         <item name="customShadows">false</item>
         <item name="layoutHorizontal">true</item>
+        <item name="iconDisplay">shortcut_popup</item>
         <item name="iconSizeOverride">@dimen/deep_shortcut_icon_size</item>
     </style>
 
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
new file mode 100644
index 0000000..65da002
--- /dev/null
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.content.Context;
+import android.support.annotation.IntDef;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import com.android.launcher3.dragndrop.DragLayer;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Base class for a View which shows a floating UI on top of the launcher UI.
+ */
+public abstract class AbstractFloatingView extends LinearLayout {
+
+    @IntDef(flag = true, value = {TYPE_FOLDER, TYPE_DEEPSHORTCUT_CONTAINER})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface FloatingViewType {}
+    public static final int TYPE_FOLDER = 1 << 0;
+    public static final int TYPE_DEEPSHORTCUT_CONTAINER = 1 << 1;
+
+    protected boolean mIsOpen;
+
+    public AbstractFloatingView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public AbstractFloatingView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public final void close(boolean animate) {
+        animate &= !Utilities.isPowerSaverOn(getContext());
+        handleClose(animate);
+        Launcher.getLauncher(getContext()).getUserEventDispatcher().resetElapsedContainerMillis();
+    }
+
+    protected abstract void handleClose(boolean animate);
+
+    /**
+     * If the view is current handling keyboard, return the active target, null otherwise
+     */
+    public ExtendedEditText getActiveTextView() {
+        return null;
+    }
+
+
+    /**
+     * Any additional view (outside of this container) where touch should be allowed while this
+     * view is visible.
+     */
+    public View getExtendedTouchView() {
+        return null;
+    }
+
+    public final boolean isOpen() {
+        return mIsOpen;
+    }
+
+    protected abstract boolean isOfType(@FloatingViewType int type);
+
+    protected static <T extends AbstractFloatingView> T getOpenView(
+            Launcher launcher, @FloatingViewType int type) {
+        DragLayer dragLayer = launcher.getDragLayer();
+        // Iterate in reverse order. AbstractFloatingView is added later to the dragLayer,
+        // and will be one of the last views.
+        for (int i = dragLayer.getChildCount() - 1; i >= 0; i--) {
+            View child = dragLayer.getChildAt(i);
+            if (child instanceof AbstractFloatingView) {
+                AbstractFloatingView view = (AbstractFloatingView) child;
+                if (view.isOfType(type) && view.isOpen()) {
+                    return (T) view;
+                }
+            }
+        }
+        return null;
+    }
+
+    protected static void closeOpenContainer(Launcher launcher, @FloatingViewType int type) {
+        AbstractFloatingView view = getOpenView(launcher, type);
+        if (view != null) {
+            view.close(true);
+        }
+    }
+
+    public static void closeAllOpenViews(Launcher launcher, boolean animate) {
+        DragLayer dragLayer = launcher.getDragLayer();
+        // Iterate in reverse order. AbstractFloatingView is added later to the dragLayer,
+        // and will be one of the last views.
+        for (int i = dragLayer.getChildCount() - 1; i >= 0; i--) {
+            View child = dragLayer.getChildAt(i);
+            if (child instanceof AbstractFloatingView) {
+                ((AbstractFloatingView) child).close(animate);
+            }
+        }
+    }
+
+    public static void closeAllOpenViews(Launcher launcher) {
+        closeAllOpenViews(launcher, true);
+    }
+
+    public static AbstractFloatingView getTopOpenView(Launcher launcher) {
+        return getOpenView(launcher, TYPE_FOLDER | TYPE_DEEPSHORTCUT_CONTAINER);
+    }
+}
diff --git a/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/AllAppsList.java
index c431593..b13c20b 100644
--- a/src/com/android/launcher3/AllAppsList.java
+++ b/src/com/android/launcher3/AllAppsList.java
@@ -23,7 +23,7 @@
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.util.FlagOp;
-import com.android.launcher3.util.StringFilter;
+import com.android.launcher3.util.ItemInfoMatcher;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -33,7 +33,7 @@
 /**
  * Stores the list of all applications for the all apps view.
  */
-class AllAppsList {
+public class AllAppsList {
     public static final int DEFAULT_APPLICATIONS_NUMBER = 42;
 
     /** The list off all apps. */
@@ -112,8 +112,7 @@
         final List<AppInfo> data = this.data;
         for (int i = data.size() - 1; i >= 0; i--) {
             AppInfo info = data.get(i);
-            final ComponentName component = info.intent.getComponent();
-            if (info.user.equals(user) && packageName.equals(component.getPackageName())) {
+            if (info.user.equals(user) && packageName.equals(info.componentName.getPackageName())) {
                 removed.add(info);
                 data.remove(i);
             }
@@ -121,14 +120,13 @@
     }
 
     /**
-     * Updates the apps for the given packageName and user based on {@param op}.
+     * Updates the disabled flags of apps matching {@param matcher} based on {@param op}.
      */
-    public void updatePackageFlags(StringFilter pkgFilter, UserHandleCompat user, FlagOp op) {
+    public void updateDisabledFlags(ItemInfoMatcher matcher, FlagOp op) {
         final List<AppInfo> data = this.data;
         for (int i = data.size() - 1; i >= 0; i--) {
             AppInfo info = data.get(i);
-            final ComponentName component = info.intent.getComponent();
-            if (info.user.equals(user) && pkgFilter.matches(component.getPackageName())) {
+            if (matcher.matches(info, info.componentName)) {
                 info.isDisabled = op.apply(info.isDisabled);
                 modified.add(info);
             }
@@ -157,10 +155,9 @@
             // to the removed list.
             for (int i = data.size() - 1; i >= 0; i--) {
                 final AppInfo applicationInfo = data.get(i);
-                final ComponentName component = applicationInfo.intent.getComponent();
                 if (user.equals(applicationInfo.user)
-                        && packageName.equals(component.getPackageName())) {
-                    if (!findActivity(matches, component)) {
+                        && packageName.equals(applicationInfo.componentName.getPackageName())) {
+                    if (!findActivity(matches, applicationInfo.componentName)) {
                         removed.add(applicationInfo);
                         data.remove(i);
                     }
@@ -184,11 +181,10 @@
             // Remove all data for this package.
             for (int i = data.size() - 1; i >= 0; i--) {
                 final AppInfo applicationInfo = data.get(i);
-                final ComponentName component = applicationInfo.intent.getComponent();
                 if (user.equals(applicationInfo.user)
-                        && packageName.equals(component.getPackageName())) {
+                        && packageName.equals(applicationInfo.componentName.getPackageName())) {
                     removed.add(applicationInfo);
-                    mIconCache.remove(component, user);
+                    mIconCache.remove(applicationInfo.componentName, user);
                     data.remove(i);
                 }
             }
@@ -240,9 +236,8 @@
     private AppInfo findApplicationInfoLocked(String packageName, UserHandleCompat user,
             String className) {
         for (AppInfo info: data) {
-            final ComponentName component = info.intent.getComponent();
-            if (user.equals(info.user) && packageName.equals(component.getPackageName())
-                    && className.equals(component.getClassName())) {
+            if (user.equals(info.user) && packageName.equals(info.componentName.getPackageName())
+                    && className.equals(info.componentName.getClassName())) {
                 return info;
             }
         }
diff --git a/src/com/android/launcher3/AnotherWindowDropTarget.java b/src/com/android/launcher3/AnotherWindowDropTarget.java
index 0e18874..7074f78 100644
--- a/src/com/android/launcher3/AnotherWindowDropTarget.java
+++ b/src/com/android/launcher3/AnotherWindowDropTarget.java
@@ -27,7 +27,7 @@
 public class AnotherWindowDropTarget implements DropTarget {
     final Launcher mLauncher;
 
-    public AnotherWindowDropTarget (Context context) { mLauncher = (Launcher) context; }
+    public AnotherWindowDropTarget (Context context) { mLauncher = Launcher.getLauncher(context); }
 
     @Override
     public boolean isDropEnabled() { return true; }
diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/AppInfo.java
index 4c4d67c..3b22f46 100644
--- a/src/com/android/launcher3/AppInfo.java
+++ b/src/com/android/launcher3/AppInfo.java
@@ -52,11 +52,6 @@
 
     public ComponentName componentName;
 
-    static final int DOWNLOADED_FLAG = 1;
-    static final int UPDATED_SYSTEM_APP_FLAG = 2;
-
-    int flags = 0;
-
     /**
      * {@see ShortcutInfo#isDisabled}
      */
@@ -88,7 +83,6 @@
             IconCache iconCache, boolean quietModeEnabled) {
         this.componentName = info.getComponentName();
         this.container = ItemInfo.NO_ID;
-        flags = initFlags(info);
         if (PackageManagerHelper.isAppSuspended(info.getApplicationInfo())) {
             isDisabled |= ShortcutInfo.FLAG_DISABLED_SUSPENDED;
         }
@@ -101,25 +95,11 @@
         this.user = user;
     }
 
-    public static int initFlags(LauncherActivityInfoCompat info) {
-        int appFlags = info.getApplicationInfo().flags;
-        int flags = 0;
-        if ((appFlags & android.content.pm.ApplicationInfo.FLAG_SYSTEM) == 0) {
-            flags |= DOWNLOADED_FLAG;
-
-            if ((appFlags & android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
-                flags |= UPDATED_SYSTEM_APP_FLAG;
-            }
-        }
-        return flags;
-    }
-
     public AppInfo(AppInfo info) {
         super(info);
         componentName = info.componentName;
         title = Utilities.trim(info.title);
         intent = new Intent(info.intent);
-        flags = info.flags;
         isDisabled = info.isDisabled;
         iconBitmap = info.iconBitmap;
     }
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index cd27b4c..0380923 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -1,7 +1,5 @@
 package com.android.launcher3;
 
-import com.android.launcher3.dragndrop.DragLayer;
-
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
@@ -13,16 +11,19 @@
 import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.view.Gravity;
+import android.util.AttributeSet;
 import android.view.KeyEvent;
+import android.view.MotionEvent;
 import android.view.View;
 import android.widget.FrameLayout;
-import android.widget.ImageView;
 
 import com.android.launcher3.accessibility.DragViewStateAnnouncer;
+import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.util.FocusLogic;
+import com.android.launcher3.util.TouchController;
 
-public class AppWidgetResizeFrame extends FrameLayout implements View.OnKeyListener {
+public class AppWidgetResizeFrame extends FrameLayout
+        implements View.OnKeyListener, TouchController {
     private static final int SNAP_DURATION = 150;
     private static final float DIMMED_HANDLE_ALPHA = 0f;
     private static final float RESIZE_THRESHOLD = 0.66f;
@@ -32,17 +33,22 @@
     // Represents the cell size on the grid in the two orientations.
     private static Point[] sCellSize;
 
+    private static final int HANDLE_COUNT = 4;
+    private static final int INDEX_LEFT = 0;
+    private static final int INDEX_TOP = 1;
+    private static final int INDEX_RIGHT = 2;
+    private static final int INDEX_BOTTOM = 3;
+
     private final Launcher mLauncher;
-    private final LauncherAppWidgetHostView mWidgetView;
-    private final CellLayout mCellLayout;
-    private final DragLayer mDragLayer;
+    private final DragViewStateAnnouncer mStateAnnouncer;
 
-    private final ImageView mLeftHandle;
-    private final ImageView mRightHandle;
-    private final ImageView mTopHandle;
-    private final ImageView mBottomHandle;
+    private final View[] mDragHandles = new View[HANDLE_COUNT];
 
-    private final Rect mWidgetPadding;
+    private LauncherAppWidgetHostView mWidgetView;
+    private CellLayout mCellLayout;
+    private DragLayer mDragLayer;
+
+    private Rect mWidgetPadding;
 
     private final int mBackgroundPadding;
     private final int mTouchTargetWidth;
@@ -51,17 +57,20 @@
     private final int[] mLastDirectionVector = new int[2];
     private final int[] mTmpPt = new int[2];
 
-    private final DragViewStateAnnouncer mStateAnnouncer;
+    private final IntRange mTempRange1 = new IntRange();
+    private final IntRange mTempRange2 = new IntRange();
+
+    private final IntRange mDeltaXRange = new IntRange();
+    private final IntRange mBaselineX = new IntRange();
+
+    private final IntRange mDeltaYRange = new IntRange();
+    private final IntRange mBaselineY = new IntRange();
 
     private boolean mLeftBorderActive;
     private boolean mRightBorderActive;
     private boolean mTopBorderActive;
     private boolean mBottomBorderActive;
 
-    private int mBaselineWidth;
-    private int mBaselineHeight;
-    private int mBaselineX;
-    private int mBaselineY;
     private int mResizeMode;
 
     private int mRunningHInc;
@@ -76,11 +85,38 @@
     private int mTopTouchRegionAdjustment = 0;
     private int mBottomTouchRegionAdjustment = 0;
 
-    public AppWidgetResizeFrame(Context context,
-            LauncherAppWidgetHostView widgetView, CellLayout cellLayout, DragLayer dragLayer) {
+    private int mXDown, mYDown;
 
-        super(context);
+    public AppWidgetResizeFrame(Context context) {
+        this(context, null);
+    }
+
+    public AppWidgetResizeFrame(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public AppWidgetResizeFrame(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+
         mLauncher = Launcher.getLauncher(context);
+        mStateAnnouncer = DragViewStateAnnouncer.createFor(this);
+
+        mBackgroundPadding = getResources()
+                .getDimensionPixelSize(R.dimen.resize_frame_background_padding);
+        mTouchTargetWidth = 2 * mBackgroundPadding;
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        for (int i = 0; i < HANDLE_COUNT; i ++) {
+            mDragHandles[i] = getChildAt(i);
+        }
+    }
+
+    public void setupForWidget(LauncherAppWidgetHostView widgetView, CellLayout cellLayout,
+            DragLayer dragLayer) {
         mCellLayout = cellLayout;
         mWidgetView = widgetView;
         LauncherAppWidgetProviderInfo info = (LauncherAppWidgetProviderInfo)
@@ -91,63 +127,23 @@
         mMinHSpan = info.minSpanX;
         mMinVSpan = info.minSpanY;
 
-        mStateAnnouncer = DragViewStateAnnouncer.createFor(this);
-
-        setBackgroundResource(R.drawable.widget_resize_shadow);
-        setForeground(getResources().getDrawable(R.drawable.widget_resize_frame));
-        setPadding(0, 0, 0, 0);
-
-        final int handleMargin = getResources().getDimensionPixelSize(R.dimen.widget_handle_margin);
-        LayoutParams lp;
-        mLeftHandle = new ImageView(context);
-        mLeftHandle.setImageResource(R.drawable.ic_widget_resize_handle);
-        lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
-                Gravity.LEFT | Gravity.CENTER_VERTICAL);
-        lp.leftMargin = handleMargin;
-        addView(mLeftHandle, lp);
-
-        mRightHandle = new ImageView(context);
-        mRightHandle.setImageResource(R.drawable.ic_widget_resize_handle);
-        lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
-                Gravity.RIGHT | Gravity.CENTER_VERTICAL);
-        lp.rightMargin = handleMargin;
-        addView(mRightHandle, lp);
-
-        mTopHandle = new ImageView(context);
-        mTopHandle.setImageResource(R.drawable.ic_widget_resize_handle);
-        lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
-                Gravity.CENTER_HORIZONTAL | Gravity.TOP);
-        lp.topMargin = handleMargin;
-        addView(mTopHandle, lp);
-
-        mBottomHandle = new ImageView(context);
-        mBottomHandle.setImageResource(R.drawable.ic_widget_resize_handle);
-        lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
-                Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
-        lp.bottomMargin = handleMargin;
-        addView(mBottomHandle, lp);
-
         if (!info.isCustomWidget) {
-            mWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context,
+            mWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(getContext(),
                     widgetView.getAppWidgetInfo().provider, null);
         } else {
-            Resources r = context.getResources();
+            Resources r = getContext().getResources();
             int padding = r.getDimensionPixelSize(R.dimen.default_widget_padding);
             mWidgetPadding = new Rect(padding, padding, padding, padding);
         }
 
         if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) {
-            mTopHandle.setVisibility(GONE);
-            mBottomHandle.setVisibility(GONE);
+            mDragHandles[INDEX_TOP].setVisibility(GONE);
+            mDragHandles[INDEX_BOTTOM].setVisibility(GONE);
         } else if (mResizeMode == AppWidgetProviderInfo.RESIZE_VERTICAL) {
-            mLeftHandle.setVisibility(GONE);
-            mRightHandle.setVisibility(GONE);
+            mDragHandles[INDEX_LEFT].setVisibility(GONE);
+            mDragHandles[INDEX_RIGHT].setVisibility(GONE);
         }
 
-        mBackgroundPadding = getResources()
-                .getDimensionPixelSize(R.dimen.resize_frame_background_padding);
-        mTouchTargetWidth = 2 * mBackgroundPadding;
-
         // When we create the resize frame, we first mark all cells as unoccupied. The appropriate
         // cells (same if not resized, or different) will be marked as occupied when the resize
         // frame is dismissed.
@@ -169,101 +165,74 @@
         boolean anyBordersActive = mLeftBorderActive || mRightBorderActive
                 || mTopBorderActive || mBottomBorderActive;
 
-        mBaselineWidth = getMeasuredWidth();
-        mBaselineHeight = getMeasuredHeight();
-        mBaselineX = getLeft();
-        mBaselineY = getTop();
-
         if (anyBordersActive) {
-            mLeftHandle.setAlpha(mLeftBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
-            mRightHandle.setAlpha(mRightBorderActive ? 1.0f :DIMMED_HANDLE_ALPHA);
-            mTopHandle.setAlpha(mTopBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
-            mBottomHandle.setAlpha(mBottomBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
+            mDragHandles[INDEX_LEFT].setAlpha(mLeftBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
+            mDragHandles[INDEX_RIGHT].setAlpha(mRightBorderActive ? 1.0f :DIMMED_HANDLE_ALPHA);
+            mDragHandles[INDEX_TOP].setAlpha(mTopBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
+            mDragHandles[INDEX_BOTTOM].setAlpha(mBottomBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
         }
+
+        if (mLeftBorderActive) {
+            mDeltaXRange.set(-getLeft(), getWidth() - 2 * mTouchTargetWidth);
+        } else if (mRightBorderActive) {
+            mDeltaXRange.set(2 * mTouchTargetWidth - getWidth(), mDragLayer.getWidth() - getRight());
+        } else {
+            mDeltaXRange.set(0, 0);
+        }
+        mBaselineX.set(getLeft(), getRight());
+
+        if (mTopBorderActive) {
+            mDeltaYRange.set(-getTop(), getHeight() - 2 * mTouchTargetWidth);
+        } else if (mBottomBorderActive) {
+            mDeltaYRange.set(2 * mTouchTargetWidth - getHeight(), mDragLayer.getHeight() - getBottom());
+        } else {
+            mDeltaYRange.set(0, 0);
+        }
+        mBaselineY.set(getTop(), getBottom());
+
         return anyBordersActive;
     }
 
     /**
-     *  Here we bound the deltas such that the frame cannot be stretched beyond the extents
-     *  of the CellLayout, and such that the frame's borders can't cross.
+     *  Based on the deltas, we resize the frame.
      */
-    public void updateDeltas(int deltaX, int deltaY) {
-        if (mLeftBorderActive) {
-            mDeltaX = Math.max(-mBaselineX, deltaX); 
-            mDeltaX = Math.min(mBaselineWidth - 2 * mTouchTargetWidth, mDeltaX);
-        } else if (mRightBorderActive) {
-            mDeltaX = Math.min(mDragLayer.getWidth() - (mBaselineX + mBaselineWidth), deltaX);
-            mDeltaX = Math.max(-mBaselineWidth + 2 * mTouchTargetWidth, mDeltaX);
-        }
-
-        if (mTopBorderActive) {
-            mDeltaY = Math.max(-mBaselineY, deltaY);
-            mDeltaY = Math.min(mBaselineHeight - 2 * mTouchTargetWidth, mDeltaY);
-        } else if (mBottomBorderActive) {
-            mDeltaY = Math.min(mDragLayer.getHeight() - (mBaselineY + mBaselineHeight), deltaY);
-            mDeltaY = Math.max(-mBaselineHeight + 2 * mTouchTargetWidth, mDeltaY);
-        }
-    }
-
     public void visualizeResizeForDelta(int deltaX, int deltaY) {
-        visualizeResizeForDelta(deltaX, deltaY, false);
+        mDeltaX = mDeltaXRange.clamp(deltaX);
+        mDeltaY = mDeltaYRange.clamp(deltaY);
+
+        DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
+        mDeltaX = mDeltaXRange.clamp(deltaX);
+        mBaselineX.applyDelta(mLeftBorderActive, mRightBorderActive, mDeltaX, mTempRange1);
+        lp.x = mTempRange1.start;
+        lp.width = mTempRange1.size();
+
+        mDeltaY = mDeltaYRange.clamp(deltaY);
+        mBaselineY.applyDelta(mTopBorderActive, mBottomBorderActive, mDeltaY, mTempRange1);
+        lp.y = mTempRange1.start;
+        lp.height = mTempRange1.size();
+
+        resizeWidgetIfNeeded(false);
+        requestLayout();
     }
 
-    /**
-     *  Based on the deltas, we resize the frame, and, if needed, we resize the widget.
-     */
-    private void visualizeResizeForDelta(int deltaX, int deltaY, boolean onDismiss) {
-        updateDeltas(deltaX, deltaY);
-        DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
-
-        if (mLeftBorderActive) {
-            lp.x = mBaselineX + mDeltaX;
-            lp.width = mBaselineWidth - mDeltaX;
-        } else if (mRightBorderActive) {
-            lp.width = mBaselineWidth + mDeltaX;
-        }
-
-        if (mTopBorderActive) {
-            lp.y = mBaselineY + mDeltaY;
-            lp.height = mBaselineHeight - mDeltaY;
-        } else if (mBottomBorderActive) {
-            lp.height = mBaselineHeight + mDeltaY;
-        }
-
-        resizeWidgetIfNeeded(onDismiss);
-        requestLayout();
+    private static int getSpanIncrement(float deltaFrac) {
+        return Math.abs(deltaFrac) > RESIZE_THRESHOLD ? Math.round(deltaFrac) : 0;
     }
 
     /**
      *  Based on the current deltas, we determine if and how to resize the widget.
      */
     private void resizeWidgetIfNeeded(boolean onDismiss) {
-        int xThreshold = mCellLayout.getCellWidth() + mCellLayout.getWidthGap();
-        int yThreshold = mCellLayout.getCellHeight() + mCellLayout.getHeightGap();
+        float xThreshold = mCellLayout.getCellWidth();
+        float yThreshold = mCellLayout.getCellHeight();
 
-        int deltaX = mDeltaX + mDeltaXAddOn;
-        int deltaY = mDeltaY + mDeltaYAddOn;
-
-        float hSpanIncF = 1.0f * deltaX / xThreshold - mRunningHInc;
-        float vSpanIncF = 1.0f * deltaY / yThreshold - mRunningVInc;
-
-        int hSpanInc = 0;
-        int vSpanInc = 0;
-        int cellXInc = 0;
-        int cellYInc = 0;
-
-        int countX = mCellLayout.getCountX();
-        int countY = mCellLayout.getCountY();
-
-        if (Math.abs(hSpanIncF) > RESIZE_THRESHOLD) {
-            hSpanInc = Math.round(hSpanIncF);
-        }
-        if (Math.abs(vSpanIncF) > RESIZE_THRESHOLD) {
-            vSpanInc = Math.round(vSpanIncF);
-        }
+        int hSpanInc = getSpanIncrement((mDeltaX + mDeltaXAddOn) / xThreshold - mRunningHInc);
+        int vSpanInc = getSpanIncrement((mDeltaY + mDeltaYAddOn) / yThreshold - mRunningVInc);
 
         if (!onDismiss && (hSpanInc == 0 && vSpanInc == 0)) return;
 
+        mDirectionVector[0] = 0;
+        mDirectionVector[1] = 0;
 
         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mWidgetView.getLayoutParams();
 
@@ -272,55 +241,24 @@
         int cellX = lp.useTmpCoords ? lp.tmpCellX : lp.cellX;
         int cellY = lp.useTmpCoords ? lp.tmpCellY : lp.cellY;
 
-        int hSpanDelta = 0;
-        int vSpanDelta = 0;
-
         // For each border, we bound the resizing based on the minimum width, and the maximum
         // expandability.
-        if (mLeftBorderActive) {
-            cellXInc = Math.max(-cellX, hSpanInc);
-            cellXInc = Math.min(lp.cellHSpan - mMinHSpan, cellXInc);
-            hSpanInc *= -1;
-            hSpanInc = Math.min(cellX, hSpanInc);
-            hSpanInc = Math.max(-(lp.cellHSpan - mMinHSpan), hSpanInc);
-            hSpanDelta = -hSpanInc;
-
-        } else if (mRightBorderActive) {
-            hSpanInc = Math.min(countX - (cellX + spanX), hSpanInc);
-            hSpanInc = Math.max(-(lp.cellHSpan - mMinHSpan), hSpanInc);
-            hSpanDelta = hSpanInc;
+        mTempRange1.set(cellX, spanX + cellX);
+        int hSpanDelta = mTempRange1.applyDeltaAndBound(mLeftBorderActive, mRightBorderActive,
+                hSpanInc, mMinHSpan, mCellLayout.getCountX(), mTempRange2);
+        cellX = mTempRange2.start;
+        spanX = mTempRange2.size();
+        if (hSpanDelta != 0) {
+            mDirectionVector[0] = mLeftBorderActive ? -1 : 1;
         }
 
-        if (mTopBorderActive) {
-            cellYInc = Math.max(-cellY, vSpanInc);
-            cellYInc = Math.min(lp.cellVSpan - mMinVSpan, cellYInc);
-            vSpanInc *= -1;
-            vSpanInc = Math.min(cellY, vSpanInc);
-            vSpanInc = Math.max(-(lp.cellVSpan - mMinVSpan), vSpanInc);
-            vSpanDelta = -vSpanInc;
-        } else if (mBottomBorderActive) {
-            vSpanInc = Math.min(countY - (cellY + spanY), vSpanInc);
-            vSpanInc = Math.max(-(lp.cellVSpan - mMinVSpan), vSpanInc);
-            vSpanDelta = vSpanInc;
-        }
-
-        mDirectionVector[0] = 0;
-        mDirectionVector[1] = 0;
-        // Update the widget's dimensions and position according to the deltas computed above
-        if (mLeftBorderActive || mRightBorderActive) {
-            spanX += hSpanInc;
-            cellX += cellXInc;
-            if (hSpanDelta != 0) {
-                mDirectionVector[0] = mLeftBorderActive ? -1 : 1;
-            }
-        }
-
-        if (mTopBorderActive || mBottomBorderActive) {
-            spanY += vSpanInc;
-            cellY += cellYInc;
-            if (vSpanDelta != 0) {
-                mDirectionVector[1] = mTopBorderActive ? -1 : 1;
-            }
+        mTempRange1.set(cellY, spanY + cellY);
+        int vSpanDelta = mTempRange1.applyDeltaAndBound(mTopBorderActive, mBottomBorderActive,
+                vSpanInc, mMinVSpan, mCellLayout.getCountY(), mTempRange2);
+        cellY = mTempRange2.start;
+        spanY = mTempRange2.size();
+        if (vSpanDelta != 0) {
+            mDirectionVector[1] = mTopBorderActive ? -1 : 1;
         }
 
         if (!onDismiss && vSpanDelta == 0 && hSpanDelta == 0) return;
@@ -398,9 +336,9 @@
         requestLayout();
     }
 
-    public void onTouchUp() {
-        int xThreshold = mCellLayout.getCellWidth() + mCellLayout.getWidthGap();
-        int yThreshold = mCellLayout.getCellHeight() + mCellLayout.getHeightGap();
+    private void onTouchUp() {
+        int xThreshold = mCellLayout.getCellWidth();
+        int yThreshold = mCellLayout.getCellHeight();
 
         mDeltaXAddOn = mRunningHInc * xThreshold; 
         mDeltaYAddOn = mRunningVInc * yThreshold; 
@@ -450,10 +388,9 @@
             lp.height = newHeight;
             lp.x = newX;
             lp.y = newY;
-            mLeftHandle.setAlpha(1.0f);
-            mRightHandle.setAlpha(1.0f);
-            mTopHandle.setAlpha(1.0f);
-            mBottomHandle.setAlpha(1.0f);
+            for (int i = 0; i < HANDLE_COUNT; i++) {
+                mDragHandles[i].setAlpha(1.0f);
+            }
             requestLayout();
         } else {
             PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", lp.width, newWidth);
@@ -463,22 +400,15 @@
             PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", lp.y, newY);
             ObjectAnimator oa =
                     LauncherAnimUtils.ofPropertyValuesHolder(lp, this, width, height, x, y);
-            ObjectAnimator leftOa = LauncherAnimUtils.ofFloat(mLeftHandle, ALPHA, 1.0f);
-            ObjectAnimator rightOa = LauncherAnimUtils.ofFloat(mRightHandle, ALPHA, 1.0f);
-            ObjectAnimator topOa = LauncherAnimUtils.ofFloat(mTopHandle, ALPHA, 1.0f);
-            ObjectAnimator bottomOa = LauncherAnimUtils.ofFloat(mBottomHandle, ALPHA, 1.0f);
             oa.addUpdateListener(new AnimatorUpdateListener() {
                 public void onAnimationUpdate(ValueAnimator animation) {
                     requestLayout();
                 }
             });
             AnimatorSet set = LauncherAnimUtils.createAnimatorSet();
-            if (mResizeMode == AppWidgetProviderInfo.RESIZE_VERTICAL) {
-                set.playTogether(oa, topOa, bottomOa);
-            } else if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) {
-                set.playTogether(oa, leftOa, rightOa);
-            } else {
-                set.playTogether(oa, leftOa, rightOa, topOa, bottomOa);
+            set.play(oa);
+            for (int i = 0; i < HANDLE_COUNT; i++) {
+                set.play(LauncherAnimUtils.ofFloat(mDragHandles[i], ALPHA, 1.0f));
             }
 
             set.setDuration(SNAP_DURATION);
@@ -493,10 +423,115 @@
     public boolean onKey(View v, int keyCode, KeyEvent event) {
         // Clear the frame and give focus to the widget host view when a directional key is pressed.
         if (FocusLogic.shouldConsume(keyCode)) {
-            mDragLayer.clearAllResizeFrames();
+            mDragLayer.clearResizeFrame();
             mWidgetView.requestFocus();
             return true;
         }
         return false;
     }
+
+    private boolean handleTouchDown(MotionEvent ev) {
+        Rect hitRect = new Rect();
+        int x = (int) ev.getX();
+        int y = (int) ev.getY();
+
+        getHitRect(hitRect);
+        if (hitRect.contains(x, y)) {
+            if (beginResizeIfPointInRegion(x - getLeft(), y - getTop())) {
+                mXDown = x;
+                mYDown = y;
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onControllerTouchEvent(MotionEvent ev) {
+        int action = ev.getAction();
+        int x = (int) ev.getX();
+        int y = (int) ev.getY();
+
+        switch (action) {
+            case MotionEvent.ACTION_DOWN:
+                return handleTouchDown(ev);
+            case MotionEvent.ACTION_MOVE:
+                visualizeResizeForDelta(x - mXDown, y - mYDown);
+                break;
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP:
+                visualizeResizeForDelta(x - mXDown, y - mYDown);
+                onTouchUp();
+                mXDown = mYDown = 0;
+                break;
+        }
+        return true;
+    }
+
+    @Override
+    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_DOWN && handleTouchDown(ev)) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * A mutable class for describing the range of two int values.
+     */
+    private static class IntRange {
+
+        public int start, end;
+
+        public int clamp(int value) {
+            return Utilities.boundToRange(value, start, end);
+        }
+
+        public void set(int s, int e) {
+            start = s;
+            end = e;
+        }
+
+        public int size() {
+            return end - start;
+        }
+
+        /**
+         * Moves either the start or end edge (but never both) by {@param delta} and  sets the
+         * result in {@param out}
+         */
+        public void applyDelta(boolean moveStart, boolean moveEnd, int delta, IntRange out) {
+            out.start = moveStart ? start + delta : start;
+            out.end = moveEnd ? end + delta : end;
+        }
+
+        /**
+         * Applies delta similar to {@link #applyDelta(boolean, boolean, int, IntRange)},
+         * with extra conditions.
+         * @param minSize minimum size after with the moving edge should not be shifted any further.
+         *                For eg, if delta = -3 when moving the endEdge brings the size to less than
+         *                minSize, only delta = -2 will applied
+         * @param maxEnd The maximum value to the end edge (start edge is always restricted to 0)
+         * @return the amount of increase when endEdge was moves and the amount of decrease when
+         * the start edge was moved.
+         */
+        public int applyDeltaAndBound(boolean moveStart, boolean moveEnd, int delta,
+                int minSize, int maxEnd, IntRange out) {
+            applyDelta(moveStart, moveEnd, delta, out);
+            if (start < 0) {
+                out.start = 0;
+            }
+            if (end > maxEnd) {
+                out.end = maxEnd;
+            }
+            if (out.size() < minSize) {
+                if (moveStart) {
+                    out.start = out.end - minSize;
+                } else if (moveEnd) {
+                    out.end = out.start + minSize;
+                }
+            }
+            return moveEnd ? out.size() - size() : size() - out.size();
+        }
+    }
 }
diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java
index d5309b4..2a4212a 100644
--- a/src/com/android/launcher3/AutoInstallsLayout.java
+++ b/src/com/android/launcher3/AutoInstallsLayout.java
@@ -17,7 +17,6 @@
 package com.android.launcher3;
 
 import android.appwidget.AppWidgetHost;
-import android.appwidget.AppWidgetManager;
 import android.content.ComponentName;
 import android.content.ContentValues;
 import android.content.Context;
@@ -38,6 +37,7 @@
 import com.android.launcher3.LauncherProvider.SqlArguments;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.graphics.LauncherIcons;
 import com.android.launcher3.util.Thunk;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -436,7 +436,8 @@
                 return -1;
             }
 
-            ItemInfo.writeBitmap(mValues, Utilities.createIconBitmap(icon, mContext));
+            mValues.put(LauncherSettings.Favorites.ICON,
+                    Utilities.flattenBitmap(LauncherIcons.createIconBitmap(icon, mContext)));
             mValues.put(Favorites.ICON_PACKAGE, mIconRes.getResourcePackageName(iconId));
             mValues.put(Favorites.ICON_RESOURCE, mIconRes.getResourceName(iconId));
 
diff --git a/src/com/android/launcher3/BaseContainerView.java b/src/com/android/launcher3/BaseContainerView.java
index 96942ee..ac7cbaf 100644
--- a/src/com/android/launcher3/BaseContainerView.java
+++ b/src/com/android/launcher3/BaseContainerView.java
@@ -16,17 +16,23 @@
 
 package com.android.launcher3;
 
+import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.res.TypedArray;
+import android.graphics.PointF;
+import android.graphics.Rect;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.InsetDrawable;
 import android.util.AttributeSet;
+import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewConfiguration;
 import android.widget.FrameLayout;
 
 import com.android.launcher3.allapps.AllAppsContainerView;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.util.TransformingTouchDelegate;
 
 /**
  * A base container view, which supports resizing.
@@ -34,17 +40,17 @@
 public abstract class BaseContainerView extends FrameLayout
         implements DeviceProfile.LauncherLayoutChangeListener {
 
-    protected int mContainerPaddingLeft;
-    protected int mContainerPaddingRight;
-    protected int mContainerPaddingTop;
-    protected int mContainerPaddingBottom;
+    private static final Rect sBgPaddingRect = new Rect();
 
-    private InsetDrawable mRevealDrawable;
     protected final Drawable mBaseDrawable;
 
     private View mRevealView;
     private View mContent;
 
+    private TransformingTouchDelegate mTouchDelegate;
+
+    private final PointF mLastTouchDownPosPx = new PointF(-1.0f, -1.0f);
+
     public BaseContainerView(Context context) {
         this(context, null);
     }
@@ -72,6 +78,12 @@
 
         DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile();
         grid.addLauncherLayoutChangedListener(this);
+
+        View touchDelegateTargetView = getTouchDelegateTargetView();
+        if (touchDelegateTargetView != null) {
+            mTouchDelegate = new TransformingTouchDelegate(touchDelegateTargetView);
+            ((View) touchDelegateTargetView.getParent()).setTouchDelegate(mTouchDelegate);
+        }
     }
 
     @Override
@@ -97,6 +109,65 @@
         updatePaddings();
     }
 
+    /**
+     * Calculate the background padding as it can change due to insets/content padding change.
+     */
+    private void updatePaddings() {
+        Context context = getContext();
+        int paddingLeft;
+        int paddingRight;
+        int paddingTop;
+        int paddingBottom;
+
+        DeviceProfile grid = Launcher.getLauncher(context).getDeviceProfile();
+        int[] padding = grid.getContainerPadding();
+        paddingLeft = padding[0] + grid.edgeMarginPx;
+        paddingRight = padding[1] + grid.edgeMarginPx;
+        if (!grid.isVerticalBarLayout()) {
+            paddingTop = paddingBottom = grid.edgeMarginPx;
+        } else {
+            paddingTop = paddingBottom = 0;
+        }
+        updateBackground(paddingLeft, paddingTop, paddingRight, paddingBottom);
+    }
+
+    /**
+     * Update the background for the reveal view and content view based on the background padding.
+     */
+    protected void updateBackground(int paddingLeft, int paddingTop,
+            int paddingRight, int paddingBottom) {
+        mRevealView.setBackground(new InsetDrawable(mBaseDrawable,
+                paddingLeft, paddingTop, paddingRight, paddingBottom));
+        mContent.setBackground(new InsetDrawable(mBaseDrawable,
+                paddingLeft, paddingTop, paddingRight, paddingBottom));
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+
+        View touchDelegateTargetView = getTouchDelegateTargetView();
+        if (touchDelegateTargetView != null) {
+            getRevealView().getBackground().getPadding(sBgPaddingRect);
+            mTouchDelegate.setBounds(
+                    touchDelegateTargetView.getLeft() - sBgPaddingRect.left,
+                    touchDelegateTargetView.getTop() - sBgPaddingRect.top,
+                    touchDelegateTargetView.getRight() + sBgPaddingRect.right,
+                    touchDelegateTargetView.getBottom() + sBgPaddingRect.bottom);
+        }
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        return handleTouchEvent(ev);
+    }
+
+    @SuppressLint("ClickableViewAccessibility")
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        return handleTouchEvent(ev);
+    }
+
     public void setRevealDrawableColor(int color) {
         ((ColorDrawable) mBaseDrawable).setColor(color);
     }
@@ -109,35 +180,41 @@
         return mRevealView;
     }
 
-    private void updatePaddings() {
-        Context context = getContext();
-        Launcher launcher = Launcher.getLauncher(context);
 
-        if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP &&
-                this instanceof AllAppsContainerView &&
-                !launcher.getDeviceProfile().isVerticalBarLayout()) {
-            mContainerPaddingLeft = mContainerPaddingRight = 0;
-            mContainerPaddingTop = mContainerPaddingBottom = 0;
-        } else {
-            DeviceProfile grid = launcher.getDeviceProfile();
-            int[] padding = grid.getContainerPadding(context);
-            mContainerPaddingLeft = padding[0] + grid.edgeMarginPx;
-            mContainerPaddingRight = padding[1] + grid.edgeMarginPx;
-            if (!launcher.getDeviceProfile().isVerticalBarLayout()) {
-                mContainerPaddingTop = mContainerPaddingBottom = grid.edgeMarginPx;
-            } else {
-                mContainerPaddingTop = mContainerPaddingBottom = 0;
-            }
+    /**
+     * Handles the touch events that shows the workspace when clicking outside the bounds of the
+     * touch delegate target view.
+     */
+    private boolean handleTouchEvent(MotionEvent ev) {
+        switch (ev.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+                // Check if the touch is outside touch delegate target view
+                View touchDelegateTargetView = getTouchDelegateTargetView();
+                float leftBoundPx = touchDelegateTargetView.getLeft();
+                if (ev.getX() < leftBoundPx ||
+                        ev.getX() > (touchDelegateTargetView.getWidth() + leftBoundPx)) {
+                    mLastTouchDownPosPx.set((int) ev.getX(), (int) ev.getY());
+                }
+                break;
+            case MotionEvent.ACTION_UP:
+                if (mLastTouchDownPosPx.x > -1) {
+                    ViewConfiguration viewConfig = ViewConfiguration.get(getContext());
+                    float dx = ev.getX() - mLastTouchDownPosPx.x;
+                    float dy = ev.getY() - mLastTouchDownPosPx.y;
+                    float distance = PointF.length(dx, dy);
+                    if (distance < viewConfig.getScaledTouchSlop()) {
+                        // The background was clicked, so just go home
+                        Launcher.getLauncher(getContext()).showWorkspace(true);
+                        return true;
+                    }
+                }
+                // Fall through
+            case MotionEvent.ACTION_CANCEL:
+                mLastTouchDownPosPx.set(-1, -1);
+                break;
         }
-
-        mRevealDrawable = new InsetDrawable(mBaseDrawable,
-                mContainerPaddingLeft, mContainerPaddingTop, mContainerPaddingRight,
-                mContainerPaddingBottom);
-        mRevealView.setBackground(mRevealDrawable);
-        if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && this instanceof AllAppsContainerView) {
-            // Skip updating the content background
-        } else {
-            mContent.setBackground(mRevealDrawable);
-        }
+        return false;
     }
+
+    public abstract View getTouchDelegateTargetView();
 }
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index 45bc940..6fdf454 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -18,10 +18,11 @@
 
 import android.content.Context;
 import android.graphics.Canvas;
-import android.graphics.Rect;
 import android.support.v7.widget.RecyclerView;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
+import android.view.ViewGroup;
+
 import com.android.launcher3.util.Thunk;
 
 
@@ -41,12 +42,11 @@
     @Thunk int mDy = 0;
     private float mDeltaThreshold;
 
-    protected BaseRecyclerViewFastScrollBar mScrollbar;
+    protected final BaseRecyclerViewFastScrollBar mScrollbar;
 
     private int mDownX;
     private int mDownY;
     private int mLastY;
-    protected Rect mBackgroundPadding = new Rect();
 
     public BaseRecyclerView(Context context) {
         this(context, null);
@@ -92,6 +92,12 @@
         addOnItemTouchListener(this);
     }
 
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mScrollbar.setPopupView(((ViewGroup) getParent()).findViewById(R.id.fast_scroller_popup));
+    }
+
     /**
      * We intercept the touch handling only to support fast scrolling when initiated from the
      * scroll bar.  Otherwise, we fall back to the default RecyclerView touch handling.
@@ -156,28 +162,11 @@
         return false;
     }
 
-    public void updateBackgroundPadding(Rect padding) {
-        mBackgroundPadding.set(padding);
-    }
-
-    public Rect getBackgroundPadding() {
-        return mBackgroundPadding;
-    }
-
     /**
-     * Returns the scroll bar width when the user is scrolling.
+     * Returns the height of the fast scroll bar
      */
-    public int getMaxScrollbarWidth() {
-        return mScrollbar.getThumbMaxWidth();
-    }
-
-    /**
-     * Returns the visible height of the recycler view:
-     *   VisibleHeight = View height - top padding - bottom padding
-     */
-    protected int getVisibleHeight() {
-        int visibleHeight = getHeight() - mBackgroundPadding.top - mBackgroundPadding.bottom;
-        return visibleHeight;
+    protected int getScrollbarTrackHeight() {
+        return getHeight();
     }
 
     /**
@@ -191,7 +180,7 @@
      *   AvailableScrollBarHeight = Total height of the visible view - thumb height
      */
     protected int getAvailableScrollBarHeight() {
-        int availableScrollBarHeight = getVisibleHeight() - mScrollbar.getThumbHeight();
+        int availableScrollBarHeight = getScrollbarTrackHeight() - mScrollbar.getThumbHeight();
         return availableScrollBarHeight;
     }
 
@@ -203,13 +192,6 @@
     }
 
     /**
-     * Returns the inactive thumb color, can be overridden by each subclass.
-     */
-    public int getFastScrollerThumbInactiveColor(int defaultInactiveThumbColor) {
-        return defaultInactiveThumbColor;
-    }
-
-    /**
      * Returns the scrollbar for this recycler view.
      */
     public BaseRecyclerViewFastScrollBar getScrollBar() {
@@ -233,31 +215,19 @@
     protected void synchronizeScrollBarThumbOffsetToViewScroll(int scrollY,
             int availableScrollHeight) {
         // Only show the scrollbar if there is height to be scrolled
-        int availableScrollBarHeight = getAvailableScrollBarHeight();
         if (availableScrollHeight <= 0) {
-            mScrollbar.setThumbOffset(-1, -1);
+            mScrollbar.setThumbOffsetY(-1);
             return;
         }
 
         // Calculate the current scroll position, the scrollY of the recycler view accounts for the
         // view padding, while the scrollBarY is drawn right up to the background padding (ignoring
         // padding)
-        int scrollBarY = mBackgroundPadding.top +
-                (int) (((float) scrollY / availableScrollHeight) * availableScrollBarHeight);
+        int scrollBarY =
+                (int) (((float) scrollY / availableScrollHeight) * getAvailableScrollBarHeight());
 
         // Calculate the position and size of the scroll bar
-        mScrollbar.setThumbOffset(getScrollBarX(), scrollBarY);
-    }
-
-    /**
-     * @return the x position for the scrollbar thumb
-     */
-    protected int getScrollBarX() {
-        if (Utilities.isRtl(getResources())) {
-            return mBackgroundPadding.left;
-        } else {
-            return getWidth() - mBackgroundPadding.right - mScrollbar.getThumbWidth();
-        }
+        mScrollbar.setThumbOffsetY(scrollBarY);
     }
 
     /**
diff --git a/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java b/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
index 3d71632..40c5ed6 100644
--- a/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
+++ b/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
@@ -15,21 +15,18 @@
  */
 package com.android.launcher3;
 
-import android.animation.AnimatorSet;
-import android.animation.ArgbEvaluator;
 import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.Path;
-import android.graphics.Point;
 import android.graphics.Rect;
+import android.util.Property;
 import android.view.MotionEvent;
+import android.view.View;
 import android.view.ViewConfiguration;
-
-import com.android.launcher3.util.Thunk;
+import android.widget.TextView;
 
 /**
  * The track and scrollbar that shows when you scroll the list.
@@ -40,29 +37,46 @@
         void setFastScrollFocusState(final FastBitmapDrawable.State focusState, boolean animated);
     }
 
+    private static final Property<BaseRecyclerViewFastScrollBar, Integer> TRACK_WIDTH =
+            new Property<BaseRecyclerViewFastScrollBar, Integer>(Integer.class, "width") {
+
+                @Override
+                public Integer get(BaseRecyclerViewFastScrollBar scrollBar) {
+                    return scrollBar.mWidth;
+                }
+
+                @Override
+                public void set(BaseRecyclerViewFastScrollBar scrollBar, Integer value) {
+                    scrollBar.setTrackWidth(value);
+                }
+            };
+
     private final static int MAX_TRACK_ALPHA = 30;
     private final static int SCROLL_BAR_VIS_DURATION = 150;
+    private static final float FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR = 1.5f;
 
-    @Thunk BaseRecyclerView mRv;
-    private BaseRecyclerViewFastScrollPopup mPopup;
+    private final Rect mTmpRect = new Rect();
+    private final BaseRecyclerView mRv;
 
-    private AnimatorSet mScrollbarAnimator;
+    private final boolean mIsRtl;
 
-    private int mThumbInactiveColor;
-    private int mThumbActiveColor;
-    @Thunk Point mThumbOffset = new Point(-1, -1);
-    @Thunk Paint mThumbPaint;
-    private int mThumbMinWidth;
-    private int mThumbMaxWidth;
-    @Thunk int mThumbWidth;
-    @Thunk int mThumbHeight;
-    private int mThumbCurvature;
-    private Path mThumbPath = new Path();
-    private Paint mTrackPaint;
-    private int mTrackWidth;
-    private float mLastTouchY;
     // The inset is the buffer around which a point will still register as a click on the scrollbar
-    private int mTouchInset;
+    private final int mTouchInset;
+
+    private final int mMinWidth;
+    private final int mMaxWidth;
+
+    // Current width of the track
+    private int mWidth;
+    private ObjectAnimator mWidthAnimator;
+
+    private final Path mThumbPath = new Path();
+    private final Paint mThumbPaint;
+    private final int mThumbHeight;
+
+    private final Paint mTrackPaint;
+
+    private float mLastTouchY;
     private boolean mIsDragging;
     private boolean mIsThumbDetached;
     private boolean mCanThumbDetach;
@@ -70,27 +84,35 @@
 
     // This is the offset from the top of the scrollbar when the user first starts touching.  To
     // prevent jumping, this offset is applied as the user scrolls.
-    private int mTouchOffset;
+    private int mTouchOffsetY;
+    private int mThumbOffsetY;
 
-    private Rect mInvalidateRect = new Rect();
-    private Rect mTmpRect = new Rect();
+    // Fast scroller popup
+    private TextView mPopupView;
+    private boolean mPopupVisible;
+    private String mPopupSectionName;
 
     public BaseRecyclerViewFastScrollBar(BaseRecyclerView rv, Resources res) {
         mRv = rv;
-        mPopup = new BaseRecyclerViewFastScrollPopup(rv, res);
         mTrackPaint = new Paint();
         mTrackPaint.setColor(rv.getFastScrollerTrackColor(Color.BLACK));
         mTrackPaint.setAlpha(MAX_TRACK_ALPHA);
-        mThumbActiveColor = mThumbInactiveColor = Utilities.getColorAccent(rv.getContext());
+
         mThumbPaint = new Paint();
         mThumbPaint.setAntiAlias(true);
-        mThumbPaint.setColor(mThumbInactiveColor);
+        mThumbPaint.setColor(Utilities.getColorAccent(rv.getContext()));
         mThumbPaint.setStyle(Paint.Style.FILL);
-        mThumbWidth = mThumbMinWidth = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_min_width);
-        mThumbMaxWidth = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_max_width);
+
+        mWidth = mMinWidth = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_min_width);
+        mMaxWidth = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_max_width);
         mThumbHeight = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_height);
-        mThumbCurvature = mThumbMaxWidth - mThumbMinWidth;
         mTouchInset = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_touch_inset);
+        mIsRtl = Utilities.isRtl(res);
+        updateThumbPath();
+    }
+
+    public void setPopupView(View popup) {
+        mPopupView = (TextView) popup;
     }
 
     public void setDetachThumbOnFastScroll() {
@@ -101,61 +123,60 @@
         mIsThumbDetached = false;
     }
 
-    public void setThumbOffset(int x, int y) {
-        if (mThumbOffset.x == x && mThumbOffset.y == y) {
+    private int getDrawLeft() {
+        return mIsRtl ? 0 : (mRv.getWidth() - mMaxWidth);
+    }
+
+    public void setThumbOffsetY(int y) {
+        if (mThumbOffsetY == y) {
             return;
         }
-        mInvalidateRect.set(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
-                mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
-        mThumbOffset.set(x, y);
+
+        // Invalidate the previous and new thumb area
+        int drawLeft = getDrawLeft();
+        mTmpRect.set(drawLeft, mThumbOffsetY, drawLeft + mMaxWidth, mThumbOffsetY + mThumbHeight);
+        mThumbOffsetY = y;
+        mTmpRect.union(drawLeft, mThumbOffsetY, drawLeft + mMaxWidth, mThumbOffsetY + mThumbHeight);
+        mRv.invalidate(mTmpRect);
+    }
+
+    public int getThumbOffsetY() {
+        return mThumbOffsetY;
+    }
+
+    private void setTrackWidth(int width) {
+        if (mWidth == width) {
+            return;
+        }
+        int left = getDrawLeft();
+        // Invalidate the whole scroll bar area.
+        mRv.invalidate(left, 0, left + mMaxWidth, mRv.getScrollbarTrackHeight());
+
+        mWidth = width;
         updateThumbPath();
-        mInvalidateRect.union(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
-                mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
-        mRv.invalidate(mInvalidateRect);
     }
 
-    public Point getThumbOffset() {
-        return mThumbOffset;
-    }
+    /**
+     * Updates the path for the thumb drawable.
+     */
+    private void updateThumbPath() {
+        int smallWidth = mIsRtl ? mWidth : -mWidth;
+        int largeWidth = mIsRtl ? mMaxWidth : -mMaxWidth;
 
-    // Setter/getter for the thumb bar width for animations
-    public void setThumbWidth(int width) {
-        mInvalidateRect.set(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
-                mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
-        mThumbWidth = width;
-        updateThumbPath();
-        mInvalidateRect.union(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
-                mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
-        mRv.invalidate(mInvalidateRect);
-    }
-
-    public int getThumbWidth() {
-        return mThumbWidth;
-    }
-
-    // Setter/getter for the track bar width for animations
-    public void setTrackWidth(int width) {
-        mInvalidateRect.set(mThumbOffset.x - mThumbCurvature, 0, mThumbOffset.x + mThumbWidth,
-                mRv.getVisibleHeight());
-        mTrackWidth = width;
-        updateThumbPath();
-        mInvalidateRect.union(mThumbOffset.x - mThumbCurvature, 0, mThumbOffset.x + mThumbWidth,
-                mRv.getVisibleHeight());
-        mRv.invalidate(mInvalidateRect);
-    }
-
-    public int getTrackWidth() {
-        return mTrackWidth;
+        mThumbPath.reset();
+        mThumbPath.moveTo(0, 0);
+        mThumbPath.lineTo(0, mThumbHeight);             // Left edge
+        mThumbPath.lineTo(smallWidth, mThumbHeight);    // bottom edge
+        mThumbPath.cubicTo(smallWidth, mThumbHeight,    // right edge
+                largeWidth, mThumbHeight / 2,
+                smallWidth, 0);
+        mThumbPath.close();
     }
 
     public int getThumbHeight() {
         return mThumbHeight;
     }
 
-    public int getThumbMaxWidth() {
-        return mThumbMaxWidth;
-    }
-
     public boolean isDraggingThumb() {
         return mIsDragging;
     }
@@ -176,7 +197,7 @@
         switch (action) {
             case MotionEvent.ACTION_DOWN:
                 if (isNearThumb(downX, downY)) {
-                    mTouchOffset = downY - mThumbOffset.y;
+                    mTouchOffsetY = downY - mThumbOffsetY;
                 }
                 break;
             case MotionEvent.ACTION_MOVE:
@@ -191,32 +212,33 @@
                     if (mCanThumbDetach) {
                         mIsThumbDetached = true;
                     }
-                    mTouchOffset += (lastY - downY);
-                    mPopup.animateVisibility(true);
+                    mTouchOffsetY += (lastY - downY);
+                    animatePopupVisibility(true);
                     showActiveScrollbar(true);
                 }
                 if (mIsDragging) {
                     // Update the fastscroller section name at this touch position
-                    int top = mRv.getBackgroundPadding().top;
-                    int bottom = top + mRv.getVisibleHeight() - mThumbHeight;
-                    float boundedY = (float) Math.max(top, Math.min(bottom, y - mTouchOffset));
-                    String sectionName = mRv.scrollToPositionAtProgress((boundedY - top) /
-                            (bottom - top));
-                    mPopup.setSectionName(sectionName);
-                    mPopup.animateVisibility(!sectionName.isEmpty());
-                    mRv.invalidate(mPopup.updateFastScrollerBounds(lastY));
+                    int bottom = mRv.getScrollbarTrackHeight() - mThumbHeight;
+                    float boundedY = (float) Math.max(0, Math.min(bottom, y - mTouchOffsetY));
+                    String sectionName = mRv.scrollToPositionAtProgress(boundedY / bottom);
+                    if (!sectionName.equals(mPopupSectionName)) {
+                        mPopupSectionName = sectionName;
+                        mPopupView.setText(sectionName);
+                    }
+                    animatePopupVisibility(!sectionName.isEmpty());
+                    updatePopupY(lastY);
                     mLastTouchY = boundedY;
-                    setThumbOffset(mRv.getScrollBarX(), (int) mLastTouchY);
+                    setThumbOffsetY((int) mLastTouchY);
                 }
                 break;
             case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_CANCEL:
-                mTouchOffset = 0;
+                mTouchOffsetY = 0;
                 mLastTouchY = 0;
                 mIgnoreDragGesture = false;
                 if (mIsDragging) {
                     mIsDragging = false;
-                    mPopup.animateVisibility(false);
+                    animatePopupVisibility(false);
                     showActiveScrollbar(false);
                 }
                 break;
@@ -224,74 +246,58 @@
     }
 
     public void draw(Canvas canvas) {
-        if (mThumbOffset.x < 0 || mThumbOffset.y < 0) {
+        if (mThumbOffsetY < 0) {
             return;
         }
-
-        // Draw the scroll bar track and thumb
-        if (mTrackPaint.getAlpha() > 0) {
-            canvas.drawRect(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth,
-                    mRv.getVisibleHeight(), mTrackPaint);
+        int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
+        if (!mIsRtl) {
+            canvas.translate(mRv.getWidth(), 0);
         }
-        canvas.drawPath(mThumbPath, mThumbPaint);
+        // Draw the track
+        int thumbWidth = mIsRtl ? mWidth : -mWidth;
+        canvas.drawRect(0, 0, thumbWidth, mRv.getScrollbarTrackHeight(), mTrackPaint);
 
-        // Draw the popup
-        mPopup.draw(canvas);
+        canvas.translate(0, mThumbOffsetY);
+        canvas.drawPath(mThumbPath, mThumbPaint);
+        canvas.restoreToCount(saveCount);
     }
 
     /**
-     * Animates the width and color of the scrollbar.
+     * Animates the width of the scrollbar.
      */
     private void showActiveScrollbar(boolean isScrolling) {
-        if (mScrollbarAnimator != null) {
-            mScrollbarAnimator.cancel();
+        if (mWidthAnimator != null) {
+            mWidthAnimator.cancel();
         }
 
-        mScrollbarAnimator = new AnimatorSet();
-        ObjectAnimator trackWidthAnim = ObjectAnimator.ofInt(this, "trackWidth",
-                isScrolling ? mThumbMaxWidth : mThumbMinWidth);
-        ObjectAnimator thumbWidthAnim = ObjectAnimator.ofInt(this, "thumbWidth",
-                isScrolling ? mThumbMaxWidth : mThumbMinWidth);
-        mScrollbarAnimator.playTogether(trackWidthAnim, thumbWidthAnim);
-        if (mThumbActiveColor != mThumbInactiveColor) {
-            ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(),
-                    mThumbPaint.getColor(), isScrolling ? mThumbActiveColor : mThumbInactiveColor);
-            colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-                @Override
-                public void onAnimationUpdate(ValueAnimator animator) {
-                    mThumbPaint.setColor((Integer) animator.getAnimatedValue());
-                    mRv.invalidate(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
-                            mThumbOffset.y + mThumbHeight);
-                }
-            });
-            mScrollbarAnimator.play(colorAnimation);
-        }
-        mScrollbarAnimator.setDuration(SCROLL_BAR_VIS_DURATION);
-        mScrollbarAnimator.start();
-    }
-
-    /**
-     * Updates the path for the thumb drawable.
-     */
-    private void updateThumbPath() {
-        mThumbCurvature = mThumbMaxWidth - mThumbWidth;
-        mThumbPath.reset();
-        mThumbPath.moveTo(mThumbOffset.x + mThumbWidth, mThumbOffset.y);                    // tr
-        mThumbPath.lineTo(mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);     // br
-        mThumbPath.lineTo(mThumbOffset.x, mThumbOffset.y + mThumbHeight);                   // bl
-        mThumbPath.cubicTo(mThumbOffset.x, mThumbOffset.y + mThumbHeight,
-                mThumbOffset.x - mThumbCurvature, mThumbOffset.y + mThumbHeight / 2,
-                mThumbOffset.x, mThumbOffset.y);                                            // bl2tl
-        mThumbPath.close();
+        mWidthAnimator = ObjectAnimator.ofInt(this, TRACK_WIDTH,
+                isScrolling ? mMaxWidth : mMinWidth);
+        mWidthAnimator.setDuration(SCROLL_BAR_VIS_DURATION);
+        mWidthAnimator.start();
     }
 
     /**
      * Returns whether the specified points are near the scroll bar bounds.
      */
     public boolean isNearThumb(int x, int y) {
-        mTmpRect.set(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
-                mThumbOffset.y + mThumbHeight);
+        int left = getDrawLeft();
+        mTmpRect.set(left, mThumbOffsetY, left + mMaxWidth, mThumbOffsetY + mThumbHeight);
         mTmpRect.inset(mTouchInset, mTouchInset);
         return mTmpRect.contains(x, y);
     }
+
+    private void animatePopupVisibility(boolean visible) {
+        if (mPopupVisible != visible) {
+            mPopupVisible = visible;
+            mPopupView.animate().cancel();
+            mPopupView.animate().alpha(visible ? 1f : 0f).setDuration(visible ? 200 : 150).start();
+        }
+    }
+
+    private void updatePopupY(int lastTouchY) {
+        int height = mPopupView.getHeight();
+        float top = lastTouchY - (FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR * height);
+        top = Math.max(mMaxWidth, Math.min(top, mRv.getScrollbarTrackHeight() - mMaxWidth - height));
+        mPopupView.setTranslationY(top);
+    }
 }
diff --git a/src/com/android/launcher3/BaseRecyclerViewFastScrollPopup.java b/src/com/android/launcher3/BaseRecyclerViewFastScrollPopup.java
deleted file mode 100644
index b9e6277..0000000
--- a/src/com/android/launcher3/BaseRecyclerViewFastScrollPopup.java
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * 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.
- */
-package com.android.launcher3;
-
-import android.animation.Animator;
-import android.animation.ObjectAnimator;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-
-/**
- * The fast scroller popup that shows the section name the list will jump to.
- */
-public class BaseRecyclerViewFastScrollPopup {
-
-    private static final float FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR = 1.5f;
-
-    private static final int SHADOW_INSET = 3;
-    private static final int SHADOW_SHIFT_Y = 2;
-    private static final float SHADOW_ALPHA_MULTIPLIER = 0.67f;
-
-    private Resources mRes;
-    private BaseRecyclerView mRv;
-
-    private Bitmap mShadow;
-    private Paint mShadowPaint;
-
-    private Drawable mBg;
-    // The absolute bounds of the fast scroller bg
-    private Rect mBgBounds = new Rect();
-    private int mBgOriginalSize;
-    private Rect mInvalidateRect = new Rect();
-    private Rect mTmpRect = new Rect();
-
-    private String mSectionName;
-    private Paint mTextPaint;
-    private Rect mTextBounds = new Rect();
-    private float mAlpha;
-
-    private Animator mAlphaAnimator;
-    private boolean mVisible;
-
-    public BaseRecyclerViewFastScrollPopup(BaseRecyclerView rv, Resources res) {
-        mRes = res;
-        mRv = rv;
-
-        mBgOriginalSize = res.getDimensionPixelSize(R.dimen.container_fastscroll_popup_size);
-        mBg = rv.getContext().getDrawable(R.drawable.container_fastscroll_popup_bg);
-        mBg.setBounds(0, 0, mBgOriginalSize, mBgOriginalSize);
-
-        mTextPaint = new Paint();
-        mTextPaint.setColor(Color.WHITE);
-        mTextPaint.setAntiAlias(true);
-        mTextPaint.setTextSize(res.getDimensionPixelSize(R.dimen.container_fastscroll_popup_text_size));
-
-        mShadowPaint = new Paint();
-        mShadowPaint.setAntiAlias(true);
-        mShadowPaint.setFilterBitmap(true);
-        mShadowPaint.setDither(true);
-    }
-
-    /**
-     * Sets the section name.
-     */
-    public void setSectionName(String sectionName) {
-        if (!sectionName.equals(mSectionName)) {
-            mSectionName = sectionName;
-            mTextPaint.getTextBounds(sectionName, 0, sectionName.length(), mTextBounds);
-            // Update the width to use measureText since that is more accurate
-            mTextBounds.right = (int) (mTextBounds.left + mTextPaint.measureText(sectionName));
-        }
-    }
-
-    /**
-     * Updates the bounds for the fast scroller.
-     *
-     * @return the invalidation rect for this update.
-     */
-    public Rect updateFastScrollerBounds(int lastTouchY) {
-        mInvalidateRect.set(mBgBounds);
-
-        if (isVisible()) {
-            // Calculate the dimensions and position of the fast scroller popup
-            int edgePadding = mRv.getMaxScrollbarWidth();
-            int bgPadding = (mBgOriginalSize - mTextBounds.height()) / 2;
-            int bgHeight = mBgOriginalSize;
-            int bgWidth = Math.max(mBgOriginalSize, mTextBounds.width() + (2 * bgPadding));
-            if (Utilities.isRtl(mRes)) {
-                mBgBounds.left = mRv.getBackgroundPadding().left + (2 * mRv.getMaxScrollbarWidth());
-                mBgBounds.right = mBgBounds.left + bgWidth;
-            } else {
-                mBgBounds.right = mRv.getWidth() - mRv.getBackgroundPadding().right -
-                        (2 * mRv.getMaxScrollbarWidth());
-                mBgBounds.left = mBgBounds.right - bgWidth;
-            }
-            mBgBounds.top = lastTouchY - (int) (FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR * bgHeight);
-            mBgBounds.top = Math.max(edgePadding,
-                    Math.min(mBgBounds.top, mRv.getVisibleHeight() - edgePadding - bgHeight));
-            mBgBounds.bottom = mBgBounds.top + bgHeight;
-
-            // Generate a bitmap for a shadow matching these bounds
-            mShadow = HolographicOutlineHelper.obtain(
-                    mRv.getContext()).createMediumDropShadow(mBg, false /* shouldCache */);
-        } else {
-            mShadow = null;
-            mBgBounds.setEmpty();
-        }
-
-        // Combine the old and new fast scroller bounds to create the full invalidate rect
-        mInvalidateRect.union(mBgBounds);
-        return mInvalidateRect;
-    }
-
-    /**
-     * Animates the visibility of the fast scroller popup.
-     */
-    public void animateVisibility(boolean visible) {
-        if (mVisible != visible) {
-            mVisible = visible;
-            if (mAlphaAnimator != null) {
-                mAlphaAnimator.cancel();
-            }
-            mAlphaAnimator = ObjectAnimator.ofFloat(this, "alpha", visible ? 1f : 0f);
-            mAlphaAnimator.setDuration(visible ? 200 : 150);
-            mAlphaAnimator.start();
-        }
-    }
-
-    // Setter/getter for the popup alpha for animations
-    public void setAlpha(float alpha) {
-        mAlpha = alpha;
-        mRv.invalidate(mBgBounds);
-    }
-
-    public float getAlpha() {
-        return mAlpha;
-    }
-
-    public int getHeight() {
-        return mBgOriginalSize;
-    }
-
-    public void draw(Canvas c) {
-        if (isVisible()) {
-            // Determine the alpha and prepare the canvas
-            final int alpha = (int) (mAlpha * 255);
-            int restoreCount = c.save(Canvas.MATRIX_SAVE_FLAG);
-            c.translate(mBgBounds.left, mBgBounds.top);
-            mTmpRect.set(mBgBounds);
-            mTmpRect.offsetTo(0, 0);
-
-            // Expand the rect (with a negative inset), translate it, and draw the shadow
-            if (mShadow != null) {
-                mTmpRect.inset(-SHADOW_INSET * 2, -SHADOW_INSET * 2);
-                mTmpRect.offset(0, SHADOW_SHIFT_Y);
-                mShadowPaint.setAlpha((int) (alpha * SHADOW_ALPHA_MULTIPLIER));
-                c.drawBitmap(mShadow, null, mTmpRect, mShadowPaint);
-                mTmpRect.inset(SHADOW_INSET * 2, SHADOW_INSET * 2);
-                mTmpRect.offset(0, -SHADOW_SHIFT_Y);
-            }
-
-            // Draw the background
-            mBg.setBounds(mTmpRect);
-            mBg.setAlpha(alpha);
-            mBg.draw(c);
-
-            // Draw the text
-            mTextPaint.setAlpha(alpha);
-            c.drawText(mSectionName, (mBgBounds.width() - mTextBounds.width()) / 2,
-                    mBgBounds.height() - (mBgBounds.height() / 2) - mTextBounds.exactCenterY(),
-                    mTextPaint);
-            c.restoreToCount(restoreCount);
-        }
-    }
-
-    public boolean isVisible() {
-        return (mAlpha > 0f) && (mSectionName != null);
-    }
-}
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index a294fa5..dbb797d 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -42,6 +42,7 @@
 
 import com.android.launcher3.IconCache.IconLoadRequest;
 import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.graphics.HolographicOutlineHelper;
 import com.android.launcher3.model.PackageItemInfo;
 
 import java.text.NumberFormat;
@@ -127,7 +128,9 @@
             setCompoundDrawablePadding(grid.allAppsIconDrawablePaddingPx);
             defaultIconSize = grid.allAppsIconSizePx;
         } else if (display == DISPLAY_FOLDER) {
+            setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.folderChildTextSizePx);
             setCompoundDrawablePadding(grid.folderChildDrawablePaddingPx);
+            defaultIconSize = grid.folderChildIconSizePx;
         }
         mCenterVertically = a.getBoolean(R.styleable.BubbleTextView_centerVertically, false);
 
@@ -150,7 +153,7 @@
         mLongPressHelper = new CheckLongPressHelper(this);
         mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
 
-        mOutlineHelper = HolographicOutlineHelper.obtain(getContext());
+        mOutlineHelper = HolographicOutlineHelper.getInstance(getContext());
         setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
     }
 
@@ -188,9 +191,7 @@
 
     private void applyIconAndLabel(Bitmap icon, ItemInfo info) {
         FastBitmapDrawable iconDrawable = mLauncher.createIconDrawable(icon);
-        if (info.isDisabled()) {
-            iconDrawable.setState(FastBitmapDrawable.State.DISABLED);
-        }
+        iconDrawable.setIsDisabled(info.isDisabled());
         setIcon(iconDrawable);
         setText(info.title);
         if (info.contentDescription != null) {
@@ -259,10 +260,7 @@
     private void updateIconState() {
         if (mIcon instanceof FastBitmapDrawable) {
             FastBitmapDrawable d = (FastBitmapDrawable) mIcon;
-            if (getTag() instanceof ItemInfo
-                    && ((ItemInfo) getTag()).isDisabled()) {
-                d.animateState(FastBitmapDrawable.State.DISABLED);
-            } else if (isPressed() || mStayPressed) {
+            if (isPressed() || mStayPressed) {
                 d.animateState(FastBitmapDrawable.State.PRESSED);
             } else {
                 d.animateState(FastBitmapDrawable.State.NORMAL);
@@ -328,7 +326,7 @@
     void setStayPressed(boolean stayPressed) {
         mStayPressed = stayPressed;
         if (!stayPressed) {
-            HolographicOutlineHelper.obtain(getContext()).recycleShadowBitmap(mPressedBackground);
+            HolographicOutlineHelper.getInstance(getContext()).recycleShadowBitmap(mPressedBackground);
             mPressedBackground = null;
         } else {
             if (mPressedBackground == null) {
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 57fd0e7..3564cec 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -87,13 +87,6 @@
     @ViewDebug.ExportedProperty(category = "launcher")
     private int mCountY;
 
-    private int mOriginalWidthGap;
-    private int mOriginalHeightGap;
-    @ViewDebug.ExportedProperty(category = "launcher")
-    @Thunk int mWidthGap;
-    @ViewDebug.ExportedProperty(category = "launcher")
-    @Thunk int mHeightGap;
-    private int mMaxGap;
     private boolean mDropPending = false;
     private boolean mIsDragTarget = true;
     private boolean mJailContent = true;
@@ -202,9 +195,6 @@
 
         mCellWidth = mCellHeight = -1;
         mFixedCellWidth = mFixedCellHeight = -1;
-        mWidthGap = mOriginalWidthGap = 0;
-        mHeightGap = mOriginalHeightGap = 0;
-        mMaxGap = Integer.MAX_VALUE;
 
         mCountX = grid.inv.numColumns;
         mCountY = grid.inv.numRows;
@@ -287,8 +277,7 @@
         }
 
         mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context);
-        mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
-                mCountX, mCountY);
+        mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY);
 
         mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
 
@@ -373,8 +362,7 @@
     public void setCellDimensions(int width, int height) {
         mFixedCellWidth = mCellWidth = width;
         mFixedCellHeight = mCellHeight = height;
-        mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
-                mCountX, mCountY);
+        mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY);
     }
 
     public void setGridSize(int x, int y) {
@@ -383,8 +371,7 @@
         mOccupied = new GridOccupancy(mCountX, mCountY);
         mTmpOccupied = new GridOccupancy(mCountX, mCountY);
         mTempRectStack.clear();
-        mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
-                mCountX, mCountY);
+        mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY);
         requestLayout();
     }
 
@@ -618,7 +605,9 @@
 
     public void setIsHotseat(boolean isHotseat) {
         mIsHotseat = isHotseat;
-        mShortcutsAndWidgets.setIsHotseat(isHotseat);
+        mShortcutsAndWidgets.setContainerType(isHotseat
+                ? ShortcutAndWidgetContainer.HOTSEAT
+                : ShortcutAndWidgetContainer.DEFAULT);
     }
 
     public boolean isHotseat() {
@@ -717,8 +706,8 @@
         final int hStartPadding = getPaddingLeft();
         final int vStartPadding = getPaddingTop();
 
-        result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
-        result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
+        result[0] = (x - hStartPadding) / mCellWidth;
+        result[1] = (y - vStartPadding) / mCellHeight;
 
         final int xAxis = mCountX;
         final int yAxis = mCountY;
@@ -751,8 +740,8 @@
         final int hStartPadding = getPaddingLeft();
         final int vStartPadding = getPaddingTop();
 
-        result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
-        result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
+        result[0] = hStartPadding + cellX * mCellWidth;
+        result[1] = vStartPadding + cellY * mCellHeight;
     }
 
     /**
@@ -778,10 +767,8 @@
     void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
         final int hStartPadding = getPaddingLeft();
         final int vStartPadding = getPaddingTop();
-        result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap) +
-                (spanX * mCellWidth + (spanX - 1) * mWidthGap) / 2;
-        result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap) +
-                (spanY * mCellHeight + (spanY - 1) * mHeightGap) / 2;
+        result[0] = hStartPadding + cellX * mCellWidth + (spanX * mCellWidth) / 2;
+        result[1] = vStartPadding + cellY * mCellHeight + (spanY * mCellHeight) / 2;
     }
 
      /**
@@ -794,10 +781,9 @@
      void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) {
         final int hStartPadding = getPaddingLeft();
         final int vStartPadding = getPaddingTop();
-        final int left = hStartPadding + cellX * (mCellWidth + mWidthGap);
-        final int top = vStartPadding + cellY * (mCellHeight + mHeightGap);
-        result.set(left, top, left + (spanX * mCellWidth + (spanX - 1) * mWidthGap),
-                top + (spanY * mCellHeight + (spanY - 1) * mHeightGap));
+        final int left = hStartPadding + cellX * mCellWidth;
+        final int top = vStartPadding + cellY * mCellHeight;
+        result.set(left, top, left + (spanX * mCellWidth), top + (spanY * mCellHeight));
     }
 
     public float getDistanceFromCell(float x, float y, int[] cell) {
@@ -813,14 +799,6 @@
         return mCellHeight;
     }
 
-    int getWidthGap() {
-        return mWidthGap;
-    }
-
-    int getHeightGap() {
-        return mHeightGap;
-    }
-
     public void setFixedSize(int width, int height) {
         mFixedWidth = width;
         mFixedHeight = height;
@@ -840,8 +818,7 @@
             if (cw != mCellWidth || ch != mCellHeight) {
                 mCellWidth = cw;
                 mCellHeight = ch;
-                mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
-                        mHeightGap, mCountX, mCountY);
+                mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY);
             }
         }
 
@@ -854,23 +831,6 @@
             throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
         }
 
-        int numWidthGaps = mCountX - 1;
-        int numHeightGaps = mCountY - 1;
-
-        if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
-            int hSpace = childWidthSize;
-            int vSpace = childHeightSize;
-            int hFreeSpace = hSpace - (mCountX * mCellWidth);
-            int vFreeSpace = vSpace - (mCountY * mCellHeight);
-            mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
-            mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
-            mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
-                    mHeightGap, mCountX, mCountY);
-        } else {
-            mWidthGap = mOriginalWidthGap;
-            mHeightGap = mOriginalHeightGap;
-        }
-
         // Make the feedback view large enough to hold the blur bitmap.
         mTouchFeedbackView.measure(
                 MeasureSpec.makeMeasureSpec(mCellWidth + mTouchFeedbackView.getExtraSize(),
@@ -930,16 +890,6 @@
         return getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - (mCountX * mCellWidth);
     }
 
-    @Override
-    protected void setChildrenDrawingCacheEnabled(boolean enabled) {
-        mShortcutsAndWidgets.setChildrenDrawingCacheEnabled(enabled);
-    }
-
-    @Override
-    protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
-        mShortcutsAndWidgets.setChildrenDrawnWithCacheEnabled(enabled);
-    }
-
     public float getBackgroundAlpha() {
         return mBackgroundAlpha;
     }
@@ -1054,11 +1004,11 @@
         final int oldDragCellX = mDragCell[0];
         final int oldDragCellY = mDragCell[1];
 
-        if (outlineProvider == null || outlineProvider.gerenatedDragOutline == null) {
+        if (outlineProvider == null || outlineProvider.generatedDragOutline == null) {
             return;
         }
 
-        Bitmap dragOutline = outlineProvider.gerenatedDragOutline;
+        Bitmap dragOutline = outlineProvider.generatedDragOutline;
         if (cellX != oldDragCellX || cellY != oldDragCellY) {
             Point dragOffset = dragObject.dragView.getDragVisualizeOffset();
             Rect dragRegion = dragObject.dragView.getDragRegion();
@@ -1093,23 +1043,19 @@
                     // outside the bounds of the view.
                     top += (v.getHeight() - dragOutline.getHeight()) / 2;
                     // We center about the x axis
-                    left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
-                            - dragOutline.getWidth()) / 2;
+                    left += ((mCellWidth * spanX) - dragOutline.getWidth()) / 2;
                 } else {
                     if (dragOffset != null && dragRegion != null) {
                         // Center the drag region *horizontally* in the cell and apply a drag
                         // outline offset
-                        left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
-                                - dragRegion.width()) / 2;
+                        left += dragOffset.x + ((mCellWidth * spanX) - dragRegion.width()) / 2;
                         int cHeight = getShortcutsAndWidgets().getCellContentHeight();
                         int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f));
                         top += dragOffset.y + cellPaddingY;
                     } else {
                         // Center the drag outline in the cell
-                        left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
-                                - dragOutline.getWidth()) / 2;
-                        top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap)
-                                - dragOutline.getHeight()) / 2;
+                        left += ((mCellWidth * spanX) - dragOutline.getWidth()) / 2;
+                        top += ((mCellHeight * spanY) - dragOutline.getHeight()) / 2;
                     }
                 }
                 r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
@@ -1198,8 +1144,8 @@
         // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
         // to the center of the item, but we are searching based on the top-left cell, so
         // we translate the point over to correspond to the top-left.
-        pixelX -= (mCellWidth + mWidthGap) * (spanX - 1) / 2f;
-        pixelY -= (mCellHeight + mHeightGap) * (spanY - 1) / 2f;
+        pixelX -= mCellWidth * (spanX - 1) / 2f;
+        pixelY -= mCellHeight * (spanY - 1) / 2f;
 
         // Keep track of best-scoring drop area
         final int[] bestXY = result != null ? result : new int[2];
@@ -2215,10 +2161,6 @@
         return solution;
     }
 
-    public void prepareChildForDrag(View child) {
-        markCellsAsUnoccupiedForView(child);
-    }
-
     /* This seems like it should be obvious and straight-forward, but when the direction vector
     needs to match with the notion of the dragView pushing other views, we have to employ
     a slightly more subtle notion of the direction vector. The question is what two points is
@@ -2598,17 +2540,14 @@
     public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
         final int cellWidth = mCellWidth;
         final int cellHeight = mCellHeight;
-        final int widthGap = mWidthGap;
-        final int heightGap = mHeightGap;
 
         final int hStartPadding = getPaddingLeft();
         final int vStartPadding = getPaddingTop();
 
-        int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
-        int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
-
-        int x = hStartPadding + cellX * (cellWidth + widthGap);
-        int y = vStartPadding + cellY * (cellHeight + heightGap);
+        int width = cellHSpan * cellWidth;
+        int height = cellVSpan * cellHeight;
+        int x = hStartPadding + cellX * cellWidth;
+        int y = vStartPadding + cellY * cellHeight;
 
         resultRect.set(x, y, x + width, y + height);
     }
@@ -2626,13 +2565,11 @@
     }
 
     public int getDesiredWidth() {
-        return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
-                (Math.max((mCountX - 1), 0) * mWidthGap);
+        return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth);
     }
 
     public int getDesiredHeight()  {
-        return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
-                (Math.max((mCountY - 1), 0) * mHeightGap);
+        return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight);
     }
 
     public boolean isOccupied(int x, int y) {
@@ -2752,8 +2689,7 @@
             this.cellVSpan = cellVSpan;
         }
 
-        public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
-                boolean invertHorizontally, int colCount) {
+        public void setup(int cellWidth, int cellHeight, boolean invertHorizontally, int colCount) {
             if (isLockedToGrid) {
                 final int myCellHSpan = cellHSpan;
                 final int myCellVSpan = cellVSpan;
@@ -2764,12 +2700,10 @@
                     myCellX = colCount - myCellX - cellHSpan;
                 }
 
-                width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
-                        leftMargin - rightMargin;
-                height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
-                        topMargin - bottomMargin;
-                x = (int) (myCellX * (cellWidth + widthGap) + leftMargin);
-                y = (int) (myCellY * (cellHeight + heightGap) + topMargin);
+                width = myCellHSpan * cellWidth - leftMargin - rightMargin;
+                height = myCellVSpan * cellHeight - topMargin - bottomMargin;
+                x = (myCellX * cellWidth + leftMargin);
+                y = (myCellY * cellHeight + topMargin);
             }
         }
 
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index f9f8e80..59ec56a 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -96,16 +96,23 @@
     public int folderBackgroundOffset;
     public int folderIconSizePx;
     public int folderIconPreviewPadding;
+
+    // Folder cell
     public int folderCellWidthPx;
     public int folderCellHeightPx;
+
+    // Folder child
+    public int folderChildIconSizePx;
+    public int folderChildTextSizePx;
     public int folderChildDrawablePaddingPx;
 
     // Hotseat
     public int hotseatCellWidthPx;
     public int hotseatCellHeightPx;
     public int hotseatIconSizePx;
-    private int hotseatBarHeightPx;
+    public int hotseatBarHeightPx;
     private int hotseatBarTopPaddingPx;
+    private int hotseatBarBottomPaddingPx;
     private int hotseatLandGutterPx;
 
     // All apps
@@ -116,10 +123,6 @@
     public int allAppsIconDrawablePaddingPx;
     public float allAppsIconTextSizePx;
 
-    // Containers
-    private final int containerLeftPaddingPx;
-    private final int containerRightPaddingPx;
-
     // Drop Target
     public int dropTargetBarSizePx;
 
@@ -183,11 +186,8 @@
         hotseatBarHeightPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_height);
         hotseatBarTopPaddingPx =
                 res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_top_padding);
+        hotseatBarBottomPaddingPx = 0;
         hotseatLandGutterPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_gutter_width);
-        containerLeftPaddingPx =
-                res.getDimensionPixelSize(R.dimen.dynamic_grid_container_land_left_padding);
-        containerRightPaddingPx =
-                res.getDimensionPixelSize(R.dimen.dynamic_grid_container_land_right_padding);
 
         // Determine sizes.
         widthPx = width;
@@ -205,6 +205,24 @@
         computeAllAppsButtonSize(context);
     }
 
+    DeviceProfile getMultiWindowProfile(Context context, Point mwSize) {
+        // In multi-window mode, we can have widthPx = availableWidthPx
+        // and heightPx = availableHeightPx because Launcher uses the InvariantDeviceProfiles'
+        // widthPx and heightPx values where it's needed.
+        DeviceProfile profile = new DeviceProfile(context, inv, mwSize, mwSize, mwSize.x, mwSize.y,
+                isLandscape);
+
+        // Hide labels on the workspace.
+        profile.iconTextSizePx = 0;
+        profile.cellHeightPx = profile.iconSizePx + profile.iconDrawablePaddingPx
+                + Utilities.calculateTextHeight(profile.iconTextSizePx);
+
+        // The nav bar is black so we add bottom padding to visually center hotseat icons.
+        profile.hotseatBarBottomPaddingPx = profile.hotseatBarTopPaddingPx;
+
+        return profile;
+    }
+
     public void addLauncherLayoutChangedListener(LauncherLayoutChangeListener listener) {
         if (!mListeners.contains(listener)) {
             mListeners.add(listener);
@@ -229,19 +247,17 @@
     }
 
     private void updateAvailableDimensions(DisplayMetrics dm, Resources res) {
-        // Check to see if the icons fit in the new available height.  If not, then we need to
-        // shrink the icon size.
-        float scale = 1f;
-        int drawablePadding = iconDrawablePaddingOriginalPx;
-        updateIconSize(1f, drawablePadding, res, dm);
-        float usedHeight = (cellHeightPx * inv.numRows);
+        updateIconSize(1f, iconDrawablePaddingOriginalPx, res, dm);
 
+        // Check to see if the icons fit within the available height.  If not, then scale down.
+        float usedHeight = (cellHeightPx * inv.numRows);
         int maxHeight = (availableHeightPx - getTotalWorkspacePadding().y);
         if (usedHeight > maxHeight) {
-            scale = maxHeight / usedHeight;
-            drawablePadding = 0;
+            float scale = maxHeight / usedHeight;
+            updateIconSize(scale, 0, res, dm);
         }
-        updateIconSize(scale, drawablePadding, res, dm);
+
+        updateAvailableFolderCellDimensions(dm, res);
     }
 
     private void updateIconSize(float scale, int drawablePadding, Resources res,
@@ -277,31 +293,47 @@
                     res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f;
         }
 
-        // Folder cell
-        int cellPaddingX = res.getDimensionPixelSize(R.dimen.folder_cell_x_padding);
-        int cellPaddingY = res.getDimensionPixelSize(R.dimen.folder_cell_y_padding);
-        final int folderChildTextSize =
-                Utilities.calculateTextHeight(res.getDimension(R.dimen.folder_child_text_size));
-
-        final int folderBottomPanelSize =
-                res.getDimensionPixelSize(R.dimen.folder_label_padding_top)
-                 + res.getDimensionPixelSize(R.dimen.folder_label_padding_bottom)
-                + Utilities.calculateTextHeight(res.getDimension(R.dimen.folder_label_text_size));
-
-        // Don't let the folder get too close to the edges of the screen.
-        folderCellWidthPx = Math.min(iconSizePx + 2 * cellPaddingX,
-                (availableWidthPx - 4 * edgeMarginPx) / inv.numFolderColumns);
-        folderCellHeightPx = Math.min(iconSizePx + 3 * cellPaddingY + folderChildTextSize,
-                (availableHeightPx - 4 * edgeMarginPx - folderBottomPanelSize) / inv.numFolderRows);
-        folderChildDrawablePaddingPx = Math.max(0,
-                (folderCellHeightPx - iconSizePx - folderChildTextSize) / 3);
-
         // Folder icon
         folderBackgroundOffset = -edgeMarginPx;
         folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset;
         folderIconPreviewPadding = res.getDimensionPixelSize(R.dimen.folder_preview_padding);
     }
 
+    private void updateAvailableFolderCellDimensions(DisplayMetrics dm, Resources res) {
+        int folderBottomPanelSize = res.getDimensionPixelSize(R.dimen.folder_label_padding_top)
+                + res.getDimensionPixelSize(R.dimen.folder_label_padding_bottom)
+                + Utilities.calculateTextHeight(res.getDimension(R.dimen.folder_label_text_size));
+
+        updateFolderCellSize(1f, dm, res, folderBottomPanelSize);
+
+        // Check to see if the icons fit within the available height.  If not, then scale down.
+        float usedHeight = (folderCellHeightPx * inv.numFolderRows) + folderBottomPanelSize;
+        int maxHeight = availableHeightPx - getTotalWorkspacePadding().y - (2 * edgeMarginPx);
+        if (usedHeight > maxHeight) {
+            float scale = maxHeight / usedHeight;
+            updateFolderCellSize(scale, dm, res, folderBottomPanelSize);
+        }
+    }
+
+    private void updateFolderCellSize(float scale, DisplayMetrics dm, Resources res,
+             int folderBottomPanelSize) {
+        folderChildIconSizePx = (int) (Utilities.pxFromDp(inv.iconSize, dm) * scale);
+        folderChildTextSizePx =
+                (int) (res.getDimensionPixelSize(R.dimen.folder_child_text_size) * scale);
+
+        int textHeight = Utilities.calculateTextHeight(folderChildTextSizePx);
+        int cellPaddingX = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_x_padding) * scale);
+        int cellPaddingY = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_y_padding) * scale);
+
+        // Don't let the folder get too close to the edges of the screen.
+        folderCellWidthPx = Math.min(folderChildIconSizePx + 2 * cellPaddingX,
+                (availableWidthPx - 4 * edgeMarginPx) / inv.numFolderColumns);
+        folderCellHeightPx = Math.min(folderChildIconSizePx + 2 * cellPaddingY + textHeight,
+                (availableHeightPx - 4 * edgeMarginPx - folderBottomPanelSize) / inv.numFolderRows);
+        folderChildDrawablePaddingPx = Math.max(0,
+                (folderCellHeightPx - folderChildIconSizePx - textHeight) / 3);
+    }
+
     public void updateInsets(Rect insets) {
         mInsets.set(insets);
     }
@@ -377,7 +409,8 @@
                 availablePaddingX = (int) Math.min(availablePaddingX,
                             width * MAX_HORIZONTAL_PADDING_PERCENT);
                 int availablePaddingY = Math.max(0, height - topWorkspacePadding - paddingBottom
-                        - (int) (2 * inv.numRows * cellHeightPx));
+                        - (2 * inv.numRows * cellHeightPx) - hotseatBarTopPaddingPx
+                        - hotseatBarBottomPaddingPx);
                 padding.set(availablePaddingX / 2, topWorkspacePadding + availablePaddingY / 2,
                         availablePaddingX / 2, paddingBottom + availablePaddingY / 2);
             } else {
@@ -463,7 +496,6 @@
     public void layout(Launcher launcher, boolean notifyListeners) {
         FrameLayout.LayoutParams lp;
         boolean hasVerticalBarLayout = isVerticalBarLayout();
-        final boolean isLayoutRtl = Utilities.isRtl(launcher.getResources());
 
         // Layout the search bar space
         Point searchBarBounds = getSearchBarDimensForWidgetOpts();
@@ -511,7 +543,7 @@
             lp.height = hotseatBarHeightPx + mInsets.bottom;
             hotseat.getLayout().setPadding(hotseatAdjustment + workspacePadding.left,
                     hotseatBarTopPaddingPx, hotseatAdjustment + workspacePadding.right,
-                    mInsets.bottom);
+                    hotseatBarBottomPaddingPx + mInsets.bottom);
         } else {
             // For phones, layout the hotseat without any bottom margin
             // to ensure that we have space for the folders
@@ -520,7 +552,7 @@
             lp.height = hotseatBarHeightPx + mInsets.bottom;
             hotseat.getLayout().setPadding(hotseatAdjustment + workspacePadding.left,
                     hotseatBarTopPaddingPx, hotseatAdjustment + workspacePadding.right,
-                    mInsets.bottom);
+                    hotseatBarBottomPaddingPx + mInsets.bottom);
         }
         hotseat.setLayoutParams(lp);
 
@@ -549,18 +581,13 @@
         // Layout the Overview Mode
         ViewGroup overviewMode = launcher.getOverviewPanel();
         if (overviewMode != null) {
-            lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams();
-            lp.gravity = Gravity.LEFT | Gravity.BOTTOM;
-
             int visibleChildCount = getVisibleChildCount(overviewMode);
             int totalItemWidth = visibleChildCount * overviewModeBarItemWidthPx;
-            int maxWidth = totalItemWidth + (visibleChildCount-1) * overviewModeBarSpacerWidthPx;
+            int maxWidth = totalItemWidth + (visibleChildCount - 1) * overviewModeBarSpacerWidthPx;
 
+            lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams();
             lp.width = Math.min(availableWidthPx, maxWidth);
-            lp.height = getOverviewModeButtonBarHeight();
-            // Center the overview buttons on the workspace page
-            lp.leftMargin = workspacePadding.left + (availableWidthPx -
-                    workspacePadding.left - workspacePadding.right - lp.width) / 2;
+            lp.height = getOverviewModeButtonBarHeight() + mInsets.bottom;
             overviewMode.setLayoutParams(lp);
         }
 
@@ -587,9 +614,7 @@
     /**
      * @return the left/right paddings for all containers.
      */
-    public final int[] getContainerPadding(Context context) {
-        Resources res = context.getResources();
-
+    public final int[] getContainerPadding() {
         // No paddings for portrait phone
         if (isPhone && !isVerticalBarLayout()) {
             return new int[] {0, 0};
@@ -600,4 +625,11 @@
                 hotseatBarHeightPx + hotseatLandGutterPx + mInsets.left) / 2;
         return new int[]{ padding, padding };
     }
+
+    public boolean shouldIgnoreLongPressToOverview(float touchX) {
+        boolean inMultiWindowMode = this != inv.landscapeProfile && this != inv.portraitProfile;
+        boolean touchedLhsEdge = mInsets.left == 0 && touchX < edgeMarginPx;
+        boolean touchedRhsEdge = mInsets.right == 0 && touchX > (widthPx - edgeMarginPx);
+        return !inMultiWindowMode && (touchedLhsEdge || touchedRhsEdge);
+    }
 }
diff --git a/src/com/android/launcher3/DragSource.java b/src/com/android/launcher3/DragSource.java
index efbb9d7..2fb495f 100644
--- a/src/com/android/launcher3/DragSource.java
+++ b/src/com/android/launcher3/DragSource.java
@@ -19,12 +19,12 @@
 import android.view.View;
 
 import com.android.launcher3.DropTarget.DragObject;
-import com.android.launcher3.logging.UserEventDispatcher.LaunchSourceProvider;
+import com.android.launcher3.logging.UserEventDispatcher.LogContainerProvider;
 
 /**
  * Interface defining an object that can originate a drag.
  */
-public interface DragSource extends LaunchSourceProvider {
+public interface DragSource extends LogContainerProvider {
 
     /**
      * @return whether items dragged from this source supports
diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java
index efdeb1f..e91fc15 100644
--- a/src/com/android/launcher3/DropTarget.java
+++ b/src/com/android/launcher3/DropTarget.java
@@ -107,17 +107,6 @@
 
     /**
      * Handle an object being dropped on the DropTarget
-     *
-     * @param source DragSource where the drag started
-     * @param x X coordinate of the drop location
-     * @param y Y coordinate of the drop location
-     * @param xOffset Horizontal offset with the object being dragged where the original
-     *          touch happened
-     * @param yOffset Vertical offset with the object being dragged where the original
-     *          touch happened
-     * @param dragView The DragView that's being dragged around on screen.
-     * @param dragInfo Data associated with the object being dragged
-     *
      */
     void onDrop(DragObject dragObject);
 
@@ -137,16 +126,6 @@
     /**
      * Check if a drop action can occur at, or near, the requested location.
      * This will be called just before onDrop.
-     *
-     * @param source DragSource where the drag started
-     * @param x X coordinate of the drop location
-     * @param y Y coordinate of the drop location
-     * @param xOffset Horizontal offset with the object being dragged where the
-     *            original touch happened
-     * @param yOffset Vertical offset with the object being dragged where the
-     *            original touch happened
-     * @param dragView The DragView that's being dragged around on screen.
-     * @param dragInfo Data associated with the object being dragged
      * @return True if the drop will be accepted, false otherwise.
      */
     boolean acceptDrop(DragObject dragObject);
diff --git a/src/com/android/launcher3/ExtendedEditText.java b/src/com/android/launcher3/ExtendedEditText.java
index c06f727..d05673c 100644
--- a/src/com/android/launcher3/ExtendedEditText.java
+++ b/src/com/android/launcher3/ExtendedEditText.java
@@ -99,4 +99,12 @@
                 ((InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE))
                     .showSoftInput(this, InputMethodManager.SHOW_IMPLICIT);
     }
+
+    public void dispatchBackKey() {
+        ((InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE))
+                .hideSoftInputFromWindow(getWindowToken(), 0);
+        if (mBackKeyListener != null) {
+            mBackKeyListener.onBackKey();
+        }
+    }
 }
diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java
index 3870080..7eaae5a 100644
--- a/src/com/android/launcher3/FastBitmapDrawable.java
+++ b/src/com/android/launcher3/FastBitmapDrawable.java
@@ -34,6 +34,8 @@
 import android.view.animation.DecelerateInterpolator;
 
 public class FastBitmapDrawable extends Drawable {
+    private static final float DISABLED_DESATURATION = 1f;
+    private static final float DISABLED_BRIGHTNESS = 0.5f;
 
     /**
      * The possible states that a FastBitmapDrawable can be in.
@@ -43,8 +45,7 @@
         NORMAL                      (0f, 0f, 1f, new DecelerateInterpolator()),
         PRESSED                     (0f, 100f / 255f, 1f, CLICK_FEEDBACK_INTERPOLATOR),
         FAST_SCROLL_HIGHLIGHTED     (0f, 0f, 1.15f, new DecelerateInterpolator()),
-        FAST_SCROLL_UNHIGHLIGHTED   (0f, 0f, 1f, new DecelerateInterpolator()),
-        DISABLED                    (1f, 0.5f, 1f, new DecelerateInterpolator());
+        FAST_SCROLL_UNHIGHLIGHTED   (0f, 0f, 1f, new DecelerateInterpolator());
 
         public final float desaturation;
         public final float brightness;
@@ -96,6 +97,7 @@
     private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
     private final Bitmap mBitmap;
     private State mState = State.NORMAL;
+    private boolean mIsDisabled;
 
     // 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
@@ -177,13 +179,14 @@
         if (mState != newState) {
             mState = newState;
 
+            float desaturation = mIsDisabled ? DISABLED_DESATURATION : newState.desaturation;
+            float brightness = mIsDisabled ? DISABLED_BRIGHTNESS: newState.brightness;
+
             mPropertyAnimator = cancelAnimator(mPropertyAnimator);
             mPropertyAnimator = new AnimatorSet();
             mPropertyAnimator.playTogether(
-                    ObjectAnimator
-                            .ofFloat(this, "desaturation", newState.desaturation),
-                    ObjectAnimator
-                            .ofFloat(this, "brightness", newState.brightness));
+                    ObjectAnimator.ofFloat(this, "desaturation", desaturation),
+                    ObjectAnimator.ofFloat(this, "brightness", brightness));
             mPropertyAnimator.setInterpolator(newState.interpolator);
             mPropertyAnimator.setDuration(getDurationForStateChange(prevState, newState));
             mPropertyAnimator.setStartDelay(getStartDelayForStateChange(prevState, newState));
@@ -204,13 +207,17 @@
 
             mPropertyAnimator = cancelAnimator(mPropertyAnimator);
 
-            setDesaturation(newState.desaturation);
-            setBrightness(newState.brightness);
+            invalidateDesaturationAndBrightness();
             return true;
         }
         return false;
     }
 
+    private void invalidateDesaturationAndBrightness() {
+        setDesaturation(mIsDisabled ? DISABLED_DESATURATION : mState.desaturation);
+        setBrightness(mIsDisabled ? DISABLED_BRIGHTNESS: mState.brightness);
+    }
+
     /**
      * Returns the current state.
      */
@@ -218,6 +225,13 @@
         return mState;
     }
 
+    public void setIsDisabled(boolean isDisabled) {
+        if (mIsDisabled != isDisabled) {
+            mIsDisabled = isDisabled;
+            invalidateDesaturationAndBrightness();
+        }
+    }
+
     /**
      * Returns the duration for the state change animation.
      */
diff --git a/src/com/android/launcher3/FocusHelper.java b/src/com/android/launcher3/FocusHelper.java
index 789c3f9..b36734b 100644
--- a/src/com/android/launcher3/FocusHelper.java
+++ b/src/com/android/launcher3/FocusHelper.java
@@ -250,14 +250,6 @@
         } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT &&
                 profile.isVerticalBarLayout()) {
             keyCode = KeyEvent.KEYCODE_PAGE_DOWN;
-        } else if (isUninstallKeyChord(e)) {
-            matrix = FocusLogic.createSparseMatrix(iconLayout);
-            if (UninstallDropTarget.supportsDrop(launcher, itemInfo)) {
-                UninstallDropTarget.startUninstallActivity(launcher, itemInfo);
-            }
-        } else if (isDeleteKeyChord(e)) {
-            matrix = FocusLogic.createSparseMatrix(iconLayout);
-            launcher.removeItem(v, itemInfo, true /* deleteFromDb */);
         } else {
             // For other KEYCODE_DPAD_LEFT and KEYCODE_DPAD_RIGHT navigation, do not use the
             // matrix extended with hotseat.
@@ -374,14 +366,6 @@
         } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT &&
                 profile.isVerticalBarLayout()) {
             matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout, profile);
-        } else if (isUninstallKeyChord(e)) {
-            matrix = FocusLogic.createSparseMatrix(iconLayout);
-            if (UninstallDropTarget.supportsDrop(launcher, itemInfo)) {
-                UninstallDropTarget.startUninstallActivity(launcher, itemInfo);
-            }
-        } else if (isDeleteKeyChord(e)) {
-            matrix = FocusLogic.createSparseMatrix(iconLayout);
-            launcher.removeItem(v, itemInfo, true /* deleteFromDb */);
         } else {
             matrix = FocusLogic.createSparseMatrix(iconLayout);
         }
@@ -532,24 +516,6 @@
         }
     }
 
-    /**
-     * Returns whether the key event represents a valid uninstall key chord.
-     */
-    private static boolean isUninstallKeyChord(KeyEvent event) {
-        int keyCode = event.getKeyCode();
-        return (keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_FORWARD_DEL) &&
-                event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON);
-    }
-
-    /**
-     * Returns whether the key event represents a valid delete key chord.
-     */
-    private static boolean isDeleteKeyChord(KeyEvent event) {
-        int keyCode = event.getKeyCode();
-        return (keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_FORWARD_DEL) &&
-                event.hasModifiers(KeyEvent.META_CTRL_ON);
-    }
-
     private static View handlePreviousPageLastItem(Workspace workspace, CellLayout hotseatLayout,
             int pageIndex, boolean isRtl) {
         if (pageIndex - 1 < 0) {
diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
index c0a8caa..c244235 100644
--- a/src/com/android/launcher3/FolderInfo.java
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -16,10 +16,10 @@
 
 package com.android.launcher3;
 
-import android.content.ContentValues;
 import android.content.Context;
 
 import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.util.ContentWriter;
 
 import java.util.ArrayList;
 
@@ -45,11 +45,6 @@
      */
     public static final int FLAG_MULTI_PAGE_ANIMATION = 0x00000004;
 
-    /**
-     * Whether this folder has been opened
-     */
-    public boolean opened;
-
     public int options;
 
     /**
@@ -98,10 +93,10 @@
     }
 
     @Override
-    void onAddToDatabase(Context context, ContentValues values) {
-        super.onAddToDatabase(context, values);
-        values.put(LauncherSettings.Favorites.TITLE, title.toString());
-        values.put(LauncherSettings.Favorites.OPTIONS, options);
+    void onAddToDatabase(ContentWriter writer) {
+        super.onAddToDatabase(writer);
+        writer.put(LauncherSettings.Favorites.TITLE, title)
+                .put(LauncherSettings.Favorites.OPTIONS, options);
 
     }
 
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 0fbbc19..b93c6df 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -42,7 +42,7 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 
 public class Hotseat extends FrameLayout
-        implements UserEventDispatcher.LaunchSourceProvider {
+        implements UserEventDispatcher.LogContainerProvider {
 
     private CellLayout mContent;
 
@@ -172,7 +172,7 @@
     }
 
     @Override
-    public void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent) {
+    public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
         target.gridX = info.cellX;
         target.gridY = info.cellY;
         targetParent.containerType = LauncherLogProto.HOTSEAT;
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index 5c86b6b..db72b2f 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -47,6 +47,7 @@
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.graphics.LauncherIcons;
 import com.android.launcher3.model.PackageItemInfo;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.SQLiteCacheHelper;
@@ -78,7 +79,7 @@
 
     @Thunk static final Object ICON_UPDATE_TOKEN = new Object();
 
-    @Thunk static class CacheEntry {
+    public static class CacheEntry {
         public Bitmap icon;
         public CharSequence title = "";
         public CharSequence contentDescription = "";
@@ -187,7 +188,7 @@
 
     private Bitmap makeDefaultIcon(UserHandleCompat user) {
         Drawable unbadged = getFullResDefaultActivityIcon();
-        return Utilities.createBadgedIconBitmap(unbadged, user, mContext);
+        return LauncherIcons.createBadgedIconBitmap(unbadged, user, mContext);
     }
 
     /**
@@ -223,7 +224,7 @@
                     PackageManager.GET_UNINSTALLED_PACKAGES);
             long userSerial = mUserManager.getSerialNumberForUser(user);
             for (LauncherActivityInfoCompat app : mLauncherApps.getActivityList(packageName, user)) {
-                addIconToDBAndMemCache(app, info, userSerial);
+                addIconToDBAndMemCache(app, info, userSerial, false /*replace existing*/);
             }
         } catch (NameNotFoundException e) {
             Log.d(TAG, "Package not found", e);
@@ -353,29 +354,14 @@
         }
     }
 
-    @Thunk void addIconToDBAndMemCache(LauncherActivityInfoCompat app, PackageInfo info,
-            long userSerial) {
-        // Reuse the existing entry if it already exists in the DB. This ensures that we do not
-        // create bitmap if it was already created during loader.
-        ContentValues values = updateCacheAndGetContentValues(app, false);
-        addIconToDB(values, app.getComponentName(), info, userSerial);
-    }
-
     /**
-     * Updates {@param values} to contain versoning information and adds it to the DB.
-     * @param values {@link ContentValues} containing icon & title
+     * Adds an entry into the DB and the in-memory cache.
+     * @param replaceExisting if true, it will recreate the bitmap even if it already exists in
+     *                        the memory. This is useful then the previous bitmap was created using
+     *                        old data.
      */
-    private void addIconToDB(ContentValues values, ComponentName key,
-            PackageInfo info, long userSerial) {
-        values.put(IconDB.COLUMN_COMPONENT, key.flattenToString());
-        values.put(IconDB.COLUMN_USER, userSerial);
-        values.put(IconDB.COLUMN_LAST_UPDATED, info.lastUpdateTime);
-        values.put(IconDB.COLUMN_VERSION, info.versionCode);
-        mIconDb.insertOrReplace(values);
-    }
-
-    @Thunk ContentValues updateCacheAndGetContentValues(LauncherActivityInfoCompat app,
-            boolean replaceExisting) {
+    @Thunk synchronized void addIconToDBAndMemCache(LauncherActivityInfoCompat app,
+            PackageInfo info, long userSerial, boolean replaceExisting) {
         final ComponentKey key = new ComponentKey(app.getComponentName(), app.getUser());
         CacheEntry entry = null;
         if (!replaceExisting) {
@@ -387,17 +373,31 @@
         }
         if (entry == null) {
             entry = new CacheEntry();
-            entry.icon = Utilities.createBadgedIconBitmap(
+            entry.icon = LauncherIcons.createBadgedIconBitmap(
                     mIconProvider.getIcon(app, mIconDpi), app.getUser(),
                     mContext);
         }
         entry.title = app.getLabel();
         entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, app.getUser());
-        mCache.put(new ComponentKey(app.getComponentName(), app.getUser()), entry);
+        mCache.put(key, entry);
 
         Bitmap lowResIcon = generateLowResIcon(entry.icon, mActivityBgColor);
-        return newContentValues(entry.icon, lowResIcon, entry.title.toString(),
+        ContentValues values = newContentValues(entry.icon, lowResIcon, entry.title.toString(),
                 app.getApplicationInfo().packageName);
+        addIconToDB(values, app.getComponentName(), info, userSerial);
+    }
+
+    /**
+     * Updates {@param values} to contain versioning information and adds it to the DB.
+     * @param values {@link ContentValues} containing icon & title
+     */
+    private void addIconToDB(ContentValues values, ComponentName key,
+            PackageInfo info, long userSerial) {
+        values.put(IconDB.COLUMN_COMPONENT, key.flattenToString());
+        values.put(IconDB.COLUMN_USER, userSerial);
+        values.put(IconDB.COLUMN_LAST_UPDATED, info.lastUpdateTime);
+        values.put(IconDB.COLUMN_VERSION, info.versionCode);
+        mIconDb.insertOrReplace(values);
     }
 
     /**
@@ -494,7 +494,6 @@
             shortcutInfo.setIcon(getDefaultIcon(user));
             shortcutInfo.title = "";
             shortcutInfo.contentDescription = "";
-            shortcutInfo.usingFallbackIcon = true;
             shortcutInfo.usingLowResIcon = false;
         } else {
             LauncherActivityInfoCompat info = mLauncherApps.resolveActivity(intent, user);
@@ -512,7 +511,6 @@
         shortcutInfo.setIcon(getNonNullIcon(entry, user));
         shortcutInfo.title = Utilities.trim(entry.title);
         shortcutInfo.contentDescription = entry.contentDescription;
-        shortcutInfo.usingFallbackIcon = isDefaultIcon(entry.icon, user);
         shortcutInfo.usingLowResIcon = entry.isLowResIcon;
     }
 
@@ -544,7 +542,7 @@
      * Retrieves the entry from the cache. If the entry is not present, it creates a new entry.
      * This method is not thread safe, it must be called from a synchronized method.
      */
-    private CacheEntry cacheLocked(ComponentName componentName, LauncherActivityInfoCompat info,
+    protected CacheEntry cacheLocked(ComponentName componentName, LauncherActivityInfoCompat info,
             UserHandleCompat user, boolean usePackageIcon, boolean useLowResIcon) {
         ComponentKey cacheKey = new ComponentKey(componentName, user);
         CacheEntry entry = mCache.get(cacheKey);
@@ -555,7 +553,7 @@
             // Check the DB first.
             if (!getEntryFromDB(cacheKey, entry, useLowResIcon) || DEBUG_IGNORE_CACHE) {
                 if (info != null) {
-                    entry.icon = Utilities.createBadgedIconBitmap(
+                    entry.icon = LauncherIcons.createBadgedIconBitmap(
                             mIconProvider.getIcon(info, mIconDpi), info.getUser(),
                             mContext);
                 } else {
@@ -606,7 +604,7 @@
             entry.title = title;
         }
         if (icon != null) {
-            entry.icon = Utilities.createIconBitmap(icon, mContext);
+            entry.icon = LauncherIcons.createIconBitmap(icon, mContext);
         }
     }
 
@@ -641,7 +639,7 @@
 
                     // Load the full res icon for the application, but if useLowResIcon is set, then
                     // only keep the low resolution icon instead of the larger full-sized icon
-                    Bitmap icon = Utilities.createBadgedIconBitmap(
+                    Bitmap icon = LauncherIcons.createBadgedIconBitmap(
                             appInfo.loadIcon(mPackageManager), user, mContext);
                     Bitmap lowResIcon =  generateLowResIcon(icon, mPackageBgColor);
                     entry.title = appInfo.loadLabel(mPackageManager);
@@ -774,13 +772,9 @@
                 LauncherActivityInfoCompat app = mAppsToUpdate.pop();
                 String pkg = app.getComponentName().getPackageName();
                 PackageInfo info = mPkgInfoMap.get(pkg);
-                if (info != null) {
-                    synchronized (IconCache.this) {
-                        ContentValues values = updateCacheAndGetContentValues(app, true);
-                        addIconToDB(values, app.getComponentName(), info, mUserSerial);
-                    }
-                    mUpdatedPackages.add(pkg);
-                }
+                addIconToDBAndMemCache(app, info, mUserSerial, true /*replace existing*/);
+                mUpdatedPackages.add(pkg);
+
                 if (mAppsToUpdate.isEmpty() && !mUpdatedPackages.isEmpty()) {
                     // No more app to update. Notify model.
                     LauncherAppState.getInstance().getModel().onPackageIconsUpdated(
@@ -792,10 +786,10 @@
             } else if (!mAppsToAdd.isEmpty()) {
                 LauncherActivityInfoCompat app = mAppsToAdd.pop();
                 PackageInfo info = mPkgInfoMap.get(app.getComponentName().getPackageName());
+                // We do not check the mPkgInfoMap when generating the mAppsToAdd. Although every
+                // app should have package info, this is not guaranteed by the api
                 if (info != null) {
-                    synchronized (IconCache.this) {
-                        addIconToDBAndMemCache(app, info, mUserSerial);
-                    }
+                    addIconToDBAndMemCache(app, info, mUserSerial, false /*replace existing*/);
                 }
 
                 if (!mAppsToAdd.isEmpty()) {
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index d8e58d8..056facb 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -22,7 +22,6 @@
 import android.content.SharedPreferences;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.text.TextUtils;
@@ -226,7 +225,8 @@
                 String packageName = pendingInfo.getTargetPackage();
                 if (!TextUtils.isEmpty(packageName)) {
                     UserHandleCompat myUserHandle = UserHandleCompat.myUserHandle();
-                    if (!LauncherModel.isValidPackage(context, packageName, myUserHandle)) {
+                    if (!LauncherAppsCompat.getInstance(context)
+                            .isPackageEnabledForProfile(packageName, myUserHandle)) {
                         if (DBG) Log.d(TAG, "Ignoring shortcut for absent package: "
                                 + pendingInfo.launchIntent);
                         continue;
@@ -240,7 +240,7 @@
             // Add the new apps to the model and bind them
             if (!addShortcuts.isEmpty()) {
                 LauncherAppState app = LauncherAppState.getInstance();
-                app.getModel().addAndBindAddedWorkspaceItems(context, addShortcuts);
+                app.getModel().addAndBindAddedWorkspaceItems(addShortcuts);
             }
         }
     }
@@ -433,22 +433,16 @@
             // Already an activity target
             return original;
         }
-        if (!Utilities.isLauncherAppTarget(original.launchIntent)
-                || !original.user.equals(UserHandleCompat.myUserHandle())) {
-            // We can only convert shortcuts which point to a main activity in the current user.
+        if (!Utilities.isLauncherAppTarget(original.launchIntent)) {
             return original;
         }
 
-        PackageManager pm = original.mContext.getPackageManager();
-        ResolveInfo info = pm.resolveActivity(original.launchIntent, 0);
-
+        LauncherActivityInfoCompat info = LauncherAppsCompat.getInstance(original.mContext)
+                .resolveActivity(original.launchIntent, original.user);
         if (info == null) {
             return original;
         }
-
         // Ignore any conflicts in the label name, as that can change based on locale.
-        LauncherActivityInfoCompat launcherInfo = LauncherActivityInfoCompat
-                .fromResolveInfo(info, original.mContext);
-        return new PendingInstallShortcutInfo(launcherInfo, original.mContext);
+        return new PendingInstallShortcutInfo(info, original.mContext);
     }
 }
diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java
index c0c22a3..3e0ae4f 100644
--- a/src/com/android/launcher3/ItemInfo.java
+++ b/src/com/android/launcher3/ItemInfo.java
@@ -18,12 +18,10 @@
 
 import android.content.ComponentName;
 import android.content.ContentValues;
-import android.content.Context;
 import android.content.Intent;
-import android.graphics.Bitmap;
 
 import com.android.launcher3.compat.UserHandleCompat;
-import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.util.ContentWriter;
 
 /**
  * Represents an item in the launcher.
@@ -59,7 +57,7 @@
     public long container = NO_ID;
 
     /**
-     * Iindicates the screen in which the shortcut appears.
+     * Indicates the screen in which the shortcut appears.
      */
     public long screenId = -1;
 
@@ -142,15 +140,15 @@
         return getIntent() == null ? null : getIntent().getComponent();
     }
 
-    public void writeToValues(ContentValues values) {
-        values.put(LauncherSettings.Favorites.ITEM_TYPE, itemType);
-        values.put(LauncherSettings.Favorites.CONTAINER, container);
-        values.put(LauncherSettings.Favorites.SCREEN, screenId);
-        values.put(LauncherSettings.Favorites.CELLX, cellX);
-        values.put(LauncherSettings.Favorites.CELLY, cellY);
-        values.put(LauncherSettings.Favorites.SPANX, spanX);
-        values.put(LauncherSettings.Favorites.SPANY, spanY);
-        values.put(LauncherSettings.Favorites.RANK, rank);
+    public void writeToValues(ContentWriter writer) {
+        writer.put(LauncherSettings.Favorites.ITEM_TYPE, itemType)
+                .put(LauncherSettings.Favorites.CONTAINER, container)
+                .put(LauncherSettings.Favorites.SCREEN, screenId)
+                .put(LauncherSettings.Favorites.CELLX, cellX)
+                .put(LauncherSettings.Favorites.CELLY, cellY)
+                .put(LauncherSettings.Favorites.SPANX, spanX)
+                .put(LauncherSettings.Favorites.SPANY, spanY)
+                .put(LauncherSettings.Favorites.RANK, rank);
     }
 
     public void readFromValues(ContentValues values) {
@@ -166,26 +164,15 @@
 
     /**
      * Write the fields of this item to the DB
-     *
-     * @param context A context object to use for getting UserManagerCompat
-     * @param values
      */
-    void onAddToDatabase(Context context, ContentValues values) {
-        writeToValues(values);
-        long serialNumber = UserManagerCompat.getInstance(context).getSerialNumberForUser(user);
-        values.put(LauncherSettings.Favorites.PROFILE_ID, serialNumber);
-
+    void onAddToDatabase(ContentWriter writer) {
         if (screenId == Workspace.EXTRA_EMPTY_SCREEN_ID) {
             // We should never persist an item on the extra empty screen.
             throw new RuntimeException("Screen id should not be EXTRA_EMPTY_SCREEN_ID");
         }
-    }
 
-    static void writeBitmap(ContentValues values, Bitmap bitmap) {
-        if (bitmap != null) {
-            byte[] data = Utilities.flattenBitmap(bitmap);
-            values.put(LauncherSettings.Favorites.ICON, data);
-        }
+        writeToValues(writer);
+        writer.put(LauncherSettings.Favorites.PROFILE_ID, user);
     }
 
     @Override
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 4672e08..9a5e186 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -18,9 +18,7 @@
 
 import android.Manifest;
 import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.annotation.SuppressLint;
 import android.annotation.TargetApi;
@@ -30,7 +28,6 @@
 import android.app.SearchManager;
 import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetManager;
-import android.appwidget.AppWidgetProviderInfo;
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.ComponentCallbacks2;
@@ -48,15 +45,13 @@
 import android.content.res.Configuration;
 import android.database.sqlite.SQLiteDatabase;
 import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.PorterDuff;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.AsyncTask;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.Message;
 import android.os.StrictMode;
 import android.os.SystemClock;
 import android.os.Trace;
@@ -69,11 +64,12 @@
 import android.view.Display;
 import android.view.HapticFeedbackConstants;
 import android.view.KeyEvent;
+import android.view.KeyboardShortcutGroup;
+import android.view.KeyboardShortcutInfo;
 import android.view.Menu;
 import android.view.MotionEvent;
 import android.view.Surface;
 import android.view.View;
-import android.view.View.OnClickListener;
 import android.view.View.OnLongClickListener;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
@@ -81,8 +77,6 @@
 import android.view.accessibility.AccessibilityManager;
 import android.view.animation.OvershootInterpolator;
 import android.view.inputmethod.InputMethodManager;
-import android.widget.Advanceable;
-import android.widget.ImageView;
 import android.widget.TextView;
 import android.widget.Toast;
 
@@ -92,6 +86,7 @@
 import com.android.launcher3.allapps.AllAppsContainerView;
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.allapps.DefaultAppSearchController;
+import com.android.launcher3.anim.AnimationLayerSet;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.LauncherActivityInfoCompat;
 import com.android.launcher3.compat.LauncherAppsCompat;
@@ -106,10 +101,12 @@
 import com.android.launcher3.dynamicui.ExtractedColors;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.keyboard.CustomActionsPopup;
 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.logging.UserEventDispatcher;
-import com.android.launcher3.model.WidgetsModel;
+import com.android.launcher3.model.PackageItemInfo;
+import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.pageindicators.PageIndicator;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.DeepShortcutsContainer;
@@ -178,9 +175,6 @@
     static final String INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION =
             "com.android.launcher3.intent.extra.shortcut.INGORE_LAUNCH_ANIMATION";
 
-    public static final String ACTION_APPWIDGET_HOST_RESET =
-            "com.android.launcher3.intent.ACTION_APPWIDGET_HOST_RESET";
-
     // Type: int
     private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen";
     // Type: int
@@ -211,18 +205,6 @@
     private static int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5;
     @Thunk static int NEW_APPS_ANIMATION_DELAY = 500;
 
-    private final BroadcastReceiver mUiBroadcastReceiver = new BroadcastReceiver() {
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (ACTION_APPWIDGET_HOST_RESET.equals(intent.getAction())) {
-                if (mAppWidgetHost != null) {
-                    mAppWidgetHost.startListening();
-                }
-            }
-        }
-    };
-
     @Thunk Workspace mWorkspace;
     private View mLauncherView;
     @Thunk DragLayer mDragLayer;
@@ -250,9 +232,8 @@
 
     // Main container view and the model for the widget tray screen.
     @Thunk WidgetsContainerView mWidgetsView;
-    @Thunk WidgetsModel mWidgetsModel;
+    @Thunk MultiHashMap<PackageItemInfo, WidgetItem> mAllWidgets;
 
-    private Bundle mSavedState;
     // We set the state in both onCreate and then onNewIntent in some cases, which causes both
     // scroll issues (because the workspace may not have been measured yet) and extra work.
     // Instead, just save the state that we need to restore Launcher to, and commit it in onResume.
@@ -273,27 +254,16 @@
     private IconCache mIconCache;
     private ExtractedColors mExtractedColors;
     private LauncherAccessibilityDelegate mAccessibilityDelegate;
+    private Handler mHandler = new Handler();
     private boolean mIsResumeFromActionScreenOff;
-    @Thunk boolean mUserPresent = true;
-    private boolean mVisible;
-    private boolean mHasFocus;
-    private boolean mAttached;
+    private boolean mHasFocus = false;
+    private boolean mAttached = false;
 
     /** Maps launcher activity components to their list of shortcut ids. */
     private MultiHashMap<ComponentKey, String> mDeepShortcutMap = new MultiHashMap<>();
 
     private View.OnTouchListener mHapticFeedbackTouchListener;
 
-    // Related to the auto-advancing of widgets
-    private final int ADVANCE_MSG = 1;
-    private static final int ADVANCE_INTERVAL = 20000;
-    private static final int ADVANCE_STAGGER = 250;
-
-    private boolean mAutoAdvanceRunning = false;
-    private long mAutoAdvanceSentTime;
-    private long mAutoAdvanceTimeLeft = -1;
-    @Thunk HashMap<View, AppWidgetProviderInfo> mWidgetsToAdvance = new HashMap<>();
-
     // Determines how long to wait after a rotation before restoring the screen orientation to
     // match the sensor state.
     private static final int RESTORE_SCREEN_ORIENTATION_DELAY = 500;
@@ -304,13 +274,6 @@
     // it from the context.
     private SharedPreferences mSharedPrefs;
 
-    // Holds the page that we need to animate to, and the icon views that we need to animate up
-    // when we scroll to that page on resume.
-    @Thunk ImageView mFolderIconImageView;
-    private Bitmap mFolderIconBitmap;
-    private Canvas mFolderIconCanvas;
-    private Rect mRectForFolderAnimation = new Rect();
-
     private DeviceProfile mDeviceProfile;
 
     private boolean mMoveToDefaultScreenFromNewIntent;
@@ -352,6 +315,8 @@
 
     private UserEventDispatcher mUserEventDispatcher;
 
+    private float mLastDispatchTouchEventX = 0.0f;
+
     public ViewGroupFocusHelper mFocusHandler;
     private boolean mRotationEnabled = false;
 
@@ -395,10 +360,17 @@
         LauncherAppState app = LauncherAppState.getInstance();
 
         // Load configuration-specific DeviceProfile
-        mDeviceProfile = getResources().getConfiguration().orientation
-                == Configuration.ORIENTATION_LANDSCAPE ?
-                app.getInvariantDeviceProfile().landscapeProfile
-                : app.getInvariantDeviceProfile().portraitProfile;
+        mDeviceProfile =
+                getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE
+                        ? app.getInvariantDeviceProfile().landscapeProfile
+                        : app.getInvariantDeviceProfile().portraitProfile;
+
+        if (Utilities.ATLEAST_NOUGAT && isInMultiWindowMode()) {
+            Display display = getWindowManager().getDefaultDisplay();
+            Point mwSize = new Point();
+            display.getSize(mwSize);
+            mDeviceProfile = mDeviceProfile.getMultiWindowProfile(this, mwSize);
+        }
 
         mSharedPrefs = Utilities.getPrefs(this);
         mIsSafeModeEnabled = getPackageManager().isSafeMode();
@@ -420,7 +392,7 @@
         // LauncherModel load.
         mPaused = false;
 
-        setContentView(R.layout.launcher);
+        mLauncherView = getLayoutInflater().inflate(R.layout.launcher, null);
 
         setupViews();
         mDeviceProfile.layout(this, false /* notifyListeners */);
@@ -432,8 +404,7 @@
 
         lockAllApps();
 
-        mSavedState = savedInstanceState;
-        restoreState(mSavedState);
+        restoreState(savedInstanceState);
 
         if (LauncherAppState.PROFILE_STARTUP) {
             Trace.endSection();
@@ -441,11 +412,18 @@
 
         // We only load the page synchronously if the user rotates (or triggers a
         // configuration change) while launcher is in the foreground
-        if (!mModel.startLoader(mWorkspace.getRestorePage())) {
+        int currentScreen = PagedView.INVALID_RESTORE_PAGE;
+        if (savedInstanceState != null) {
+            currentScreen = savedInstanceState.getInt(RUNTIME_STATE_CURRENT_SCREEN, currentScreen);
+        }
+        if (!mModel.startLoader(currentScreen)) {
             // If we are not binding synchronously, show a fade in animation when
             // the first page bind completes.
             mDragLayer.setAlpha(0);
         } else {
+            // Pages bound synchronously.
+            mWorkspace.setCurrentPage(currentScreen);
+
             setWorkspaceLoading(true);
         }
 
@@ -453,9 +431,6 @@
         mDefaultKeySsb = new SpannableStringBuilder();
         Selection.setSelection(mDefaultKeySsb, 0);
 
-        IntentFilter filter = new IntentFilter(ACTION_APPWIDGET_HOST_RESET);
-        registerReceiver(mUiBroadcastReceiver, filter);
-
         mRotationEnabled = getResources().getBoolean(R.bool.allow_rotation);
         // In case we are on a device with locked rotation, we should look at preferences to check
         // if the user has specifically allowed rotation.
@@ -469,19 +444,32 @@
         // we want the screen to auto-rotate based on the current orientation
         setOrientation();
 
+        setContentView(mLauncherView);
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onCreate(savedInstanceState);
         }
     }
 
     @Override
+    public View findViewById(int id) {
+        return mLauncherView.findViewById(id);
+    }
+
+    @Override
     public void onExtractedColorsChanged() {
         loadExtractedColorsAndColorItems();
     }
 
+    @Override
+    public void onAppWidgetHostReset() {
+        if (mAppWidgetHost != null) {
+            mAppWidgetHost.startListening();
+        }
+    }
+
     private void loadExtractedColorsAndColorItems() {
         // TODO: do this in pre-N as well, once the extraction part is complete.
-        if (Utilities.isNycOrAbove()) {
+        if (Utilities.ATLEAST_NOUGAT) {
             mExtractedColors.load(this);
             mHotseat.updateColor(mExtractedColors, !mPaused);
             mWorkspace.getPageIndicator().updateColor(mExtractedColors);
@@ -582,7 +570,7 @@
     }
 
     @Override
-    public void onLauncherProviderChange() {
+    public void onLauncherProviderChanged() {
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onLauncherProviderChange();
         }
@@ -608,7 +596,7 @@
     }
 
     /**
-     * Invoked by subclasses to signal a change to the {@link #addCustomContentToLeft} value to
+     * Invoked by subclasses to signal a change to the {@link #addToCustomContentPage} value to
      * ensure the custom content page is added or removed if necessary.
      */
     protected void invalidateHasCustomContentToLeft() {
@@ -928,7 +916,7 @@
             mLauncherCallbacks.onStop();
         }
 
-        if (Utilities.isNycMR1OrAbove()) {
+        if (Utilities.ATLEAST_NOUGAT_MR1) {
             mAppWidgetHost.stopListening();
         }
     }
@@ -942,7 +930,7 @@
             mLauncherCallbacks.onStart();
         }
 
-        if (Utilities.isNycMR1OrAbove()) {
+        if (Utilities.ATLEAST_NOUGAT_MR1) {
             mAppWidgetHost.startListening();
         }
     }
@@ -1227,11 +1215,8 @@
         if (keyCode == KeyEvent.KEYCODE_MENU) {
             // Ignore the menu key if we are currently dragging or are on the custom content screen
             if (!isOnCustomContent() && !mDragController.isDragging()) {
-                // Close any open folders
-                closeFolder();
-
-                // Close any shortcuts containers
-                closeShortcutsContainer();
+                // Close any open floating view
+                AbstractFloatingView.closeAllOpenViews(this);
 
                 // Stop resizing any widgets
                 mWorkspace.exitWidgetResizeMode();
@@ -1260,22 +1245,6 @@
     }
 
     /**
-     * Given the integer (ordinal) value of a State enum instance, convert it to a variable of type
-     * State
-     */
-    private static State intToState(int stateOrdinal) {
-        State state = State.WORKSPACE;
-        final State[] stateValues = State.values();
-        for (int i = 0; i < stateValues.length; i++) {
-            if (stateValues[i].ordinal() == stateOrdinal) {
-                state = stateValues[i];
-                break;
-            }
-        }
-        return state;
-    }
-
-    /**
      * Restores the previous state, if it exists.
      *
      * @param savedState The previous state.
@@ -1285,17 +1254,14 @@
             return;
         }
 
-        State state = intToState(savedState.getInt(RUNTIME_STATE, State.WORKSPACE.ordinal()));
+        int stateOrdinal = savedState.getInt(RUNTIME_STATE, State.WORKSPACE.ordinal());
+        State[] stateValues = State.values();
+        State state = (stateOrdinal >= 0 && stateOrdinal < stateValues.length)
+                ? stateValues[stateOrdinal] : State.WORKSPACE;
         if (state == State.APPS || state == State.WIDGETS) {
             mOnResumeState = state;
         }
 
-        int currentScreen = savedState.getInt(RUNTIME_STATE_CURRENT_SCREEN,
-                PagedView.INVALID_RESTORE_PAGE);
-        if (currentScreen != PagedView.INVALID_RESTORE_PAGE) {
-            mWorkspace.setRestorePage(currentScreen);
-        }
-
         PendingRequestArgs requestArgs = savedState.getParcelable(RUNTIME_STATE_PENDING_REQUEST_ARGS);
         if (requestArgs != null) {
             setWaitingForResult(requestArgs);
@@ -1308,7 +1274,6 @@
      * Finds all the views we need and configure them properly.
      */
     private void setupViews() {
-        mLauncherView = findViewById(R.id.launcher);
         mDragLayer = (DragLayer) findViewById(R.id.drag_layer);
         mFocusHandler = mDragLayer.getFocusIndicatorHelper();
         mWorkspace = (Workspace) mDragLayer.findViewById(R.id.workspace);
@@ -1355,7 +1320,7 @@
         }
 
         // Setup the drag controller (drop targets have to be added in reverse order in priority)
-        mDragController.setDragScoller(mWorkspace);
+        mDragController.setDragScroller(mWorkspace);
         mDragController.setScrollView(mDragLayer);
         mDragController.setMoveTarget(mWorkspace);
         mDragController.addDropTarget(mWorkspace);
@@ -1373,53 +1338,36 @@
     private void setupOverviewPanel() {
         mOverviewPanel = (ViewGroup) findViewById(R.id.overview_panel);
 
-        // Long-clicking buttons in the overview panel does the same thing as clicking them.
-        OnLongClickListener performClickOnLongClick = new OnLongClickListener() {
-            @Override
-            public boolean onLongClick(View v) {
-                return v.performClick();
-            }
-        };
-
         // Bind wallpaper button actions
         View wallpaperButton = findViewById(R.id.wallpaper_button);
-        wallpaperButton.setOnClickListener(new OnClickListener() {
+        new OverviewButtonClickListener(LauncherLogProto.WALLPAPER_BUTTON) {
             @Override
-            public void onClick(View view) {
-                if (!mWorkspace.isSwitchingState()) {
-                    onClickWallpaperPicker(view);
-                }
+            public void handleViewClick(View view) {
+                onClickWallpaperPicker(view);
             }
-        });
-        wallpaperButton.setOnLongClickListener(performClickOnLongClick);
+        }.attachTo(wallpaperButton);
         wallpaperButton.setOnTouchListener(getHapticFeedbackTouchListener());
 
         // Bind widget button actions
         mWidgetsButton = findViewById(R.id.widget_button);
-        mWidgetsButton.setOnClickListener(new OnClickListener() {
+        new OverviewButtonClickListener(LauncherLogProto.WIDGETS_BUTTON) {
             @Override
-            public void onClick(View view) {
-                if (!mWorkspace.isSwitchingState()) {
-                    onClickAddWidgetButton(view);
-                }
+            public void handleViewClick(View view) {
+                onClickAddWidgetButton(view);
             }
-        });
-        mWidgetsButton.setOnLongClickListener(performClickOnLongClick);
+        }.attachTo(mWidgetsButton);
         mWidgetsButton.setOnTouchListener(getHapticFeedbackTouchListener());
 
         // Bind settings actions
         View settingsButton = findViewById(R.id.settings_button);
         boolean hasSettings = hasSettings();
         if (hasSettings) {
-            settingsButton.setOnClickListener(new OnClickListener() {
+            new OverviewButtonClickListener(LauncherLogProto.SETTINGS_BUTTON) {
                 @Override
-                public void onClick(View view) {
-                    if (!mWorkspace.isSwitchingState()) {
-                        onClickSettingsButton(view);
-                    }
+                public void handleViewClick(View view) {
+                    onClickSettingsButton(view);
                 }
-            });
-            settingsButton.setOnLongClickListener(performClickOnLongClick);
+            }.attachTo(settingsButton);
             settingsButton.setOnTouchListener(getHapticFeedbackTouchListener());
         } else {
             settingsButton.setVisibility(View.GONE);
@@ -1517,7 +1465,7 @@
         }
 
         if (!foundCellSpan) {
-            showOutOfSpaceMessage(isHotseatLayout(layout));
+            mWorkspace.onNoCellFound(layout);
             return;
         }
 
@@ -1573,10 +1521,6 @@
 
         mWorkspace.addInScreen(hostView, item.container, item.screenId,
                 item.cellX, item.cellY, item.spanX, item.spanY, insert);
-
-        if (!item.isCustomWidget()) {
-            addWidgetToAutoAdvanceIfNeeded(hostView, appWidgetInfo);
-        }
     }
 
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -1584,9 +1528,7 @@
         public void onReceive(Context context, Intent intent) {
             final String action = intent.getAction();
             if (Intent.ACTION_SCREEN_OFF.equals(action)) {
-                mUserPresent = false;
-                mDragLayer.clearAllResizeFrames();
-                updateAutoAdvanceState();
+                mDragLayer.clearResizeFrame();
 
                 // Reset AllApps to its initial state only if we are not in the middle of
                 // processing a multi-step drop
@@ -1597,9 +1539,6 @@
                     }
                 }
                 mIsResumeFromActionScreenOff = true;
-            } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
-                mUserPresent = true;
-                updateAutoAdvanceState();
             }
         }
     };
@@ -1611,11 +1550,9 @@
         // Listen for broadcasts related to user-presence
         final IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_SCREEN_OFF);
-        filter.addAction(Intent.ACTION_USER_PRESENT);
         registerReceiver(mReceiver, filter);
         FirstFrameAnimatorHelper.initializeDrawListener(getWindow().getDecorView());
         mAttached = true;
-        mVisible = true;
 
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onAttachedToWindow();
@@ -1625,13 +1562,10 @@
     @Override
     public void onDetachedFromWindow() {
         super.onDetachedFromWindow();
-        mVisible = false;
-
         if (mAttached) {
             unregisterReceiver(mReceiver);
             mAttached = false;
         }
-        updateAutoAdvanceState();
 
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onDetachedFromWindow();
@@ -1639,12 +1573,10 @@
     }
 
     public void onWindowVisibilityChanged(int visibility) {
-        mVisible = visibility == View.VISIBLE;
-        updateAutoAdvanceState();
         // The following code used to be in onResume, but it turns out onResume is called when
         // you're in All Apps and click home to go to the workspace. onWindowVisibilityChanged
         // is a more appropriate event to handle
-        if (mVisible) {
+        if (visibility == View.VISIBLE) {
             if (!mWorkspaceLoading) {
                 final ViewTreeObserver observer = mWorkspace.getViewTreeObserver();
                 // We want to let Launcher draw itself at least once before we force it to build
@@ -1679,77 +1611,6 @@
         }
     }
 
-    @Thunk void sendAdvanceMessage(long delay) {
-        mHandler.removeMessages(ADVANCE_MSG);
-        Message msg = mHandler.obtainMessage(ADVANCE_MSG);
-        mHandler.sendMessageDelayed(msg, delay);
-        mAutoAdvanceSentTime = System.currentTimeMillis();
-    }
-
-    @Thunk void updateAutoAdvanceState() {
-        boolean autoAdvanceRunning = mVisible && mUserPresent && !mWidgetsToAdvance.isEmpty();
-        if (autoAdvanceRunning != mAutoAdvanceRunning) {
-            mAutoAdvanceRunning = autoAdvanceRunning;
-            if (autoAdvanceRunning) {
-                long delay = mAutoAdvanceTimeLeft == -1 ? ADVANCE_INTERVAL : mAutoAdvanceTimeLeft;
-                sendAdvanceMessage(delay);
-            } else {
-                if (!mWidgetsToAdvance.isEmpty()) {
-                    mAutoAdvanceTimeLeft = Math.max(0, ADVANCE_INTERVAL -
-                            (System.currentTimeMillis() - mAutoAdvanceSentTime));
-                }
-                mHandler.removeMessages(ADVANCE_MSG);
-                mHandler.removeMessages(0); // Remove messages sent using postDelayed()
-            }
-        }
-    }
-
-    @Thunk final Handler mHandler = new Handler(new Handler.Callback() {
-
-        @Override
-        public boolean handleMessage(Message msg) {
-            if (msg.what == ADVANCE_MSG) {
-                int i = 0;
-                for (View key: mWidgetsToAdvance.keySet()) {
-                    final View v = key.findViewById(mWidgetsToAdvance.get(key).autoAdvanceViewId);
-                    final int delay = ADVANCE_STAGGER * i;
-                    if (v instanceof Advanceable) {
-                        mHandler.postDelayed(new Runnable() {
-                           public void run() {
-                               ((Advanceable) v).advance();
-                           }
-                       }, delay);
-                    }
-                    i++;
-                }
-                sendAdvanceMessage(ADVANCE_INTERVAL);
-            }
-            return true;
-        }
-    });
-
-    private void addWidgetToAutoAdvanceIfNeeded(View hostView, AppWidgetProviderInfo appWidgetInfo) {
-        if (appWidgetInfo == null || appWidgetInfo.autoAdvanceViewId == -1) return;
-        View v = hostView.findViewById(appWidgetInfo.autoAdvanceViewId);
-        if (v instanceof Advanceable) {
-            mWidgetsToAdvance.put(hostView, appWidgetInfo);
-            ((Advanceable) v).fyiWillBeAdvancedByHostKThx();
-            updateAutoAdvanceState();
-        }
-    }
-
-    private void removeWidgetToAutoAdvance(View hostView) {
-        if (mWidgetsToAdvance.containsKey(hostView)) {
-            mWidgetsToAdvance.remove(hostView);
-            updateAutoAdvanceState();
-        }
-    }
-
-    public void showOutOfSpaceMessage(boolean isHotseatLayout) {
-        int strId = (isHotseatLayout ? R.string.hotseat_out_of_space : R.string.out_of_space);
-        Toast.makeText(this, getString(strId), Toast.LENGTH_SHORT).show();
-    }
-
     public DragLayer getDragLayer() {
         return mDragLayer;
     }
@@ -1798,13 +1659,6 @@
         return mDeviceProfile;
     }
 
-    public void closeSystemDialogs() {
-        getWindow().closeAllPanels();
-
-        // Whatever we were doing is hereby canceled.
-        setWaitingForResult(null);
-    }
-
     @Override
     protected void onNewIntent(Intent intent) {
         long startTime = 0;
@@ -1819,13 +1673,10 @@
 
         // Check this condition before handling isActionMain, as this will get reset.
         boolean shouldMoveToDefaultScreen = alreadyOnHome &&
-                mState == State.WORKSPACE && getTopFloatingView() == null;
+                mState == State.WORKSPACE && AbstractFloatingView.getTopOpenView(this) == null;
 
         boolean isActionMain = Intent.ACTION_MAIN.equals(intent.getAction());
         if (isActionMain) {
-            // also will cancel mWaitingForResult.
-            closeSystemDialogs();
-
             if (mWorkspace == null) {
                 // Can be cases where mWorkspace is null, this prevents a NPE
                 return;
@@ -1833,8 +1684,7 @@
             // In all these cases, only animate if we're already on home
             mWorkspace.exitWidgetResizeMode();
 
-            closeFolder(alreadyOnHome);
-            closeShortcutsContainer(alreadyOnHome);
+            AbstractFloatingView.closeAllOpenViews(this, alreadyOnHome);
             exitSpringLoadedDragMode();
 
             // If we are already on home, then just animate back to the workspace,
@@ -1917,11 +1767,9 @@
         super.onSaveInstanceState(outState);
 
         outState.putInt(RUNTIME_STATE, mState.ordinal());
-        // We close any open folder since it will not be re-opened, and we need to make sure
-        // this state is reflected.
-        // TODO: Move folderInfo.isOpened out of the model and make it a UI state.
-        closeFolder(false);
-        closeShortcutsContainer(false);
+        // We close any open folders and shortcut containers since they will not be re-opened,
+        // and we need to make sure this state is reflected.
+        AbstractFloatingView.closeAllOpenViews(this, false);
 
         if (mPendingRequestArgs != null) {
             outState.putParcelable(RUNTIME_STATE_PENDING_REQUEST_ARGS, mPendingRequestArgs);
@@ -1939,9 +1787,6 @@
     public void onDestroy() {
         super.onDestroy();
 
-        // Remove all pending runnables
-        mHandler.removeMessages(ADVANCE_MSG);
-        mHandler.removeMessages(0);
         mWorkspace.removeCallbacks(mBuildLayersRunnable);
         mWorkspace.removeFolderListeners();
 
@@ -1964,15 +1809,11 @@
         }
         mAppWidgetHost = null;
 
-        mWidgetsToAdvance.clear();
-
         TextKeyListener.getInstance().release();
 
         ((AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE))
                 .removeAccessibilityStateChangeListener(this);
 
-        unregisterReceiver(mUiBroadcastReceiver);
-
         LauncherAnimUtils.onDestroyActivity();
 
         if (mLauncherCallbacks != null) {
@@ -2155,7 +1996,7 @@
 
     protected void moveToCustomContentScreen(boolean animate) {
         // Close any folders that may be open.
-        closeFolder();
+        AbstractFloatingView.closeAllOpenViews(this, animate);
         mWorkspace.moveToCustomContentScreen(animate);
     }
 
@@ -2285,7 +2126,6 @@
         } else if (itemInfo instanceof LauncherAppWidgetInfo) {
             final LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) itemInfo;
             mWorkspace.removeWorkspaceItem(v);
-            removeWidgetToAutoAdvance(v);
             if (deleteFromDb) {
                 deleteWidgetInfo(widgetInfo);
             }
@@ -2347,21 +2187,19 @@
             return;
         }
 
-        if (getOpenShortcutsContainer() != null) {
-            closeShortcutsContainer();
+        AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this);
+        if (topView != null) {
+            if (topView.getActiveTextView() != null) {
+                topView.getActiveTextView().dispatchBackKey();
+            } else {
+                topView.close(true);
+            }
         } else if (isAppsViewVisible()) {
             showWorkspace(true);
         } else if (isWidgetsViewVisible())  {
             showOverviewMode(true);
         } else if (mWorkspace.isInOverviewMode()) {
             showWorkspace(true);
-        } else if (mWorkspace.getOpenFolder() != null) {
-            Folder openFolder = mWorkspace.getOpenFolder();
-            if (openFolder.isEditingName()) {
-                openFolder.dismissEditingName();
-            } else {
-                closeFolder();
-            }
         } else {
             mWorkspace.exitWidgetResizeMode();
 
@@ -2611,10 +2449,10 @@
             throw new IllegalArgumentException("Input must be a FolderIcon");
         }
 
-        FolderIcon folderIcon = (FolderIcon) v;
-        if (!folderIcon.getFolderInfo().opened && !folderIcon.getFolder().isDestroyed()) {
+        Folder folder = ((FolderIcon) v).getFolder();
+        if (!folder.isOpen() && !folder.isDestroyed()) {
             // Open the requested folder
-            openFolder(folderIcon);
+            folder.animateOpen();
         }
     }
 
@@ -2636,25 +2474,32 @@
      * on the home screen.
      */
     public void onClickWallpaperPicker(View v) {
-        if (!Utilities.isWallapaperAllowed(this)) {
+        if (!Utilities.isWallpaperAllowed(this)) {
             Toast.makeText(this, R.string.msg_disabled_by_admin, Toast.LENGTH_SHORT).show();
             return;
         }
 
-        String pickerPackage = getString(R.string.wallpaper_picker_package);
-        if (TextUtils.isEmpty(pickerPackage)) {
-            pickerPackage =  PackageManagerHelper.getWallpaperPickerPackage(getPackageManager());
-        }
-
         int pageScroll = mWorkspace.getScrollForPage(mWorkspace.getPageNearestToCenterOfScreen());
         float offset = mWorkspace.mWallpaperOffset.wallpaperOffsetForScroll(pageScroll);
-
         setWaitingForResult(new PendingRequestArgs(new ItemInfo()));
         Intent intent = new Intent(Intent.ACTION_SET_WALLPAPER)
-                .setPackage(pickerPackage)
                 .putExtra(Utilities.EXTRA_WALLPAPER_OFFSET, offset);
+
+        String pickerPackage = getString(R.string.wallpaper_picker_package);
+        boolean hasTargetPackage = TextUtils.isEmpty(pickerPackage);
+        if (!hasTargetPackage) {
+            intent.setPackage(pickerPackage);
+        }
+
         intent.setSourceBounds(getViewBounds(v));
-        startActivityForResult(intent, REQUEST_PICK_WALLPAPER, getActivityLaunchOptions(v));
+        try {
+            startActivityForResult(intent, REQUEST_PICK_WALLPAPER,
+                    // If there is no target package, use the default intent chooser animation
+                    hasTargetPackage ? getActivityLaunchOptions(v) : null);
+        } catch (ActivityNotFoundException e) {
+            setWaitingForResult(null);
+            Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
+        }
     }
 
     /**
@@ -2749,7 +2594,7 @@
                 if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
                     String id = ((ShortcutInfo) info).getDeepShortcutId();
                     String packageName = intent.getPackage();
-                    LauncherAppState.getInstance().getShortcutManager().startShortcut(
+                    DeepShortcutManager.getInstance(this).startShortcut(
                             packageName, id, intent.getSourceBounds(), optsBundle, info.user);
                 } else {
                     // Could be launching some bookkeeping activity
@@ -2778,6 +2623,7 @@
         }
     }
 
+    @TargetApi(Build.VERSION_CODES.M)
     private Bundle getActivityLaunchOptions(View v) {
         if (Utilities.ATLEAST_MARSHMALLOW) {
             int left = 0, top = 0;
@@ -2854,227 +2700,10 @@
         return false;
     }
 
-    /**
-     * This method draws the FolderIcon to an ImageView and then adds and positions that ImageView
-     * in the DragLayer in the exact absolute location of the original FolderIcon.
-     */
-    private void copyFolderIconToImage(FolderIcon fi) {
-        final int width = fi.getMeasuredWidth();
-        final int height = fi.getMeasuredHeight();
-
-        // Lazy load ImageView, Bitmap and Canvas
-        if (mFolderIconImageView == null) {
-            mFolderIconImageView = new ImageView(this);
-        }
-        if (mFolderIconBitmap == null || mFolderIconBitmap.getWidth() != width ||
-                mFolderIconBitmap.getHeight() != height) {
-            mFolderIconBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
-            mFolderIconCanvas = new Canvas(mFolderIconBitmap);
-        }
-
-        DragLayer.LayoutParams lp;
-        if (mFolderIconImageView.getLayoutParams() instanceof DragLayer.LayoutParams) {
-            lp = (DragLayer.LayoutParams) mFolderIconImageView.getLayoutParams();
-        } else {
-            lp = new DragLayer.LayoutParams(width, height);
-        }
-
-        // The layout from which the folder is being opened may be scaled, adjust the starting
-        // view size by this scale factor.
-        float scale = mDragLayer.getDescendantRectRelativeToSelf(fi, mRectForFolderAnimation);
-        lp.customPosition = true;
-        lp.x = mRectForFolderAnimation.left;
-        lp.y = mRectForFolderAnimation.top;
-        lp.width = (int) (scale * width);
-        lp.height = (int) (scale * height);
-
-        mFolderIconCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
-        fi.draw(mFolderIconCanvas);
-        mFolderIconImageView.setImageBitmap(mFolderIconBitmap);
-        if (fi.getFolder() != null) {
-            mFolderIconImageView.setPivotX(fi.getFolder().getPivotXForIconAnimation());
-            mFolderIconImageView.setPivotY(fi.getFolder().getPivotYForIconAnimation());
-        }
-        // Just in case this image view is still in the drag layer from a previous animation,
-        // we remove it and re-add it.
-        if (mDragLayer.indexOfChild(mFolderIconImageView) != -1) {
-            mDragLayer.removeView(mFolderIconImageView);
-        }
-        mDragLayer.addView(mFolderIconImageView, lp);
-        if (fi.getFolder() != null) {
-            fi.getFolder().bringToFront();
-        }
-    }
-
-    private void growAndFadeOutFolderIcon(FolderIcon fi) {
-        if (fi == null) return;
-        FolderInfo info = (FolderInfo) fi.getTag();
-        if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
-            CellLayout cl = (CellLayout) fi.getParent().getParent();
-            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) fi.getLayoutParams();
-            cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY);
-        }
-
-        // Push an ImageView copy of the FolderIcon into the DragLayer and hide the original
-        copyFolderIconToImage(fi);
-        fi.setVisibility(View.INVISIBLE);
-
-        ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(
-                mFolderIconImageView, 0, 1.5f, 1.5f);
-        if (Utilities.ATLEAST_LOLLIPOP) {
-            oa.setInterpolator(new LogDecelerateInterpolator(100, 0));
-        }
-        oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
-        oa.start();
-    }
-
-    private void shrinkAndFadeInFolderIcon(final FolderIcon fi, boolean animate) {
-        if (fi == null) return;
-        final CellLayout cl = (CellLayout) fi.getParent().getParent();
-
-        // We remove and re-draw the FolderIcon in-case it has changed
-        mDragLayer.removeView(mFolderIconImageView);
-        copyFolderIconToImage(fi);
-
-        if (cl != null) {
-            cl.clearFolderLeaveBehind();
-        }
-
-        ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(mFolderIconImageView, 1, 1, 1);
-        oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
-        oa.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                if (cl != null) {
-                    // Remove the ImageView copy of the FolderIcon and make the original visible.
-                    mDragLayer.removeView(mFolderIconImageView);
-                    fi.setVisibility(View.VISIBLE);
-                }
-            }
-        });
-        oa.start();
-        if (!animate) {
-            oa.end();
-        }
-    }
-
-    /**
-     * Opens the user folder described by the specified tag. The opening of the folder
-     * is animated relative to the specified View. If the View is null, no animation
-     * is played.
-     *
-     * @param folderIcon The FolderIcon describing the folder to open.
-     */
-    public void openFolder(FolderIcon folderIcon) {
-
-        Folder folder = folderIcon.getFolder();
-        Folder openFolder = mWorkspace != null ? mWorkspace.getOpenFolder() : null;
-        if (openFolder != null && openFolder != folder) {
-            // Close any open folder before opening a folder.
-            closeFolder();
-        }
-
-        FolderInfo info = folder.mInfo;
-
-        info.opened = true;
-
-        // While the folder is open, the position of the icon cannot change.
-        ((CellLayout.LayoutParams) folderIcon.getLayoutParams()).canReorder = false;
-
-        // Just verify that the folder hasn't already been added to the DragLayer.
-        // There was a one-off crash where the folder had a parent already.
-        if (folder.getParent() == null) {
-            mDragLayer.addView(folder);
-            mDragController.addDropTarget(folder);
-        } else {
-            Log.w(TAG, "Opening folder (" + folder + ") which already has a parent (" +
-                    folder.getParent() + ").");
-        }
-        folder.animateOpen();
-
-        growAndFadeOutFolderIcon(folderIcon);
-
-        // Notify the accessibility manager that this folder "window" has appeared and occluded
-        // the workspace items
-        folder.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
-        getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
-    }
-
-    public void closeFolder() {
-        closeFolder(true);
-    }
-
-    public void closeFolder(boolean animate) {
-        Folder folder = mWorkspace != null ? mWorkspace.getOpenFolder() : null;
-        if (folder != null) {
-            if (folder.isEditingName()) {
-                folder.dismissEditingName();
-            }
-            closeFolder(folder, animate);
-        }
-    }
-
-    public void closeFolder(Folder folder, boolean animate) {
-        animate &= !Utilities.isPowerSaverOn(this);
-
-        folder.getInfo().opened = false;
-
-        ViewGroup parent = (ViewGroup) folder.getParent().getParent();
-        if (parent != null) {
-            FolderIcon fi = (FolderIcon) mWorkspace.getViewForTag(folder.mInfo);
-            shrinkAndFadeInFolderIcon(fi, animate);
-            if (fi != null) {
-                ((CellLayout.LayoutParams) fi.getLayoutParams()).canReorder = true;
-            }
-        }
-        if (animate) {
-            folder.animateClosed();
-        } else {
-            folder.close(false);
-        }
-
-        // Notify the accessibility manager that this folder "window" has disappeared and no
-        // longer occludes the workspace items
-        getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
-    }
-
-    public void closeShortcutsContainer() {
-        closeShortcutsContainer(true);
-    }
-
-    public void closeShortcutsContainer(boolean animate) {
-        DeepShortcutsContainer deepShortcutsContainer = getOpenShortcutsContainer();
-        if (deepShortcutsContainer != null) {
-            if (animate) {
-                deepShortcutsContainer.animateClose();
-            } else {
-                deepShortcutsContainer.close();
-            }
-        }
-    }
-
-    public View getTopFloatingView() {
-        View topView = getOpenShortcutsContainer();
-        if (topView == null) {
-            topView = getWorkspace().getOpenFolder();
-        }
-        return topView;
-    }
-
-    /**
-     * @return The open shortcuts container, or null if there is none
-     */
-    public DeepShortcutsContainer getOpenShortcutsContainer() {
-        // Iterate in reverse order. Shortcuts container is added later to the dragLayer,
-        // and will be one of the last views.
-        for (int i = mDragLayer.getChildCount() - 1; i >= 0; i--) {
-            View child = mDragLayer.getChildAt(i);
-            if (child instanceof DeepShortcutsContainer
-                    && ((DeepShortcutsContainer) child).isOpen()) {
-                return (DeepShortcutsContainer) child;
-            }
-        }
-        return null;
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        mLastDispatchTouchEventX = ev.getX();
+        return super.dispatchTouchEvent(ev);
     }
 
     @Override
@@ -3089,9 +2718,16 @@
             return true;
         }
 
+
+        boolean ignoreLongPressToOverview =
+                mDeviceProfile.shouldIgnoreLongPressToOverview(mLastDispatchTouchEventX);
+
         if (v instanceof Workspace) {
             if (!mWorkspace.isInOverviewMode()) {
-                if (!mWorkspace.isTouchActive()) {
+                if (!mWorkspace.isTouchActive() && !ignoreLongPressToOverview) {
+                    getUserEventDispatcher().logActionOnContainer(LauncherLogProto.Action.LONGPRESS,
+                            LauncherLogProto.Action.NONE, LauncherLogProto.WORKSPACE,
+                            mWorkspace.getCurrentPage());
                     showOverviewMode(true);
                     mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
                             HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
@@ -3118,13 +2754,21 @@
         if (!mDragController.isDragging()) {
             if (itemUnderLongClick == null) {
                 // User long pressed on empty space
-                mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
-                        HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
                 if (mWorkspace.isInOverviewMode()) {
                     mWorkspace.startReordering(v);
+                    getUserEventDispatcher().logActionOnContainer(LauncherLogProto.Action.LONGPRESS,
+                            LauncherLogProto.Action.NONE, LauncherLogProto.OVERVIEW);
                 } else {
+                    if (ignoreLongPressToOverview) {
+                        return false;
+                    }
+                    getUserEventDispatcher().logActionOnContainer(LauncherLogProto.Action.LONGPRESS,
+                            LauncherLogProto.Action.NONE, LauncherLogProto.WORKSPACE,
+                            mWorkspace.getCurrentPage());
                     showOverviewMode(true);
                 }
+                mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
+                        HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
             } else {
                 final boolean isAllAppsButton =
                         !FeatureFlags.NO_ALL_APPS_ICON && isHotseatLayout(v) &&
@@ -3132,17 +2776,7 @@
                                         longClickCellInfo.cellX, longClickCellInfo.cellY));
                 if (!(itemUnderLongClick instanceof Folder || isAllAppsButton)) {
                     // User long pressed on an item
-                    DragOptions dragOptions = new DragOptions();
-                    if (itemUnderLongClick instanceof BubbleTextView) {
-                        BubbleTextView icon = (BubbleTextView) itemUnderLongClick;
-                        if (icon.hasDeepShortcuts()) {
-                            DeepShortcutsContainer dsc = DeepShortcutsContainer.showForIcon(icon);
-                            if (dsc != null) {
-                                dragOptions.deferDragCondition = dsc.createDeferDragCondition(null);
-                            }
-                        }
-                    }
-                    mWorkspace.startDrag(longClickCellInfo, dragOptions);
+                    mWorkspace.startDrag(longClickCellInfo, new DragOptions());
                 }
             }
         }
@@ -3221,10 +2855,6 @@
         // Change the state *after* we've called all the transition code
         mState = State.WORKSPACE;
 
-        // Resume the auto-advance of widgets
-        mUserPresent = true;
-        updateAutoAdvanceState();
-
         if (changed) {
             // Send an accessibility event to announce the context change
             getWindow().getDecorView()
@@ -3321,20 +2951,14 @@
         }
 
         if (toState == State.APPS) {
-            mStateTransitionAnimation.startAnimationToAllApps(mWorkspace.getState(), animated,
-                    focusSearchBar);
+            mStateTransitionAnimation.startAnimationToAllApps(animated, focusSearchBar);
         } else {
-            mStateTransitionAnimation.startAnimationToWidgets(mWorkspace.getState(), animated);
+            mStateTransitionAnimation.startAnimationToWidgets(animated);
         }
 
         // Change the state *after* we've called all the transition code
         mState = toState;
-
-        // Pause the auto-advance of widgets until we are out of AllApps
-        mUserPresent = false;
-        updateAutoAdvanceState();
-        closeFolder();
-        closeShortcutsContainer();
+        AbstractFloatingView.closeAllOpenViews(this);
 
         // Send an accessibility event to announce the context change
         getWindow().getDecorView()
@@ -3347,7 +2971,7 @@
      * new state.
      */
     public Animator startWorkspaceStateChangeAnimation(Workspace.State toState,
-            boolean animated, HashMap<View, Integer> layerViews) {
+            boolean animated, AnimationLayerSet layerViews) {
         Workspace.State fromState = mWorkspace.getState();
         Animator anim = mWorkspace.setStateWithAnimation(toState, animated, layerViews);
         updateInteraction(fromState, toState);
@@ -3558,7 +3182,6 @@
         mWorkspace.clearDropTargets();
         mWorkspace.removeAllWorkspaceScreens();
 
-        mWidgetsToAdvance.clear();
         if (mHotseat != null) {
             mHotseat.resetLayout();
         }
@@ -3806,7 +3429,7 @@
                 if (DEBUG_WIDGETS) {
                     Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
                             + " belongs to component " + item.providerName
-                            + ", as the povider is null");
+                            + ", as the provider is null");
                 }
                 LauncherModel.deleteItemFromDatabase(this, item);
                 return;
@@ -3981,14 +3604,6 @@
         if (LauncherAppState.PROFILE_STARTUP) {
             Trace.beginSection("Page bind completed");
         }
-        if (mSavedState != null) {
-            if (!mWorkspace.hasFocus()) {
-                mWorkspace.getChildAt(mWorkspace.getCurrentPage()).requestFocus();
-            }
-
-            mSavedState = null;
-        }
-
         mWorkspace.restoreInstanceStateForRemainingPages();
 
         setWorkspaceLoading(false);
@@ -4239,22 +3854,22 @@
         }
     }
 
-    private Runnable mBindWidgetModelRunnable = new Runnable() {
+    private Runnable mBindAllWidgetsRunnable = new Runnable() {
             public void run() {
-                bindWidgetsModel(mWidgetsModel);
+                bindAllWidgets(mAllWidgets);
             }
         };
 
     @Override
-    public void bindWidgetsModel(WidgetsModel model) {
-        if (waitUntilResume(mBindWidgetModelRunnable, true)) {
-            mWidgetsModel = model;
+    public void bindAllWidgets(MultiHashMap<PackageItemInfo, WidgetItem> allWidgets) {
+        if (waitUntilResume(mBindAllWidgetsRunnable, true)) {
+            mAllWidgets = allWidgets;
             return;
         }
 
-        if (mWidgetsView != null && model != null) {
-            mWidgetsView.addWidgets(model);
-            mWidgetsModel = null;
+        if (mWidgetsView != null && allWidgets != null) {
+            mWidgetsView.setWidgets(allWidgets);
+            mAllWidgets = null;
         }
     }
 
@@ -4410,7 +4025,6 @@
      */
     public void dumpState() {
         Log.d(TAG, "BEGIN launcher3 dump state for launcher " + this);
-        Log.d(TAG, "mSavedState=" + mSavedState);
         Log.d(TAG, "mWorkspaceLoading=" + mWorkspaceLoading);
         Log.d(TAG, "mPendingRequestArgs=" + mPendingRequestArgs);
         Log.d(TAG, "mPendingActivityResult=" + mPendingActivityResult);
@@ -4457,6 +4071,65 @@
         }
     }
 
+    @Override
+    @TargetApi(Build.VERSION_CODES.N)
+    public void onProvideKeyboardShortcuts(
+            List<KeyboardShortcutGroup> data, Menu menu, int deviceId) {
+
+        ArrayList<KeyboardShortcutInfo> shortcutInfos = new ArrayList<>();
+        if (mState == State.WORKSPACE) {
+            shortcutInfos.add(new KeyboardShortcutInfo(getString(R.string.all_apps_button_label),
+                    KeyEvent.KEYCODE_A, KeyEvent.META_CTRL_ON));
+        }
+        View currentFocus = getCurrentFocus();
+        if (new CustomActionsPopup(this, currentFocus).canShow()) {
+            shortcutInfos.add(new KeyboardShortcutInfo(getString(R.string.custom_actions),
+                    KeyEvent.KEYCODE_O, KeyEvent.META_CTRL_ON));
+        }
+        if (currentFocus instanceof BubbleTextView &&
+                ((BubbleTextView) currentFocus).hasDeepShortcuts()) {
+            shortcutInfos.add(new KeyboardShortcutInfo(getString(R.string.action_deep_shortcut),
+                    KeyEvent.KEYCODE_S, KeyEvent.META_CTRL_ON));
+        }
+        if (!shortcutInfos.isEmpty()) {
+            data.add(new KeyboardShortcutGroup(getString(R.string.home_screen), shortcutInfos));
+        }
+
+        super.onProvideKeyboardShortcuts(data, menu, deviceId);
+    }
+
+    @Override
+    public boolean onKeyShortcut(int keyCode, KeyEvent event) {
+        if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
+            switch (keyCode) {
+                case KeyEvent.KEYCODE_A:
+                    if (mState == State.WORKSPACE) {
+                        showAppsView(true, true, false);
+                        return true;
+                    }
+                    break;
+                case KeyEvent.KEYCODE_S: {
+                    View focusedView = getCurrentFocus();
+                    if (focusedView instanceof BubbleTextView
+                            && focusedView.getTag() instanceof ItemInfo
+                            && mAccessibilityDelegate.performAction(focusedView,
+                                    (ItemInfo) focusedView.getTag(),
+                                    LauncherAccessibilityDelegate.DEEP_SHORTCUTS)) {
+                        DeepShortcutsContainer.getOpen(this).requestFocus();
+                        return true;
+                    }
+                    break;
+                }
+                case KeyEvent.KEYCODE_O:
+                    if (new CustomActionsPopup(this, getCurrentFocus()).show()) {
+                        return true;
+                    }
+                    break;
+            }
+        }
+        return super.onKeyShortcut(keyCode, event);
+    }
+
     public static CustomAppWidget getCustomAppWidget(String name) {
         return sCustomAppWidgets.get(name);
     }
@@ -4465,14 +4138,6 @@
         return sCustomAppWidgets;
     }
 
-    public static List<View> getFolderContents(View icon) {
-        if (icon instanceof FolderIcon) {
-            return ((FolderIcon) icon).getFolder().getItemsInReadingOrder();
-        } else {
-            return Collections.EMPTY_LIST;
-        }
-    }
-
     public static Launcher getLauncher(Context context) {
         if (context instanceof Launcher) {
             return (Launcher) context;
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 7861a10..5937d78 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -28,8 +28,6 @@
 import com.android.launcher3.config.ProviderConfig;
 import com.android.launcher3.dynamicui.ExtractionUtils;
 import com.android.launcher3.logging.FileLog;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
-import com.android.launcher3.shortcuts.ShortcutCache;
 import com.android.launcher3.util.ConfigMonitor;
 import com.android.launcher3.util.TestingUtils;
 import com.android.launcher3.util.Thunk;
@@ -44,7 +42,6 @@
     @Thunk final LauncherModel mModel;
     private final IconCache mIconCache;
     private final WidgetPreviewLoader mWidgetCache;
-    private final DeepShortcutManager mDeepShortcutManager;
 
     @Thunk boolean mWallpaperChangedSinceLastCheck;
 
@@ -86,10 +83,10 @@
 
     private LauncherAppState() {
         if (sContext == null) {
-            throw new IllegalStateException("LauncherAppState inited before app context set");
+            throw new IllegalStateException("LauncherAppState initiated before app context set");
         }
 
-        Log.v(Launcher.TAG, "LauncherAppState inited");
+        Log.v(Launcher.TAG, "LauncherAppState initiated");
 
         if (TestingUtils.MEMORY_DUMP_ENABLED) {
             TestingUtils.startTrackingMemory(sContext);
@@ -98,10 +95,9 @@
         mInvariantDeviceProfile = new InvariantDeviceProfile(sContext);
         mIconCache = new IconCache(sContext, mInvariantDeviceProfile);
         mWidgetCache = new WidgetPreviewLoader(sContext, mIconCache);
-        mDeepShortcutManager = new DeepShortcutManager(sContext, new ShortcutCache());
 
         mAppFilter = AppFilter.loadByName(sContext.getString(R.string.app_filter_class));
-        mModel = new LauncherModel(this, mIconCache, mAppFilter, mDeepShortcutManager);
+        mModel = new LauncherModel(this, mIconCache, mAppFilter);
 
         LauncherAppsCompat.getInstance(sContext).addOnAppsChangedCallback(mModel);
 
@@ -115,7 +111,7 @@
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED);
         // For extracting colors from the wallpaper
-        if (Utilities.isNycOrAbove()) {
+        if (Utilities.ATLEAST_NOUGAT) {
             // TODO: add a broadcast entry to the manifest for pre-N.
             filter.addAction(Intent.ACTION_WALLPAPER_CHANGED);
         }
@@ -173,10 +169,6 @@
         return mWidgetCache;
     }
 
-    public DeepShortcutManager getShortcutManager() {
-        return mDeepShortcutManager;
-    }
-
     public boolean hasWallpaperChangedSinceLastCheck() {
         boolean result = mWallpaperChangedSinceLastCheck;
         mWallpaperChangedSinceLastCheck = false;
diff --git a/src/com/android/launcher3/LauncherAppWidgetHost.java b/src/com/android/launcher3/LauncherAppWidgetHost.java
index d3e5350..657b024 100644
--- a/src/com/android/launcher3/LauncherAppWidgetHost.java
+++ b/src/com/android/launcher3/LauncherAppWidgetHost.java
@@ -20,10 +20,8 @@
 import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
-import android.os.DeadObjectException;
-import android.os.TransactionTooLargeException;
+import android.util.SparseArray;
 import android.view.LayoutInflater;
-import android.view.View;
 
 import java.util.ArrayList;
 
@@ -36,6 +34,7 @@
 public class LauncherAppWidgetHost extends AppWidgetHost {
 
     private final ArrayList<Runnable> mProviderChangeListeners = new ArrayList<Runnable>();
+    private final SparseArray<LauncherAppWidgetHostView> mViews = new SparseArray<>();
 
     private Launcher mLauncher;
 
@@ -45,9 +44,11 @@
     }
 
     @Override
-    protected AppWidgetHostView onCreateView(Context context, int appWidgetId,
+    protected LauncherAppWidgetHostView onCreateView(Context context, int appWidgetId,
             AppWidgetProviderInfo appWidget) {
-        return new LauncherAppWidgetHostView(context);
+        LauncherAppWidgetHostView view = new LauncherAppWidgetHostView(context);
+        mViews.put(appWidgetId, view);
+        return view;
     }
 
     @Override
@@ -55,15 +56,13 @@
         try {
             super.startListening();
         } catch (Exception e) {
-            if (e.getCause() instanceof TransactionTooLargeException ||
-                    e.getCause() instanceof DeadObjectException) {
-                // We're willing to let this slide. The exception is being caused by the list of
-                // RemoteViews which is being passed back. The startListening relationship will
-                // have been established by this point, and we will end up populating the
-                // widgets upon bind anyway. See issue 14255011 for more context.
-            } else {
+            if (!Utilities.isBinderSizeError(e)) {
                 throw new RuntimeException(e);
             }
+            // We're willing to let this slide. The exception is being caused by the list of
+            // RemoteViews which is being passed back. The startListening relationship will
+            // have been established by this point, and we will end up populating the
+            // widgets upon bind anyway. See issue 14255011 for more context.
         }
     }
 
@@ -98,7 +97,24 @@
             lahv.updateLastInflationOrientation();
             return lahv;
         } else {
-            return super.createView(context, appWidgetId, appWidget);
+            try {
+                return super.createView(context, appWidgetId, appWidget);
+            } catch (Exception e) {
+                if (!Utilities.isBinderSizeError(e)) {
+                    throw new RuntimeException(e);
+                }
+
+                // If the exception was thrown while fetching the remote views, let the view stay.
+                // This will ensure that if the widget posts a valid update later, the view
+                // will update.
+                LauncherAppWidgetHostView view = mViews.get(appWidgetId);
+                if (view == null) {
+                    view = onCreateView(mLauncher, appWidgetId, appWidget);
+                }
+                view.setAppWidget(appWidgetId, appWidget);
+                view.switchToErrorView();
+                return  view;
+            }
         }
     }
 
@@ -114,4 +130,16 @@
         // launcher spans accordingly.
         info.initSpans();
     }
+
+    @Override
+    public void deleteAppWidgetId(int appWidgetId) {
+        super.deleteAppWidgetId(appWidgetId);
+        mViews.remove(appWidgetId);
+    }
+
+    @Override
+    protected void clearViews() {
+        super.clearViews();
+        mViews.clear();
+    }
 }
diff --git a/src/com/android/launcher3/LauncherAppWidgetHostView.java b/src/com/android/launcher3/LauncherAppWidgetHostView.java
index ed1079f..a4ea449 100644
--- a/src/com/android/launcher3/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/LauncherAppWidgetHostView.java
@@ -20,6 +20,10 @@
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
 import android.graphics.Rect;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.SparseBooleanArray;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
@@ -28,17 +32,29 @@
 import android.view.ViewDebug;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.Advanceable;
 import android.widget.RemoteViews;
 
 import com.android.launcher3.dragndrop.DragLayer.TouchCompleteListener;
 
+import java.lang.reflect.Method;
 import java.util.ArrayList;
+import java.util.concurrent.Executor;
 
 /**
  * {@inheritDoc}
  */
 public class LauncherAppWidgetHostView extends AppWidgetHostView implements TouchCompleteListener {
 
+    private static final String TAG = "LauncherWidgetHostView";
+
+    // Related to the auto-advancing of widgets
+    private static final long ADVANCE_INTERVAL = 20000;
+    private static final long ADVANCE_STAGGER = 250;
+
+    // Maintains a list of widget ids which are supposed to be auto advanced.
+    private static final SparseBooleanArray sAutoAdvanceWidgetIds = new SparseBooleanArray();
+
     LayoutInflater mInflater;
 
     private CheckLongPressHelper mLongPressHelper;
@@ -52,21 +68,33 @@
     @ViewDebug.ExportedProperty(category = "launcher")
     private boolean mChildrenFocused;
 
-    protected int mErrorViewId = R.layout.appwidget_error;
+    private boolean mIsAttachedToWindow;
+    private boolean mIsAutoAdvanceRegistered;
+    private Runnable mAutoAdvanceRunnable;
 
     public LauncherAppWidgetHostView(Context context) {
         super(context);
         mContext = context;
         mLongPressHelper = new CheckLongPressHelper(this);
         mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
-        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        mInflater = LayoutInflater.from(context);
         setAccessibilityDelegate(Launcher.getLauncher(context).getAccessibilityDelegate());
         setBackgroundResource(R.drawable.widget_internal_focus_bg);
+
+        if (Utilities.isAtLeastO()) {
+            try {
+                Method asyncMethod = AppWidgetHostView.class
+                        .getMethod("setAsyncExecutor", Executor.class);
+                asyncMethod.invoke(this, Utilities.THREAD_POOL_EXECUTOR);
+            } catch (Exception e) {
+                Log.e(TAG, "Unable to set async executor", e);
+            }
+        }
     }
 
     @Override
     protected View getErrorView() {
-        return mInflater.inflate(mErrorViewId, this, false);
+        return mInflater.inflate(R.layout.appwidget_error, this, false);
     }
 
     public void updateLastInflationOrientation() {
@@ -78,6 +106,9 @@
         // Store the orientation in which the widget was inflated
         updateLastInflationOrientation();
         super.updateAppWidget(remoteViews);
+
+        // The provider info or the views might have changed.
+        checkIfAutoAdvance();
     }
 
     public boolean isReinflateRequired() {
@@ -153,6 +184,19 @@
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+
+        mIsAttachedToWindow = true;
+        checkIfAutoAdvance();
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+
+        // We can't directly use isAttachedToWindow() here, as this is called before the internal
+        // state is updated. So isAttachedToWindow() will return true until next frame.
+        mIsAttachedToWindow = false;
+        checkIfAutoAdvance();
     }
 
     @Override
@@ -171,10 +215,6 @@
         return info;
     }
 
-    public LauncherAppWidgetProviderInfo getLauncherAppWidgetProviderInfo() {
-        return (LauncherAppWidgetProviderInfo) getAppWidgetInfo();
-    }
-
     @Override
     public void onTouchComplete() {
         if (!mLongPressHelper.hasPerformedLongPress()) {
@@ -276,6 +316,11 @@
         setSelected(childIsFocused);
     }
 
+    public void switchToErrorView() {
+        // Update the widget with 0 Layout id, to reset the view to error view.
+        updateAppWidget(new RemoteViews(getAppWidgetInfo().provider.getPackageName(), 0));
+    }
+
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         try {
@@ -284,8 +329,7 @@
             post(new Runnable() {
                 @Override
                 public void run() {
-                    // Update the widget with 0 Layout id, to reset the view to error view.
-                    updateAppWidget(new RemoteViews(getAppWidgetInfo().provider.getPackageName(), 0));
+                    switchToErrorView();
                 }
             });
         }
@@ -296,4 +340,79 @@
         super.onInitializeAccessibilityNodeInfo(info);
         info.setClassName(getClass().getName());
     }
+
+    @Override
+    protected void onWindowVisibilityChanged(int visibility) {
+        super.onWindowVisibilityChanged(visibility);
+        maybeRegisterAutoAdvance();
+    }
+
+    private void checkIfAutoAdvance() {
+        boolean isAutoAdvance = false;
+        Advanceable target = getAdvanceable();
+        if (target != null) {
+            isAutoAdvance = true;
+            target.fyiWillBeAdvancedByHostKThx();
+        }
+
+        boolean wasAutoAdvance = sAutoAdvanceWidgetIds.indexOfKey(getAppWidgetId()) >= 0;
+        if (isAutoAdvance != wasAutoAdvance) {
+            if (isAutoAdvance) {
+                sAutoAdvanceWidgetIds.put(getAppWidgetId(), true);
+            } else {
+                sAutoAdvanceWidgetIds.delete(getAppWidgetId());
+            }
+            maybeRegisterAutoAdvance();
+        }
+    }
+
+    private Advanceable getAdvanceable() {
+        AppWidgetProviderInfo info = getAppWidgetInfo();
+        if (info == null || info.autoAdvanceViewId == NO_ID || !mIsAttachedToWindow) {
+            return null;
+        }
+        View v = findViewById(info.autoAdvanceViewId);
+        return (v instanceof Advanceable) ? (Advanceable) v : null;
+    }
+
+    private void maybeRegisterAutoAdvance() {
+        Handler handler = getHandler();
+        boolean shouldRegisterAutoAdvance = getWindowVisibility() == VISIBLE && handler != null
+                && (sAutoAdvanceWidgetIds.indexOfKey(getAppWidgetId()) >= 0);
+        if (shouldRegisterAutoAdvance != mIsAutoAdvanceRegistered) {
+            mIsAutoAdvanceRegistered = shouldRegisterAutoAdvance;
+            if (mAutoAdvanceRunnable == null) {
+                mAutoAdvanceRunnable = new Runnable() {
+                    @Override
+                    public void run() {
+                        runAutoAdvance();
+                    }
+                };
+            }
+
+            handler.removeCallbacks(mAutoAdvanceRunnable);
+            scheduleNextAdvance();
+        }
+    }
+
+    private void scheduleNextAdvance() {
+        if (!mIsAutoAdvanceRegistered) {
+            return;
+        }
+        long now = SystemClock.uptimeMillis();
+        long advanceTime = now + (ADVANCE_INTERVAL - (now % ADVANCE_INTERVAL)) +
+                ADVANCE_STAGGER * sAutoAdvanceWidgetIds.indexOfKey(getAppWidgetId());
+        Handler handler = getHandler();
+        if (handler != null) {
+            handler.postAtTime(mAutoAdvanceRunnable, advanceTime);
+        }
+    }
+
+    private void runAutoAdvance() {
+        Advanceable target = getAdvanceable();
+        if (target != null) {
+            target.advance();
+        }
+        scheduleNextAdvance();
+    }
 }
diff --git a/src/com/android/launcher3/LauncherAppWidgetInfo.java b/src/com/android/launcher3/LauncherAppWidgetInfo.java
index 66d8957..2218767 100644
--- a/src/com/android/launcher3/LauncherAppWidgetInfo.java
+++ b/src/com/android/launcher3/LauncherAppWidgetInfo.java
@@ -18,11 +18,10 @@
 
 import android.appwidget.AppWidgetHostView;
 import android.content.ComponentName;
-import android.content.ContentValues;
-import android.content.Context;
 import android.content.Intent;
 
 import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.util.ContentWriter;
 
 /**
  * Represents a widget (either instantiated or about to be) in the Launcher.
@@ -84,12 +83,12 @@
     /**
      * Indicates the restore status of the widget.
      */
-    int restoreStatus;
+    public int restoreStatus;
 
     /**
      * Indicates the installation progress of the widget provider
      */
-    int installProgress = -1;
+    public int installProgress = -1;
 
     /**
      * Optional extras sent during widget bind. See {@link #FLAG_DIRECT_CONFIG}.
@@ -98,7 +97,7 @@
 
     private boolean mHasNotifiedInitialWidgetSizeChanged;
 
-    LauncherAppWidgetInfo(int appWidgetId, ComponentName providerName) {
+    public LauncherAppWidgetInfo(int appWidgetId, ComponentName providerName) {
         if (appWidgetId == CUSTOM_WIDGET_ID) {
             itemType = LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
         } else {
@@ -117,18 +116,22 @@
         restoreStatus = RESTORE_COMPLETED;
     }
 
+    /** Used for testing **/
+    public LauncherAppWidgetInfo() {
+        itemType = LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
+    }
+
     public boolean isCustomWidget() {
         return appWidgetId == CUSTOM_WIDGET_ID;
     }
 
     @Override
-    void onAddToDatabase(Context context, ContentValues values) {
-        super.onAddToDatabase(context, values);
-        values.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId);
-        values.put(LauncherSettings.Favorites.APPWIDGET_PROVIDER, providerName.flattenToString());
-        values.put(LauncherSettings.Favorites.RESTORED, restoreStatus);
-        values.put(LauncherSettings.Favorites.INTENT,
-                bindOptions == null ? null : bindOptions.toUri(0));
+    void onAddToDatabase(ContentWriter writer) {
+        super.onAddToDatabase(writer);
+        writer.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId)
+                .put(LauncherSettings.Favorites.APPWIDGET_PROVIDER, providerName.flattenToString())
+                .put(LauncherSettings.Favorites.RESTORED, restoreStatus)
+                .put(LauncherSettings.Favorites.INTENT, bindOptions);
     }
 
     /**
diff --git a/src/com/android/launcher3/LauncherClings.java b/src/com/android/launcher3/LauncherClings.java
deleted file mode 100644
index c1282b5..0000000
--- a/src/com/android/launcher3/LauncherClings.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2008 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;
-
-import android.content.Context;
-
-@Deprecated
-public class LauncherClings {
-    private static final String WORKSPACE_CLING_DISMISSED_KEY = "cling_gel.workspace.dismissed";
-
-    public static void markFirstRunClingDismissed(Context ctx) {
-        Utilities.getPrefs(ctx).edit()
-                .putBoolean(WORKSPACE_CLING_DISMISSED_KEY, true)
-                .apply();
-    }
-}
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 3ac9773..cfb28f9 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -27,7 +27,6 @@
 import android.content.Intent.ShortcutIconResource;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.net.Uri;
@@ -43,7 +42,6 @@
 import android.util.Log;
 import android.util.LongSparseArray;
 import android.util.MutableInt;
-import android.util.Pair;
 
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.LauncherActivityInfoCompat;
@@ -57,8 +55,20 @@
 import com.android.launcher3.dynamicui.ExtractionUtils;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.graphics.LauncherIcons;
 import com.android.launcher3.logging.FileLog;
+import com.android.launcher3.model.AddWorkspaceItemsTask;
+import com.android.launcher3.model.ExtendedModelTask;
+import com.android.launcher3.model.BgDataModel;
+import com.android.launcher3.model.CacheDataUpdatedTask;
 import com.android.launcher3.model.GridSizeMigrationTask;
+import com.android.launcher3.model.PackageItemInfo;
+import com.android.launcher3.model.SdCardAvailableReceiver;
+import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.model.PackageInstallStateChangedTask;
+import com.android.launcher3.model.PackageUpdatedTask;
+import com.android.launcher3.model.ShortcutsChangedTask;
+import com.android.launcher3.model.UserLockStateChangedTask;
 import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.provider.ImportDataTask;
 import com.android.launcher3.provider.LauncherDbUtils;
@@ -66,15 +76,15 @@
 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.ContentWriter;
 import com.android.launcher3.util.CursorIconInfo;
-import com.android.launcher3.util.FlagOp;
 import com.android.launcher3.util.GridOccupancy;
+import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.LongArrayMap;
 import com.android.launcher3.util.ManagedProfileHeuristic;
 import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.Preconditions;
-import com.android.launcher3.util.StringFilter;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.ViewOnDrawExecutor;
 
@@ -82,7 +92,6 @@
 import java.net.URISyntaxException;
 import java.security.InvalidParameterException;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
@@ -90,7 +99,6 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
 import java.util.Set;
 import java.util.concurrent.Executor;
 
@@ -142,16 +150,14 @@
     // Entire list of widgets.
     private final WidgetsModel mBgWidgetsModel;
 
-    // Maps all launcher activities to the id's of their shortcuts (if they have any).
-    private final MultiHashMap<ComponentKey, String> mBgDeepShortcutMap = new MultiHashMap<>();
-
     private boolean mHasShortcutHostPermission;
     // Runnable to check if the shortcuts permission has changed.
     private final Runnable mShortcutPermissionCheckRunnable = new Runnable() {
         @Override
         public void run() {
             if (mDeepShortcutsLoaded) {
-                boolean hasShortcutHostPermission = mDeepShortcutManager.hasHostPermission();
+                boolean hasShortcutHostPermission =
+                        DeepShortcutManager.getInstance(mApp.getContext()).hasHostPermission();
                 if (hasShortcutHostPermission != mHasShortcutHostPermission) {
                     mApp.reloadWorkspace();
                 }
@@ -159,43 +165,15 @@
         }
     };
 
-    // The lock that must be acquired before referencing any static bg data structures.  Unlike
-    // other locks, this one can generally be held long-term because we never expect any of these
-    // static data structures to be referenced outside of the worker thread except on the first
-    // load after configuration change.
-    static final Object sBgLock = new Object();
-
-    // sBgItemsIdMap maps *all* the ItemInfos (shortcuts, folders, and widgets) created by
-    // LauncherModel to their ids
-    static final LongArrayMap<ItemInfo> sBgItemsIdMap = new LongArrayMap<>();
-
-    // sBgWorkspaceItems is passed to bindItems, which expects a list of all folders and shortcuts
-    //       created by LauncherModel that are directly on the home screen (however, no widgets or
-    //       shortcuts within folders).
-    static final ArrayList<ItemInfo> sBgWorkspaceItems = new ArrayList<ItemInfo>();
-
-    // sBgAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget()
-    static final ArrayList<LauncherAppWidgetInfo> sBgAppWidgets =
-        new ArrayList<LauncherAppWidgetInfo>();
-
-    // sBgFolders is all FolderInfos created by LauncherModel. Passed to bindFolders()
-    static final LongArrayMap<FolderInfo> sBgFolders = new LongArrayMap<>();
-
-    // sBgWorkspaceScreens is the ordered set of workspace screens.
-    static final ArrayList<Long> sBgWorkspaceScreens = new ArrayList<Long>();
-
-    // sBgPinnedShortcutCounts is the ComponentKey representing a pinned shortcut to the number of
-    // times it is pinned.
-    static final Map<ShortcutKey, MutableInt> sBgPinnedShortcutCounts = new HashMap<>();
-
-    // sPendingPackages is a set of packages which could be on sdcard and are not available yet
-    static final HashMap<UserHandleCompat, HashSet<String>> sPendingPackages =
-            new HashMap<UserHandleCompat, HashSet<String>>();
+    /**
+     * All the static data should be accessed on the background thread, A lock should be acquired
+     * on this object when accessing any data from this model.
+     */
+    static final BgDataModel sBgDataModel = new BgDataModel();
 
     // </ only access in worker thread >
 
-    private IconCache mIconCache;
-    private DeepShortcutManager mDeepShortcutManager;
+    private final IconCache mIconCache;
 
     private final LauncherAppsCompat mLauncherApps;
     private final UserManagerCompat mUserManager;
@@ -226,24 +204,18 @@
                 UserHandleCompat user);
         public void bindAppInfosRemoved(ArrayList<AppInfo> appInfos);
         public void notifyWidgetProvidersChanged();
-        public void bindWidgetsModel(WidgetsModel model);
+        public void bindAllWidgets(MultiHashMap<PackageItemInfo, WidgetItem> widgets);
         public void onPageBoundSynchronously(int page);
         public void executeOnNextDraw(ViewOnDrawExecutor executor);
         public void bindDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMap);
     }
 
-    public interface ItemInfoFilter {
-        public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn);
-    }
-
-    LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter,
-            DeepShortcutManager deepShortcutManager) {
+    LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
         Context context = app.getContext();
         mApp = app;
         mBgAllAppsList = new AllAppsList(iconCache, appFilter);
-        mBgWidgetsModel = new WidgetsModel(context, iconCache, appFilter);
+        mBgWidgetsModel = new WidgetsModel(iconCache, appFilter);
         mIconCache = iconCache;
-        mDeepShortcutManager = deepShortcutManager;
 
         mLauncherApps = LauncherAppsCompat.getInstance(context);
         mUserManager = UserManagerCompat.getInstance(context);
@@ -271,294 +243,26 @@
         }
     }
 
-    public void setPackageState(final PackageInstallInfo installInfo) {
-        Runnable updateRunnable = new Runnable() {
-
-            @Override
-            public void run() {
-                synchronized (sBgLock) {
-                    final HashSet<ItemInfo> updates = new HashSet<>();
-
-                    if (installInfo.state == PackageInstallerCompat.STATUS_INSTALLED) {
-                        // Ignore install success events as they are handled by Package add events.
-                        return;
-                    }
-
-                    for (ItemInfo info : sBgItemsIdMap) {
-                        if (info instanceof ShortcutInfo) {
-                            ShortcutInfo si = (ShortcutInfo) info;
-                            ComponentName cn = si.getTargetComponent();
-                            if (si.isPromise() && (cn != null)
-                                    && installInfo.packageName.equals(cn.getPackageName())) {
-                                si.setInstallProgress(installInfo.progress);
-
-                                if (installInfo.state == PackageInstallerCompat.STATUS_FAILED) {
-                                    // Mark this info as broken.
-                                    si.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
-                                }
-                                updates.add(si);
-                            }
-                        }
-                    }
-
-                    for (LauncherAppWidgetInfo widget : sBgAppWidgets) {
-                        if (widget.providerName.getPackageName().equals(installInfo.packageName)) {
-                            widget.installProgress = installInfo.progress;
-                            updates.add(widget);
-                        }
-                    }
-
-                    if (!updates.isEmpty()) {
-                        // Push changes to the callback.
-                        Runnable r = new Runnable() {
-                            public void run() {
-                                Callbacks callbacks = getCallback();
-                                if (callbacks != null) {
-                                    callbacks.bindRestoreItemsChange(updates);
-                                }
-                            }
-                        };
-                        mHandler.post(r);
-                    }
-                }
-            }
-        };
-        runOnWorkerThread(updateRunnable);
+    public void setPackageState(PackageInstallInfo installInfo) {
+        enqueueModelUpdateTask(new PackageInstallStateChangedTask(installInfo));
     }
 
     /**
      * Updates the icons and label of all pending icons for the provided package name.
      */
     public void updateSessionDisplayInfo(final String packageName) {
-        Runnable updateRunnable = new Runnable() {
-
-            @Override
-            public void run() {
-                synchronized (sBgLock) {
-                    ArrayList<ShortcutInfo> updates = new ArrayList<>();
-                    UserHandleCompat user = UserHandleCompat.myUserHandle();
-
-                    for (ItemInfo info : sBgItemsIdMap) {
-                        if (info instanceof ShortcutInfo) {
-                            ShortcutInfo si = (ShortcutInfo) info;
-                            ComponentName cn = si.getTargetComponent();
-                            if (si.isPromise() && (cn != null)
-                                    && packageName.equals(cn.getPackageName())) {
-                                if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
-                                    // For auto install apps update the icon as well as label.
-                                    mIconCache.getTitleAndIcon(si,
-                                            si.promisedIntent, user,
-                                            si.shouldUseLowResIcon());
-                                } else {
-                                    // Only update the icon for restored apps.
-                                    si.updateIcon(mIconCache);
-                                }
-                                updates.add(si);
-                            }
-                        }
-                    }
-
-                    bindUpdatedShortcuts(updates, user);
-                }
-            }
-        };
-        runOnWorkerThread(updateRunnable);
-    }
-
-    public void addAppsToAllApps(final Context ctx, final ArrayList<AppInfo> allAppsApps) {
-        final Callbacks callbacks = getCallback();
-
-        if (allAppsApps == null) {
-            throw new RuntimeException("allAppsApps must not be null");
-        }
-        if (allAppsApps.isEmpty()) {
-            return;
-        }
-
-        // Process the newly added applications and add them to the database first
-        Runnable r = new Runnable() {
-            public void run() {
-                runOnMainThread(new Runnable() {
-                    public void run() {
-                        Callbacks cb = getCallback();
-                        if (callbacks == cb && cb != null) {
-                            callbacks.bindAppsAdded(null, null, null, allAppsApps);
-                        }
-                    }
-                });
-            }
-        };
-        runOnWorkerThread(r);
-    }
-
-    private static boolean findNextAvailableIconSpaceInScreen(ArrayList<ItemInfo> occupiedPos,
-            int[] xy, int spanX, int spanY) {
-        LauncherAppState app = LauncherAppState.getInstance();
-        InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
-
-        GridOccupancy occupied = new GridOccupancy(profile.numColumns, profile.numRows);
-        if (occupiedPos != null) {
-            for (ItemInfo r : occupiedPos) {
-                occupied.markCells(r, true);
-            }
-        }
-        return occupied.findVacantCell(xy, spanX, spanY);
-    }
-
-    /**
-     * Find a position on the screen for the given size or adds a new screen.
-     * @return screenId and the coordinates for the item.
-     */
-    @Thunk Pair<Long, int[]> findSpaceForItem(
-            Context context,
-            ArrayList<Long> workspaceScreens,
-            ArrayList<Long> addedWorkspaceScreensFinal,
-            int spanX, int spanY) {
-        LongSparseArray<ArrayList<ItemInfo>> screenItems = new LongSparseArray<>();
-
-        // Use sBgItemsIdMap as all the items are already loaded.
-        assertWorkspaceLoaded();
-        synchronized (sBgLock) {
-            for (ItemInfo info : sBgItemsIdMap) {
-                if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
-                    ArrayList<ItemInfo> items = screenItems.get(info.screenId);
-                    if (items == null) {
-                        items = new ArrayList<>();
-                        screenItems.put(info.screenId, items);
-                    }
-                    items.add(info);
-                }
-            }
-        }
-
-        // Find appropriate space for the item.
-        long screenId = 0;
-        int[] cordinates = new int[2];
-        boolean found = false;
-
-        int screenCount = workspaceScreens.size();
-        // First check the preferred screen.
-        int preferredScreenIndex = workspaceScreens.isEmpty() ? 0 : 1;
-        if (preferredScreenIndex < screenCount) {
-            screenId = workspaceScreens.get(preferredScreenIndex);
-            found = findNextAvailableIconSpaceInScreen(
-                    screenItems.get(screenId), cordinates, spanX, spanY);
-        }
-
-        if (!found) {
-            // Search on any of the screens starting from the first screen.
-            for (int screen = 1; screen < screenCount; screen++) {
-                screenId = workspaceScreens.get(screen);
-                if (findNextAvailableIconSpaceInScreen(
-                        screenItems.get(screenId), cordinates, spanX, spanY)) {
-                    // We found a space for it
-                    found = true;
-                    break;
-                }
-            }
-        }
-
-        if (!found) {
-            // Still no position found. Add a new screen to the end.
-            screenId = LauncherSettings.Settings.call(context.getContentResolver(),
-                    LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
-                    .getLong(LauncherSettings.Settings.EXTRA_VALUE);
-
-            // Save the screen id for binding in the workspace
-            workspaceScreens.add(screenId);
-            addedWorkspaceScreensFinal.add(screenId);
-
-            // If we still can't find an empty space, then God help us all!!!
-            if (!findNextAvailableIconSpaceInScreen(
-                    screenItems.get(screenId), cordinates, spanX, spanY)) {
-                throw new RuntimeException("Can't find space to add the item");
-            }
-        }
-        return Pair.create(screenId, cordinates);
+        HashSet<String> packages = new HashSet<>();
+        packages.add(packageName);
+        enqueueModelUpdateTask(new CacheDataUpdatedTask(
+                CacheDataUpdatedTask.OP_SESSION_UPDATE, UserHandleCompat.myUserHandle(), packages));
     }
 
     /**
      * Adds the provided items to the workspace.
      */
-    public void addAndBindAddedWorkspaceItems(final Context context,
+    public void addAndBindAddedWorkspaceItems(
             final ArrayList<? extends ItemInfo> workspaceApps) {
-        final Callbacks callbacks = getCallback();
-        if (workspaceApps.isEmpty()) {
-            return;
-        }
-        // Process the newly added applications and add them to the database first
-        Runnable r = new Runnable() {
-            public void run() {
-                final ArrayList<ItemInfo> addedShortcutsFinal = new ArrayList<ItemInfo>();
-                final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<Long>();
-
-                // Get the list of workspace screens.  We need to append to this list and
-                // can not use sBgWorkspaceScreens because loadWorkspace() may not have been
-                // called.
-                ArrayList<Long> workspaceScreens = loadWorkspaceScreensDb(context);
-                synchronized(sBgLock) {
-                    for (ItemInfo item : workspaceApps) {
-                        if (item instanceof ShortcutInfo) {
-                            // Short-circuit this logic if the icon exists somewhere on the workspace
-                            if (shortcutExists(context, item.getIntent(), item.user)) {
-                                continue;
-                            }
-                        }
-
-                        // Find appropriate space for the item.
-                        Pair<Long, int[]> coords = findSpaceForItem(context,
-                                workspaceScreens, addedWorkspaceScreensFinal, 1, 1);
-                        long screenId = coords.first;
-                        int[] cordinates = coords.second;
-
-                        ItemInfo itemInfo;
-                        if (item instanceof ShortcutInfo || item instanceof FolderInfo) {
-                            itemInfo = item;
-                        } else if (item instanceof AppInfo) {
-                            itemInfo = ((AppInfo) item).makeShortcut();
-                        } else {
-                            throw new RuntimeException("Unexpected info type");
-                        }
-
-                        // Add the shortcut to the db
-                        addItemToDatabase(context, itemInfo,
-                                LauncherSettings.Favorites.CONTAINER_DESKTOP,
-                                screenId, cordinates[0], cordinates[1]);
-                        // Save the ShortcutInfo for binding in the workspace
-                        addedShortcutsFinal.add(itemInfo);
-                    }
-                }
-
-                // Update the workspace screens
-                updateWorkspaceScreenOrder(context, workspaceScreens);
-
-                if (!addedShortcutsFinal.isEmpty()) {
-                    runOnMainThread(new Runnable() {
-                        public void run() {
-                            Callbacks cb = getCallback();
-                            if (callbacks == cb && cb != null) {
-                                final ArrayList<ItemInfo> addAnimated = new ArrayList<ItemInfo>();
-                                final ArrayList<ItemInfo> addNotAnimated = new ArrayList<ItemInfo>();
-                                if (!addedShortcutsFinal.isEmpty()) {
-                                    ItemInfo info = addedShortcutsFinal.get(addedShortcutsFinal.size() - 1);
-                                    long lastScreenId = info.screenId;
-                                    for (ItemInfo i : addedShortcutsFinal) {
-                                        if (i.screenId == lastScreenId) {
-                                            addAnimated.add(i);
-                                        } else {
-                                            addNotAnimated.add(i);
-                                        }
-                                    }
-                                }
-                                callbacks.bindAppsAdded(addedWorkspaceScreensFinal,
-                                        addNotAnimated, addAnimated, null);
-                            }
-                        }
-                    });
-                }
-            }
-        };
-        runOnWorkerThread(r);
+        enqueueModelUpdateTask(new AddWorkspaceItemsTask(workspaceApps));
     }
 
     /**
@@ -578,7 +282,7 @@
 
     static void checkItemInfoLocked(
             final long itemId, final ItemInfo item, StackTraceElement[] stackTrace) {
-        ItemInfo modelItem = sBgItemsIdMap.get(itemId);
+        ItemInfo modelItem = sBgDataModel.itemsIdMap.get(itemId);
         if (modelItem != null && item != modelItem) {
             // check all the data is consistent
             if (modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
@@ -619,7 +323,7 @@
         final long itemId = item.id;
         Runnable r = new Runnable() {
             public void run() {
-                synchronized (sBgLock) {
+                synchronized (sBgDataModel) {
                     checkItemInfoLocked(itemId, item, stackTrace);
                 }
             }
@@ -627,7 +331,7 @@
         runOnWorkerThread(r);
     }
 
-    static void updateItemInDatabaseHelper(Context context, final ContentValues values,
+    static void updateItemInDatabaseHelper(Context context, final ContentWriter writer,
             final ItemInfo item, final String callingFunction) {
         final long itemId = item.id;
         final Uri uri = LauncherSettings.Favorites.getContentUri(itemId);
@@ -636,7 +340,7 @@
         final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
         Runnable r = new Runnable() {
             public void run() {
-                cr.update(uri, values, null, null);
+                cr.update(uri, writer.getValues(), null, null);
                 updateItemArrays(item, itemId, stackTrace);
             }
         };
@@ -675,13 +379,13 @@
 
     static void updateItemArrays(ItemInfo item, long itemId, StackTraceElement[] stackTrace) {
         // Lock on mBgLock *after* the db operation
-        synchronized (sBgLock) {
+        synchronized (sBgDataModel) {
             checkItemInfoLocked(itemId, item, stackTrace);
 
             if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
                     item.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
                 // Item is in a folder, make sure this folder exists
-                if (!sBgFolders.containsKey(item.container)) {
+                if (!sBgDataModel.folders.containsKey(item.container)) {
                     // An items container is being set to a that of an item which is not in
                     // the list of Folders.
                     String msg = "item: " + item + " container being set to: " +
@@ -693,7 +397,7 @@
             // Items are added/removed from the corresponding FolderInfo elsewhere, such
             // as in Workspace.onDrop. Here, we just add/remove them from the list of items
             // that are on the desktop, as appropriate
-            ItemInfo modelItem = sBgItemsIdMap.get(itemId);
+            ItemInfo modelItem = sBgDataModel.itemsIdMap.get(itemId);
             if (modelItem != null &&
                     (modelItem.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
                      modelItem.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT)) {
@@ -702,15 +406,15 @@
                     case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
                     case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
                     case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
-                        if (!sBgWorkspaceItems.contains(modelItem)) {
-                            sBgWorkspaceItems.add(modelItem);
+                        if (!sBgDataModel.workspaceItems.contains(modelItem)) {
+                            sBgDataModel.workspaceItems.add(modelItem);
                         }
                         break;
                     default:
                         break;
                 }
             } else {
-                sBgWorkspaceItems.remove(modelItem);
+                sBgDataModel.workspaceItems.remove(modelItem);
             }
         }
     }
@@ -734,14 +438,14 @@
             item.screenId = screenId;
         }
 
-        final ContentValues values = new ContentValues();
-        values.put(LauncherSettings.Favorites.CONTAINER, item.container);
-        values.put(LauncherSettings.Favorites.CELLX, item.cellX);
-        values.put(LauncherSettings.Favorites.CELLY, item.cellY);
-        values.put(LauncherSettings.Favorites.RANK, item.rank);
-        values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
+        final ContentWriter writer = new ContentWriter(context)
+                .put(LauncherSettings.Favorites.CONTAINER, item.container)
+                .put(LauncherSettings.Favorites.CELLX, item.cellX)
+                .put(LauncherSettings.Favorites.CELLY, item.cellY)
+                .put(LauncherSettings.Favorites.RANK, item.rank)
+                .put(LauncherSettings.Favorites.SCREEN, item.screenId);
 
-        updateItemInDatabaseHelper(context, values, item, "moveItemInDatabase");
+        updateItemInDatabaseHelper(context, writer, item, "moveItemInDatabase");
     }
 
     /**
@@ -801,79 +505,25 @@
             item.screenId = screenId;
         }
 
-        final ContentValues values = new ContentValues();
-        values.put(LauncherSettings.Favorites.CONTAINER, item.container);
-        values.put(LauncherSettings.Favorites.CELLX, item.cellX);
-        values.put(LauncherSettings.Favorites.CELLY, item.cellY);
-        values.put(LauncherSettings.Favorites.RANK, item.rank);
-        values.put(LauncherSettings.Favorites.SPANX, item.spanX);
-        values.put(LauncherSettings.Favorites.SPANY, item.spanY);
-        values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
+        final ContentWriter writer = new ContentWriter(context)
+                .put(LauncherSettings.Favorites.CONTAINER, item.container)
+                .put(LauncherSettings.Favorites.CELLX, item.cellX)
+                .put(LauncherSettings.Favorites.CELLY, item.cellY)
+                .put(LauncherSettings.Favorites.RANK, item.rank)
+                .put(LauncherSettings.Favorites.SPANX, item.spanX)
+                .put(LauncherSettings.Favorites.SPANY, item.spanY)
+                .put(LauncherSettings.Favorites.SCREEN, item.screenId);
 
-        updateItemInDatabaseHelper(context, values, item, "modifyItemInDatabase");
+        updateItemInDatabaseHelper(context, writer, item, "modifyItemInDatabase");
     }
 
     /**
      * Update an item to the database in a specified container.
      */
     public static void updateItemInDatabase(Context context, final ItemInfo item) {
-        final ContentValues values = new ContentValues();
-        item.onAddToDatabase(context, values);
-        updateItemInDatabaseHelper(context, values, item, "updateItemInDatabase");
-    }
-
-    private void assertWorkspaceLoaded() {
-        if (ProviderConfig.IS_DOGFOOD_BUILD) {
-            synchronized (mLock) {
-                if (!mHasLoaderCompletedOnce ||
-                        (mLoaderTask != null && mLoaderTask.mIsLoadingAndBindingWorkspace)) {
-                    throw new RuntimeException("Trying to add shortcut while loader is running");
-                }
-            }
-        }
-    }
-
-    /**
-     * Returns true if the shortcuts already exists on the workspace. This must be called after
-     * the workspace has been loaded. We identify a shortcut by its intent.
-     */
-    @Thunk boolean shortcutExists(Context context, Intent intent, UserHandleCompat user) {
-        assertWorkspaceLoaded();
-        final String intentWithPkg, intentWithoutPkg;
-        if (intent.getComponent() != null) {
-            // If component is not null, an intent with null package will produce
-            // the same result and should also be a match.
-            String packageName = intent.getComponent().getPackageName();
-            if (intent.getPackage() != null) {
-                intentWithPkg = intent.toUri(0);
-                intentWithoutPkg = new Intent(intent).setPackage(null).toUri(0);
-            } else {
-                intentWithPkg = new Intent(intent).setPackage(packageName).toUri(0);
-                intentWithoutPkg = intent.toUri(0);
-            }
-        } else {
-            intentWithPkg = intent.toUri(0);
-            intentWithoutPkg = intent.toUri(0);
-        }
-
-        synchronized (sBgLock) {
-            for (ItemInfo item : sBgItemsIdMap) {
-                if (item instanceof ShortcutInfo) {
-                    ShortcutInfo info = (ShortcutInfo) item;
-                    Intent targetIntent = info.promisedIntent == null
-                            ? info.intent : info.promisedIntent;
-                    if (targetIntent != null && info.user.equals(user)) {
-                        Intent copyIntent = new Intent(targetIntent);
-                        copyIntent.setSourceBounds(intent.getSourceBounds());
-                        String s = copyIntent.toUri(0);
-                        if (intentWithPkg.equals(s) || intentWithoutPkg.equals(s)) {
-                            return true;
-                        }
-                    }
-                }
-            }
-        }
-        return false;
+        ContentWriter writer = new ContentWriter(context);
+        item.onAddToDatabase(writer);
+        updateItemInDatabaseHelper(context, writer, item, "updateItemInDatabase");
     }
 
     /**
@@ -895,90 +545,50 @@
             item.screenId = screenId;
         }
 
-        final ContentValues values = new ContentValues();
+        final ContentWriter writer = new ContentWriter(context);
         final ContentResolver cr = context.getContentResolver();
-        item.onAddToDatabase(context, values);
+        item.onAddToDatabase(writer);
 
         item.id = LauncherSettings.Settings.call(cr, LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
                 .getLong(LauncherSettings.Settings.EXTRA_VALUE);
 
-        values.put(LauncherSettings.Favorites._ID, item.id);
+        writer.put(LauncherSettings.Favorites._ID, item.id);
 
         final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
         Runnable r = new Runnable() {
             public void run() {
-                cr.insert(LauncherSettings.Favorites.CONTENT_URI, values);
+                cr.insert(LauncherSettings.Favorites.CONTENT_URI, writer.getValues());
 
-                // Lock on mBgLock *after* the db operation
-                synchronized (sBgLock) {
+                synchronized (sBgDataModel) {
                     checkItemInfoLocked(item.id, item, stackTrace);
-                    sBgItemsIdMap.put(item.id, item);
-                    switch (item.itemType) {
-                        case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
-                            sBgFolders.put(item.id, (FolderInfo) item);
-                            // Fall through
-                        case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
-                        case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
-                        case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
-                            if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
-                                    item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
-                                sBgWorkspaceItems.add(item);
-                            } else {
-                                if (!sBgFolders.containsKey(item.container)) {
-                                    // Adding an item to a folder that doesn't exist.
-                                    String msg = "adding item: " + item + " to a folder that " +
-                                            " doesn't exist";
-                                    Log.e(TAG, msg);
-                                }
-                            }
-                            if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
-                                incrementPinnedShortcutCount(
-                                        ShortcutKey.fromShortcutInfo((ShortcutInfo) item),
-                                        true /* shouldPin */);
-                            }
-                            break;
-                        case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
-                            sBgAppWidgets.add((LauncherAppWidgetInfo) item);
-                            break;
-                    }
+                    sBgDataModel.addItem(item, true);
                 }
             }
         };
         runOnWorkerThread(r);
     }
 
-    private static ArrayList<ItemInfo> getItemsByPackageName(
-            final String pn, final UserHandleCompat user) {
-        ItemInfoFilter filter  = new ItemInfoFilter() {
-            @Override
-            public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
-                return cn.getPackageName().equals(pn) && info.user.equals(user);
-            }
-        };
-        return filterItemInfos(sBgItemsIdMap, filter);
-    }
-
-    /**
-     * Removes all the items from the database corresponding to the specified package.
-     */
-    static void deletePackageFromDatabase(Context context, final String pn,
-            final UserHandleCompat user) {
-        deleteItemsFromDatabase(context, getItemsByPackageName(pn, user));
-    }
-
     /**
      * Removes the specified item from the database
      */
     public static void deleteItemFromDatabase(Context context, final ItemInfo item) {
-        ArrayList<ItemInfo> items = new ArrayList<ItemInfo>();
+        ArrayList<ItemInfo> items = new ArrayList<>();
         items.add(item);
         deleteItemsFromDatabase(context, items);
     }
 
     /**
+     * Removes all the items from the database matching {@param matcher}.
+     */
+    public static void deleteItemsFromDatabase(Context context, ItemInfoMatcher matcher) {
+        deleteItemsFromDatabase(context, matcher.filterItemInfos(sBgDataModel.itemsIdMap));
+    }
+
+    /**
      * Removes the specified items from the database
      */
-    static void deleteItemsFromDatabase(Context context, final ArrayList<? extends ItemInfo> items) {
+    public static void deleteItemsFromDatabase(Context context,
+            final Iterable<? extends ItemInfo> items) {
         final ContentResolver cr = context.getContentResolver();
         Runnable r = new Runnable() {
             public void run() {
@@ -986,36 +596,7 @@
                     final Uri uri = LauncherSettings.Favorites.getContentUri(item.id);
                     cr.delete(uri, null, null);
 
-                    // Lock on mBgLock *after* the db operation
-                    synchronized (sBgLock) {
-                        switch (item.itemType) {
-                            case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
-                                sBgFolders.remove(item.id);
-                                for (ItemInfo info: sBgItemsIdMap) {
-                                    if (info.container == item.id) {
-                                        // We are deleting a folder which still contains items that
-                                        // think they are contained by that folder.
-                                        String msg = "deleting a folder (" + item + ") which still " +
-                                                "contains items (" + info + ")";
-                                        Log.e(TAG, msg);
-                                    }
-                                }
-                                sBgWorkspaceItems.remove(item);
-                                break;
-                            case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
-                                decrementPinnedShortcutCount(ShortcutKey.fromShortcutInfo(
-                                        (ShortcutInfo) item));
-                                // Fall through.
-                            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
-                            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
-                                sBgWorkspaceItems.remove(item);
-                                break;
-                            case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
-                                sBgAppWidgets.remove((LauncherAppWidgetInfo) item);
-                                break;
-                        }
-                        sBgItemsIdMap.remove(item.id);
-                    }
+                    sBgDataModel.removeItem(item);
                 }
             }
         };
@@ -1023,43 +604,10 @@
     }
 
     /**
-     * Decrement the count for the given pinned shortcut, unpinning it if the count becomes 0.
-     */
-    private static void decrementPinnedShortcutCount(final ShortcutKey pinnedShortcut) {
-        synchronized (sBgLock) {
-            MutableInt count = sBgPinnedShortcutCounts.get(pinnedShortcut);
-            if (count == null || --count.value == 0) {
-                LauncherAppState.getInstance().getShortcutManager().unpinShortcut(pinnedShortcut);
-            }
-        }
-    }
-
-    /**
-     * Increment the count for the given shortcut, pinning it if the count becomes 1.
-     *
-     * As an optimization, the caller can pass shouldPin == false to avoid
-     * unnecessary RPC's if the shortcut is already pinned.
-     */
-    private static void incrementPinnedShortcutCount(ShortcutKey pinnedShortcut, boolean shouldPin) {
-        synchronized (sBgLock) {
-            MutableInt count = sBgPinnedShortcutCounts.get(pinnedShortcut);
-            if (count == null) {
-                count = new MutableInt(1);
-                sBgPinnedShortcutCounts.put(pinnedShortcut, count);
-            } else {
-                count.value++;
-            }
-            if (shouldPin && count.value == 1) {
-                LauncherAppState.getInstance().getShortcutManager().pinShortcut(pinnedShortcut);
-            }
-        }
-    }
-
-    /**
      * Update the order of the workspace screens in the database. The array list contains
      * a list of screen ids in the order that they should appear.
      */
-    public void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
+    public static void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
         final ArrayList<Long> screensCopy = new ArrayList<Long>(screens);
         final ContentResolver cr = context.getContentResolver();
         final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
@@ -1094,9 +642,9 @@
                     throw new RuntimeException(ex);
                 }
 
-                synchronized (sBgLock) {
-                    sBgWorkspaceScreens.clear();
-                    sBgWorkspaceScreens.addAll(screensCopy);
+                synchronized (sBgDataModel) {
+                    sBgDataModel.workspaceScreens.clear();
+                    sBgDataModel.workspaceScreens.addAll(screensCopy);
                 }
             }
         };
@@ -1111,22 +659,13 @@
 
         Runnable r = new Runnable() {
             public void run() {
-                cr.delete(LauncherSettings.Favorites.getContentUri(info.id), null, null);
-                // Lock on mBgLock *after* the db operation
-                synchronized (sBgLock) {
-                    sBgItemsIdMap.remove(info.id);
-                    sBgFolders.remove(info.id);
-                    sBgWorkspaceItems.remove(info);
-                }
-
                 cr.delete(LauncherSettings.Favorites.CONTENT_URI,
                         LauncherSettings.Favorites.CONTAINER + "=" + info.id, null);
-                // Lock on mBgLock *after* the db operation
-                synchronized (sBgLock) {
-                    for (ItemInfo childInfo : info.contents) {
-                        sBgItemsIdMap.remove(childInfo.id);
-                    }
-                }
+                sBgDataModel.removeItem(info.contents);
+                info.contents.clear();
+
+                cr.delete(LauncherSettings.Favorites.getContentUri(info.id), null, null);
+                sBgDataModel.removeItem(info);
             }
         };
         runOnWorkerThread(r);
@@ -1147,64 +686,62 @@
     @Override
     public void onPackageChanged(String packageName, UserHandleCompat user) {
         int op = PackageUpdatedTask.OP_UPDATE;
-        enqueueItemUpdatedTask(new PackageUpdatedTask(op, new String[] { packageName },
-                user));
+        enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName));
     }
 
     @Override
     public void onPackageRemoved(String packageName, UserHandleCompat user) {
+        onPackagesRemoved(user, packageName);
+    }
+
+    public void onPackagesRemoved(UserHandleCompat user, String... packages) {
         int op = PackageUpdatedTask.OP_REMOVE;
-        enqueueItemUpdatedTask(new PackageUpdatedTask(op, new String[] { packageName },
-                user));
+        enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packages));
     }
 
     @Override
     public void onPackageAdded(String packageName, UserHandleCompat user) {
         int op = PackageUpdatedTask.OP_ADD;
-        enqueueItemUpdatedTask(new PackageUpdatedTask(op, new String[] { packageName },
-                user));
+        enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName));
     }
 
     @Override
     public void onPackagesAvailable(String[] packageNames, UserHandleCompat user,
             boolean replacing) {
-        enqueueItemUpdatedTask(
-                new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE, packageNames, user));
+        enqueueModelUpdateTask(
+                new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE, user, packageNames));
     }
 
     @Override
     public void onPackagesUnavailable(String[] packageNames, UserHandleCompat user,
             boolean replacing) {
         if (!replacing) {
-            enqueueItemUpdatedTask(new PackageUpdatedTask(
-                    PackageUpdatedTask.OP_UNAVAILABLE, packageNames,
-                    user));
+            enqueueModelUpdateTask(new PackageUpdatedTask(
+                    PackageUpdatedTask.OP_UNAVAILABLE, user, packageNames));
         }
     }
 
     @Override
     public void onPackagesSuspended(String[] packageNames, UserHandleCompat user) {
-        enqueueItemUpdatedTask(new PackageUpdatedTask(
-                PackageUpdatedTask.OP_SUSPEND, packageNames,
-                user));
+        enqueueModelUpdateTask(new PackageUpdatedTask(
+                PackageUpdatedTask.OP_SUSPEND, user, packageNames));
     }
 
     @Override
     public void onPackagesUnsuspended(String[] packageNames, UserHandleCompat user) {
-        enqueueItemUpdatedTask(new PackageUpdatedTask(
-                PackageUpdatedTask.OP_UNSUSPEND, packageNames,
-                user));
+        enqueueModelUpdateTask(new PackageUpdatedTask(
+                PackageUpdatedTask.OP_UNSUSPEND, user, packageNames));
     }
 
     @Override
     public void onShortcutsChanged(String packageName, List<ShortcutInfoCompat> shortcuts,
             UserHandleCompat user) {
-        enqueueItemUpdatedTask(new ShortcutsChangedTask(packageName, shortcuts, user, true));
+        enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, true));
     }
 
     public void updatePinnedShortcuts(String packageName, List<ShortcutInfoCompat> shortcuts,
             UserHandleCompat user) {
-        enqueueItemUpdatedTask(new ShortcutsChangedTask(packageName, shortcuts, user, false));
+        enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, false));
     }
 
     /**
@@ -1230,16 +767,15 @@
             if (user != null) {
                 if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) ||
                         Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) {
-                    enqueueItemUpdatedTask(new PackageUpdatedTask(
-                            PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE,
-                            new String[0], user));
+                    enqueueModelUpdateTask(new PackageUpdatedTask(
+                            PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user));
                 }
 
                 // ACTION_MANAGED_PROFILE_UNAVAILABLE sends the profile back to locked mode, so
                 // we need to run the state change task again.
                 if (Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) ||
                         Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) {
-                    enqueueItemUpdatedTask(new UserLockStateChangedTask(user));
+                    enqueueModelUpdateTask(new UserLockStateChangedTask(user));
                 }
             }
         } else if (Intent.ACTION_WALLPAPER_CHANGED.equals(action)) {
@@ -1642,18 +1178,6 @@
             }
         }
 
-        /** Clears all the sBg data structures */
-        private void clearSBgDataStructures() {
-            synchronized (sBgLock) {
-                sBgWorkspaceItems.clear();
-                sBgAppWidgets.clear();
-                sBgFolders.clear();
-                sBgItemsIdMap.clear();
-                sBgWorkspaceScreens.clear();
-                sBgPinnedShortcutCounts.clear();
-            }
-        }
-
         private void loadWorkspace() {
             if (LauncherAppState.PROFILE_STARTUP) {
                 Trace.beginSection("Loading Workspace");
@@ -1665,7 +1189,9 @@
             final PackageManager manager = context.getPackageManager();
             final boolean isSafeMode = manager.isSafeMode();
             final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
+            final DeepShortcutManager shortcutManager = DeepShortcutManager.getInstance(context);
             final boolean isSdCardReady = Utilities.isBootCompleted();
+            final MultiHashMap<UserHandleCompat, String> pendingPackages = new MultiHashMap<>();
 
             LauncherAppState app = LauncherAppState.getInstance();
             InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
@@ -1696,11 +1222,12 @@
             LauncherSettings.Settings.call(contentResolver,
                     LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES);
 
-            synchronized (sBgLock) {
-                clearSBgDataStructures();
+            synchronized (sBgDataModel) {
+                sBgDataModel.clear();
+
                 final HashMap<String, Integer> installingPkgs = PackageInstallerCompat
                         .getInstance(mContext).updateAndGetActiveSessionCache();
-                sBgWorkspaceScreens.addAll(loadWorkspaceScreensDb(mContext));
+                sBgDataModel.workspaceScreens.addAll(loadWorkspaceScreensDb(mContext));
 
                 final ArrayList<Long> itemsToRemove = new ArrayList<>();
                 final ArrayList<Long> restoredRows = new ArrayList<>();
@@ -1760,8 +1287,8 @@
                         // We can only query for shortcuts when the user is unlocked.
                         if (userUnlocked) {
                             List<ShortcutInfoCompat> pinnedShortcuts =
-                                    mDeepShortcutManager.queryForPinnedShortcuts(null, user);
-                            if (mDeepShortcutManager.wasLastCallSuccess()) {
+                                    shortcutManager.queryForPinnedShortcuts(null, user);
+                            if (shortcutManager.wasLastCallSuccess()) {
                                 for (ShortcutInfoCompat shortcut : pinnedShortcuts) {
                                     shortcutKeyToPinnedShortcuts.put(ShortcutKey.fromInfo(shortcut),
                                             shortcut);
@@ -1904,12 +1431,7 @@
                                             // SdCard is not ready yet. Package might get available,
                                             // once it is ready.
                                             Log.d(TAG, "Invalid package: " + cn + " (check again later)");
-                                            HashSet<String> pkgs = sPendingPackages.get(user);
-                                            if (pkgs == null) {
-                                                pkgs = new HashSet<String>();
-                                                sPendingPackages.put(user, pkgs);
-                                            }
-                                            pkgs.add(cn.getPackageName());
+                                            pendingPackages.addToList(user, cn.getPackageName());
                                             allowMissingTarget = true;
                                             // Add the icon on the workspace anyway.
 
@@ -1980,7 +1502,6 @@
 
                                         info.isDisabled |= ShortcutInfo.FLAG_DISABLED_LOCKED_USER;
                                     }
-                                    incrementPinnedShortcutCount(key, false /* shouldPin */);
                                 } else { // item type == ITEM_TYPE_SHORTCUT
                                     info = getShortcutInfo(c, cursorIconInfo);
 
@@ -2022,7 +1543,7 @@
                                     }
 
                                     // check & update map of what's occupied
-                                    if (!checkItemPlacement(occupied, info, sBgWorkspaceScreens)) {
+                                    if (!checkItemPlacement(occupied, info, sBgDataModel.workspaceScreens)) {
                                         itemsToRemove.add(id);
                                         break;
                                     }
@@ -2039,19 +1560,7 @@
                                         }
                                     }
 
-                                    switch (container) {
-                                    case LauncherSettings.Favorites.CONTAINER_DESKTOP:
-                                    case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
-                                        sBgWorkspaceItems.add(info);
-                                        break;
-                                    default:
-                                        // Item is in a user folder
-                                        FolderInfo folderInfo =
-                                                findOrMakeFolder(sBgFolders, container);
-                                        folderInfo.add(info, false);
-                                        break;
-                                    }
-                                    sBgItemsIdMap.put(info.id, info);
+                                    sBgDataModel.addItem(info, false);
                                 } else {
                                     throw new RuntimeException("Unexpected null ShortcutInfo");
                                 }
@@ -2059,7 +1568,7 @@
 
                             case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
                                 id = c.getLong(idIndex);
-                                FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id);
+                                FolderInfo folderInfo = sBgDataModel.findOrMakeFolder(id);
 
                                 // Do not trim the folder label, as is was set by the user.
                                 folderInfo.title = c.getString(cursorIconInfo.titleIndex);
@@ -2073,25 +1582,16 @@
                                 folderInfo.options = c.getInt(optionsIndex);
 
                                 // check & update map of what's occupied
-                                if (!checkItemPlacement(occupied, folderInfo, sBgWorkspaceScreens)) {
+                                if (!checkItemPlacement(occupied, folderInfo, sBgDataModel.workspaceScreens)) {
                                     itemsToRemove.add(id);
                                     break;
                                 }
-
-                                switch (container) {
-                                    case LauncherSettings.Favorites.CONTAINER_DESKTOP:
-                                    case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
-                                        sBgWorkspaceItems.add(folderInfo);
-                                        break;
-                                }
-
                                 if (restored) {
                                     // no special handling required for restored folders
                                     restoredRows.add(id);
                                 }
 
-                                sBgItemsIdMap.put(folderInfo.id, folderInfo);
-                                sBgFolders.put(folderInfo.id, folderInfo);
+                                sBgDataModel.addItem(folderInfo, false);
                                 break;
 
                             case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
@@ -2208,7 +1708,7 @@
 
                                     appWidgetInfo.container = container;
                                     // check & update map of what's occupied
-                                    if (!checkItemPlacement(occupied, appWidgetInfo, sBgWorkspaceScreens)) {
+                                    if (!checkItemPlacement(occupied, appWidgetInfo, sBgDataModel.workspaceScreens)) {
                                         itemsToRemove.add(id);
                                         break;
                                     }
@@ -2227,8 +1727,7 @@
                                             updateItem(id, values);
                                         }
                                     }
-                                    sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo);
-                                    sBgAppWidgets.add(appWidgetInfo);
+                                    sBgDataModel.addItem(appWidgetInfo, false);
                                 }
                                 break;
                             }
@@ -2242,7 +1741,7 @@
 
                 // Break early if we've stopped loading
                 if (mStopped) {
-                    clearSBgDataStructures();
+                    sBgDataModel.clear();
                     return;
                 }
 
@@ -2262,23 +1761,23 @@
                                     LauncherSettings.Settings.METHOD_DELETE_EMPTY_FOLDERS)
                             .getSerializable(LauncherSettings.Settings.EXTRA_VALUE);
                     for (long folderId : deletedFolderIds) {
-                        sBgWorkspaceItems.remove(sBgFolders.get(folderId));
-                        sBgFolders.remove(folderId);
-                        sBgItemsIdMap.remove(folderId);
+                        sBgDataModel.workspaceItems.remove(sBgDataModel.folders.get(folderId));
+                        sBgDataModel.folders.remove(folderId);
+                        sBgDataModel.itemsIdMap.remove(folderId);
                     }
                 }
 
                 // Unpin shortcuts that don't exist on the workspace.
                 for (ShortcutKey key : shortcutKeyToPinnedShortcuts.keySet()) {
-                    MutableInt numTimesPinned = sBgPinnedShortcutCounts.get(key);
+                    MutableInt numTimesPinned = sBgDataModel.pinnedShortcutCounts.get(key);
                     if (numTimesPinned == null || numTimesPinned.value == 0) {
                         // Shortcut is pinned but doesn't exist on the workspace; unpin it.
-                        mDeepShortcutManager.unpinShortcut(key);
+                        shortcutManager.unpinShortcut(key);
                     }
                 }
 
                 // Sort all the folder items and make sure the first 3 items are high resolution.
-                for (FolderInfo folder : sBgFolders) {
+                for (FolderInfo folder : sBgDataModel.folders) {
                     Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR);
                     int pos = 0;
                     for (ShortcutInfo info : folder.contents) {
@@ -2301,15 +1800,18 @@
                                     LauncherSettings.Favorites._ID, restoredRows), null);
                 }
 
-                if (!isSdCardReady && !sPendingPackages.isEmpty()) {
-                    context.registerReceiver(new AppsAvailabilityCheck(),
+                if (!isSdCardReady && !pendingPackages.isEmpty()) {
+                    context.registerReceiver(
+                            new SdCardAvailableReceiver(
+                                    LauncherModel.this, mContext, pendingPackages),
                             new IntentFilter(Intent.ACTION_BOOT_COMPLETED),
-                            null, sWorker);
+                            null,
+                            sWorker);
                 }
 
                 // Remove any empty screens
-                ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgWorkspaceScreens);
-                for (ItemInfo item: sBgItemsIdMap) {
+                ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgDataModel.workspaceScreens);
+                for (ItemInfo item: sBgDataModel.itemsIdMap) {
                     long screenId = item.screenId;
                     if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
                             unusedScreens.contains(screenId)) {
@@ -2319,8 +1821,8 @@
 
                 // If there are any empty screens remove them, and update.
                 if (unusedScreens.size() != 0) {
-                    sBgWorkspaceScreens.removeAll(unusedScreens);
-                    updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
+                    sBgDataModel.workspaceScreens.removeAll(unusedScreens);
+                    updateWorkspaceScreenOrder(context, sBgDataModel.workspaceScreens);
                 }
 
                 if (DEBUG_LOADERS) {
@@ -2533,10 +2035,10 @@
             ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
             ArrayList<Long> orderedScreenIds = new ArrayList<>();
 
-            synchronized (sBgLock) {
-                workspaceItems.addAll(sBgWorkspaceItems);
-                appWidgets.addAll(sBgAppWidgets);
-                orderedScreenIds.addAll(sBgWorkspaceScreens);
+            synchronized (sBgDataModel) {
+                workspaceItems.addAll(sBgDataModel.workspaceItems);
+                appWidgets.addAll(sBgDataModel.appWidgets);
+                orderedScreenIds.addAll(sBgDataModel.workspaceScreens);
             }
 
             final int currentScreen;
@@ -2680,8 +2182,8 @@
         private void updateIconCache() {
             // Ignore packages which have a promise icon.
             HashSet<String> packagesToIgnore = new HashSet<>();
-            synchronized (sBgLock) {
-                for (ItemInfo info : sBgItemsIdMap) {
+            synchronized (sBgDataModel) {
+                for (ItemInfo info : sBgDataModel.itemsIdMap) {
                     if (info instanceof ShortcutInfo) {
                         ShortcutInfo si = (ShortcutInfo) info;
                         if (si.isPromise() && si.getTargetComponent() != null) {
@@ -2822,14 +2324,15 @@
                 Log.d(TAG, "loadAndBindDeepShortcuts mDeepShortcutsLoaded=" + mDeepShortcutsLoaded);
             }
             if (!mDeepShortcutsLoaded) {
-                mBgDeepShortcutMap.clear();
-                mHasShortcutHostPermission = mDeepShortcutManager.hasHostPermission();
+                sBgDataModel.deepShortcutMap.clear();
+                DeepShortcutManager shortcutManager = DeepShortcutManager.getInstance(mContext);
+                mHasShortcutHostPermission = shortcutManager.hasHostPermission();
                 if (mHasShortcutHostPermission) {
                     for (UserHandleCompat user : mUserManager.getUserProfiles()) {
                         if (mUserManager.isUserUnlocked(user)) {
-                            List<ShortcutInfoCompat> shortcuts = mDeepShortcutManager
-                                    .queryForAllShortcuts(user);
-                            updateDeepShortcutMap(null, user, shortcuts);
+                            List<ShortcutInfoCompat> shortcuts =
+                                    shortcutManager.queryForAllShortcuts(user);
+                            sBgDataModel.updateDeepShortcutMap(null, user, shortcuts);
                         }
                     }
                 }
@@ -2844,45 +2347,18 @@
         }
 
         public void dumpState() {
-            synchronized (sBgLock) {
+            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=" + sBgWorkspaceItems.size());
-            }
-        }
-    }
-
-    /**
-     * Clear all the shortcuts for the given package, and re-add the new shortcuts.
-     */
-    private void updateDeepShortcutMap(
-            String packageName, UserHandleCompat user, List<ShortcutInfoCompat> shortcuts) {
-        if (packageName != null) {
-            Iterator<ComponentKey> keysIter = mBgDeepShortcutMap.keySet().iterator();
-            while (keysIter.hasNext()) {
-                ComponentKey next = keysIter.next();
-                if (next.componentName.getPackageName().equals(packageName)
-                        && next.user.equals(user)) {
-                    keysIter.remove();
-                }
-            }
-        }
-
-        // Now add the new shortcuts to the map.
-        for (ShortcutInfoCompat shortcut : shortcuts) {
-            boolean shouldShowInContainer = shortcut.isEnabled()
-                    && (shortcut.isDeclaredInManifest() || shortcut.isDynamic());
-            if (shouldShowInContainer) {
-                ComponentKey targetComponent
-                        = new ComponentKey(shortcut.getActivity(), shortcut.getUserHandle());
-                mBgDeepShortcutMap.addToList(targetComponent, shortcut.getId());
+                Log.d(TAG, "mItems size=" + sBgDataModel.workspaceItems.size());
             }
         }
     }
 
     public void bindDeepShortcuts() {
-        final MultiHashMap<ComponentKey, String> shortcutMapCopy = mBgDeepShortcutMap.clone();
+        final MultiHashMap<ComponentKey, String> shortcutMapCopy =
+                sBgDataModel.deepShortcutMap.clone();
         Runnable r = new Runnable() {
             @Override
             public void run() {
@@ -2901,7 +2377,7 @@
      * use partial updates similar to {@link UserManagerCompat}
      */
     public void refreshShortcutsIfRequired() {
-        if (Utilities.isNycMR1OrAbove()) {
+        if (Utilities.ATLEAST_NOUGAT_MR1) {
             sWorker.removeCallbacks(mShortcutPermissionCheckRunnable);
             sWorker.post(mShortcutPermissionCheckRunnable);
         }
@@ -2911,441 +2387,68 @@
      * Called when the icons for packages have been updated in the icon cache.
      */
     public void onPackageIconsUpdated(HashSet<String> updatedPackages, UserHandleCompat user) {
-        final Callbacks callbacks = getCallback();
-        final ArrayList<AppInfo> updatedApps = new ArrayList<>();
-        final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<>();
-
         // If any package icon has changed (app was updated while launcher was dead),
         // update the corresponding shortcuts.
-        synchronized (sBgLock) {
-            for (ItemInfo info : sBgItemsIdMap) {
-                if (info instanceof ShortcutInfo && user.equals(info.user)
-                        && info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
-                    ShortcutInfo si = (ShortcutInfo) info;
-                    ComponentName cn = si.getTargetComponent();
-                    if (cn != null && updatedPackages.contains(cn.getPackageName())) {
-                        si.updateIcon(mIconCache);
-                        updatedShortcuts.add(si);
-                    }
-                }
-            }
-            mBgAllAppsList.updateIconsAndLabels(updatedPackages, user, updatedApps);
+        enqueueModelUpdateTask(new CacheDataUpdatedTask(
+                CacheDataUpdatedTask.OP_CACHE_UPDATE, user, updatedPackages));
+    }
+
+    void enqueueModelUpdateTask(BaseModelUpdateTask task) {
+        task.init(this);
+        runOnWorkerThread(task);
+    }
+
+    /**
+     * A task to be executed on the current callbacks on the UI thread.
+     * If there is no current callbacks, the task is ignored.
+     */
+    public interface CallbackTask {
+
+        void execute(Callbacks callbacks);
+    }
+
+    /**
+     * A runnable which changes/updates the data model of the launcher based on certain events.
+     */
+    public static abstract class BaseModelUpdateTask implements Runnable {
+
+        private LauncherModel mModel;
+        private DeferredHandler mUiHandler;
+
+        /* package private */
+        void init(LauncherModel model) {
+            mModel = model;
+            mUiHandler = mModel.mHandler;
         }
 
-        bindUpdatedShortcuts(updatedShortcuts, user);
-
-        if (!updatedApps.isEmpty()) {
-            mHandler.post(new Runnable() {
-
-                public void run() {
-                    Callbacks cb = getCallback();
-                    if (cb != null && callbacks == cb) {
-                        cb.bindAppsUpdated(updatedApps);
-                    }
-                }
-            });
-        }
-    }
-
-    private void bindUpdatedShortcuts(
-            ArrayList<ShortcutInfo> updatedShortcuts, UserHandleCompat user) {
-        bindUpdatedShortcuts(updatedShortcuts, new ArrayList<ShortcutInfo>(), user);
-    }
-
-    private void bindUpdatedShortcuts(
-            final ArrayList<ShortcutInfo> updatedShortcuts,
-            final ArrayList<ShortcutInfo> removedShortcuts,
-            final UserHandleCompat user) {
-        if (!updatedShortcuts.isEmpty() || !removedShortcuts.isEmpty()) {
-            final Callbacks callbacks = getCallback();
-            mHandler.post(new Runnable() {
-
-                public void run() {
-                    Callbacks cb = getCallback();
-                    if (cb != null && callbacks == cb) {
-                        cb.bindShortcutsChanged(updatedShortcuts, removedShortcuts, user);
-                    }
-                }
-            });
-        }
-    }
-
-    void enqueueItemUpdatedTask(Runnable task) {
-        sWorker.post(task);
-    }
-
-    @Thunk class AppsAvailabilityCheck extends BroadcastReceiver {
-
         @Override
-        public void onReceive(Context context, Intent intent) {
-            synchronized (sBgLock) {
-                final LauncherAppsCompat launcherApps = LauncherAppsCompat
-                        .getInstance(mApp.getContext());
-                final PackageManager manager = context.getPackageManager();
-                final ArrayList<String> packagesRemoved = new ArrayList<String>();
-                final ArrayList<String> packagesUnavailable = new ArrayList<String>();
-                for (Entry<UserHandleCompat, HashSet<String>> entry : sPendingPackages.entrySet()) {
-                    UserHandleCompat user = entry.getKey();
-                    packagesRemoved.clear();
-                    packagesUnavailable.clear();
-                    for (String pkg : entry.getValue()) {
-                        if (!launcherApps.isPackageEnabledForProfile(pkg, user)) {
-                            if (PackageManagerHelper.isAppOnSdcard(manager, pkg)) {
-                                packagesUnavailable.add(pkg);
-                            } else {
-                                packagesRemoved.add(pkg);
-                            }
-                        }
-                    }
-                    if (!packagesRemoved.isEmpty()) {
-                        enqueueItemUpdatedTask(new PackageUpdatedTask(PackageUpdatedTask.OP_REMOVE,
-                                packagesRemoved.toArray(new String[packagesRemoved.size()]), user));
-                    }
-                    if (!packagesUnavailable.isEmpty()) {
-                        enqueueItemUpdatedTask(new PackageUpdatedTask(PackageUpdatedTask.OP_UNAVAILABLE,
-                                packagesUnavailable.toArray(new String[packagesUnavailable.size()]), user));
-                    }
-                }
-                sPendingPackages.clear();
-            }
-        }
-    }
-
-    private class PackageUpdatedTask implements Runnable {
-        int mOp;
-        String[] mPackages;
-        UserHandleCompat mUser;
-
-        public static final int OP_NONE = 0;
-        public static final int OP_ADD = 1;
-        public static final int OP_UPDATE = 2;
-        public static final int OP_REMOVE = 3; // uninstlled
-        public static final int OP_UNAVAILABLE = 4; // external media unmounted
-        public static final int OP_SUSPEND = 5; // package suspended
-        public static final int OP_UNSUSPEND = 6; // package unsuspended
-        public static final int OP_USER_AVAILABILITY_CHANGE = 7; // user available/unavailable
-
-        public PackageUpdatedTask(int op, String[] packages, UserHandleCompat user) {
-            mOp = op;
-            mPackages = packages;
-            mUser = user;
-        }
-
         public void run() {
-            if (!mHasLoaderCompletedOnce) {
+            if (!mModel.mHasLoaderCompletedOnce) {
                 // Loader has not yet run.
                 return;
             }
-            final Context context = mApp.getContext();
+            execute(mModel.mApp, sBgDataModel, mModel.mBgAllAppsList);
+        }
 
-            final String[] packages = mPackages;
-            final int N = packages.length;
-            FlagOp flagOp = FlagOp.NO_OP;
-            StringFilter pkgFilter = StringFilter.of(new HashSet<>(Arrays.asList(packages)));
-            switch (mOp) {
-                case OP_ADD: {
-                    for (int i=0; i<N; i++) {
-                        if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
-                        mIconCache.updateIconsForPkg(packages[i], mUser);
-                        mBgAllAppsList.addPackage(context, packages[i], mUser);
-                    }
+        /**
+         * Execute the actual task. Called on the worker thread.
+         */
+        public abstract void execute(
+                LauncherAppState app, BgDataModel dataModel, AllAppsList apps);
 
-                    ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser);
-                    if (heuristic != null) {
-                        heuristic.processPackageAdd(mPackages);
-                    }
-                    break;
-                }
-                case OP_UPDATE:
-                    for (int i=0; i<N; i++) {
-                        if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
-                        mIconCache.updateIconsForPkg(packages[i], mUser);
-                        mBgAllAppsList.updatePackage(context, packages[i], mUser);
-                        mApp.getWidgetCache().removePackage(packages[i], mUser);
-                    }
-                    // Since package was just updated, the target must be available now.
-                    flagOp = FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE);
-                    break;
-                case OP_REMOVE: {
-                    ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser);
-                    if (heuristic != null) {
-                        heuristic.processPackageRemoved(mPackages);
-                    }
-                    for (int i=0; i<N; i++) {
-                        if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
-                        mIconCache.removeIconsForPkg(packages[i], mUser);
-                    }
-                    // Fall through
-                }
-                case OP_UNAVAILABLE:
-                    for (int i=0; i<N; i++) {
-                        if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
-                        mBgAllAppsList.removePackage(packages[i], mUser);
-                        mApp.getWidgetCache().removePackage(packages[i], mUser);
-                    }
-                    flagOp = FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE);
-                    break;
-                case OP_SUSPEND:
-                case OP_UNSUSPEND:
-                    flagOp = mOp == OP_SUSPEND ?
-                            FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_SUSPENDED) :
-                                    FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_SUSPENDED);
-                    if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.(un)suspend " + N);
-                    mBgAllAppsList.updatePackageFlags(pkgFilter, mUser, flagOp);
-                    break;
-                case OP_USER_AVAILABILITY_CHANGE:
-                    flagOp = UserManagerCompat.getInstance(context).isQuietModeEnabled(mUser)
-                            ? FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_QUIET_USER)
-                            : FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_QUIET_USER);
-                    // We want to update all packages for this user.
-                    pkgFilter = StringFilter.matchesAll();
-                    mBgAllAppsList.updatePackageFlags(pkgFilter, mUser, flagOp);
-                    break;
-            }
-
-            ArrayList<AppInfo> added = null;
-            ArrayList<AppInfo> modified = null;
-            final ArrayList<AppInfo> removedApps = new ArrayList<AppInfo>();
-
-            if (mBgAllAppsList.added.size() > 0) {
-                added = new ArrayList<>(mBgAllAppsList.added);
-                mBgAllAppsList.added.clear();
-            }
-            if (mBgAllAppsList.modified.size() > 0) {
-                modified = new ArrayList<>(mBgAllAppsList.modified);
-                mBgAllAppsList.modified.clear();
-            }
-            if (mBgAllAppsList.removed.size() > 0) {
-                removedApps.addAll(mBgAllAppsList.removed);
-                mBgAllAppsList.removed.clear();
-            }
-
-            final HashMap<ComponentName, AppInfo> addedOrUpdatedApps = new HashMap<>();
-
-            if (added != null) {
-                addAppsToAllApps(context, added);
-                for (AppInfo ai : added) {
-                    addedOrUpdatedApps.put(ai.componentName, ai);
-                }
-            }
-
-            if (modified != null) {
-                final Callbacks callbacks = getCallback();
-                final ArrayList<AppInfo> modifiedFinal = modified;
-                for (AppInfo ai : modified) {
-                    addedOrUpdatedApps.put(ai.componentName, ai);
-                }
-
-                mHandler.post(new Runnable() {
-                    public void run() {
-                        Callbacks cb = getCallback();
-                        if (callbacks == cb && cb != null) {
-                            callbacks.bindAppsUpdated(modifiedFinal);
-                        }
-                    }
-                });
-            }
-
-            // Update shortcut infos
-            if (mOp == OP_ADD || flagOp != FlagOp.NO_OP) {
-                final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<ShortcutInfo>();
-                final ArrayList<ShortcutInfo> removedShortcuts = new ArrayList<ShortcutInfo>();
-                final ArrayList<LauncherAppWidgetInfo> widgets = new ArrayList<LauncherAppWidgetInfo>();
-
-                synchronized (sBgLock) {
-                    for (ItemInfo info : sBgItemsIdMap) {
-                        if (info instanceof ShortcutInfo && mUser.equals(info.user)) {
-                            ShortcutInfo si = (ShortcutInfo) info;
-                            boolean infoUpdated = false;
-                            boolean shortcutUpdated = false;
-
-                            // Update shortcuts which use iconResource.
-                            if ((si.iconResource != null)
-                                    && pkgFilter.matches(si.iconResource.packageName)) {
-                                Bitmap icon = Utilities.createIconBitmap(
-                                        si.iconResource.packageName,
-                                        si.iconResource.resourceName, context);
-                                if (icon != null) {
-                                    si.setIcon(icon);
-                                    si.usingFallbackIcon = false;
-                                    infoUpdated = true;
-                                }
-                            }
-
-                            ComponentName cn = si.getTargetComponent();
-                            if (cn != null && pkgFilter.matches(cn.getPackageName())) {
-                                AppInfo appInfo = addedOrUpdatedApps.get(cn);
-
-                                if (si.isPromise()) {
-                                    if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
-                                        // Auto install icon
-                                        PackageManager pm = context.getPackageManager();
-                                        ResolveInfo matched = pm.resolveActivity(
-                                                new Intent(Intent.ACTION_MAIN)
-                                                .setComponent(cn).addCategory(Intent.CATEGORY_LAUNCHER),
-                                                PackageManager.MATCH_DEFAULT_ONLY);
-                                        if (matched == null) {
-                                            // Try to find the best match activity.
-                                            Intent intent = pm.getLaunchIntentForPackage(
-                                                    cn.getPackageName());
-                                            if (intent != null) {
-                                                cn = intent.getComponent();
-                                                appInfo = addedOrUpdatedApps.get(cn);
-                                            }
-
-                                            if ((intent == null) || (appInfo == null)) {
-                                                removedShortcuts.add(si);
-                                                continue;
-                                            }
-                                            si.promisedIntent = intent;
-                                        }
-                                    }
-
-                                    // Restore the shortcut.
-                                    if (appInfo != null) {
-                                        si.flags = appInfo.flags;
-                                    }
-
-                                    si.intent = si.promisedIntent;
-                                    si.promisedIntent = null;
-                                    si.status = ShortcutInfo.DEFAULT;
-                                    infoUpdated = true;
-                                    si.updateIcon(mIconCache);
-                                }
-
-                                if (appInfo != null && Intent.ACTION_MAIN.equals(si.intent.getAction())
-                                        && si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
-                                    si.updateIcon(mIconCache);
-                                    si.title = Utilities.trim(appInfo.title);
-                                    si.contentDescription = appInfo.contentDescription;
-                                    infoUpdated = true;
-                                }
-
-                                int oldDisabledFlags = si.isDisabled;
-                                si.isDisabled = flagOp.apply(si.isDisabled);
-                                if (si.isDisabled != oldDisabledFlags) {
-                                    shortcutUpdated = true;
-                                }
-                            }
-
-                            if (infoUpdated || shortcutUpdated) {
-                                updatedShortcuts.add(si);
-                            }
-                            if (infoUpdated) {
-                                updateItemInDatabase(context, si);
-                            }
-                        } else if (info instanceof LauncherAppWidgetInfo && mOp == OP_ADD) {
-                            LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) info;
-                            if (mUser.equals(widgetInfo.user)
-                                    && widgetInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)
-                                    && pkgFilter.matches(widgetInfo.providerName.getPackageName())) {
-                                widgetInfo.restoreStatus &=
-                                        ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY &
-                                        ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
-
-                                // adding this flag ensures that launcher shows 'click to setup'
-                                // if the widget has a config activity. In case there is no config
-                                // activity, it will be marked as 'restored' during bind.
-                                widgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
-
-                                widgets.add(widgetInfo);
-                                updateItemInDatabase(context, widgetInfo);
-                            }
-                        }
+        /**
+         * Schedules a {@param task} to be executed on the current callbacks.
+         */
+        public final void scheduleCallbackTask(final CallbackTask task) {
+            final Callbacks callbacks = mModel.getCallback();
+            mUiHandler.post(new Runnable() {
+                public void run() {
+                    Callbacks cb = mModel.getCallback();
+                    if (callbacks == cb && cb != null) {
+                        task.execute(callbacks);
                     }
                 }
-
-                bindUpdatedShortcuts(updatedShortcuts, removedShortcuts, mUser);
-                if (!removedShortcuts.isEmpty()) {
-                    deleteItemsFromDatabase(context, removedShortcuts);
-                }
-
-                if (!widgets.isEmpty()) {
-                    final Callbacks callbacks = getCallback();
-                    mHandler.post(new Runnable() {
-                        public void run() {
-                            Callbacks cb = getCallback();
-                            if (callbacks == cb && cb != null) {
-                                callbacks.bindWidgetsRestored(widgets);
-                            }
-                        }
-                    });
-                }
-            }
-
-            final HashSet<String> removedPackages = new HashSet<>();
-            final HashSet<ComponentName> removedComponents = new HashSet<>();
-            if (mOp == OP_REMOVE) {
-                // Mark all packages in the broadcast to be removed
-                Collections.addAll(removedPackages, packages);
-
-                // No need to update the removedComponents as
-                // removedPackages is a super-set of removedComponents
-            } else if (mOp == OP_UPDATE) {
-                // Mark disabled packages in the broadcast to be removed
-                for (int i=0; i<N; i++) {
-                    if (isPackageDisabled(context, packages[i], mUser)) {
-                        removedPackages.add(packages[i]);
-                    }
-                }
-
-                // Update removedComponents as some components can get removed during package update
-                for (AppInfo info : removedApps) {
-                    removedComponents.add(info.componentName);
-                }
-            }
-
-            if (!removedPackages.isEmpty() || !removedComponents.isEmpty()) {
-                for (String pn : removedPackages) {
-                    deletePackageFromDatabase(context, pn, mUser);
-                }
-                for (ComponentName cn : removedComponents) {
-                    deleteItemsFromDatabase(context, getItemInfoForComponentName(cn, mUser));
-                }
-
-                // Remove any queued items from the install queue
-                InstallShortcutReceiver.removeFromInstallQueue(context, removedPackages, mUser);
-
-                // Call the components-removed callback
-                final Callbacks callbacks = getCallback();
-                mHandler.post(new Runnable() {
-                    public void run() {
-                        Callbacks cb = getCallback();
-                        if (callbacks == cb && cb != null) {
-                            callbacks.bindWorkspaceComponentsRemoved(
-                                    removedPackages, removedComponents, mUser);
-                        }
-                    }
-                });
-            }
-
-            if (!removedApps.isEmpty()) {
-                // Remove corresponding apps from All-Apps
-                final Callbacks callbacks = getCallback();
-                mHandler.post(new Runnable() {
-                    public void run() {
-                        Callbacks cb = getCallback();
-                        if (callbacks == cb && cb != null) {
-                            callbacks.bindAppInfosRemoved(removedApps);
-                        }
-                    }
-                });
-            }
-
-            // Notify launcher of widget update. From marshmallow onwards we use AppWidgetHost to
-            // get widget update signals.
-            if (!Utilities.ATLEAST_MARSHMALLOW &&
-                    (mOp == OP_ADD || mOp == OP_REMOVE || mOp == OP_UPDATE)) {
-                final Callbacks callbacks = getCallback();
-                mHandler.post(new Runnable() {
-                    public void run() {
-                        Callbacks cb = getCallback();
-                        if (callbacks == cb && cb != null) {
-                            callbacks.notifyWidgetProvidersChanged();
-                        }
-                    }
-                });
-            }
+            });
         }
     }
 
@@ -3353,177 +2456,27 @@
      * Repopulates the shortcut info, possibly updating any icon already on the workspace.
      */
     public void updateShortcutInfo(final ShortcutInfoCompat fullDetail, final ShortcutInfo info) {
-        enqueueItemUpdatedTask(new Runnable() {
+        enqueueModelUpdateTask(new ExtendedModelTask() {
             @Override
-            public void run() {
-                info.updateFromDeepShortcutInfo(
-                        fullDetail, LauncherAppState.getInstance().getContext());
-                ArrayList<ShortcutInfo> update = new ArrayList<ShortcutInfo>();
+            public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+                info.updateFromDeepShortcutInfo(fullDetail, app.getContext());
+
+                ArrayList<ShortcutInfo> update = new ArrayList<>();
                 update.add(info);
                 bindUpdatedShortcuts(update, fullDetail.getUserHandle());
             }
         });
     }
 
-    private class ShortcutsChangedTask implements Runnable {
-        private final String mPackageName;
-        private final List<ShortcutInfoCompat> mShortcuts;
-        private final UserHandleCompat mUser;
-        private final boolean mUpdateIdMap;
-
-        public ShortcutsChangedTask(String packageName, List<ShortcutInfoCompat> shortcuts,
-                UserHandleCompat user, boolean updateIdMap) {
-            mPackageName = packageName;
-            mShortcuts = shortcuts;
-            mUser = user;
-            mUpdateIdMap = updateIdMap;
-        }
-
-        @Override
-        public void run() {
-            mDeepShortcutManager.onShortcutsChanged(mShortcuts);
-
-            // Find ShortcutInfo's that have changed on the workspace.
-            final ArrayList<ShortcutInfo> removedShortcutInfos = new ArrayList<>();
-            MultiHashMap<String, ShortcutInfo> idsToWorkspaceShortcutInfos = new MultiHashMap<>();
-            for (ItemInfo itemInfo : sBgItemsIdMap) {
-                if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
-                    ShortcutInfo si = (ShortcutInfo) itemInfo;
-                    if (si.getPromisedIntent().getPackage().equals(mPackageName)
-                            && si.user.equals(mUser)) {
-                        idsToWorkspaceShortcutInfos.addToList(si.getDeepShortcutId(), si);
-                    }
-                }
-            }
-
-            final Context context = LauncherAppState.getInstance().getContext();
-            final ArrayList<ShortcutInfo> updatedShortcutInfos = new ArrayList<>();
-            if (!idsToWorkspaceShortcutInfos.isEmpty()) {
-                // Update the workspace to reflect the changes to updated shortcuts residing on it.
-                List<ShortcutInfoCompat> shortcuts = mDeepShortcutManager.queryForFullDetails(
-                        mPackageName, new ArrayList<>(idsToWorkspaceShortcutInfos.keySet()), mUser);
-                for (ShortcutInfoCompat fullDetails : shortcuts) {
-                    List<ShortcutInfo> shortcutInfos = idsToWorkspaceShortcutInfos
-                            .remove(fullDetails.getId());
-                    if (!fullDetails.isPinned()) {
-                        // The shortcut was previously pinned but is no longer, so remove it from
-                        // the workspace and our pinned shortcut counts.
-                        // Note that we put this check here, after querying for full details,
-                        // because there's a possible race condition between pinning and
-                        // receiving this callback.
-                        removedShortcutInfos.addAll(shortcutInfos);
-                        continue;
-                    }
-                    for (ShortcutInfo shortcutInfo : shortcutInfos) {
-                        shortcutInfo.updateFromDeepShortcutInfo(fullDetails, context);
-                        updatedShortcutInfos.add(shortcutInfo);
-                    }
-                }
-            }
-
-            // If there are still entries in idsToWorkspaceShortcutInfos, that means that
-            // the corresponding shortcuts weren't passed in onShortcutsChanged(). This
-            // means they were cleared, so we remove and unpin them now.
-            for (String id : idsToWorkspaceShortcutInfos.keySet()) {
-                removedShortcutInfos.addAll(idsToWorkspaceShortcutInfos.get(id));
-            }
-
-            bindUpdatedShortcuts(updatedShortcutInfos, removedShortcutInfos, mUser);
-            if (!removedShortcutInfos.isEmpty()) {
-                deleteItemsFromDatabase(context, removedShortcutInfos);
-            }
-
-            if (mUpdateIdMap) {
-                // Update the deep shortcut map if the list of ids has changed for an activity.
-                updateDeepShortcutMap(mPackageName, mUser, mShortcuts);
-                bindDeepShortcuts();
-            }
-        }
-    }
-
-    /**
-     * Task to handle changing of lock state of the user
-     */
-    private class UserLockStateChangedTask implements Runnable {
-
-        private final UserHandleCompat mUser;
-
-        public UserLockStateChangedTask(UserHandleCompat user) {
-            mUser = user;
-        }
-
-        @Override
-        public void run() {
-            boolean isUserUnlocked = mUserManager.isUserUnlocked(mUser);
-            Context context = mApp.getContext();
-
-            HashMap<ShortcutKey, ShortcutInfoCompat> pinnedShortcuts = new HashMap<>();
-            if (isUserUnlocked) {
-                List<ShortcutInfoCompat> shortcuts =
-                        mDeepShortcutManager.queryForPinnedShortcuts(null, mUser);
-                if (mDeepShortcutManager.wasLastCallSuccess()) {
-                    for (ShortcutInfoCompat shortcut : shortcuts) {
-                        pinnedShortcuts.put(ShortcutKey.fromInfo(shortcut), shortcut);
-                    }
-                } else {
-                    // Shortcut manager can fail due to some race condition when the lock state
-                    // changes too frequently. For the purpose of the update,
-                    // consider it as still locked.
-                    isUserUnlocked = false;
-                }
-            }
-
-            // Update the workspace to reflect the changes to updated shortcuts residing on it.
-            ArrayList<ShortcutInfo> updatedShortcutInfos = new ArrayList<>();
-            ArrayList<ShortcutInfo> deletedShortcutInfos = new ArrayList<>();
-            for (ItemInfo itemInfo : sBgItemsIdMap) {
-                if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
-                        && mUser.equals(itemInfo.user)) {
-                    ShortcutInfo si = (ShortcutInfo) itemInfo;
-                    if (isUserUnlocked) {
-                        ShortcutInfoCompat shortcut =
-                                pinnedShortcuts.get(ShortcutKey.fromShortcutInfo(si));
-                        // We couldn't verify the shortcut during loader. If its no longer available
-                        // (probably due to clear data), delete the workspace item as well
-                        if (shortcut == null) {
-                            deletedShortcutInfos.add(si);
-                            continue;
-                        }
-                        si.isDisabled &= ~ShortcutInfo.FLAG_DISABLED_LOCKED_USER;
-                        si.updateFromDeepShortcutInfo(shortcut, context);
-                    } else {
-                        si.isDisabled |= ShortcutInfo.FLAG_DISABLED_LOCKED_USER;
-                    }
-                    updatedShortcutInfos.add(si);
-                }
-            }
-            bindUpdatedShortcuts(updatedShortcutInfos, deletedShortcutInfos, mUser);
-            if (!deletedShortcutInfos.isEmpty()) {
-                deleteItemsFromDatabase(context, deletedShortcutInfos);
-            }
-
-            // Remove shortcut id map for that user
-            Iterator<ComponentKey> keysIter = mBgDeepShortcutMap.keySet().iterator();
-            while (keysIter.hasNext()) {
-                if (keysIter.next().user.equals(mUser)) {
-                    keysIter.remove();
-                }
-            }
-
-            if (isUserUnlocked) {
-                updateDeepShortcutMap(null, mUser, mDeepShortcutManager.queryForAllShortcuts(mUser));
-            }
-            bindDeepShortcuts();
-        }
-    }
-
-    private void bindWidgetsModel(final Callbacks callbacks, final WidgetsModel model) {
+    private void bindWidgetsModel(final Callbacks callbacks) {
+        final MultiHashMap<PackageItemInfo, WidgetItem> widgets
+                = mBgWidgetsModel.getWidgetsMap().clone();
         mHandler.post(new Runnable() {
             @Override
             public void run() {
                 Callbacks cb = getCallback();
                 if (callbacks == cb && cb != null) {
-                    callbacks.bindWidgetsModel(model);
+                    callbacks.bindAllWidgets(widgets);
                 }
             }
         });
@@ -3535,44 +2488,17 @@
             @Override
             public void run() {
                 if (bindFirst && !mBgWidgetsModel.isEmpty()) {
-                    bindWidgetsModel(callbacks, mBgWidgetsModel.clone());
+                    bindWidgetsModel(callbacks);
                 }
-                final WidgetsModel model = mBgWidgetsModel.updateAndClone(mApp.getContext());
-                bindWidgetsModel(callbacks, model);
+                ArrayList<WidgetItem> allWidgets = mBgWidgetsModel.update(mApp.getContext());
+                bindWidgetsModel(callbacks);
+
                 // update the Widget entries inside DB on the worker thread.
-                LauncherAppState.getInstance().getWidgetCache().removeObsoletePreviews(
-                        model.getRawList());
+                LauncherAppState.getInstance().getWidgetCache().removeObsoletePreviews(allWidgets);
             }
         });
     }
 
-    @Thunk static boolean isPackageDisabled(Context context, String packageName,
-            UserHandleCompat user) {
-        final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
-        return !launcherApps.isPackageEnabledForProfile(packageName, user);
-    }
-
-    public static boolean isValidPackageActivity(Context context, ComponentName cn,
-            UserHandleCompat user) {
-        if (cn == null) {
-            return false;
-        }
-        final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
-        if (!launcherApps.isPackageEnabledForProfile(cn.getPackageName(), user)) {
-            return false;
-        }
-        return launcherApps.isActivityEnabledForProfile(cn, user);
-    }
-
-    public static boolean isValidPackage(Context context, String packageName,
-            UserHandleCompat user) {
-        if (packageName == null) {
-            return false;
-        }
-        final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
-        return launcherApps.isPackageEnabledForProfile(packageName, user);
-    }
-
     /**
      * Make an ShortcutInfo object for a restored application or shortcut item that points
      * to a package that is not yet installed on the system.
@@ -3680,56 +2606,9 @@
         info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
         info.user = user;
         info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
-        if (lai != null) {
-            info.flags = AppInfo.initFlags(lai);
-        }
         return info;
     }
 
-    static ArrayList<ItemInfo> filterItemInfos(Iterable<ItemInfo> infos,
-            ItemInfoFilter f) {
-        HashSet<ItemInfo> filtered = new HashSet<ItemInfo>();
-        for (ItemInfo i : infos) {
-            if (i instanceof ShortcutInfo) {
-                ShortcutInfo info = (ShortcutInfo) i;
-                ComponentName cn = info.getTargetComponent();
-                if (cn != null && f.filterItem(null, info, cn)) {
-                    filtered.add(info);
-                }
-            } else if (i instanceof FolderInfo) {
-                FolderInfo info = (FolderInfo) i;
-                for (ShortcutInfo s : info.contents) {
-                    ComponentName cn = s.getTargetComponent();
-                    if (cn != null && f.filterItem(info, s, cn)) {
-                        filtered.add(s);
-                    }
-                }
-            } else if (i instanceof LauncherAppWidgetInfo) {
-                LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) i;
-                ComponentName cn = info.providerName;
-                if (cn != null && f.filterItem(null, info, cn)) {
-                    filtered.add(info);
-                }
-            }
-        }
-        return new ArrayList<ItemInfo>(filtered);
-    }
-
-    @Thunk ArrayList<ItemInfo> getItemInfoForComponentName(final ComponentName cname,
-            final UserHandleCompat user) {
-        ItemInfoFilter filter  = new ItemInfoFilter() {
-            @Override
-            public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
-                if (info.user == null) {
-                    return cn.equals(cname);
-                } else {
-                    return cn.equals(cname) && info.user.equals(user);
-                }
-            }
-        };
-        return filterItemInfos(sBgItemsIdMap, filter);
-    }
-
     /**
      * Make an ShortcutInfo object for a shortcut that isn't an application.
      */
@@ -3754,7 +2633,6 @@
         // the fallback icon
         if (icon == null) {
             icon = mIconCache.getDefaultIcon(info.user);
-            info.usingFallbackIcon = true;
         }
         info.setIcon(icon);
     }
@@ -3771,17 +2649,15 @@
         }
 
         Bitmap icon = null;
-        boolean customIcon = false;
         ShortcutIconResource iconResource = null;
 
         if (bitmap instanceof Bitmap) {
-            icon = Utilities.createIconBitmap((Bitmap) bitmap, context);
-            customIcon = true;
+            icon = LauncherIcons.createIconBitmap((Bitmap) bitmap, context);
         } else {
             Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
             if (extra instanceof ShortcutIconResource) {
                 iconResource = (ShortcutIconResource) extra;
-                icon = Utilities.createIconBitmap(iconResource.packageName,
+                icon = LauncherIcons.createIconBitmap(iconResource.packageName,
                         iconResource.resourceName, context);
             }
         }
@@ -3793,7 +2669,6 @@
         info.user = UserHandleCompat.myUserHandle();
         if (icon == null) {
             icon = mIconCache.getDefaultIcon(info.user);
-            info.usingFallbackIcon = true;
         }
         info.setIcon(icon);
 
@@ -3805,22 +2680,6 @@
         return info;
     }
 
-    /**
-     * Return an existing FolderInfo object if we have encountered this ID previously,
-     * or make a new one.
-     */
-    @Thunk static FolderInfo findOrMakeFolder(LongArrayMap<FolderInfo> folders, long id) {
-        // See if a placeholder was created for us already
-        FolderInfo folderInfo = folders.get(id);
-        if (folderInfo == null) {
-            // No placeholder -- create a new instance
-            folderInfo = new FolderInfo();
-            folders.put(id, folderInfo);
-        }
-        return folderInfo;
-    }
-
-
     static boolean isValidProvider(AppWidgetProviderInfo provider) {
         return (provider != null) && (provider.provider != null)
                 && (provider.provider.getPackageName() != null);
@@ -3847,8 +2706,8 @@
      * @return {@link FolderInfo} if its already loaded.
      */
     public FolderInfo findFolderById(Long folderId) {
-        synchronized (sBgLock) {
-            return sBgFolders.get(folderId);
+        synchronized (sBgDataModel) {
+            return sBgDataModel.folders.get(folderId);
         }
     }
 
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index f3d9493..349f094 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -649,11 +649,8 @@
             // Database was just created, so wipe any previous widgets
             if (mWidgetHostResetHandler != null) {
                 new AppWidgetHost(mContext, Launcher.APPWIDGET_HOST_ID).deleteHost();
-                mWidgetHostResetHandler.sendMessage(Message.obtain(
-                        mWidgetHostResetHandler,
-                        ChangeListenerWrapper.MSG_APP_WIDGET_HOST_RESET,
-                        mContext
-                ));
+                mWidgetHostResetHandler.sendEmptyMessage(
+                        ChangeListenerWrapper.MSG_EXTRACTED_COLORS_CHANGED);
             }
 
             // Set the flag for empty DB
@@ -764,12 +761,7 @@
                     }
                 }
                 case 16: {
-                    // We use the db version upgrade here to identify users who may not have seen
-                    // clings yet (because they weren't available), but for whom the clings are now
-                    // available (tablet users). Because one of the possible cling flows (migration)
-                    // is very destructive (wipes out workspaces), we want to prevent this from showing
-                    // until clear data. We do so by marking that the clings have been shown.
-                    LauncherClings.markFirstRunClingDismissed(mContext);
+                    // No-op
                 }
                 case 17: {
                     // No-op
@@ -1019,7 +1011,7 @@
 
         public void checkId(String table, ContentValues values) {
             long id = values.getAsLong(LauncherSettings.BaseLauncherColumns._ID);
-            if (table == WorkspaceScreens.TABLE_NAME) {
+            if (WorkspaceScreens.TABLE_NAME.equals(table)) {
                 mMaxScreenId = Math.max(id, mMaxScreenId);
             }  else {
                 mMaxItemId = Math.max(id, mMaxItemId);
@@ -1141,17 +1133,13 @@
             if (mListener != null) {
                 switch (msg.what) {
                     case MSG_LAUNCHER_PROVIDER_CHANGED:
-                        mListener.onLauncherProviderChange();
+                        mListener.onLauncherProviderChanged();
                         break;
                     case MSG_EXTRACTED_COLORS_CHANGED:
                         mListener.onExtractedColorsChanged();
                         break;
                     case MSG_APP_WIDGET_HOST_RESET:
-                        Context context = (Context) msg.obj;
-                        if (context != null) {
-                            context.sendBroadcast(new Intent(Launcher.ACTION_APPWIDGET_HOST_RESET)
-                                    .setPackage(context.getPackageName()));
-                        }
+                        mListener.onAppWidgetHostReset();
                         break;
                 }
             }
diff --git a/src/com/android/launcher3/LauncherProviderChangeListener.java b/src/com/android/launcher3/LauncherProviderChangeListener.java
index 5998dad..7044812 100644
--- a/src/com/android/launcher3/LauncherProviderChangeListener.java
+++ b/src/com/android/launcher3/LauncherProviderChangeListener.java
@@ -7,7 +7,9 @@
  */
 public interface LauncherProviderChangeListener {
 
-    public void onLauncherProviderChange();
+    void onLauncherProviderChanged();
 
-    public void onExtractedColorsChanged();
+    void onExtractedColorsChanged();
+
+    void onAppWidgetHostReset();
 }
diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
index eb70650..19cc0fb 100644
--- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java
+++ b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
@@ -23,24 +23,23 @@
 import android.animation.PropertyValuesHolder;
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
-import android.annotation.SuppressLint;
 import android.annotation.TargetApi;
 import android.content.res.Resources;
 import android.os.Build;
 import android.util.Log;
 import android.view.View;
 import android.view.animation.AccelerateInterpolator;
+import android.view.animation.Animation;
 import android.view.animation.DecelerateInterpolator;
 
 import com.android.launcher3.allapps.AllAppsContainerView;
 import com.android.launcher3.allapps.AllAppsTransitionController;
+import com.android.launcher3.anim.AnimationLayerSet;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.CircleRevealOutlineProvider;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.widget.WidgetsContainerView;
 
-import java.util.HashMap;
-
 /**
  * TODO: figure out what kind of tests we can write for this
  *
@@ -119,9 +118,6 @@
 
     public static final String TAG = "LSTAnimation";
 
-    // Flags to determine how to set the layers on views before the transition animation
-    public static final int BUILD_LAYER = 0;
-    public static final int BUILD_AND_SET_LAYER = 1;
     public static final int SINGLE_FRAME_DELAY = 16;
 
     @Thunk Launcher mLauncher;
@@ -139,7 +135,7 @@
      * @param startSearchAfterTransition Immediately starts app search after the transition to
      *                                   All Apps is completed.
      */
-    public void startAnimationToAllApps(final Workspace.State fromWorkspaceState,
+    public void startAnimationToAllApps(
             final boolean animated, final boolean startSearchAfterTransition) {
         final AllAppsContainerView toView = mLauncher.getAppsView();
         final View buttonView = mLauncher.getStartViewForAllAppsRevealAnimation();
@@ -174,18 +170,17 @@
             animType = PULLUP;
         }
         // Only animate the search bar if animating from spring loaded mode back to all apps
-        startAnimationToOverlay(fromWorkspaceState,
+        startAnimationToOverlay(
                 Workspace.State.NORMAL_HIDDEN, buttonView, toView, animated, animType, cb);
     }
 
     /**
      * Starts an animation to the widgets view.
      */
-    public void startAnimationToWidgets(final Workspace.State fromWorkspaceState,
-            final boolean animated) {
+    public void startAnimationToWidgets(final boolean animated) {
         final WidgetsContainerView toView = mLauncher.getWidgetsView();
         final View buttonView = mLauncher.getWidgetsButton();
-        startAnimationToOverlay(fromWorkspaceState,
+        startAnimationToOverlay(
                 Workspace.State.OVERVIEW_HIDDEN, buttonView, toView, animated, CIRCULAR_REVEAL,
                 new PrivateTransitionCallbacks(FINAL_REVEAL_ALPHA_FOR_WIDGETS){
                     @Override
@@ -228,9 +223,8 @@
     /**
      * Creates and starts a new animation to a particular overlay view.
      */
-    @SuppressLint("NewApi")
     private void startAnimationToOverlay(
-            final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState,
+            final Workspace.State toWorkspaceState,
             final View buttonView, final BaseContainerView toView,
             final boolean animated, int animType, final PrivateTransitionCallbacks pCb) {
         final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
@@ -241,9 +235,7 @@
 
         final int itemsAlphaStagger = res.getInteger(R.integer.config_overlayItemsAlphaStagger);
 
-        final View fromView = mLauncher.getWorkspace();
-
-        final HashMap<View, Integer> layerViews = new HashMap<>();
+        final AnimationLayerSet layerViews = new AnimationLayerSet();
 
         // If for some reason our views aren't initialized, don't animate
         boolean initialized = buttonView != null;
@@ -252,7 +244,7 @@
         cancelAnimation();
 
         final View contentView = toView.getContentView();
-        playCommonTransitionAnimations(toWorkspaceState, fromView, toView,
+        playCommonTransitionAnimations(toWorkspaceState,
                 animated, initialized, animation, layerViews);
         if (!animated || !initialized) {
             if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP &&
@@ -268,13 +260,6 @@
 
             // Show the content view
             contentView.setVisibility(View.VISIBLE);
-
-            dispatchOnLauncherTransitionPrepare(fromView, animated, false);
-            dispatchOnLauncherTransitionStart(fromView, animated, false);
-            dispatchOnLauncherTransitionEnd(fromView, animated, false);
-            dispatchOnLauncherTransitionPrepare(toView, animated, false);
-            dispatchOnLauncherTransitionStart(toView, animated, false);
-            dispatchOnLauncherTransitionEnd(toView, animated, false);
             pCb.onTransitionComplete();
             return;
         }
@@ -319,14 +304,14 @@
             panelAlphaAndDrift.setInterpolator(new LogDecelerateInterpolator(100, 0));
 
             // Play the animation
-            layerViews.put(revealView, BUILD_AND_SET_LAYER);
+            layerViews.addView(revealView);
             animation.play(panelAlphaAndDrift);
 
             // Setup the animation for the content view
             contentView.setVisibility(View.VISIBLE);
             contentView.setAlpha(0f);
             contentView.setTranslationY(revealViewToYDrift);
-            layerViews.put(contentView, BUILD_AND_SET_LAYER);
+            layerViews.addView(contentView);
 
             // Create the individual animators
             ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY",
@@ -359,19 +344,9 @@
             animation.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
-                    dispatchOnLauncherTransitionEnd(fromView, animated, false);
-                    dispatchOnLauncherTransitionEnd(toView, animated, false);
-
                     // Hide the reveal view
                     revealView.setVisibility(View.INVISIBLE);
 
-                    // Disable all necessary layers
-                    for (View v : layerViews.keySet()) {
-                        if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
-                            v.setLayerType(View.LAYER_TYPE_NONE, null);
-                        }
-                    }
-
                     // This can hold unnecessary references to views.
                     cleanupAnimation();
                     pCb.onTransitionComplete();
@@ -379,92 +354,28 @@
 
             });
 
-            // Dispatch the prepare transition signal
-            dispatchOnLauncherTransitionPrepare(fromView, animated, false);
-            dispatchOnLauncherTransitionPrepare(toView, animated, false);
-
-            final AnimatorSet stateAnimation = animation;
-            final Runnable startAnimRunnable = new Runnable() {
-                public void run() {
-                    // Check that mCurrentAnimation hasn't changed while
-                    // we waited for a layout/draw pass
-                    if (mCurrentAnimation != stateAnimation)
-                        return;
-                    dispatchOnLauncherTransitionStart(fromView, animated, false);
-                    dispatchOnLauncherTransitionStart(toView, animated, false);
-
-                    // Enable all necessary layers
-                    for (View v : layerViews.keySet()) {
-                        if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
-                            v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
-                        }
-                        if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
-                            v.buildLayer();
-                        }
-                    }
-
-                    // Focus the new view
-                    toView.requestFocus();
-
-                    stateAnimation.start();
-                }
-            };
             toView.bringToFront();
             toView.setVisibility(View.VISIBLE);
-            toView.post(startAnimRunnable);
+
+            animation.addListener(layerViews);
+            toView.post(new StartAnimRunnable(animation, toView));
             mCurrentAnimation = animation;
         } else if (animType == PULLUP) {
             // We are animating the content view alpha, so ensure we have a layer for it
-            layerViews.put(contentView, BUILD_AND_SET_LAYER);
+            layerViews.addView(contentView);
 
             animation.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
-                    dispatchOnLauncherTransitionEnd(fromView, animated, false);
-                    dispatchOnLauncherTransitionEnd(toView, animated, false);
-
-                    // Disable all necessary layers
-                    for (View v : layerViews.keySet()) {
-                        if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
-                            v.setLayerType(View.LAYER_TYPE_NONE, null);
-                        }
-                    }
-
                     cleanupAnimation();
                     pCb.onTransitionComplete();
                 }
             });
             boolean shouldPost = mAllAppsController.animateToAllApps(animation, revealDurationSlide);
 
-            dispatchOnLauncherTransitionPrepare(fromView, animated, false);
-            dispatchOnLauncherTransitionPrepare(toView, animated, false);
-
-            final AnimatorSet stateAnimation = animation;
-            final Runnable startAnimRunnable = new Runnable() {
-                public void run() {
-                    // Check that mCurrentAnimation hasn't changed while
-                    // we waited for a layout/draw pass
-                    if (mCurrentAnimation != stateAnimation)
-                        return;
-
-                    dispatchOnLauncherTransitionStart(fromView, animated, false);
-                    dispatchOnLauncherTransitionStart(toView, animated, false);
-
-                    // Enable all necessary layers
-                    for (View v : layerViews.keySet()) {
-                        if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
-                            v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
-                        }
-                        if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
-                            v.buildLayer();
-                        }
-                    }
-
-                    toView.requestFocus();
-                    stateAnimation.start();
-                }
-            };
+            Runnable startAnimRunnable = new StartAnimRunnable(animation, toView);
             mCurrentAnimation = animation;
+            mCurrentAnimation.addListener(layerViews);
             if (shouldPost) {
                 toView.post(startAnimRunnable);
             } else {
@@ -477,9 +388,9 @@
      * Plays animations used by various transitions.
      */
     private void playCommonTransitionAnimations(
-            Workspace.State toWorkspaceState, View fromView, View toView,
+            Workspace.State toWorkspaceState,
             boolean animated, boolean initialized, AnimatorSet animation,
-            HashMap<View, Integer> layerViews) {
+            AnimationLayerSet layerViews) {
         // Create the workspace animation.
         // NOTE: this call apparently also sets the state for the workspace if !animated
         Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState,
@@ -490,36 +401,16 @@
             if (workspaceAnim != null) {
                 animation.play(workspaceAnim);
             }
-            // Dispatch onLauncherTransitionStep() as the animation interpolates.
-            animation.play(dispatchOnLauncherTransitionStepAnim(fromView, toView));
         }
     }
 
     /**
-     * Returns an Animator that calls {@link #dispatchOnLauncherTransitionStep(View, float)} on
-     * {@param fromView} and {@param toView} as the animation interpolates.
-     *
-     * This is a bit hacky: we create a dummy ValueAnimator just for the AnimatorUpdateListener.
-     */
-    private Animator dispatchOnLauncherTransitionStepAnim(final View fromView, final View toView) {
-        ValueAnimator updateAnimator = ValueAnimator.ofFloat(0, 1);
-        updateAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                dispatchOnLauncherTransitionStep(fromView, animation.getAnimatedFraction());
-                dispatchOnLauncherTransitionStep(toView, animation.getAnimatedFraction());
-            }
-        });
-        return updateAnimator;
-    }
-
-    /**
      * Starts an animation to the workspace from the apps view.
      */
     private void startAnimationToWorkspaceFromAllApps(final Workspace.State fromWorkspaceState,
             final Workspace.State toWorkspaceState, final boolean animated, int type,
             final Runnable onCompleteRunnable) {
-        AllAppsContainerView appsView = mLauncher.getAppsView();
+        final AllAppsContainerView appsView = mLauncher.getAppsView();
         // No alpha anim from all apps
         PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks(1f) {
             @Override
@@ -549,6 +440,7 @@
             @Override
             void onTransitionComplete() {
                 mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
+                appsView.reset();
             }
         };
         // Only animate the search bar if animating to spring loaded mode from all apps
@@ -594,73 +486,32 @@
             final Workspace.State toWorkspaceState, final boolean animated,
             final Runnable onCompleteRunnable) {
         final View fromWorkspace = mLauncher.getWorkspace();
-        final HashMap<View, Integer> layerViews = new HashMap<>();
+        final AnimationLayerSet layerViews = new AnimationLayerSet();
         final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
-        final int revealDuration = mLauncher.getResources()
-                .getInteger(R.integer.config_overlayRevealTime);
 
         // Cancel the current animation
         cancelAnimation();
 
-        boolean multiplePagesVisible = toWorkspaceState.hasMultipleVisiblePages;
-
-        playCommonTransitionAnimations(toWorkspaceState, fromWorkspace, null,
-                animated, animated, animation, layerViews);
+        playCommonTransitionAnimations(toWorkspaceState, animated, animated, animation, layerViews);
+        mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
 
         if (animated) {
-            dispatchOnLauncherTransitionPrepare(fromWorkspace, animated, multiplePagesVisible);
-
-            final AnimatorSet stateAnimation = animation;
-            final Runnable startAnimRunnable = new Runnable() {
-                @TargetApi(Build.VERSION_CODES.LOLLIPOP)
-                public void run() {
-                    // Check that mCurrentAnimation hasn't changed while
-                    // we waited for a layout/draw pass
-                    if (mCurrentAnimation != stateAnimation)
-                        return;
-
-                    dispatchOnLauncherTransitionStart(fromWorkspace, animated, true);
-
-                    // Enable all necessary layers
-                    for (View v : layerViews.keySet()) {
-                        if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
-                            v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
-                        }
-                        if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
-                            v.buildLayer();
-                        }
-                    }
-                    stateAnimation.start();
-                }
-            };
             animation.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
-                    dispatchOnLauncherTransitionEnd(fromWorkspace, animated, true);
-
                     // Run any queued runnables
                     if (onCompleteRunnable != null) {
                         onCompleteRunnable.run();
                     }
 
-                    // Disable all necessary layers
-                    for (View v : layerViews.keySet()) {
-                        if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
-                            v.setLayerType(View.LAYER_TYPE_NONE, null);
-                        }
-                    }
-
                     // This can hold unnecessary references to views.
                     cleanupAnimation();
                 }
             });
-            fromWorkspace.post(startAnimRunnable);
+            animation.addListener(layerViews);
+            fromWorkspace.post(new StartAnimRunnable(animation, null));
             mCurrentAnimation = animation;
         } else /* if (!animated) */ {
-            dispatchOnLauncherTransitionPrepare(fromWorkspace, animated, multiplePagesVisible);
-            dispatchOnLauncherTransitionStart(fromWorkspace, animated, true);
-            dispatchOnLauncherTransitionEnd(fromWorkspace, animated, true);
-
             // Run any queued runnables
             if (onCompleteRunnable != null) {
                 onCompleteRunnable.run();
@@ -690,7 +541,7 @@
         final View revealView = fromView.getRevealView();
         final View contentView = fromView.getContentView();
 
-        final HashMap<View, Integer> layerViews = new HashMap<>();
+        final AnimationLayerSet layerViews = new AnimationLayerSet();
 
         // If for some reason our views aren't initialized, don't animate
         boolean initialized = buttonView != null;
@@ -698,9 +549,7 @@
         // Cancel the current animation
         cancelAnimation();
 
-        boolean multiplePagesVisible = toWorkspaceState.hasMultipleVisiblePages;
-
-        playCommonTransitionAnimations(toWorkspaceState, fromView, toView,
+        playCommonTransitionAnimations(toWorkspaceState,
                 animated, initialized, animation, layerViews);
         if (!animated || !initialized) {
             if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP &&
@@ -708,12 +557,6 @@
                 mAllAppsController.finishPullDown();
             }
             fromView.setVisibility(View.GONE);
-            dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible);
-            dispatchOnLauncherTransitionStart(fromView, animated, true);
-            dispatchOnLauncherTransitionEnd(fromView, animated, true);
-            dispatchOnLauncherTransitionPrepare(toView, animated, multiplePagesVisible);
-            dispatchOnLauncherTransitionStart(toView, animated, true);
-            dispatchOnLauncherTransitionEnd(toView, animated, true);
             pCb.onTransitionComplete();
 
             // Run any queued runnables
@@ -733,7 +576,7 @@
                 revealView.setVisibility(View.VISIBLE);
                 revealView.setAlpha(1f);
                 revealView.setTranslationY(0);
-                layerViews.put(revealView, BUILD_AND_SET_LAYER);
+                layerViews.addView(revealView);
 
                 // Calculate the final animation values
                 final float revealViewToXDrift;
@@ -781,7 +624,7 @@
                 }
 
                 // Setup the animation for the content view
-                layerViews.put(contentView, BUILD_AND_SET_LAYER);
+                layerViews.addView(contentView);
 
                 // Create the individual animators
                 ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY",
@@ -826,28 +669,15 @@
                 }
             }
 
-            dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible);
-            dispatchOnLauncherTransitionPrepare(toView, animated, multiplePagesVisible);
-
             animation.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
                     fromView.setVisibility(View.GONE);
-                    dispatchOnLauncherTransitionEnd(fromView, animated, true);
-                    dispatchOnLauncherTransitionEnd(toView, animated, true);
-
                     // Run any queued runnables
                     if (onCompleteRunnable != null) {
                         onCompleteRunnable.run();
                     }
 
-                    // Disable all necessary layers
-                    for (View v : layerViews.keySet()) {
-                        if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
-                            v.setLayerType(View.LAYER_TYPE_NONE, null);
-                        }
-                    }
-
                     // Reset page transforms
                     if (contentView != null) {
                         contentView.setTranslationX(0);
@@ -861,35 +691,12 @@
                 }
             });
 
-            final AnimatorSet stateAnimation = animation;
-            final Runnable startAnimRunnable = new Runnable() {
-                @TargetApi(Build.VERSION_CODES.LOLLIPOP)
-                public void run() {
-                    // Check that mCurrentAnimation hasn't changed while
-                    // we waited for a layout/draw pass
-                    if (mCurrentAnimation != stateAnimation)
-                        return;
-
-                    dispatchOnLauncherTransitionStart(fromView, animated, false);
-                    dispatchOnLauncherTransitionStart(toView, animated, false);
-
-                    // Enable all necessary layers
-                    for (View v : layerViews.keySet()) {
-                        if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
-                            v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
-                        }
-                        if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
-                            v.buildLayer();
-                        }
-                    }
-                    stateAnimation.start();
-                }
-            };
             mCurrentAnimation = animation;
-            fromView.post(startAnimRunnable);
+            mCurrentAnimation.addListener(layerViews);
+            fromView.post(new StartAnimRunnable(animation, null));
         } else if (animType == PULLUP) {
             // We are animating the content view alpha, so ensure we have a layer for it
-            layerViews.put(contentView, BUILD_AND_SET_LAYER);
+            layerViews.addView(contentView);
 
             animation.addListener(new AnimatorListenerAdapter() {
                 boolean canceled = false;
@@ -901,20 +708,11 @@
                 @Override
                 public void onAnimationEnd(Animator animation) {
                     if (canceled) return;
-                    dispatchOnLauncherTransitionEnd(fromView, animated, true);
-                    dispatchOnLauncherTransitionEnd(toView, animated, true);
                     // Run any queued runnables
                     if (onCompleteRunnable != null) {
                         onCompleteRunnable.run();
                     }
 
-                    // Disable all necessary layers
-                    for (View v : layerViews.keySet()) {
-                        if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
-                            v.setLayerType(View.LAYER_TYPE_NONE, null);
-                        }
-                    }
-
                     cleanupAnimation();
                     pCb.onTransitionComplete();
                 }
@@ -922,37 +720,9 @@
             });
             boolean shouldPost = mAllAppsController.animateToWorkspace(animation, revealDurationSlide);
 
-            // Dispatch the prepare transition signal
-            dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible);
-            dispatchOnLauncherTransitionPrepare(toView, animated, multiplePagesVisible);
-
-            final AnimatorSet stateAnimation = animation;
-            final Runnable startAnimRunnable = new Runnable() {
-                public void run() {
-                    // Check that mCurrentAnimation hasn't changed while
-                    // we waited for a layout/draw pass
-                    if (mCurrentAnimation != stateAnimation)
-                        return;
-
-                    dispatchOnLauncherTransitionStart(fromView, animated, false);
-                    dispatchOnLauncherTransitionStart(toView, animated, false);
-
-                    // Enable all necessary layers
-                    for (View v : layerViews.keySet()) {
-                        if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
-                            v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
-                        }
-                        if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
-                            v.buildLayer();
-                        }
-                    }
-
-                    // Focus the new view
-                    toView.requestFocus();
-                    stateAnimation.start();
-                }
-            };
+            Runnable startAnimRunnable = new StartAnimRunnable(animation, toView);
             mCurrentAnimation = animation;
+            mCurrentAnimation.addListener(layerViews);
             if (shouldPost) {
                 fromView.post(startAnimRunnable);
             } else {
@@ -963,52 +733,6 @@
     }
 
     /**
-     * Dispatches the prepare-transition event to suitable views.
-     */
-    void dispatchOnLauncherTransitionPrepare(View v, boolean animated,
-            boolean multiplePagesVisible) {
-        if (v instanceof LauncherTransitionable) {
-            ((LauncherTransitionable) v).onLauncherTransitionPrepare(mLauncher, animated,
-                    multiplePagesVisible);
-        }
-    }
-
-    /**
-     * Dispatches the start-transition event to suitable views.
-     */
-    void dispatchOnLauncherTransitionStart(View v, boolean animated, boolean toWorkspace) {
-        if (v instanceof LauncherTransitionable) {
-            ((LauncherTransitionable) v).onLauncherTransitionStart(mLauncher, animated,
-                    toWorkspace);
-        }
-
-        // Update the workspace transition step as well
-        dispatchOnLauncherTransitionStep(v, 0f);
-    }
-
-    /**
-     * Dispatches the step-transition event to suitable views.
-     */
-    void dispatchOnLauncherTransitionStep(View v, float t) {
-        if (v instanceof LauncherTransitionable) {
-            ((LauncherTransitionable) v).onLauncherTransitionStep(mLauncher, t);
-        }
-    }
-
-    /**
-     * Dispatches the end-transition event to suitable views.
-     */
-    void dispatchOnLauncherTransitionEnd(View v, boolean animated, boolean toWorkspace) {
-        if (v instanceof LauncherTransitionable) {
-            ((LauncherTransitionable) v).onLauncherTransitionEnd(mLauncher, animated,
-                    toWorkspace);
-        }
-
-        // Update the workspace transition step as well
-        dispatchOnLauncherTransitionStep(v, 1f);
-    }
-
-    /**
      * Cancels the current animation.
      */
     private void cancelAnimation() {
@@ -1022,4 +746,26 @@
     @Thunk void cleanupAnimation() {
         mCurrentAnimation = null;
     }
+
+    private class StartAnimRunnable implements Runnable {
+
+        private final AnimatorSet mAnim;
+        private final View mViewToFocus;
+
+        public StartAnimRunnable(AnimatorSet anim, View viewToFocus) {
+            mAnim = anim;
+            mViewToFocus = viewToFocus;
+        }
+
+        @Override
+        public void run() {
+            if (mCurrentAnimation != mAnim) {
+                return;
+            }
+            if (mViewToFocus != null) {
+                mViewToFocus.requestFocus();
+            }
+            mAnim.start();
+        }
+    }
 }
diff --git a/src/com/android/launcher3/LauncherTransitionable.java b/src/com/android/launcher3/LauncherTransitionable.java
deleted file mode 100644
index b97aaec..0000000
--- a/src/com/android/launcher3/LauncherTransitionable.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.launcher3;
-
-/**
- * An interface to get callbacks during a launcher transition.
- */
-public interface LauncherTransitionable {
-    void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean multiplePagesVisible);
-    void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace);
-    void onLauncherTransitionStep(Launcher l, float t);
-    void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace);
-}
diff --git a/src/com/android/launcher3/OverviewButtonClickListener.java b/src/com/android/launcher3/OverviewButtonClickListener.java
new file mode 100644
index 0000000..c98f1d7
--- /dev/null
+++ b/src/com/android/launcher3/OverviewButtonClickListener.java
@@ -0,0 +1,51 @@
+package com.android.launcher3;
+
+import android.view.View;
+
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+
+/**
+ * A specialized listener for Overview buttons where both clicks and long clicks are logged
+ * handled the same via {@link #handleViewClick(View)}.
+ */
+public abstract class OverviewButtonClickListener implements View.OnClickListener,
+        View.OnLongClickListener {
+
+    private int mControlType; /** ControlType enum as defined in {@link LauncherLogProto} */
+
+    public OverviewButtonClickListener(int controlType) {
+        mControlType = controlType;
+    }
+
+    public void attachTo(View v) {
+        v.setOnClickListener(this);
+        v.setOnLongClickListener(this);
+    }
+
+    @Override
+    public void onClick(View view) {
+        if (shouldPerformClick(view)) {
+            handleViewClick(view, LauncherLogProto.Action.TAP);
+        }
+    }
+
+    @Override
+    public boolean onLongClick(View view) {
+        if (shouldPerformClick(view)) {
+            handleViewClick(view, LauncherLogProto.Action.LONGPRESS);
+        }
+        return true;
+    }
+
+    private boolean shouldPerformClick(View view) {
+        return !Launcher.getLauncher(view.getContext()).getWorkspace().isSwitchingState();
+    }
+
+    private void handleViewClick(View view, int action) {
+        handleViewClick(view);
+        Launcher.getLauncher(view.getContext()).getUserEventDispatcher()
+                .logActionOnControl(action, mControlType);
+    }
+
+    public abstract void handleViewClick(View view);
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index e380e26..ee7f9f8 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -28,7 +28,6 @@
 import android.graphics.Canvas;
 import android.graphics.Matrix;
 import android.graphics.Rect;
-import android.graphics.RectF;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Parcel;
@@ -98,7 +97,6 @@
 
     @ViewDebug.ExportedProperty(category = "launcher")
     protected int mCurrentPage;
-    protected int mRestorePage = INVALID_RESTORE_PAGE;
     private int mChildCountOnLastLayout;
 
     @ViewDebug.ExportedProperty(category = "launcher")
@@ -119,7 +117,6 @@
     private float mLastMotionXRemainder;
     private float mLastMotionY;
     private float mTotalMotionX;
-    private int mLastScreenCenter = -1;
 
     private boolean mCancelTap;
 
@@ -132,7 +129,6 @@
     protected final static int TOUCH_STATE_REORDERING = 4;
 
     protected int mTouchState = TOUCH_STATE_REST;
-    private boolean mForceScreenScrolled = false;
 
     protected OnLongClickListener mLongClickListener;
 
@@ -145,7 +141,7 @@
 
     protected int mActivePointerId = INVALID_POINTER;
 
-    protected boolean mIsPageMoving = false;
+    protected boolean mIsPageInTransition = false;
 
     protected boolean mWasInOverscroll = false;
 
@@ -184,7 +180,6 @@
     private static final float[] sTmpPoint = new float[2];
     private static final int[] sTmpIntPoint = new int[2];
     private static final Rect sTmpRect = new Rect();
-    private static final RectF sTmpRectF = new RectF();
 
     protected final Rect mInsets = new Rect();
     protected final boolean mIsRtl;
@@ -410,7 +405,6 @@
         if (getChildCount() == 0) {
             return;
         }
-        mForceScreenScrolled = true;
         mCurrentPage = validateNewPage(currentPage);
         updateCurrentPageScroll();
         notifyPageSwitchListener();
@@ -418,17 +412,6 @@
     }
 
     /**
-     * The restore page will be set in place of the current page at the next (likely first)
-     * layout.
-     */
-    void setRestorePage(int restorePage) {
-        mRestorePage = restorePage;
-    }
-    int getRestorePage() {
-        return mRestorePage;
-    }
-
-    /**
      * Should be called whenever the page changes. In the case of a scroll, we wait until the page
      * has settled.
      */
@@ -445,30 +428,36 @@
             }
         }
     }
-    protected void pageBeginMoving() {
-        if (!mIsPageMoving) {
-            mIsPageMoving = true;
-            onPageBeginMoving();
+    protected void pageBeginTransition() {
+        if (!mIsPageInTransition) {
+            mIsPageInTransition = true;
+            onPageBeginTransition();
         }
     }
 
-    protected void pageEndMoving() {
-        if (mIsPageMoving) {
-            mIsPageMoving = false;
-            onPageEndMoving();
+    protected void pageEndTransition() {
+        if (mIsPageInTransition) {
+            mIsPageInTransition = false;
+            onPageEndTransition();
         }
     }
 
-    protected boolean isPageMoving() {
-        return mIsPageMoving;
+    protected boolean isPageInTransition() {
+        return mIsPageInTransition;
     }
 
-    // a method that subclasses can override to add behavior
-    protected void onPageBeginMoving() {
+    /**
+     * Called when the page starts moving as part of the scroll. Subclasses can override this
+     * to provide custom behavior during animation.
+     */
+    protected void onPageBeginTransition() {
     }
 
-    // a method that subclasses can override to add behavior
-    protected void onPageEndMoving() {
+    /**
+     * Called when the page ends moving as part of the scroll. Subclasses can override this
+     * to provide custom behavior during animation.
+     */
+    protected void onPageEndTransition() {
         mWasInOverscroll = false;
     }
 
@@ -596,7 +585,7 @@
             // We don't want to trigger a page end moving unless the page has settled
             // and the user has stopped scrolling
             if (mTouchState == TOUCH_STATE_REST) {
-                pageEndMoving();
+                pageEndTransition();
             }
 
             onPostReorderingAnimationCompleted();
@@ -879,12 +868,7 @@
         }
 
         if (mScroller.isFinished() && mChildCountOnLastLayout != childCount) {
-            if (mRestorePage != INVALID_RESTORE_PAGE) {
-                setCurrentPage(mRestorePage);
-                mRestorePage = INVALID_RESTORE_PAGE;
-            } else {
-                setCurrentPage(getNextPage());
-            }
+            setCurrentPage(getNextPage());
         }
         mChildCountOnLastLayout = childCount;
 
@@ -916,11 +900,6 @@
         requestLayout();
     }
 
-    /**
-     * Called when the center screen changes during scrolling.
-     */
-    protected void screenScrolled(int screenCenter) { }
-
     @Override
     public void onChildViewAdded(View parent, View child) {
         // Update the page indicator, we don't update the page indicator as we
@@ -931,14 +910,12 @@
 
         // This ensures that when children are added, they get the correct transforms / alphas
         // in accordance with any scroll effects.
-        mForceScreenScrolled = true;
         updateFreescrollBounds();
         invalidate();
     }
 
     @Override
     public void onChildViewRemoved(View parent, View child) {
-        mForceScreenScrolled = true;
         updateFreescrollBounds();
         invalidate();
     }
@@ -996,99 +973,6 @@
         range[1] = Math.max(0, getChildCount() - 1);
     }
 
-    protected void getVisiblePages(int[] range) {
-        final int count = getChildCount();
-        range[0] = -1;
-        range[1] = -1;
-
-        if (count > 0) {
-            final int visibleLeft = -getLeft();
-            final int visibleRight = visibleLeft + getViewportWidth();
-            final Matrix pageShiftMatrix = getPageShiftMatrix();
-            int curScreen = 0;
-
-            for (int i = 0; i < count; i++) {
-                View currPage = getPageAt(i);
-
-                // Verify if the page bounds are within the visible range.
-                sTmpRectF.left = 0;
-                sTmpRectF.right = currPage.getMeasuredWidth();
-                currPage.getMatrix().mapRect(sTmpRectF);
-                sTmpRectF.offset(currPage.getLeft() - getScrollX(), 0);
-                pageShiftMatrix.mapRect(sTmpRectF);
-
-                if (sTmpRectF.left > visibleRight || sTmpRectF.right < visibleLeft) {
-                    if (range[0] == -1) {
-                        continue;
-                    } else {
-                        break;
-                    }
-                }
-                curScreen = i;
-                if (range[0] < 0) {
-                    range[0] = curScreen;
-                }
-            }
-
-            range[1] = curScreen;
-        } else {
-            range[0] = -1;
-            range[1] = -1;
-        }
-    }
-
-    protected Matrix getPageShiftMatrix() {
-        return getMatrix();
-    }
-
-    protected boolean shouldDrawChild(View child) {
-        return child.getVisibility() == VISIBLE;
-    }
-
-    @Override
-    protected void dispatchDraw(Canvas canvas) {
-        // Find out which screens are visible; as an optimization we only call draw on them
-        final int pageCount = getChildCount();
-        if (pageCount > 0) {
-            int halfScreenSize = getViewportWidth() / 2;
-            int screenCenter = getScrollX() + halfScreenSize;
-
-            if (screenCenter != mLastScreenCenter || mForceScreenScrolled) {
-                // set mForceScreenScrolled before calling screenScrolled so that screenScrolled can
-                // set it for the next frame
-                mForceScreenScrolled = false;
-                screenScrolled(screenCenter);
-                mLastScreenCenter = screenCenter;
-            }
-
-            getVisiblePages(mTempVisiblePagesRange);
-            final int leftScreen = mTempVisiblePagesRange[0];
-            final int rightScreen = mTempVisiblePagesRange[1];
-            if (leftScreen != -1 && rightScreen != -1) {
-                final long drawingTime = getDrawingTime();
-                // Clip to the bounds
-                canvas.save();
-                canvas.clipRect(getScrollX(), getScrollY(), getScrollX() + getRight() - getLeft(),
-                        getScrollY() + getBottom() - getTop());
-
-                // Draw all the children, leaving the drag view for last
-                for (int i = pageCount - 1; i >= 0; i--) {
-                    final View v = getPageAt(i);
-                    if (v == mDragView) continue;
-                    if (leftScreen <= i && i <= rightScreen && shouldDrawChild(v)) {
-                        drawChild(canvas, v, drawingTime);
-                    }
-                }
-                // Draw the drag view on top (if there is one)
-                if (mDragView != null) {
-                    drawChild(canvas, mDragView, drawingTime);
-                }
-
-                canvas.restore();
-            }
-        }
-    }
-
     @Override
     public void draw(Canvas canvas) {
         super.draw(canvas);
@@ -1099,7 +983,7 @@
                 canvas.translate(display.left, display.top);
                 canvas.rotate(270);
 
-                getEdgeVerticalPostion(sTmpIntPoint);
+                getEdgeVerticalPosition(sTmpIntPoint);
                 canvas.translate(display.top - sTmpIntPoint[1], 0);
                 mEdgeGlowLeft.setSize(sTmpIntPoint[1] - sTmpIntPoint[0], display.width());
                 if (mEdgeGlowLeft.draw(canvas)) {
@@ -1113,7 +997,7 @@
                 canvas.translate(display.left + mPageScrolls[mIsRtl ? 0 : (getPageCount() - 1)], display.top);
                 canvas.rotate(90);
 
-                getEdgeVerticalPostion(sTmpIntPoint);
+                getEdgeVerticalPosition(sTmpIntPoint);
 
                 canvas.translate(sTmpIntPoint[0] - display.top, -display.width());
                 mEdgeGlowRight.setSize(sTmpIntPoint[1] - sTmpIntPoint[0], display.width());
@@ -1128,7 +1012,7 @@
     /**
      * Returns the top and bottom position for the edge effect.
      */
-    protected abstract void getEdgeVerticalPostion(int[] pos);
+    protected abstract void getEdgeVerticalPosition(int[] pos);
 
     @Override
     public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
@@ -1184,6 +1068,10 @@
 
     @Override
     public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
+        if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) {
+            return;
+        }
+
         // XXX-RTL: This will be fixed in a future CL
         if (mCurrentPage >= 0 && mCurrentPage < getPageCount()) {
             getPageAt(mCurrentPage).addFocusables(views, direction, focusableMode);
@@ -1338,7 +1226,7 @@
                     mTouchState = TOUCH_STATE_REST;
                     if (!mScroller.isFinished() && !mFreeScroll) {
                         setCurrentPage(getNextPage());
-                        pageEndMoving();
+                        pageEndTransition();
                     }
                 } else {
                     if (isTouchPointInViewportWithBuffer((int) mDownMotionX, (int) mDownMotionY)) {
@@ -1399,7 +1287,7 @@
             mLastMotionX = x;
             mLastMotionXRemainder = 0;
             onScrollInteractionBegin();
-            pageBeginMoving();
+            pageBeginTransition();
             // Stop listening for things like pinches.
             requestDisallowInterceptTouchEvent(true);
         }
@@ -1581,7 +1469,7 @@
 
             if (mTouchState == TOUCH_STATE_SCROLLING) {
                 onScrollInteractionBegin();
-                pageBeginMoving();
+                pageBeginTransition();
             }
             break;
 
@@ -2027,7 +1915,6 @@
 
         mNextPage = whichPage;
 
-        pageBeginMoving();
         awakenScrollBars(duration);
         if (immediate) {
             duration = 0;
@@ -2035,6 +1922,10 @@
             duration = Math.abs(delta);
         }
 
+        if (duration != 0) {
+            pageBeginTransition();
+        }
+
         if (!mScroller.isFinished()) {
             abortScrollerAnimation(false);
         }
@@ -2054,7 +1945,6 @@
             computeScroll();
         }
 
-        mForceScreenScrolled = true;
         invalidate();
     }
 
diff --git a/src/com/android/launcher3/PendingAddItemInfo.java b/src/com/android/launcher3/PendingAddItemInfo.java
index 31820d7..76de3e7 100644
--- a/src/com/android/launcher3/PendingAddItemInfo.java
+++ b/src/com/android/launcher3/PendingAddItemInfo.java
@@ -20,7 +20,7 @@
 
 /**
  * Meta data that is used for deferred binding.
- * e.g., this object is used to pass information on dragable targets when they are dropped onto
+ * e.g., this object is used to pass information on draggable targets when they are dropped onto
  * the workspace from another container.
  */
 public class PendingAddItemInfo extends ItemInfo {
diff --git a/src/com/android/launcher3/PendingAppWidgetHostView.java b/src/com/android/launcher3/PendingAppWidgetHostView.java
index f01c7f2..bf39774 100644
--- a/src/com/android/launcher3/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/PendingAppWidgetHostView.java
@@ -134,7 +134,7 @@
             //   3) Setup icon in the center and app icon in the top right corner.
             if (mDisabledForSafeMode) {
                 FastBitmapDrawable disabledIcon = mLauncher.createIconDrawable(mIcon);
-                disabledIcon.setState(FastBitmapDrawable.State.DISABLED);
+                disabledIcon.setIsDisabled(true);
                 mCenterDrawable = disabledIcon;
                 mSettingIconDrawable = null;
             } else if (isReadyForClickSetup()) {
diff --git a/src/com/android/launcher3/PinchAnimationManager.java b/src/com/android/launcher3/PinchAnimationManager.java
index baeb77c..c1d60fd 100644
--- a/src/com/android/launcher3/PinchAnimationManager.java
+++ b/src/com/android/launcher3/PinchAnimationManager.java
@@ -24,6 +24,8 @@
 import android.view.View;
 import android.view.animation.LinearInterpolator;
 
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+
 import static com.android.launcher3.Workspace.State.NORMAL;
 import static com.android.launcher3.Workspace.State.OVERVIEW;
 
@@ -58,7 +60,6 @@
 
     private final Animator[] mAnimators = new Animator[4];
 
-    private final int[] mVisiblePageRange = new int[2];
     private Launcher mLauncher;
     private Workspace mWorkspace;
 
@@ -107,7 +108,7 @@
             public void onAnimationEnd(Animator animation) {
                 mIsAnimating = false;
                 thresholdManager.reset();
-                mWorkspace.onLauncherTransitionEnd(mLauncher, false, true);
+                mWorkspace.onEndStateTransition();
             }
         });
         animator.setDuration(duration).start();
@@ -162,9 +163,15 @@
         } else if (threshold == PinchThresholdManager.THRESHOLD_THREE) {
             // Passing threshold 3 ends the pinch and snaps to the new state.
             if (startState == OVERVIEW && goingTowards == NORMAL) {
+                mLauncher.getUserEventDispatcher().logActionOnContainer(
+                        LauncherLogProto.Action.PINCH, LauncherLogProto.Action.NONE,
+                        LauncherLogProto.OVERVIEW, mWorkspace.getCurrentPage());
                 mLauncher.showWorkspace(true);
                 mWorkspace.snapToPage(mWorkspace.getCurrentPage());
             } else if (startState == NORMAL && goingTowards == OVERVIEW) {
+                mLauncher.getUserEventDispatcher().logActionOnContainer(
+                        LauncherLogProto.Action.PINCH, LauncherLogProto.Action.NONE,
+                        LauncherLogProto.WORKSPACE, mWorkspace.getCurrentPage());
                 mLauncher.showOverviewMode(true);
             }
         } else {
@@ -173,17 +180,13 @@
     }
 
     private void setOverviewPanelsAlpha(float alpha, int duration) {
-        mWorkspace.getVisiblePages(mVisiblePageRange);
-        for (int i = mVisiblePageRange[0]; i <= mVisiblePageRange[1]; i++) {
-            View page = mWorkspace.getPageAt(i);
-            if (!mWorkspace.shouldDrawChild(page)) {
-                continue;
-            }
+        int childCount = mWorkspace.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final CellLayout cl = (CellLayout) mWorkspace.getChildAt(i);
             if (duration == 0) {
-                ((CellLayout) page).setBackgroundAlpha(alpha);
+                cl.setBackgroundAlpha(alpha);
             } else {
-                ObjectAnimator.ofFloat(page, "backgroundAlpha", alpha)
-                        .setDuration(duration).start();
+                ObjectAnimator.ofFloat(cl, "backgroundAlpha", alpha).setDuration(duration).start();
             }
         }
     }
diff --git a/src/com/android/launcher3/PinchToOverviewListener.java b/src/com/android/launcher3/PinchToOverviewListener.java
index 48a75d1..42515d1 100644
--- a/src/com/android/launcher3/PinchToOverviewListener.java
+++ b/src/com/android/launcher3/PinchToOverviewListener.java
@@ -61,12 +61,12 @@
         mPinchDetector = new ScaleGestureDetector((Context) mLauncher, this);
     }
 
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
+    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
         mPinchDetector.onTouchEvent(ev);
         return mPinchStarted;
     }
 
-    public boolean onTouchEvent(MotionEvent ev) {
+    public boolean onControllerTouchEvent(MotionEvent ev) {
         if (mPinchStarted) {
             if (ev.getPointerCount() > 2) {
                 // Using more than two fingers causes weird behavior, so just cancel the pinch.
@@ -102,7 +102,7 @@
             // once the state switching animation is complete.
             return false;
         }
-        if (mLauncher.getTopFloatingView() != null) {
+        if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
             // Don't listen for the pinch gesture if a floating view is open.
             return false;
         }
@@ -112,7 +112,7 @@
         mInterpolator = mWorkspace.isInOverviewMode() ? new LogDecelerateInterpolator(100, 0)
                 : new LogAccelerateInterpolator(100, 0);
         mPinchStarted = true;
-        mWorkspace.onLauncherTransitionPrepare(mLauncher, false, true);
+        mWorkspace.onPrepareStateTransition(true);
         return true;
     }
 
@@ -142,7 +142,7 @@
                     mThresholdManager);
         } else {
             mThresholdManager.reset();
-            mWorkspace.onLauncherTransitionEnd(mLauncher, false, true);
+            mWorkspace.onEndStateTransition();
         }
         mPinchStarted = false;
         mPinchCanceled = false;
diff --git a/src/com/android/launcher3/PreloadIconDrawable.java b/src/com/android/launcher3/PreloadIconDrawable.java
index b064c47..efc0eac 100644
--- a/src/com/android/launcher3/PreloadIconDrawable.java
+++ b/src/com/android/launcher3/PreloadIconDrawable.java
@@ -18,7 +18,7 @@
     private static final float ANIMATION_PROGRESS_STARTED = 0f;
     private static final float ANIMATION_PROGRESS_COMPLETED = 1.0f;
 
-    private static final float MIN_SATUNATION = 0.2f;
+    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;
@@ -177,10 +177,8 @@
             // 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).setState(level <= 0 ?
-                    FastBitmapDrawable.State.DISABLED : FastBitmapDrawable.State.NORMAL);
+        } else if (mIcon instanceof FastBitmapDrawable) {
+            ((FastBitmapDrawable) mIcon).setIsDisabled(true);
         }
 
         invalidateSelf();
@@ -242,7 +240,7 @@
         // 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_SATUNATION) {
+        if (hsv[1] < MIN_SATURATION) {
             mIndicatorColor = DEFAULT_COLOR;
             return mIndicatorColor;
         }
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index 37cbf98..5f89af6 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -18,13 +18,25 @@
 
 import android.app.WallpaperManager;
 import android.content.Context;
-import android.graphics.Paint;
 import android.graphics.Rect;
+import android.support.annotation.IntDef;
 import android.view.View;
 import android.view.ViewGroup;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 public class ShortcutAndWidgetContainer extends ViewGroup {
-    static final String TAG = "CellLayoutChildren";
+    static final String TAG = "ShortcutAndWidgetContainer";
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({DEFAULT, HOTSEAT, FOLDER})
+    public @interface ContainerType{}
+    public static final int DEFAULT = 0;
+    public static final int HOTSEAT = 1;
+    public static final int FOLDER = 2;
+
+    private int mContainerType = DEFAULT;
 
     // These are temporary variables to prevent having to allocate a new object just to
     // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
@@ -32,14 +44,9 @@
 
     private final WallpaperManager mWallpaperManager;
 
-    private boolean mIsHotseatLayout;
-
     private int mCellWidth;
     private int mCellHeight;
 
-    private int mWidthGap;
-    private int mHeightGap;
-
     private int mCountX;
 
     private Launcher mLauncher;
@@ -52,12 +59,9 @@
         mWallpaperManager = WallpaperManager.getInstance(context);
     }
 
-    public void setCellDimensions(int cellWidth, int cellHeight, int widthGap, int heightGap,
-            int countX, int countY) {
+    public void setCellDimensions(int cellWidth, int cellHeight, int countX, int countY) {
         mCellWidth = cellWidth;
         mCellHeight = cellHeight;
-        mWidthGap = widthGap;
-        mHeightGap = heightGap;
         mCountX = countX;
     }
 
@@ -92,8 +96,7 @@
     }
 
     public void setupLp(CellLayout.LayoutParams lp) {
-        lp.setup(mCellWidth, mCellHeight, mWidthGap, mHeightGap, invertLayoutHorizontally(),
-                mCountX);
+        lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX);
     }
 
     // Set whether or not to invert the layout horizontally if the layout is in RTL mode.
@@ -101,20 +104,19 @@
         mInvertIfRtl = invert;
     }
 
-    public void setIsHotseat(boolean isHotseat) {
-        mIsHotseatLayout = isHotseat;
-    }
-
-    int getCellContentWidth() {
-        final DeviceProfile grid = mLauncher.getDeviceProfile();
-        return Math.min(getMeasuredHeight(), mIsHotseatLayout ?
-                grid.hotseatCellWidthPx: grid.cellWidthPx);
+    public void setContainerType(@ContainerType int containerType) {
+        mContainerType = containerType;
     }
 
     int getCellContentHeight() {
         final DeviceProfile grid = mLauncher.getDeviceProfile();
-        return Math.min(getMeasuredHeight(), mIsHotseatLayout ?
-                grid.hotseatCellHeightPx : grid.cellHeightPx);
+        int cellContentHeight = grid.cellHeightPx;
+        if (mContainerType == HOTSEAT) {
+            cellContentHeight = grid.hotseatCellHeightPx;
+        } else if (mContainerType == FOLDER) {
+            cellContentHeight = grid.folderCellHeightPx;
+        }
+        return Math.min(getMeasuredHeight(), cellContentHeight);
     }
 
     public void measureChild(View child) {
@@ -123,8 +125,7 @@
         final int cellHeight = mCellHeight;
         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
         if (!lp.isFullscreen) {
-            lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, invertLayoutHorizontally(),
-                    mCountX);
+            lp.setup(cellWidth, cellHeight, invertLayoutHorizontally(), mCountX);
 
             if (child instanceof LauncherAppWidgetHostView) {
                 // Widgets have their own padding, so skip
@@ -201,28 +202,4 @@
             child.cancelLongPress();
         }
     }
-
-    @Override
-    protected void setChildrenDrawingCacheEnabled(boolean enabled) {
-        final int count = getChildCount();
-        for (int i = 0; i < count; i++) {
-            final View view = getChildAt(i);
-            view.setDrawingCacheEnabled(enabled);
-            // Update the drawing caches
-            if (!view.isHardwareAccelerated() && enabled) {
-                view.buildDrawingCache(true);
-            }
-        }
-    }
-
-    protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
-        super.setChildrenDrawnWithCacheEnabled(enabled);
-    }
-
-    @Override
-    public void setLayerType(int layerType, Paint paint) {
-        // When clip children is disabled do not use hardware layer,
-        // as hardware layer forces clip children.
-        super.setLayerType(getClipChildren() ? layerType : LAYER_TYPE_NONE, paint);
-    }
 }
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index fb93743..d6d03d3 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -18,7 +18,6 @@
 
 import android.annotation.TargetApi;
 import android.content.ComponentName;
-import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Bitmap;
@@ -31,7 +30,10 @@
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.graphics.LauncherIcons;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+import com.android.launcher3.util.ContentWriter;
 
 /**
  * Represents a launchable icon on the workspaces and in folders.
@@ -76,12 +78,6 @@
     public Intent intent;
 
     /**
-     * Indicates whether we're using the default fallback icon instead of something from the
-     * app.
-     */
-    boolean usingFallbackIcon;
-
-    /**
      * Indicates whether we're using a low res icon
      */
     boolean usingLowResIcon;
@@ -131,7 +127,7 @@
      * Could be disabled, if the the app is installed but unavailable (eg. in safe mode or when
      * sd-card is not available).
      */
-    int isDisabled = DEFAULT;
+    public int isDisabled = DEFAULT;
 
     /**
      * A message to display when the user tries to start a disabled shortcut.
@@ -139,7 +135,7 @@
      */
     CharSequence disabledMessage;
 
-    int status;
+    public int status;
 
     /**
      * The installation progress [0-100] of the package that this shortcut represents.
@@ -147,16 +143,11 @@
     private int mInstallProgress;
 
     /**
-     * TODO move this to {@link #status}
-     */
-    int flags = 0;
-
-    /**
      * If this shortcut is a placeholder, then intent will be a market intent for the package, and
      * this will hold the original intent from the database.  Otherwise, null.
      * Refer {@link #FLAG_RESTORED_ICON}, {@link #FLAG_AUTOINTALL_ICON}
      */
-    Intent promisedIntent;
+    public Intent promisedIntent;
 
     public ShortcutInfo() {
         itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT;
@@ -188,11 +179,9 @@
         intent = new Intent(info.intent);
         iconResource = info.iconResource;
         mIcon = info.mIcon; // TODO: should make a copy here.  maybe we don't need this ctor at all
-        flags = info.flags;
         status = info.status;
         mInstallProgress = info.mInstallProgress;
         isDisabled = info.isDisabled;
-        usingFallbackIcon = info.usingFallbackIcon;
     }
 
     /** TODO: Remove this.  It's only called by ApplicationInfo.makeShortcut. */
@@ -200,7 +189,6 @@
         super(info);
         title = Utilities.trim(info.title);
         intent = new Intent(info.intent);
-        flags = info.flags;
         isDisabled = info.isDisabled;
     }
 
@@ -211,7 +199,6 @@
                 .getBadgedLabelForUser(info.getLabel(), info.getUser());
         intent = AppInfo.makeLaunchIntent(context, info, info.getUser());
         itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
-        flags = AppInfo.initFlags(info);
     }
 
     /**
@@ -221,7 +208,6 @@
     public ShortcutInfo(ShortcutInfoCompat shortcutInfo, Context context) {
         user = shortcutInfo.getUserHandle();
         itemType = LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
-        flags = 0;
         updateFromDeepShortcutInfo(shortcutInfo, context);
     }
 
@@ -248,25 +234,19 @@
     }
 
     @Override
-    void onAddToDatabase(Context context, ContentValues values) {
-        super.onAddToDatabase(context, values);
+    void onAddToDatabase(ContentWriter writer) {
+        super.onAddToDatabase(writer);
+        writer.put(LauncherSettings.BaseLauncherColumns.TITLE, title)
+                .put(LauncherSettings.BaseLauncherColumns.INTENT, getPromisedIntent())
+                .put(LauncherSettings.Favorites.RESTORED, status);
 
-        String titleStr = title != null ? title.toString() : null;
-        values.put(LauncherSettings.BaseLauncherColumns.TITLE, titleStr);
-
-        String uri = promisedIntent != null ? promisedIntent.toUri(0)
-                : (intent != null ? intent.toUri(0) : null);
-        values.put(LauncherSettings.BaseLauncherColumns.INTENT, uri);
-        values.put(LauncherSettings.Favorites.RESTORED, status);
-
-        if (!usingFallbackIcon && !usingLowResIcon) {
-            writeBitmap(values, mIcon);
+        if (!usingLowResIcon) {
+            writer.putIcon(mIcon, user);
         }
         if (iconResource != null) {
-            values.put(LauncherSettings.BaseLauncherColumns.ICON_PACKAGE,
-                    iconResource.packageName);
-            values.put(LauncherSettings.BaseLauncherColumns.ICON_RESOURCE,
-                    iconResource.resourceName);
+            writer.put(LauncherSettings.BaseLauncherColumns.ICON_PACKAGE, iconResource.packageName)
+                    .put(LauncherSettings.BaseLauncherColumns.ICON_RESOURCE,
+                            iconResource.resourceName);
         }
     }
 
@@ -316,20 +296,20 @@
 
         // TODO: Use cache for this
         LauncherAppState launcherAppState = LauncherAppState.getInstance();
-        Drawable unbadgedDrawable = launcherAppState.getShortcutManager()
+        Drawable unbadgedDrawable = DeepShortcutManager.getInstance(context)
                 .getShortcutIconDrawable(shortcutInfo,
                         launcherAppState.getInvariantDeviceProfile().fillResIconDpi);
 
         IconCache cache = launcherAppState.getIconCache();
         Bitmap unbadgedBitmap = unbadgedDrawable == null
                 ? cache.getDefaultIcon(UserHandleCompat.myUserHandle())
-                : Utilities.createScaledBitmapWithoutShadow(unbadgedDrawable, context);
+                : LauncherIcons.createScaledBitmapWithoutShadow(unbadgedDrawable, context);
         setIcon(getBadgedIcon(unbadgedBitmap, shortcutInfo, cache, context));
     }
 
     protected Bitmap getBadgedIcon(Bitmap unbadgedBitmap, ShortcutInfoCompat shortcutInfo,
             IconCache cache, Context context) {
-        unbadgedBitmap = Utilities.addShadowToIcon(unbadgedBitmap);
+        unbadgedBitmap = LauncherIcons.addShadowToIcon(unbadgedBitmap);
         // Get the app info for the source activity.
         AppInfo appInfo = new AppInfo();
         appInfo.user = user;
@@ -338,9 +318,9 @@
             cache.getTitleAndIcon(appInfo, shortcutInfo.getActivityInfo(context), false);
         } catch (NullPointerException e) {
             // This may happen when we fail to load the activity info. Worst case ignore badging.
-            return Utilities.badgeIconForUser(unbadgedBitmap, user, context);
+            return LauncherIcons.badgeIconForUser(unbadgedBitmap, user, context);
         }
-        return Utilities.badgeWithBitmap(unbadgedBitmap, appInfo.iconBitmap, context);
+        return LauncherIcons.badgeWithBitmap(unbadgedBitmap, appInfo.iconBitmap, context);
     }
 
     /** Returns the ShortcutInfo id associated with the deep shortcut. */
diff --git a/src/com/android/launcher3/UninstallDropTarget.java b/src/com/android/launcher3/UninstallDropTarget.java
index 9153943..7ea9aca 100644
--- a/src/com/android/launcher3/UninstallDropTarget.java
+++ b/src/com/android/launcher3/UninstallDropTarget.java
@@ -4,6 +4,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ApplicationInfo;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
@@ -12,6 +13,8 @@
 import android.util.Pair;
 import android.widget.Toast;
 
+import com.android.launcher3.compat.LauncherActivityInfoCompat;
+import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.UserHandleCompat;
 
 public class UninstallDropTarget extends ButtonDropTarget {
@@ -49,23 +52,34 @@
             }
         }
 
-        Pair<ComponentName, Integer> componentInfo = getAppInfoFlags(info);
-        return componentInfo != null && (componentInfo.second & AppInfo.DOWNLOADED_FLAG) != 0;
+        return getUninstallTarget(context, info) != null;
     }
 
     /**
-     * @return the component name and flags if {@param info} is an AppInfo or an app shortcut.
+     * @return the component name that should be uninstalled or null.
      */
-    private static Pair<ComponentName, Integer> getAppInfoFlags(Object item) {
+    private static ComponentName getUninstallTarget(Context context, Object item) {
+        Intent intent = null;
+        UserHandleCompat user = null;
         if (item instanceof AppInfo) {
             AppInfo info = (AppInfo) item;
-            return Pair.create(info.componentName, info.flags);
+            intent = info.intent;
+            user = info.user;
         } else if (item instanceof ShortcutInfo) {
             ShortcutInfo info = (ShortcutInfo) item;
-            ComponentName component = info.getTargetComponent();
-            if (info.itemType == LauncherSettings.BaseLauncherColumns.ITEM_TYPE_APPLICATION
-                    && component != null) {
-                return Pair.create(component, info.flags);
+            if (info.itemType == LauncherSettings.BaseLauncherColumns.ITEM_TYPE_APPLICATION) {
+                // Do not use restore/target intent here as we cannot uninstall an app which is
+                // being installed/restored.
+                intent = info.intent;
+                user = info.user;
+            }
+        }
+        if (intent != null) {
+            LauncherActivityInfoCompat info = LauncherAppsCompat.getInstance(context)
+                    .resolveActivity(intent, user);
+            if (info != null
+                    && (info.getApplicationInfo().flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
+                return info.getComponentName();
             }
         }
         return null;
@@ -93,11 +107,10 @@
 
     public static boolean startUninstallActivity(
             final Launcher launcher, ItemInfo info, DropTargetResultCallback callback) {
-        Pair<ComponentName, Integer> componentInfo = getAppInfoFlags(info);
-        ComponentName cn = componentInfo.first;
+        final ComponentName cn = getUninstallTarget(launcher, info);
 
         final boolean isUninstallable;
-        if ((componentInfo.second & AppInfo.DOWNLOADED_FLAG) == 0) {
+        if (cn == null) {
             // System applications cannot be installed. For now, show a toast explaining that.
             // We may give them the option of disabling apps this way.
             Toast.makeText(launcher, R.string.uninstall_system_app_text, Toast.LENGTH_SHORT).show();
@@ -112,8 +125,7 @@
             isUninstallable = true;
         }
         if (callback != null) {
-            sendUninstallResult(
-                    launcher, isUninstallable, componentInfo.first, info.user, callback);
+            sendUninstallResult(launcher, isUninstallable, cn, info.user, callback);
         }
         return isUninstallable;
     }
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index b0e096a..416ca8e 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -31,22 +31,16 @@
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
-import android.database.Cursor;
 import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Matrix;
 import android.graphics.Paint;
-import android.graphics.PaintFlagsDrawFilter;
 import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.PaintDrawable;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.DeadObjectException;
 import android.os.PowerManager;
+import android.os.TransactionTooLargeException;
 import android.text.Spannable;
 import android.text.SpannableString;
 import android.text.TextUtils;
@@ -56,23 +50,17 @@
 import android.util.Pair;
 import android.util.SparseArray;
 import android.util.TypedValue;
-import android.view.MotionEvent;
 import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.Toast;
 
-import com.android.launcher3.compat.UserHandleCompat;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.config.ProviderConfig;
-import com.android.launcher3.graphics.ShadowGenerator;
-import com.android.launcher3.util.IconNormalizer;
 
 import java.io.ByteArrayOutputStream;
 import java.io.Closeable;
 import java.io.IOException;
 import java.lang.reflect.Method;
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Locale;
 import java.util.Set;
@@ -90,29 +78,26 @@
 
     private static final String TAG = "Launcher.Utilities";
 
-    private static final Rect sOldBounds = new Rect();
-    private static final Canvas sCanvas = new Canvas();
-
     private static final Pattern sTrimPattern =
             Pattern.compile("^[\\s|\\p{javaSpaceChar}]*(.*)[\\s|\\p{javaSpaceChar}]*$");
 
-    static {
-        sCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
-                Paint.FILTER_BITMAP_FLAG));
-    }
-    static int sColors[] = { 0xffff0000, 0xff00ff00, 0xff0000ff };
-    static int sColorIndex = 0;
-
     private static final int[] sLoc0 = new int[2];
     private static final int[] sLoc1 = new int[2];
+    private static final float[] sPoint = new float[2];
+    private static final Matrix sMatrix = new Matrix();
+    private static final Matrix sInverseMatrix = new Matrix();
 
-    public static boolean isNycMR1OrAbove() {
-        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1;
+    public static boolean isAtLeastO() {
+        // TODO: Clean this up: b/32610406
+        return !"REL".equals(Build.VERSION.CODENAME)
+                && "O".compareTo(Build.VERSION.CODENAME) <= 0;
     }
 
-    public static boolean isNycOrAbove() {
-        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
-    }
+    public static final boolean ATLEAST_NOUGAT_MR1 =
+        Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1;
+
+    public static final boolean ATLEAST_NOUGAT =
+        Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
 
     public static final boolean ATLEAST_MARSHMALLOW =
             Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
@@ -159,7 +144,7 @@
     }
 
     public static boolean getAllowRotationDefaultValue(Context context) {
-        if (isNycOrAbove()) {
+        if (ATLEAST_NOUGAT) {
             // If the device was scaled, used the original dimensions to determine if rotation
             // is allowed of not.
             Resources res = context.getResources();
@@ -170,198 +155,6 @@
         return false;
     }
 
-    public static Bitmap createIconBitmap(Cursor c, int iconIndex, Context context) {
-        byte[] data = c.getBlob(iconIndex);
-        try {
-            return createIconBitmap(BitmapFactory.decodeByteArray(data, 0, data.length), context);
-        } catch (Exception e) {
-            return null;
-        }
-    }
-
-    /**
-     * Returns a bitmap suitable for the all apps view. If the package or the resource do not
-     * exist, it returns null.
-     */
-    public static Bitmap createIconBitmap(String packageName, String resourceName,
-            Context context) {
-        PackageManager packageManager = context.getPackageManager();
-        // the resource
-        try {
-            Resources resources = packageManager.getResourcesForApplication(packageName);
-            if (resources != null) {
-                final int id = resources.getIdentifier(resourceName, null, null);
-                return createIconBitmap(
-                        resources.getDrawableForDensity(id, LauncherAppState.getInstance()
-                                .getInvariantDeviceProfile().fillResIconDpi), context);
-            }
-        } catch (Exception e) {
-            // Icon not found.
-        }
-        return null;
-    }
-
-    private static int getIconBitmapSize() {
-        return LauncherAppState.getInstance().getInvariantDeviceProfile().iconBitmapSize;
-    }
-
-    /**
-     * Returns a bitmap which is of the appropriate size to be displayed as an icon
-     */
-    public static Bitmap createIconBitmap(Bitmap icon, Context context) {
-        final int iconBitmapSize = getIconBitmapSize();
-        if (iconBitmapSize == icon.getWidth() && iconBitmapSize == icon.getHeight()) {
-            return icon;
-        }
-        return createIconBitmap(new BitmapDrawable(context.getResources(), icon), context);
-    }
-
-    /**
-     * Returns a bitmap suitable for the all apps view. The icon is badged for {@param user}.
-     * The bitmap is also visually normalized with other icons.
-     */
-    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
-    public static Bitmap createBadgedIconBitmap(
-            Drawable icon, UserHandleCompat user, Context context) {
-        float scale = FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION ?
-                1 : IconNormalizer.getInstance().getScale(icon, null);
-        Bitmap bitmap = createIconBitmap(icon, context, scale);
-        return badgeIconForUser(bitmap, user, context);
-    }
-
-    /**
-     * Badges the provided icon with the user badge if required.
-     */
-    public static Bitmap badgeIconForUser(Bitmap icon,  UserHandleCompat user, Context context) {
-        if (Utilities.ATLEAST_LOLLIPOP && user != null
-                && !UserHandleCompat.myUserHandle().equals(user)) {
-            BitmapDrawable drawable = new FixedSizeBitmapDrawable(icon);
-            Drawable badged = context.getPackageManager().getUserBadgedIcon(
-                    drawable, user.getUser());
-            if (badged instanceof BitmapDrawable) {
-                return ((BitmapDrawable) badged).getBitmap();
-            } else {
-                return createIconBitmap(badged, context);
-            }
-        } else {
-            return icon;
-        }
-    }
-
-    /**
-     * Creates a normalized bitmap suitable for the all apps view. The bitmap is also visually
-     * normalized with other icons and has enough spacing to add shadow.
-     */
-    public static Bitmap createScaledBitmapWithoutShadow(Drawable icon, Context context) {
-        RectF iconBounds = new RectF();
-        float scale = FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION ?
-                1 : IconNormalizer.getInstance().getScale(icon, iconBounds);
-        scale = Math.min(scale, ShadowGenerator.getScaleForBounds(iconBounds));
-        return createIconBitmap(icon, context, scale);
-    }
-
-    /**
-     * Adds a shadow to the provided icon. It assumes that the icon has already been scaled using
-     * {@link #createScaledBitmapWithoutShadow(Drawable, Context)}
-     */
-    public static Bitmap addShadowToIcon(Bitmap icon) {
-        return ShadowGenerator.getInstance().recreateIcon(icon);
-    }
-
-    /**
-     * Adds the {@param badge} on top of {@param srcTgt} using the badge dimensions.
-     */
-    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
-    public static Bitmap badgeWithBitmap(Bitmap srcTgt, Bitmap badge, Context context) {
-        int badgeSize = context.getResources().getDimensionPixelSize(R.dimen.profile_badge_size);
-        synchronized (sCanvas) {
-            sCanvas.setBitmap(srcTgt);
-            sCanvas.drawBitmap(badge, new Rect(0, 0, badge.getWidth(), badge.getHeight()),
-                    new Rect(srcTgt.getWidth() - badgeSize,
-                            srcTgt.getHeight() - badgeSize, srcTgt.getWidth(), srcTgt.getHeight()),
-                    new Paint(Paint.FILTER_BITMAP_FLAG));
-            sCanvas.setBitmap(null);
-        }
-        return srcTgt;
-    }
-
-    /**
-     * Returns a bitmap suitable for the all apps view.
-     */
-    public static Bitmap createIconBitmap(Drawable icon, Context context) {
-        return createIconBitmap(icon, context, 1.0f /* scale */);
-    }
-
-    /**
-     * @param scale the scale to apply before drawing {@param icon} on the canvas
-     */
-    public static Bitmap createIconBitmap(Drawable icon, Context context, float scale) {
-        synchronized (sCanvas) {
-            final int iconBitmapSize = getIconBitmapSize();
-
-            int width = iconBitmapSize;
-            int height = iconBitmapSize;
-
-            if (icon instanceof PaintDrawable) {
-                PaintDrawable painter = (PaintDrawable) icon;
-                painter.setIntrinsicWidth(width);
-                painter.setIntrinsicHeight(height);
-            } else if (icon instanceof BitmapDrawable) {
-                // Ensure the bitmap has a density.
-                BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
-                Bitmap bitmap = bitmapDrawable.getBitmap();
-                if (bitmap != null && bitmap.getDensity() == Bitmap.DENSITY_NONE) {
-                    bitmapDrawable.setTargetDensity(context.getResources().getDisplayMetrics());
-                }
-            }
-            int sourceWidth = icon.getIntrinsicWidth();
-            int sourceHeight = icon.getIntrinsicHeight();
-            if (sourceWidth > 0 && sourceHeight > 0) {
-                // Scale the icon proportionally to the icon dimensions
-                final float ratio = (float) sourceWidth / sourceHeight;
-                if (sourceWidth > sourceHeight) {
-                    height = (int) (width / ratio);
-                } else if (sourceHeight > sourceWidth) {
-                    width = (int) (height * ratio);
-                }
-            }
-
-            // no intrinsic size --> use default size
-            int textureWidth = iconBitmapSize;
-            int textureHeight = iconBitmapSize;
-
-            final Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight,
-                    Bitmap.Config.ARGB_8888);
-            final Canvas canvas = sCanvas;
-            canvas.setBitmap(bitmap);
-
-            final int left = (textureWidth-width) / 2;
-            final int top = (textureHeight-height) / 2;
-
-            @SuppressWarnings("all") // suppress dead code warning
-            final boolean debug = false;
-            if (debug) {
-                // draw a big box for the icon for debugging
-                canvas.drawColor(sColors[sColorIndex]);
-                if (++sColorIndex >= sColors.length) sColorIndex = 0;
-                Paint debugPaint = new Paint();
-                debugPaint.setColor(0xffcccc00);
-                canvas.drawRect(left, top, left+width, top+height, debugPaint);
-            }
-
-            sOldBounds.set(icon.getBounds());
-            icon.setBounds(left, top, left+width, top+height);
-            canvas.save(Canvas.MATRIX_SAVE_FLAG);
-            canvas.scale(scale, scale, textureWidth / 2, textureHeight / 2);
-            icon.draw(canvas);
-            canvas.restore();
-            icon.setBounds(sOldBounds);
-            canvas.setBitmap(null);
-
-            return bitmap;
-        }
-    }
-
     /**
      * Given a coordinate relative to the descendant, find the coordinate in a parent view's
      * coordinates.
@@ -377,68 +170,52 @@
      */
     public static float getDescendantCoordRelativeToAncestor(
             View descendant, View ancestor, int[] coord, boolean includeRootScroll) {
-        float[] pt = {coord[0], coord[1]};
+        sPoint[0] = coord[0];
+        sPoint[1] = coord[1];
+
         float scale = 1.0f;
         View v = descendant;
         while(v != ancestor && v != null) {
             // For TextViews, scroll has a meaning which relates to the text position
             // which is very strange... ignore the scroll.
             if (v != descendant || includeRootScroll) {
-                pt[0] -= v.getScrollX();
-                pt[1] -= v.getScrollY();
+                sPoint[0] -= v.getScrollX();
+                sPoint[1] -= v.getScrollY();
             }
 
-            v.getMatrix().mapPoints(pt);
-            pt[0] += v.getLeft();
-            pt[1] += v.getTop();
+            v.getMatrix().mapPoints(sPoint);
+            sPoint[0] += v.getLeft();
+            sPoint[1] += v.getTop();
             scale *= v.getScaleX();
 
             v = (View) v.getParent();
         }
 
-        coord[0] = Math.round(pt[0]);
-        coord[1] = Math.round(pt[1]);
+        coord[0] = Math.round(sPoint[0]);
+        coord[1] = Math.round(sPoint[1]);
         return scale;
     }
 
     /**
      * Inverse of {@link #getDescendantCoordRelativeToAncestor(View, View, int[], boolean)}.
      */
-    public static float mapCoordInSelfToDescendent(View descendant, View root,
-                                                   int[] coord) {
-        ArrayList<View> ancestorChain = new ArrayList<View>();
-
-        float[] pt = {coord[0], coord[1]};
-
+    public static void mapCoordInSelfToDescendant(View descendant, View root, int[] coord) {
+        sMatrix.reset();
         View v = descendant;
         while(v != root) {
-            ancestorChain.add(v);
+            sMatrix.postTranslate(-v.getScrollX(), -v.getScrollY());
+            sMatrix.postConcat(v.getMatrix());
+            sMatrix.postTranslate(v.getLeft(), v.getTop());
             v = (View) v.getParent();
         }
-        ancestorChain.add(root);
+        sMatrix.postTranslate(-v.getScrollX(), -v.getScrollY());
+        sMatrix.invert(sInverseMatrix);
 
-        float scale = 1.0f;
-        Matrix inverse = new Matrix();
-        int count = ancestorChain.size();
-        for (int i = count - 1; i >= 0; i--) {
-            View ancestor = ancestorChain.get(i);
-            View next = i > 0 ? ancestorChain.get(i-1) : null;
-
-            pt[0] += ancestor.getScrollX();
-            pt[1] += ancestor.getScrollY();
-
-            if (next != null) {
-                pt[0] -= next.getLeft();
-                pt[1] -= next.getTop();
-                next.getMatrix().invert(inverse);
-                inverse.mapPoints(pt);
-                scale *= next.getScaleX();
-            }
-        }
-
-        coord[0] = (int) Math.round(pt[0]);
-        coord[1] = (int) Math.round(pt[1]);
-        return scale;
+        sPoint[0] = coord[0];
+        sPoint[1] = coord[1];
+        sInverseMatrix.mapPoints(sPoint);
+        coord[0] = Math.round(sPoint[0]);
+        coord[1] = Math.round(sPoint[1]);
     }
 
     /**
@@ -452,30 +229,6 @@
                 localY < (v.getHeight() + slop);
     }
 
-    /** Translates MotionEvents from src's coordinate system to dst's. */
-    public static void translateEventCoordinates(View src, View dst, MotionEvent dstEvent) {
-        toGlobalMotionEvent(src, dstEvent);
-        toLocalMotionEvent(dst, dstEvent);
-    }
-
-    /**
-     * Emulates View.toGlobalMotionEvent(). This implementation does not handle transformations
-     * (scaleX, scaleY, etc).
-     */
-    private static void toGlobalMotionEvent(View view, MotionEvent event) {
-        view.getLocationOnScreen(sLoc0);
-        event.offsetLocation(sLoc0[0], sLoc0[1]);
-    }
-
-    /**
-     * Emulates View.toLocalMotionEvent(). This implementation does not handle transformations
-     * (scaleX, scaleY, etc).
-     */
-    private static void toLocalMotionEvent(View view, MotionEvent event) {
-        view.getLocationOnScreen(sLoc0);
-        event.offsetLocation(-sLoc0[0], -sLoc0[1]);
-    }
-
     public static int[] getCenterDeltaInScreenSpace(View v0, View v1, int[] delta) {
         v0.getLocationInWindow(sLoc0);
         v1.getLocationInWindow(sLoc1);
@@ -832,8 +585,8 @@
         return ATLEAST_LOLLIPOP && powerManager.isPowerSaveMode();
     }
 
-    public static boolean isWallapaperAllowed(Context context) {
-        if (isNycOrAbove()) {
+    public static boolean isWallpaperAllowed(Context context) {
+        if (ATLEAST_NOUGAT) {
             try {
                 WallpaperManager wm = context.getSystemService(WallpaperManager.class);
                 return (Boolean) wm.getClass().getDeclaredMethod("isSetWallpaperAllowed")
@@ -880,28 +633,6 @@
         return c == null || c.isEmpty();
     }
 
-    /**
-     * An extension of {@link BitmapDrawable} which returns the bitmap pixel size as intrinsic size.
-     * This allows the badging to be done based on the action bitmap size rather than
-     * the scaled bitmap size.
-     */
-    private static class FixedSizeBitmapDrawable extends BitmapDrawable {
-
-        public FixedSizeBitmapDrawable(Bitmap bitmap) {
-            super(null, bitmap);
-        }
-
-        @Override
-        public int getIntrinsicHeight() {
-            return getBitmap().getWidth();
-        }
-
-        @Override
-        public int getIntrinsicWidth() {
-            return getBitmap().getWidth();
-        }
-    }
-
     public static int getColorAccent(Context context) {
         TypedArray ta = context.obtainStyledAttributes(new int[]{android.R.attr.colorAccent});
         int colorAccent = ta.getColor(0, 0);
@@ -919,4 +650,9 @@
             accessibilityManager.sendAccessibilityEvent(event);
         }
     }
+
+    public static boolean isBinderSizeError(Exception e) {
+        return e.getCause() instanceof TransactionTooLargeException
+                || e.getCause() instanceof DeadObjectException;
+    }
 }
diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java
index 45e65b5..354b8ec 100644
--- a/src/com/android/launcher3/WidgetPreviewLoader.java
+++ b/src/com/android/launcher3/WidgetPreviewLoader.java
@@ -242,7 +242,7 @@
                     CacheDb.COLUMN_COMPONENT + " = ? AND " + CacheDb.COLUMN_USER + " = ? AND "
                             + CacheDb.COLUMN_SIZE + " = ?",
                     new String[]{
-                            key.componentName.flattenToString(),
+                            key.componentName.flattenToShortString(),
                             Long.toString(mUserManager.getSerialNumberForUser(key.user)),
                             key.size
                     });
@@ -301,7 +301,14 @@
 
         Drawable drawable = null;
         if (info.previewImage != 0) {
-            drawable = mWidgetManager.loadPreview(info);
+            try {
+                drawable = mWidgetManager.loadPreview(info);
+            } catch (OutOfMemoryError e) {
+                Log.w(TAG, "Error loading widget preview for: " + info.provider, e);
+                // During OutOfMemoryError, the previous heap stack is not affected. Catching
+                // an OOM error here should be safe & not affect other parts of launcher.
+                drawable = null;
+            }
             if (drawable != null) {
                 drawable = mutateOnMainThread(drawable);
             } else {
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 950a407..f6b5072 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -28,12 +28,10 @@
 import android.app.WallpaperManager;
 import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetProviderInfo;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
-import android.graphics.Matrix;
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
@@ -50,17 +48,20 @@
 import android.view.ViewDebug;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityManager;
+import android.view.animation.Animation.AnimationListener;
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
 import android.widget.TextView;
+import android.widget.Toast;
 
 import com.android.launcher3.Launcher.CustomContentCallbacks;
 import com.android.launcher3.Launcher.LauncherOverlay;
 import com.android.launcher3.UninstallDropTarget.DropTargetSource;
-import com.android.launcher3.accessibility.AccessibileDragListenerAdapter;
+import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
 import com.android.launcher3.accessibility.OverviewAccessibilityDelegate;
 import com.android.launcher3.accessibility.OverviewScreenAccessibilityDelegate;
 import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
+import com.android.launcher3.anim.AnimationLayerSet;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.config.FeatureFlags;
@@ -74,6 +75,7 @@
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.graphics.DragPreviewProvider;
+import com.android.launcher3.shortcuts.DeepShortcutsContainer;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.ItemInfoMatcher;
@@ -86,7 +88,6 @@
 import com.android.launcher3.widget.PendingAddWidgetInfo;
 
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.HashSet;
 
 /**
@@ -96,7 +97,7 @@
  */
 public class Workspace extends PagedView
         implements DropTarget, DragSource, DragScroller, View.OnTouchListener,
-        DragController.DragListener, LauncherTransitionable, ViewGroup.OnHierarchyChangeListener,
+        DragController.DragListener, ViewGroup.OnHierarchyChangeListener,
         Insettable, DropTargetSource {
     private static final String TAG = "Launcher.Workspace";
 
@@ -170,11 +171,10 @@
     // These are temporary variables to prevent having to allocate a new object just to
     // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
     private static final Rect sTempRect = new Rect();
+
     private final int[] mTempXY = new int[2];
     @Thunk float[] mDragViewVisualCenter = new float[2];
     private float[] mTempCellLayoutCenterCoordinates = new float[2];
-    private int[] mTempVisiblePagesRange = new int[2];
-    private Matrix mTempMatrix = new Matrix();
 
     private SpringLoadedDragController mSpringLoadedDragController;
     private float mOverviewModeShrinkFactor;
@@ -303,7 +303,7 @@
     LauncherOverlay mLauncherOverlay;
     boolean mScrollInteractionBegan;
     boolean mStartedSendingScrollEvents;
-    float mLastOverlaySroll = 0;
+    float mLastOverlayScroll = 0;
     // Total over scrollX in the overlay direction.
     private int mUnboundedScrollX;
     private boolean mForceDrawAdjacentPages = false;
@@ -407,7 +407,12 @@
     @Override
     public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
         if (ENFORCE_DRAG_EVENT_ORDER) {
-            enfoceDragParity("onDragStart", 0, 0);
+            enforceDragParity("onDragStart", 0, 0);
+        }
+
+        if (mDragInfo != null && mDragInfo.cell != null) {
+            CellLayout layout = (CellLayout) mDragInfo.cell.getParent().getParent();
+            layout.markCellsAsUnoccupiedForView(mDragInfo.cell);
         }
 
         if (mOutlineProvider != null) {
@@ -464,7 +469,7 @@
     @Override
     public void onDragEnd() {
         if (ENFORCE_DRAG_EVENT_ORDER) {
-            enfoceDragParity("onDragEnd", 0, 0);
+            enforceDragParity("onDragEnd", 0, 0);
         }
 
         if (!mDeferRemoveExtraEmptyScreen) {
@@ -477,6 +482,8 @@
         // Re-enable any Un/InstallShortcutReceiver and now process any queued items
         InstallShortcutReceiver.disableAndFlushInstallQueue(getContext());
 
+        mOutlineProvider = null;
+        mDragInfo = null;
         mDragSourceInternal = null;
         mLauncher.onInteractionEnd();
     }
@@ -492,7 +499,6 @@
         setWillNotDraw(false);
         setClipChildren(false);
         setClipToPadding(false);
-        setChildrenDrawnWithCacheEnabled(true);
 
         setMinScale(mOverviewModeShrinkFactor);
         setupLayoutTransition();
@@ -545,32 +551,6 @@
         super.onChildViewAdded(parent, child);
     }
 
-    protected boolean shouldDrawChild(View child) {
-        final CellLayout cl = (CellLayout) child;
-        return super.shouldDrawChild(child) &&
-            (mIsSwitchingState ||
-             cl.getShortcutsAndWidgets().getAlpha() > 0 ||
-             cl.getBackgroundAlpha() > 0);
-    }
-
-    /**
-     * @return The open folder on the current screen, or null if there is none
-     */
-    public Folder getOpenFolder() {
-        DragLayer dragLayer = mLauncher.getDragLayer();
-        // Iterate in reverse order. Folder is added later to the dragLayer,
-        // and will be one of the last views.
-        for (int i = dragLayer.getChildCount() - 1; i >= 0; i--) {
-            View child = dragLayer.getChildAt(i);
-            if (child instanceof Folder) {
-                Folder folder = (Folder) child;
-                if (folder.getInfo().opened)
-                    return folder;
-            }
-        }
-        return null;
-    }
-
     boolean isTouchActive() {
         return mTouchState != TOUCH_STATE_REST;
     }
@@ -582,7 +562,7 @@
 
     /**
      * Initializes and binds the first page
-     * @param qsb an exisitng qsb to recycle or null.
+     * @param qsb an existing qsb to recycle or null.
      */
     public void bindAndInitFirstWorkspaceScreen(View qsb) {
         if (!FeatureFlags.QSB_ON_FIRST_SCREEN) {
@@ -737,11 +717,7 @@
         addFullScreenPage(customScreen);
 
         // Update the custom content hint
-        if (mRestorePage != INVALID_RESTORE_PAGE) {
-            mRestorePage = mRestorePage + 1;
-        } else {
-            setCurrentPage(getCurrentPage() + 1);
-        }
+        setCurrentPage(getCurrentPage() + 1);
     }
 
     public void removeCustomContentPage() {
@@ -762,11 +738,7 @@
         mCustomContentCallbacks = null;
 
         // Update the custom content hint
-        if (mRestorePage != INVALID_RESTORE_PAGE) {
-            mRestorePage = mRestorePage - 1;
-        } else {
-            setCurrentPage(getCurrentPage() - 1);
-        }
+        setCurrentPage(getCurrentPage() - 1);
     }
 
     public void addToCustomContentPage(View customContent, CustomContentCallbacks callbacks,
@@ -1012,7 +984,7 @@
             return;
         }
 
-        if (isPageMoving()) {
+        if (isPageInTransition()) {
             mStripScreensOnPageStopMoving = true;
             return;
         }
@@ -1328,31 +1300,14 @@
         }
     }
 
-    protected void onPageBeginMoving() {
-        super.onPageBeginMoving();
-
-        if (isHardwareAccelerated()) {
-            updateChildrenLayersEnabled(false);
-        } else {
-            if (mNextPage != INVALID_PAGE) {
-                // we're snapping to a particular screen
-                enableChildrenCache(mCurrentPage, mNextPage);
-            } else {
-                // this is when user is actively dragging a particular screen, they might
-                // swipe it either left or right (but we won't advance by more than one screen)
-                enableChildrenCache(mCurrentPage - 1, mCurrentPage + 1);
-            }
-        }
+    protected void onPageBeginTransition() {
+        super.onPageBeginTransition();
+        updateChildrenLayersEnabled(false);
     }
 
-    protected void onPageEndMoving() {
-        super.onPageEndMoving();
-
-        if (isHardwareAccelerated()) {
-            updateChildrenLayersEnabled(false);
-        } else {
-            clearChildrenCache();
-        }
+    protected void onPageEndTransition() {
+        super.onPageEndTransition();
+        updateChildrenLayersEnabled(false);
 
         if (mDragController.isDragging()) {
             if (workspaceInModalState()) {
@@ -1454,6 +1409,10 @@
         if (!isTransitioning) {
             showPageIndicatorAtCurrentScroll();
         }
+
+        updatePageAlphaValues();
+        updateStateForCustomContent();
+        enableHwLayersOnVisiblePages();
     }
 
     private void showPageIndicatorAtCurrentScroll() {
@@ -1470,7 +1429,7 @@
         boolean shouldScrollOverlay = mLauncherOverlay != null &&
                 ((amount <= 0 && !mIsRtl) || (amount >= 0 && mIsRtl));
 
-        boolean shouldZeroOverlay = mLauncherOverlay != null && mLastOverlaySroll != 0 &&
+        boolean shouldZeroOverlay = mLauncherOverlay != null && mLastOverlayScroll != 0 &&
                 ((amount >= 0 && !mIsRtl) || (amount <= 0 && mIsRtl));
 
         if (shouldScrollOverlay) {
@@ -1479,8 +1438,8 @@
                 mLauncherOverlay.onScrollInteractionBegin();
             }
 
-            mLastOverlaySroll = Math.abs(amount / getViewportWidth());
-            mLauncherOverlay.onScrollChange(mLastOverlaySroll, mIsRtl);
+            mLastOverlayScroll = Math.abs(amount / getViewportWidth());
+            mLauncherOverlay.onScrollChange(mLastOverlayScroll, mIsRtl);
         } else if (shouldOverScroll) {
             dampedOverScroll(amount);
         }
@@ -1612,19 +1571,7 @@
     }
 
     @Override
-    protected Matrix getPageShiftMatrix() {
-        if (Float.compare(mOverlayTranslation, 0) != 0) {
-            // The pages are translated by mOverlayTranslation. incorporate that in the
-            // visible page calculation by shifting everything back by that same amount.
-            mTempMatrix.set(getMatrix());
-            mTempMatrix.postTranslate(-mOverlayTranslation, 0);
-            return mTempMatrix;
-        }
-        return super.getPageShiftMatrix();
-    }
-
-    @Override
-    protected void getEdgeVerticalPostion(int[] pos) {
+    protected void getEdgeVerticalPosition(int[] pos) {
         View child = getChildAt(getPageCount() - 1);
         pos[0] = child.getTop();
         pos[1] = child.getBottom();
@@ -1723,15 +1670,16 @@
     }
 
     public void showOutlinesTemporarily() {
-        if (!mIsPageMoving && !isTouchActive()) {
+        if (!mIsPageInTransition && !isTouchActive()) {
             snapToPage(mCurrentPage);
         }
     }
 
-    private void updatePageAlphaValues(int screenCenter) {
+    private void updatePageAlphaValues() {
         if (mWorkspaceFadeInAdjacentScreens &&
                 !workspaceInModalState() &&
                 !mIsSwitchingState) {
+            int screenCenter = getScrollX() + getViewportWidth() / 2;
             for (int i = numCustomPages(); i < getChildCount(); i++) {
                 CellLayout child = (CellLayout) getChildAt(i);
                 if (child != null) {
@@ -1756,10 +1704,10 @@
     }
 
     public boolean isOnOrMovingToCustomContent() {
-        return hasCustomContent() && getNextPage() == 0 && mRestorePage == INVALID_RESTORE_PAGE;
+        return hasCustomContent() && getNextPage() == 0;
     }
 
-    private void updateStateForCustomContent(int screenCenter) {
+    private void updateStateForCustomContent() {
         float translationX = 0;
         float progress = 0;
         if (hasCustomContent()) {
@@ -1807,13 +1755,6 @@
         }
     }
 
-    @Override
-    protected void screenScrolled(int screenCenter) {
-        updatePageAlphaValues(screenCenter);
-        updateStateForCustomContent(screenCenter);
-        enableHwLayersOnVisiblePages();
-    }
-
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         IBinder windowToken = getWindowToken();
@@ -1875,7 +1816,7 @@
                 }
             });
         }
-
+        updatePageAlphaValues();
     }
 
     @Override
@@ -1890,40 +1831,9 @@
         return mState != State.NORMAL;
     }
 
-    void enableChildrenCache(int fromPage, int toPage) {
-        if (fromPage > toPage) {
-            final int temp = fromPage;
-            fromPage = toPage;
-            toPage = temp;
-        }
-
-        final int screenCount = getChildCount();
-
-        fromPage = Math.max(fromPage, 0);
-        toPage = Math.min(toPage, screenCount - 1);
-
-        for (int i = fromPage; i <= toPage; i++) {
-            final CellLayout layout = (CellLayout) getChildAt(i);
-            layout.setChildrenDrawnWithCacheEnabled(true);
-            layout.setChildrenDrawingCacheEnabled(true);
-        }
-    }
-
-    void clearChildrenCache() {
-        final int screenCount = getChildCount();
-        for (int i = 0; i < screenCount; i++) {
-            final CellLayout layout = (CellLayout) getChildAt(i);
-            layout.setChildrenDrawnWithCacheEnabled(false);
-            // In software mode, we don't want the items to continue to be drawn into bitmaps
-            if (!isHardwareAccelerated()) {
-                layout.setChildrenDrawingCacheEnabled(false);
-            }
-        }
-    }
-
     @Thunk void updateChildrenLayersEnabled(boolean force) {
         boolean small = mState == State.OVERVIEW || mIsSwitchingState;
-        boolean enableChildrenLayers = force || small || mAnimatingViewIntoPlace || isPageMoving();
+        boolean enableChildrenLayers = force || small || mAnimatingViewIntoPlace || isPageInTransition();
 
         if (enableChildrenLayers != mChildrenLayersEnabled) {
             mChildrenLayersEnabled = enableChildrenLayers;
@@ -1941,9 +1851,37 @@
     private void enableHwLayersOnVisiblePages() {
         if (mChildrenLayersEnabled) {
             final int screenCount = getChildCount();
-            getVisiblePages(mTempVisiblePagesRange);
-            int leftScreen = mTempVisiblePagesRange[0];
-            int rightScreen = mTempVisiblePagesRange[1];
+
+            float visibleLeft = getViewportOffsetX();
+            float visibleRight = visibleLeft + getViewportWidth();
+            float scaleX = getScaleX();
+            if (scaleX < 1 && scaleX > 0) {
+                float mid = getMeasuredWidth() / 2;
+                visibleLeft = mid - ((mid - visibleLeft) / scaleX);
+                visibleRight = mid + ((visibleRight - mid) / scaleX);
+            }
+
+            int leftScreen = -1;
+            int rightScreen = -1;
+            for (int i = numCustomPages(); i < screenCount; i++) {
+                final View child = getPageAt(i);
+
+                float left = child.getLeft() + child.getTranslationX() - getScrollX();
+                if (left <= visibleRight && (left + child.getMeasuredWidth()) >= visibleLeft) {
+                    if (leftScreen == -1) {
+                        leftScreen = i;
+                    }
+                    rightScreen = i;
+                }
+            }
+            if (mForceDrawAdjacentPages) {
+                // In overview mode, make sure that the two side pages are visible.
+                leftScreen = Utilities.boundToRange(getCurrentPage() - 1,
+                    numCustomPages(), rightScreen);
+                rightScreen = Utilities.boundToRange(getCurrentPage() + 1,
+                    leftScreen, getPageCount() - 1);
+            }
+
             if (leftScreen == rightScreen) {
                 // make sure we're caching at least two pages always
                 if (rightScreen < screenCount - 1) {
@@ -1953,14 +1891,10 @@
                 }
             }
 
-            final CellLayout customScreen = mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID);
-            for (int i = 0; i < screenCount; i++) {
+            for (int i = numCustomPages(); i < screenCount; i++) {
                 final CellLayout layout = (CellLayout) getPageAt(i);
-
-                // enable layers between left and right screen inclusive, except for the
-                // customScreen, which may animate its content during transitions.
-                boolean enableLayer = layout != customScreen &&
-                        leftScreen <= i && i <= rightScreen && shouldDrawChild(layout);
+                // enable layers between left and right screen inclusive.
+                boolean enableLayer = leftScreen <= i && i <= rightScreen;
                 layout.enableHardwareLayer(enableLayer);
             }
         }
@@ -1979,16 +1913,6 @@
         updateChildrenLayersEnabled(false);
     }
 
-    @Override
-    protected void getVisiblePages(int[] range) {
-        super.getVisiblePages(range);
-        if (mForceDrawAdjacentPages) {
-            // In overview mode, make sure that the two side pages are visible.
-            range[0] = Utilities.boundToRange(getCurrentPage() - 1, numCustomPages(), range[1]);
-            range[1] = Utilities.boundToRange(getCurrentPage() + 1, range[0], getPageCount() - 1);
-        }
-    }
-
     protected void onWallpaperTap(MotionEvent ev) {
         final int[] position = mTempXY;
         getLocationOnScreen(position);
@@ -2009,7 +1933,7 @@
 
     public void exitWidgetResizeMode() {
         DragLayer dragLayer = mLauncher.getDragLayer();
-        dragLayer.clearAllResizeFrames();
+        dragLayer.clearResizeFrame();
     }
 
     @Override
@@ -2106,7 +2030,7 @@
      * to that new state.
      */
     public Animator setStateWithAnimation(State toState, boolean animated,
-            HashMap<View, Integer> layerViews) {
+            AnimationLayerSet layerViews) {
         // Create the animation to the new state
         AnimatorSet workspaceAnim =  mStateTransitionAnimation.getAnimationToState(mState,
                 toState, animated, layerViews);
@@ -2125,6 +2049,20 @@
             mOnStateChangeListener.prepareStateChange(toState, animated ? workspaceAnim : null);
         }
 
+        onPrepareStateTransition(mState.hasMultipleVisiblePages);
+
+        StateTransitionListener listener = new StateTransitionListener();
+        if (animated) {
+            ValueAnimator stepAnimator = ValueAnimator.ofFloat(0, 1);
+            stepAnimator.addListener(listener);
+
+            workspaceAnim.play(stepAnimator);
+            workspaceAnim.addListener(listener);
+        } else {
+            listener.onAnimationStart(null);
+            listener.onAnimationEnd(null);
+        }
+
         return workspaceAnim;
     }
 
@@ -2177,9 +2115,7 @@
         }
     }
 
-    @Override
-    public void onLauncherTransitionPrepare(Launcher l, boolean animated,
-            boolean multiplePagesVisible) {
+    public void onPrepareStateTransition(boolean multiplePagesVisible) {
         mIsSwitchingState = true;
         mTransitionProgress = 0;
 
@@ -2192,32 +2128,12 @@
         hideCustomContentIfNecessary();
     }
 
-    @Override
-    public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
-        if (mPageIndicator != null) {
-            boolean isNewStateSpringLoaded = mState == State.SPRING_LOADED;
-            mPageIndicator.setShouldAutoHide(!isNewStateSpringLoaded);
-            if (isNewStateSpringLoaded) {
-                // Show the page indicator at the same time as the rest of the transition.
-                showPageIndicatorAtCurrentScroll();
-            }
-        }
-    }
-
-    @Override
-    public void onLauncherTransitionStep(Launcher l, float t) {
-        mTransitionProgress = t;
-    }
-
-    @Override
-    public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
+    public void onEndStateTransition() {
         mIsSwitchingState = false;
         updateChildrenLayersEnabled(false);
         showCustomContentIfNecessary();
         mForceDrawAdjacentPages = false;
-        if (mState == State.SPRING_LOADED) {
-            showPageIndicatorAtCurrentScroll();
-        }
+        mTransitionProgress = 1;
     }
 
     void updateCustomContentVisibility() {
@@ -2270,11 +2186,9 @@
 
         mDragInfo = cellInfo;
         child.setVisibility(INVISIBLE);
-        CellLayout layout = (CellLayout) child.getParent().getParent();
-        layout.prepareChildForDrag(child);
 
         if (options.isAccessibleDrag) {
-            mDragController.addDragListener(new AccessibileDragListenerAdapter(
+            mDragController.addDragListener(new AccessibleDragListenerAdapter(
                     this, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG) {
                 @Override
                 protected void enableAccessibleDrag(boolean enable) {
@@ -2348,6 +2262,15 @@
             mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
         }
 
+        if (child instanceof BubbleTextView) {
+            DeepShortcutsContainer dsc = DeepShortcutsContainer.showForIcon((BubbleTextView) child);
+            if (dsc != null) {
+                dragOptions.preDragCondition = dsc.createPreDragCondition();
+
+                mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
+            }
+        }
+
         DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source,
                 dragObject, dragVisualizeOffset, dragRect, scale, dragOptions);
         dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
@@ -2423,18 +2346,7 @@
 
             // Don't accept the drop if there's no room for the item
             if (!foundCell) {
-                // Don't show the message if we are dropping on the AllApps button and the hotseat
-                // is full
-                boolean isHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
-                if (mTargetCell != null && isHotseat && !FeatureFlags.NO_ALL_APPS_ICON) {
-                    Hotseat hotseat = mLauncher.getHotseat();
-                    if (mLauncher.getDeviceProfile().inv.isAllAppsButtonRank(
-                            hotseat.getOrderInHotseat(mTargetCell[0], mTargetCell[1]))) {
-                        return false;
-                    }
-                }
-
-                mLauncher.showOutOfSpaceMessage(isHotseat);
+                onNoCellFound(dropTargetLayout);
                 return false;
             }
         }
@@ -2609,6 +2521,7 @@
             onDropExternal(touchXY, d.dragInfo, dropTargetLayout, false, d);
         } else if (mDragInfo != null) {
             final View cell = mDragInfo.cell;
+            boolean droppedOnOriginalCellDuringTransition = false;
 
             if (dropTargetLayout != null && !d.cancelled) {
                 // Move internally
@@ -2651,6 +2564,10 @@
                     minSpanY = item.minSpanY;
                 }
 
+                droppedOnOriginalCellDuringTransition = mIsSwitchingState
+                        && item.screenId == screenId && item.container == container
+                        && item.cellX == mTargetCell[0] && item.cellY == mTargetCell[1];
+
                 int[] resultSpan = new int[2];
                 mTargetCell = dropTargetLayout.performReorder((int) mDragViewVisualCenter[0],
                         (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, cell,
@@ -2669,12 +2586,12 @@
                             resultSpan[1]);
                 }
 
-                if (getScreenIdForPageIndex(mCurrentPage) != screenId && !hasMovedIntoHotseat) {
-                    snapScreen = getPageIndexForScreenId(screenId);
-                    snapToPage(snapScreen);
-                }
-
                 if (foundCell) {
+                    if (getScreenIdForPageIndex(mCurrentPage) != screenId && !hasMovedIntoHotseat) {
+                        snapScreen = getPageIndexForScreenId(screenId);
+                        snapToPage(snapScreen);
+                    }
+
                     final ItemInfo info = (ItemInfo) cell.getTag();
                     if (hasMovedLayouts) {
                         // Reparent the view
@@ -2708,9 +2625,9 @@
                                 && !d.accessibleDrag) {
                             mDelayedResizeRunnable = new Runnable() {
                                 public void run() {
-                                    if (!isPageMoving() && !mIsSwitchingState) {
+                                    if (!isPageInTransition()) {
                                         DragLayer dragLayer = mLauncher.getDragLayer();
-                                        dragLayer.addResizeFrame(info, hostView, cellLayout);
+                                        dragLayer.addResizeFrame(hostView, cellLayout);
                                     }
                                 }
                             };
@@ -2720,6 +2637,8 @@
                     LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId, lp.cellX,
                             lp.cellY, item.spanX, item.spanY);
                 } else {
+                    onNoCellFound(dropTargetLayout);
+
                     // If we can't find a drop location, we return the item to its original position
                     CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
                     mTargetCell[0] = lp.cellX;
@@ -2741,6 +2660,17 @@
             };
             mAnimatingViewIntoPlace = true;
             if (d.dragView.hasDrawn()) {
+                if (droppedOnOriginalCellDuringTransition) {
+                    // Animate the item to its original position, while simultaneously exiting
+                    // spring-loaded mode so the page meets the icon where it was picked up.
+                    mLauncher.getDragController().animateDragViewToOriginalPosition(
+                            mDelayedResizeRunnable, cell,
+                            mStateTransitionAnimation.mSpringLoadedTransitionTime);
+                    mLauncher.exitSpringLoadedDragMode();
+                    mLauncher.getDropTargetBar().onDragEnd();
+                    parent.onDropChild(cell);
+                    return;
+                }
                 final ItemInfo info = (ItemInfo) cell.getTag();
                 boolean isWidget = info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
                         || info.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
@@ -2765,6 +2695,26 @@
         }
     }
 
+    public void onNoCellFound(View dropTargetLayout) {
+        if (mLauncher.isHotseatLayout(dropTargetLayout)) {
+            Hotseat hotseat = mLauncher.getHotseat();
+            boolean droppedOnAllAppsIcon = !FeatureFlags.NO_ALL_APPS_ICON
+                    && mTargetCell != null && !mLauncher.getDeviceProfile().inv.isAllAppsButtonRank(
+                    hotseat.getOrderInHotseat(mTargetCell[0], mTargetCell[1]));
+            if (!droppedOnAllAppsIcon) {
+                // Only show message when hotseat is full and drop target was not AllApps button
+                showOutOfSpaceMessage(true);
+            }
+        } else {
+            showOutOfSpaceMessage(false);
+        }
+    }
+
+    private void showOutOfSpaceMessage(boolean isHotseatLayout) {
+        int strId = (isHotseatLayout ? R.string.hotseat_out_of_space : R.string.out_of_space);
+        Toast.makeText(mLauncher, mLauncher.getString(strId), Toast.LENGTH_SHORT).show();
+    }
+
     /**
      * Computes the area relative to dragLayer which is used to display a page.
      */
@@ -2790,7 +2740,7 @@
     @Override
     public void onDragEnter(DragObject d) {
         if (ENFORCE_DRAG_EVENT_ORDER) {
-            enfoceDragParity("onDragEnter", 1, 1);
+            enforceDragParity("onDragEnter", 1, 1);
         }
 
         mCreateUserFolderOnDrop = false;
@@ -2807,13 +2757,13 @@
     @Override
     public void onDragExit(DragObject d) {
         if (ENFORCE_DRAG_EVENT_ORDER) {
-            enfoceDragParity("onDragExit", -1, 0);
+            enforceDragParity("onDragExit", -1, 0);
         }
 
         // Here we store the final page that will be dropped to, if the workspace in fact
         // receives the drop
         if (mInScrollArea) {
-            if (isPageMoving()) {
+            if (isPageInTransition()) {
                 // If the user drops while the page is scrolling, we should use that page as the
                 // destination instead of the page that is being hovered over.
                 mDropToLayout = (CellLayout) getPageAt(getNextPage());
@@ -2840,14 +2790,14 @@
         mLauncher.getDragLayer().hidePageHints();
     }
 
-    private void enfoceDragParity(String event, int update, int expectedValue) {
-        enfoceDragParity(this, event, update, expectedValue);
+    private void enforceDragParity(String event, int update, int expectedValue) {
+        enforceDragParity(this, event, update, expectedValue);
         for (int i = 0; i < getChildCount(); i++) {
-            enfoceDragParity(getChildAt(i), event, update, expectedValue);
+            enforceDragParity(getChildAt(i), event, update, expectedValue);
         }
     }
 
-    private void enfoceDragParity(View v, String event, int update, int expectedValue) {
+    private void enforceDragParity(View v, String event, int update, int expectedValue) {
         Object tag = v.getTag(R.id.drag_event_parity);
         int value = tag == null ? 0 : (Integer) tag;
         value += update;
@@ -2968,7 +2918,7 @@
        mTempXY[0] = (int) xy[0];
        mTempXY[1] = (int) xy[1];
        mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempXY, true);
-       mLauncher.getDragLayer().mapCoordInSelfToDescendent(hotseat.getLayout(), mTempXY);
+       mLauncher.getDragLayer().mapCoordInSelfToDescendant(hotseat.getLayout(), mTempXY);
 
        xy[0] = mTempXY[0];
        xy[1] = mTempXY[1];
@@ -3656,8 +3606,10 @@
     public void onDropCompleted(final View target, final DragObject d,
             final boolean isFlingToDelete, final boolean success) {
         if (mDeferDropAfterUninstall) {
+            final CellLayout.CellInfo dragInfo = mDragInfo;
             mDeferredAction = new Runnable() {
                 public void run() {
+                    mDragInfo = dragInfo; // Restore the drag info that was cleared in onDragEnd()
                     onDropCompleted(target, d, isFlingToDelete, success);
                     mDeferredAction = null;
                 }
@@ -3685,7 +3637,6 @@
                 && mDragInfo.cell != null) {
             mDragInfo.cell.setVisibility(VISIBLE);
         }
-        mOutlineProvider = null;
         mDragInfo = null;
 
         if (!isFlingToDelete) {
@@ -3813,7 +3764,7 @@
         if (!workspaceInModalState() && !mIsSwitchingState) {
             super.scrollLeft();
         }
-        Folder openFolder = getOpenFolder();
+        Folder openFolder = Folder.getOpen(mLauncher);
         if (openFolder != null) {
             openFolder.completeDragExit();
         }
@@ -3824,7 +3775,7 @@
         if (!workspaceInModalState() && !mIsSwitchingState) {
             super.scrollRight();
         }
-        Folder openFolder = getOpenFolder();
+        Folder openFolder = Folder.getOpen(mLauncher);
         if (openFolder != null) {
             openFolder.completeDragExit();
         }
@@ -3843,7 +3794,7 @@
         }
 
         boolean result = false;
-        if (!workspaceInModalState() && !mIsSwitchingState && getOpenFolder() == null) {
+        if (!workspaceInModalState() && !mIsSwitchingState && Folder.getOpen(mLauncher) == null) {
             mInScrollArea = true;
 
             final int page = getNextPage() +
@@ -4003,63 +3954,34 @@
         for (final CellLayout layoutParent: cellLayouts) {
             final ViewGroup layout = layoutParent.getShortcutsAndWidgets();
 
-            final HashMap<ItemInfo, View> children = new HashMap<>();
+            LongArrayMap<View> idToViewMap = new LongArrayMap<>();
+            ArrayList<ItemInfo> items = new ArrayList<>();
             for (int j = 0; j < layout.getChildCount(); j++) {
                 final View view = layout.getChildAt(j);
-                children.put((ItemInfo) view.getTag(), view);
+                if (view.getTag() instanceof ItemInfo) {
+                    ItemInfo item = (ItemInfo) view.getTag();
+                    items.add(item);
+                    idToViewMap.put(item.id, view);
+                }
             }
 
-            final ArrayList<View> childrenToRemove = new ArrayList<>();
-            final HashMap<FolderInfo, ArrayList<ShortcutInfo>> folderAppsToRemove = new HashMap<>();
-            LauncherModel.ItemInfoFilter filter = new LauncherModel.ItemInfoFilter() {
-                @Override
-                public boolean filterItem(ItemInfo parent, ItemInfo info,
-                        ComponentName cn) {
-                    if (parent instanceof FolderInfo) {
-                        if (matcher.matches(info, cn)) {
-                            FolderInfo folder = (FolderInfo) parent;
-                            ArrayList<ShortcutInfo> appsToRemove;
-                            if (folderAppsToRemove.containsKey(folder)) {
-                                appsToRemove = folderAppsToRemove.get(folder);
-                            } else {
-                                appsToRemove = new ArrayList<ShortcutInfo>();
-                                folderAppsToRemove.put(folder, appsToRemove);
-                            }
-                            appsToRemove.add((ShortcutInfo) info);
-                            return true;
-                        }
-                    } else {
-                        if (matcher.matches(info, cn)) {
-                            childrenToRemove.add(children.get(info));
-                            return true;
-                        }
+            for (ItemInfo itemToRemove : matcher.filterItemInfos(items)) {
+                View child = idToViewMap.get(itemToRemove.id);
+
+                if (child != null) {
+                    // Note: We can not remove the view directly from CellLayoutChildren as this
+                    // does not re-mark the spaces as unoccupied.
+                    layoutParent.removeViewInLayout(child);
+                    if (child instanceof DropTarget) {
+                        mDragController.removeDropTarget((DropTarget) child);
                     }
-                    return false;
+                } else if (itemToRemove.container >= 0) {
+                    // The item may belong to a folder.
+                    View parent = idToViewMap.get(itemToRemove.container);
+                    if (parent != null) {
+                        ((FolderInfo) parent.getTag()).remove((ShortcutInfo) itemToRemove, false);
+                    }
                 }
-            };
-            LauncherModel.filterItemInfos(children.keySet(), filter);
-
-            // Remove all the apps from their folders
-            for (FolderInfo folder : folderAppsToRemove.keySet()) {
-                ArrayList<ShortcutInfo> appsToRemove = folderAppsToRemove.get(folder);
-                for (ShortcutInfo info : appsToRemove) {
-                    folder.remove(info, false);
-                }
-            }
-
-            // Remove all the other children
-            for (View child : childrenToRemove) {
-                // Note: We can not remove the view directly from CellLayoutChildren as this
-                // does not re-mark the spaces as unoccupied.
-                layoutParent.removeViewInLayout(child);
-                if (child instanceof DropTarget) {
-                    mDragController.removeDropTarget((DropTarget) child);
-                }
-            }
-
-            if (childrenToRemove.size() > 0) {
-                layout.requestLayout();
-                layout.invalidate();
             }
         }
 
@@ -4160,8 +4082,9 @@
     public void removeAbandonedPromise(String packageName, UserHandleCompat user) {
         HashSet<String> packages = new HashSet<>(1);
         packages.add(packageName);
-        LauncherModel.deletePackageFromDatabase(mLauncher, packageName, user);
-        removeItemsByMatcher(ItemInfoMatcher.ofPackages(packages, user));
+        ItemInfoMatcher matcher = ItemInfoMatcher.ofPackages(packages, user);
+        LauncherModel.deleteItemsFromDatabase(mLauncher, matcher);
+        removeItemsByMatcher(matcher);
     }
 
     public void updateRestoreItems(final HashSet<ItemInfo> updates) {
@@ -4286,7 +4209,7 @@
     }
 
     @Override
-    public void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent) {
+    public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
         target.gridX = info.cellX;
         target.gridY = info.cellY;
         target.pageIndex = getCurrentPage();
@@ -4338,7 +4261,6 @@
                 @Override
                 public boolean evaluate(ItemInfo info, View view) {
                     if (view instanceof PendingAppWidgetHostView && mInfos.contains(info)) {
-                        PendingAppWidgetHostView hostView = (PendingAppWidgetHostView) view;
                         mLauncher.removeItem(view, info, false /* deleteFromDb */);
                         mLauncher.bindAppWidget((LauncherAppWidgetInfo) info);
                     }
@@ -4362,4 +4284,26 @@
     public static final boolean isQsbContainerPage(int pageNo) {
         return pageNo == 0;
     }
+
+    private class StateTransitionListener extends AnimatorListenerAdapter
+            implements AnimatorUpdateListener {
+        @Override
+        public void onAnimationUpdate(ValueAnimator anim) {
+            mTransitionProgress = anim.getAnimatedFraction();
+        }
+
+        @Override
+        public void onAnimationStart(Animator animation) {
+            if (mState == State.SPRING_LOADED) {
+                // Show the page indicator at the same time as the rest of the transition.
+                showPageIndicatorAtCurrentScroll();
+            }
+            mTransitionProgress = 0;
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            onEndStateTransition();
+        }
+    }
 }
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 598ba74..1f36468 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -30,12 +30,11 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.animation.DecelerateInterpolator;
 
+import com.android.launcher3.anim.AnimationLayerSet;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.util.Thunk;
 
-import java.util.HashMap;
-
 /**
  * A convenience class to update a view's visibility state after an alpha animation.
  */
@@ -226,7 +225,7 @@
     }
 
     public AnimatorSet getAnimationToState(Workspace.State fromState, Workspace.State toState,
-            boolean animated, HashMap<View, Integer> layerViews) {
+            boolean animated, AnimationLayerSet layerViews) {
         AccessibilityManager am = (AccessibilityManager)
                 mLauncher.getSystemService(Context.ACCESSIBILITY_SERVICE);
         final boolean accessibilityEnabled = am.isEnabled();
@@ -262,8 +261,7 @@
      * Starts a transition animation for the workspace.
      */
     private void animateWorkspace(final TransitionStates states, final boolean animated,
-                                  final int duration, final HashMap<View, Integer> layerViews,
-                                  final boolean accessibilityEnabled) {
+            final int duration, AnimationLayerSet layerViews, final boolean accessibilityEnabled) {
         // Cancel existing workspace animations and create a new animator set if requested
         cancelAnimation();
         if (animated) {
@@ -396,12 +394,10 @@
 
             // For animation optimization, we may need to provide the Launcher transition
             // with a set of views on which to force build and manage layers in certain scenarios.
-            layerViews.put(overviewPanel, LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER);
-            layerViews.put(qsbContainer, LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER);
-            layerViews.put(mLauncher.getHotseat(),
-                    LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER);
-            layerViews.put(mWorkspace.getPageIndicator(),
-                    LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER);
+            layerViews.addView(overviewPanel);
+            layerViews.addView(qsbContainer);
+            layerViews.addView(mLauncher.getHotseat());
+            layerViews.addView(mWorkspace.getPageIndicator());
 
             if (states.workspaceToOverview) {
                 hotseatAlpha.setInterpolator(new DecelerateInterpolator(2));
@@ -426,6 +422,11 @@
                 }
 
                 @Override
+                public void onAnimationStart(Animator animation) {
+                    mWorkspace.getPageIndicator().setShouldAutoHide(!states.stateIsSpringLoaded);
+                }
+
+                @Override
                 public void onAnimationEnd(Animator animation) {
                     mStateAnimator = null;
                     if (canceled) return;
@@ -438,6 +439,7 @@
         } else {
             overviewPanel.setAlpha(finalOverviewPanelAlpha);
             AlphaUpdateListener.updateVisibility(overviewPanel, accessibilityEnabled);
+            mWorkspace.getPageIndicator().setShouldAutoHide(!states.stateIsSpringLoaded);
 
             qsbAlphaAnimation.end();
             mWorkspace.createHotseatAlphaAnimator(finalHotseatAlpha).end();
diff --git a/src/com/android/launcher3/accessibility/AccessibileDragListenerAdapter.java b/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java
similarity index 93%
rename from src/com/android/launcher3/accessibility/AccessibileDragListenerAdapter.java
rename to src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java
index 62a9a6d..f8df5d7 100644
--- a/src/com/android/launcher3/accessibility/AccessibileDragListenerAdapter.java
+++ b/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java
@@ -28,7 +28,7 @@
  * Utility listener to enable/disable accessibility drag flags for a ViewGroup
  * containing CellLayouts
  */
-public class AccessibileDragListenerAdapter implements DragListener {
+public class AccessibleDragListenerAdapter implements DragListener {
 
     private final ViewGroup mViewGroup;
     private final int mDragType;
@@ -38,7 +38,7 @@
      * @param dragType either {@link CellLayout#WORKSPACE_ACCESSIBILITY_DRAG} or
      *                 {@link CellLayout#FOLDER_ACCESSIBILITY_DRAG}
      */
-    public AccessibileDragListenerAdapter(ViewGroup parent, int dragType) {
+    public AccessibleDragListenerAdapter(ViewGroup parent, int dragType) {
         mViewGroup = parent;
         mDragType = dragType;
     }
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index 173aad0..83391f3 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -57,7 +57,7 @@
     protected static final int MOVE = R.id.action_move;
     protected static final int MOVE_TO_WORKSPACE = R.id.action_move_to_workspace;
     protected static final int RESIZE = R.id.action_resize;
-    protected static final int DEEP_SHORTCUTS = R.id.action_deep_shortcuts;
+    public static final int DEEP_SHORTCUTS = R.id.action_deep_shortcuts;
 
     public enum DragType {
         ICON,
@@ -100,14 +100,17 @@
     @Override
     public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
         super.onInitializeAccessibilityNodeInfo(host, info);
-        addActions(host, info);
+        addSupportedActions(host, info, false);
     }
 
-    protected void addActions(View host, AccessibilityNodeInfo info) {
+    public void addSupportedActions(View host, AccessibilityNodeInfo info, boolean fromKeyboard) {
         if (!(host.getTag() instanceof ItemInfo)) return;
         ItemInfo item = (ItemInfo) host.getTag();
 
-        if (host instanceof BubbleTextView && ((BubbleTextView) host).hasDeepShortcuts()) {
+        // If the request came from keyboard, do not add custom shortcuts as that is already
+        // exposed as a direct shortcut
+        if (!fromKeyboard && host instanceof BubbleTextView
+                && ((BubbleTextView) host).hasDeepShortcuts()) {
             info.addAction(mActions.get(DEEP_SHORTCUTS));
         }
 
@@ -121,9 +124,10 @@
             info.addAction(mActions.get(INFO));
         }
 
-        if ((item instanceof ShortcutInfo)
+        // Do not add move actions for keyboard request as this uses virtual nodes.
+        if (!fromKeyboard && ((item instanceof ShortcutInfo)
                 || (item instanceof LauncherAppWidgetInfo)
-                || (item instanceof FolderInfo)) {
+                || (item instanceof FolderInfo))) {
             info.addAction(mActions.get(MOVE));
 
             if (item.container >= 0) {
@@ -188,8 +192,8 @@
             });
             return true;
         } else if (action == MOVE_TO_WORKSPACE) {
-            Folder folder = mLauncher.getWorkspace().getOpenFolder();
-            mLauncher.closeFolder(folder, true);
+            Folder folder = Folder.getOpen(mLauncher);
+            folder.close(true);
             ShortcutInfo info = (ShortcutInfo) item;
             folder.getInfo().remove(info, false);
 
@@ -369,12 +373,10 @@
         mLauncher.getDragLayer().getDescendantRectRelativeToSelf(item, pos);
         mLauncher.getDragController().prepareAccessibleDrag(pos.centerX(), pos.centerY());
 
-        Workspace workspace = mLauncher.getWorkspace();
-
-        Folder folder = workspace.getOpenFolder();
+        Folder folder = Folder.getOpen(mLauncher);
         if (folder != null) {
             if (!folder.getItemsInReadingOrder().contains(item)) {
-                mLauncher.closeFolder();
+                folder.close(true);
                 folder = null;
             }
         }
@@ -386,7 +388,7 @@
         if (folder != null) {
             folder.startDrag(cellInfo.cell, options);
         } else {
-            workspace.startDrag(cellInfo, options);
+            mLauncher.getWorkspace().startDrag(cellInfo, options);
         }
     }
 
diff --git a/src/com/android/launcher3/accessibility/OverviewAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/OverviewAccessibilityDelegate.java
index 385a766..29dd95c 100644
--- a/src/com/android/launcher3/accessibility/OverviewAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/OverviewAccessibilityDelegate.java
@@ -44,7 +44,7 @@
         Context context = host.getContext();
         info.addAction(new AccessibilityAction(OVERVIEW, context.getText(OVERVIEW)));
 
-        if (Utilities.isWallapaperAllowed(context)) {
+        if (Utilities.isWallpaperAllowed(context)) {
             info.addAction(new AccessibilityAction(WALLPAPERS, context.getText(WALLPAPERS)));
         }
         info.addAction(new AccessibilityAction(WIDGETS, context.getText(WIDGETS)));
diff --git a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
index 0baa8f3..f7ca703 100644
--- a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
@@ -19,6 +19,7 @@
 import android.view.View;
 import android.view.accessibility.AccessibilityNodeInfo;
 
+import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherModel;
@@ -40,8 +41,10 @@
     }
 
     @Override
-    protected void addActions(View host, AccessibilityNodeInfo info) {
-        info.addAction(mActions.get(ADD_TO_WORKSPACE));
+    public void addSupportedActions(View host, AccessibilityNodeInfo info, boolean fromKeyboard) {
+        if ((host.getParent() instanceof DeepShortcutView)) {
+            info.addAction(mActions.get(ADD_TO_WORKSPACE));
+        }
     }
 
     @Override
@@ -62,7 +65,7 @@
                     ArrayList<ItemInfo> itemList = new ArrayList<>();
                     itemList.add(info);
                     mLauncher.bindItems(itemList, 0, itemList.size(), true);
-                    mLauncher.closeShortcutsContainer();
+                    AbstractFloatingView.closeAllOpenViews(mLauncher);
                     announceConfirmation(R.string.item_added_to_workspace);
                 }
             };
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 77ef642..5fc1d97 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -15,11 +15,11 @@
  */
 package com.android.launcher3.allapps;
 
-import android.annotation.SuppressLint;
 import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Point;
+import android.graphics.Color;
 import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.InsetDrawable;
 import android.support.v7.widget.RecyclerView;
 import android.text.Selection;
 import android.text.Spannable;
@@ -31,25 +31,22 @@
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.BaseContainerView;
-import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeleteDropTarget;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.DragSource;
 import com.android.launcher3.DropTarget;
 import com.android.launcher3.ExtendedEditText;
+import com.android.launcher3.Insettable;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherTransitionable;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.Workspace;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.graphics.TintedDrawableSpan;
@@ -58,112 +55,32 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.ComponentKey;
 
-import java.nio.charset.Charset;
-import java.nio.charset.CharsetEncoder;
 import java.util.ArrayList;
 import java.util.List;
 
-
-/**
- * A merge algorithm that merges every section indiscriminately.
- */
-final class FullMergeAlgorithm implements AlphabeticalAppsList.MergeAlgorithm {
-
-    @Override
-    public boolean continueMerging(AlphabeticalAppsList.SectionInfo section,
-            AlphabeticalAppsList.SectionInfo withSection,
-            int sectionAppCount, int numAppsPerRow, int mergeCount) {
-        // Don't merge the predicted apps
-        if (section.firstAppItem.viewType != AllAppsGridAdapter.VIEW_TYPE_ICON) {
-            return false;
-        }
-        // Otherwise, merge every other section
-        return true;
-    }
-}
-
-/**
- * The logic we use to merge multiple sections.  We only merge sections when their final row
- * contains less than a certain number of icons, and stop at a specified max number of merges.
- * In addition, we will try and not merge sections that identify apps from different scripts.
- */
-final class SimpleSectionMergeAlgorithm implements AlphabeticalAppsList.MergeAlgorithm {
-
-    private int mMinAppsPerRow;
-    private int mMinRowsInMergedSection;
-    private int mMaxAllowableMerges;
-    private CharsetEncoder mAsciiEncoder;
-
-    public SimpleSectionMergeAlgorithm(int minAppsPerRow, int minRowsInMergedSection, int maxNumMerges) {
-        mMinAppsPerRow = minAppsPerRow;
-        mMinRowsInMergedSection = minRowsInMergedSection;
-        mMaxAllowableMerges = maxNumMerges;
-        mAsciiEncoder = Charset.forName("US-ASCII").newEncoder();
-    }
-
-    @Override
-    public boolean continueMerging(AlphabeticalAppsList.SectionInfo section,
-            AlphabeticalAppsList.SectionInfo withSection,
-            int sectionAppCount, int numAppsPerRow, int mergeCount) {
-        // Don't merge the predicted apps
-        if (section.firstAppItem.viewType != AllAppsGridAdapter.VIEW_TYPE_ICON) {
-            return false;
-        }
-
-        // Continue merging if the number of hanging apps on the final row is less than some
-        // fixed number (ragged), the merged rows has yet to exceed some minimum row count,
-        // and while the number of merged sections is less than some fixed number of merges
-        int rows = sectionAppCount / numAppsPerRow;
-        int cols = sectionAppCount % numAppsPerRow;
-
-        // Ensure that we do not merge across scripts, currently we only allow for english and
-        // native scripts so we can test if both can just be ascii encoded
-        boolean isCrossScript = false;
-        if (section.firstAppItem != null && withSection.firstAppItem != null) {
-            isCrossScript = mAsciiEncoder.canEncode(section.firstAppItem.sectionName) !=
-                    mAsciiEncoder.canEncode(withSection.firstAppItem.sectionName);
-        }
-        return (0 < cols && cols < mMinAppsPerRow) &&
-                rows < mMinRowsInMergedSection &&
-                mergeCount < mMaxAllowableMerges &&
-                !isCrossScript;
-    }
-}
-
 /**
  * The all apps view container.
  */
 public class AllAppsContainerView extends BaseContainerView implements DragSource,
-        LauncherTransitionable, View.OnLongClickListener, AllAppsSearchBarController.Callbacks {
-
-    private static final int MIN_ROWS_IN_MERGED_SECTION_PHONE = 3;
-    private static final int MAX_NUM_MERGES_PHONE = 2;
+        View.OnLongClickListener, AllAppsSearchBarController.Callbacks, Insettable {
 
     private final Launcher mLauncher;
     private final AlphabeticalAppsList mApps;
     private final AllAppsGridAdapter mAdapter;
     private final RecyclerView.LayoutManager mLayoutManager;
-    private final RecyclerView.ItemDecoration mItemDecoration;
-
-    // The computed bounds of the container
-    private final Rect mContentBounds = new Rect();
 
     private AllAppsRecyclerView mAppsRecyclerView;
     private AllAppsSearchBarController mSearchBarController;
 
     private View mSearchContainer;
+    private int mSearchContainerMinHeight;
     private ExtendedEditText mSearchInput;
     private HeaderElevationController mElevationController;
-    private int mSearchContainerOffsetTop;
 
     private SpannableStringBuilder mSearchQueryBuilder = null;
 
-    private int mSectionNamesMargin;
     private int mNumAppsPerRow;
     private int mNumPredictedAppsPerRow;
-    private int mRecyclerViewBottomPadding;
-    // This coordinate is relative to this container view
-    private final Point mBoundsCheckLastTouchDownPos = new Point(-1, -1);
 
     public AllAppsContainerView(Context context) {
         this(context, null);
@@ -175,27 +92,37 @@
 
     public AllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
-        Resources res = context.getResources();
 
         mLauncher = Launcher.getLauncher(context);
-        mSectionNamesMargin = res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin);
         mApps = new AlphabeticalAppsList(context);
         mAdapter = new AllAppsGridAdapter(mLauncher, mApps, mLauncher, this);
         mApps.setAdapter(mAdapter);
         mLayoutManager = mAdapter.getLayoutManager();
-        mItemDecoration = mAdapter.getItemDecoration();
-        DeviceProfile grid = mLauncher.getDeviceProfile();
-        if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && !grid.isVerticalBarLayout()) {
-            mRecyclerViewBottomPadding = 0;
-            setPadding(0, 0, 0, 0);
-        } else {
-            mRecyclerViewBottomPadding =
-                    res.getDimensionPixelSize(R.dimen.all_apps_list_bottom_padding);
-        }
         mSearchQueryBuilder = new SpannableStringBuilder();
+        mSearchContainerMinHeight
+                = getResources().getDimensionPixelSize(R.dimen.all_apps_search_bar_height);
+
         Selection.setSelection(mSearchQueryBuilder, 0);
     }
 
+    @Override
+    protected void updateBackground(
+            int paddingLeft, int paddingTop, int paddingRight, int paddingBottom) {
+        if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
+            if (mLauncher.getDeviceProfile().isVerticalBarLayout()) {
+                getRevealView().setBackground(new InsetDrawable(mBaseDrawable,
+                        paddingLeft, paddingTop, paddingRight, paddingBottom));
+                getContentView().setBackground(
+                        new InsetDrawable(new ColorDrawable(Color.TRANSPARENT),
+                                paddingLeft, paddingTop, paddingRight, paddingBottom));
+            } else {
+                getRevealView().setBackground(mBaseDrawable);
+            }
+        } else {
+            super.updateBackground(paddingLeft, paddingTop, paddingRight, paddingBottom);
+        }
+    }
+
     /**
      * Sets the current set of predicted apps.
      */
@@ -268,7 +195,7 @@
         int[] point = new int[2];
         point[0] = (int) ev.getX();
         point[1] = (int) ev.getY();
-        Utilities.mapCoordInSelfToDescendent(mAppsRecyclerView, this, point);
+        Utilities.mapCoordInSelfToDescendant(mAppsRecyclerView, this, point);
 
         // IF the MotionEvent is inside the search box, and the container keeps on receiving
         // touch input, container should move down.
@@ -282,13 +209,13 @@
         }
 
         // IF a shortcuts container is open, container should not be pulled down.
-        if (mLauncher.getOpenShortcutsContainer() != null) {
+        if (DeepShortcutsContainer.getOpen(mLauncher) != null) {
             return false;
         }
 
         // IF scroller is at the very top OR there is no scroll bar because there is probably not
         // enough items to scroll, THEN it's okay for the container to be pulled down.
-        if (mAppsRecyclerView.getScrollBar().getThumbOffset().y <= 0) {
+        if (mAppsRecyclerView.getScrollBar().getThumbOffsetY() <= 0) {
             return true;
         }
         return false;
@@ -340,9 +267,6 @@
                 0, 1, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
         mSearchInput.setHint(spanned);
 
-        mSearchContainerOffsetTop = getResources().getDimensionPixelSize(
-                R.dimen.all_apps_search_bar_margin_top);
-
         mElevationController = Utilities.ATLEAST_LOLLIPOP
                 ? new HeaderElevationController.ControllerVL(mSearchContainer)
                 : new HeaderElevationController.ControllerV16(mSearchContainer);
@@ -356,10 +280,6 @@
         mAppsRecyclerView.addOnScrollListener(mElevationController);
         mAppsRecyclerView.setElevationController(mElevationController);
 
-        if (mItemDecoration != null) {
-            mAppsRecyclerView.addItemDecoration(mItemDecoration);
-        }
-
         FocusedItemDecorator focusedItemDecorator = new FocusedItemDecorator(mAppsRecyclerView);
         mAppsRecyclerView.addItemDecoration(focusedItemDecorator);
         mAppsRecyclerView.preMeasureViews(mAdapter);
@@ -373,15 +293,15 @@
     }
 
     @Override
+    public View getTouchDelegateTargetView() {
+        return mAppsRecyclerView;
+    }
+
+    @Override
     public void onBoundsChanged(Rect newBounds) { }
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        int widthPx = MeasureSpec.getSize(widthMeasureSpec);
-        int heightPx = MeasureSpec.getSize(heightMeasureSpec);
-        updatePaddingsAndMargins(widthPx, heightPx);
-        mContentBounds.set(mContainerPaddingLeft, 0, widthPx - mContainerPaddingRight, heightPx);
-
         DeviceProfile grid = mLauncher.getDeviceProfile();
         grid.updateAppsViewNumCols();
         if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
@@ -392,18 +312,15 @@
 
                 mAppsRecyclerView.setNumAppsPerRow(grid, mNumAppsPerRow);
                 mAdapter.setNumAppsPerRow(mNumAppsPerRow);
-                mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow, new FullMergeAlgorithm());
-                if (mNumAppsPerRow > 0) {
-                    int rvPadding = mAppsRecyclerView.getPaddingStart(); // Assumes symmetry
-                    final int thumbMaxWidth =
-                            getResources().getDimensionPixelSize(
-                                    R.dimen.container_fastscroll_thumb_max_width);
-                    mSearchContainer.setPadding(
-                            rvPadding - mContainerPaddingLeft + thumbMaxWidth,
-                            mSearchContainer.getPaddingTop(),
-                            rvPadding - mContainerPaddingRight + thumbMaxWidth,
-                            mSearchContainer.getPaddingBottom());
-                }
+                mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow);
+            }
+            if (!grid.isVerticalBarLayout()) {
+                MarginLayoutParams searchContainerLp =
+                        (MarginLayoutParams) mSearchContainer.getLayoutParams();
+
+                searchContainerLp.height = mLauncher.getDragLayer().getInsets().top
+                        + mSearchContainerMinHeight;
+                mSearchContainer.setLayoutParams(searchContainerLp);
             }
             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
             return;
@@ -412,98 +329,21 @@
         // --- remove START when {@code FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP} is enabled. ---
 
         // Update the number of items in the grid before we measure the view
-        // TODO: mSectionNamesMargin is currently 0, but also account for it,
-        // if it's enabled in the future.
         grid.updateAppsViewNumCols();
         if (mNumAppsPerRow != grid.allAppsNumCols ||
                 mNumPredictedAppsPerRow != grid.allAppsNumPredictiveCols) {
             mNumAppsPerRow = grid.allAppsNumCols;
             mNumPredictedAppsPerRow = grid.allAppsNumPredictiveCols;
 
-            // If there is a start margin to draw section names, determine how we are going to merge
-            // app sections
-            boolean mergeSectionsFully = mSectionNamesMargin == 0 || !grid.isPhone;
-            AlphabeticalAppsList.MergeAlgorithm mergeAlgorithm = mergeSectionsFully ?
-                    new FullMergeAlgorithm() :
-                    new SimpleSectionMergeAlgorithm((int) Math.ceil(mNumAppsPerRow / 2f),
-                            MIN_ROWS_IN_MERGED_SECTION_PHONE, MAX_NUM_MERGES_PHONE);
-
             mAppsRecyclerView.setNumAppsPerRow(grid, mNumAppsPerRow);
             mAdapter.setNumAppsPerRow(mNumAppsPerRow);
-            mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow, mergeAlgorithm);
+            mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow);
         }
 
         // --- remove END when {@code FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP} is enabled. ---
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
     }
 
-    /**
-     * Update the background and padding of the Apps view and children.  Instead of insetting the
-     * container view, we inset the background and padding of the recycler view to allow for the
-     * recycler view to handle touch events (for fast scrolling) all the way to the edge.
-     */
-    private void updatePaddingsAndMargins(int widthPx, int heightPx) {
-        Rect bgPadding = new Rect();
-        getRevealView().getBackground().getPadding(bgPadding);
-
-        mAppsRecyclerView.updateBackgroundPadding(bgPadding);
-        mAdapter.updateBackgroundPadding(bgPadding);
-        mElevationController.updateBackgroundPadding(bgPadding);
-
-        // Pad the recycler view by the background padding plus the start margin (for the section
-        // names)
-        int maxScrollBarWidth = mAppsRecyclerView.getMaxScrollbarWidth();
-        int startInset = Math.max(mSectionNamesMargin, maxScrollBarWidth);
-        if (Utilities.isRtl(getResources())) {
-            mAppsRecyclerView.setPadding(bgPadding.left + maxScrollBarWidth, 0, bgPadding.right
-                    + startInset, mRecyclerViewBottomPadding);
-        } else {
-            mAppsRecyclerView.setPadding(bgPadding.left + startInset, 0, bgPadding.right +
-                    maxScrollBarWidth, mRecyclerViewBottomPadding);
-        }
-
-        MarginLayoutParams lp = (MarginLayoutParams) mSearchContainer.getLayoutParams();
-        lp.leftMargin = bgPadding.left;
-        lp.rightMargin = bgPadding.right;
-
-        // Clip the view to the left and right edge of the background to
-        // to prevent shadows from rendering beyond the edges
-        final Rect newClipBounds = new Rect(
-                bgPadding.left, 0, widthPx - bgPadding.right, heightPx);
-        setClipBounds(newClipBounds);
-
-        // Allow the overscroll effect to reach the edges of the view
-        mAppsRecyclerView.setClipToPadding(false);
-
-        DeviceProfile grid = mLauncher.getDeviceProfile();
-        if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
-            if (!grid.isVerticalBarLayout()) {
-                MarginLayoutParams mlp = (MarginLayoutParams) mAppsRecyclerView.getLayoutParams();
-
-                Rect insets = mLauncher.getDragLayer().getInsets();
-                getContentView().setPadding(0, 0, 0, 0);
-                int height = insets.top + grid.hotseatCellHeightPx;
-
-                mlp.topMargin = height;
-                mAppsRecyclerView.setLayoutParams(mlp);
-
-                mSearchContainer.setPadding(
-                        mSearchContainer.getPaddingLeft(),
-                        insets.top + mSearchContainerOffsetTop,
-                        mSearchContainer.getPaddingRight(),
-                        mSearchContainer.getPaddingBottom());
-                lp.height = height;
-
-                View navBarBg = findViewById(R.id.nav_bar_bg);
-                ViewGroup.LayoutParams params = navBarBg.getLayoutParams();
-                params.height = insets.bottom;
-                navBarBg.setLayoutParams(params);
-                navBarBg.setVisibility(View.VISIBLE);
-            }
-        }
-        mSearchContainer.setLayoutParams(lp);
-    }
-
     @Override
     public boolean dispatchKeyEvent(KeyEvent event) {
         // Determine if the key event was actual text, if so, focus the search bar and then dispatch
@@ -526,18 +366,7 @@
     }
 
     @Override
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
-        return handleTouchEvent(ev);
-    }
-
-    @SuppressLint("ClickableViewAccessibility")
-    @Override
-    public boolean onTouchEvent(MotionEvent ev) {
-        return handleTouchEvent(ev);
-    }
-
-    @Override
-    public boolean onLongClick(View v) {
+    public boolean onLongClick(final View v) {
         // Return early if this is not initiated from a touch
         if (!v.isInTouchMode()) return false;
         // When we have exited all apps or are in transition, disregard long clicks
@@ -549,22 +378,20 @@
         if (mLauncher.getDragController().isDragging()) return false;
 
         // Start the drag
-        DragOptions dragOptions = new DragOptions();
-        if (v instanceof BubbleTextView) {
-            final BubbleTextView icon = (BubbleTextView) v;
-            if (icon.hasDeepShortcuts()) {
-                DeepShortcutsContainer dsc = DeepShortcutsContainer.showForIcon(icon);
-                if (dsc != null) {
-                    dragOptions.deferDragCondition = dsc.createDeferDragCondition(new Runnable() {
-                        @Override
-                        public void run() {
-                            icon.setVisibility(VISIBLE);
-                        }
-                    });
-                }
+        final DragController dragController = mLauncher.getDragController();
+        dragController.addDragListener(new DragController.DragListener() {
+            @Override
+            public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
+                v.setVisibility(INVISIBLE);
             }
-        }
-        mLauncher.getWorkspace().beginDragShared(v, this, dragOptions);
+
+            @Override
+            public void onDragEnd() {
+                v.setVisibility(VISIBLE);
+                dragController.removeDragListener(this);
+            }
+        });
+        mLauncher.getWorkspace().beginDragShared(v, this, new DragOptions());
         if (FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND) {
             // Enter spring loaded mode (the new workspace does this in
             // onDragStart(), so we don't want to do it here)
@@ -615,101 +442,12 @@
         }
         mLauncher.unlockScreenOrientation(false);
 
-        // Display an error message if the drag failed due to there not being enough space on the
-        // target layout we were dropping on.
         if (!success) {
-            boolean showOutOfSpaceMessage = false;
-            if (target instanceof Workspace && !mLauncher.getDragController().isDeferringDrag()) {
-                int currentScreen = mLauncher.getCurrentWorkspaceScreen();
-                Workspace workspace = (Workspace) target;
-                CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen);
-                ItemInfo itemInfo = d.dragInfo;
-                if (layout != null) {
-                    showOutOfSpaceMessage =
-                            !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY);
-                }
-            }
-            if (showOutOfSpaceMessage) {
-                mLauncher.showOutOfSpaceMessage(false);
-            }
-
             d.deferDragViewCleanupPostAnimation = false;
         }
     }
 
     @Override
-    public void onLauncherTransitionPrepare(Launcher l, boolean animated,
-            boolean multiplePagesVisible) {
-        // Do nothing
-    }
-
-    @Override
-    public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
-        // Do nothing
-    }
-
-    @Override
-    public void onLauncherTransitionStep(Launcher l, float t) {
-        // Do nothing
-    }
-
-    @Override
-    public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
-        if (toWorkspace) {
-            reset();
-        }
-    }
-
-    /**
-     * Handles the touch events to dismiss all apps when clicking outside the bounds of the
-     * recycler view.
-     */
-    private boolean handleTouchEvent(MotionEvent ev) {
-        DeviceProfile grid = mLauncher.getDeviceProfile();
-        int x = (int) ev.getX();
-        int y = (int) ev.getY();
-
-        switch (ev.getAction()) {
-            case MotionEvent.ACTION_DOWN:
-                if (!mContentBounds.isEmpty()) {
-                    // Outset the fixed bounds and check if the touch is outside all apps
-                    Rect tmpRect = new Rect(mContentBounds);
-                    tmpRect.inset(-grid.allAppsIconSizePx / 2, 0);
-                    if (ev.getX() < tmpRect.left || ev.getX() > tmpRect.right) {
-                        mBoundsCheckLastTouchDownPos.set(x, y);
-                        return true;
-                    }
-                } else {
-                    // Check if the touch is outside all apps
-                    if (ev.getX() < getPaddingLeft() ||
-                            ev.getX() > (getWidth() - getPaddingRight())) {
-                        mBoundsCheckLastTouchDownPos.set(x, y);
-                        return true;
-                    }
-                }
-                break;
-            case MotionEvent.ACTION_UP:
-                if (mBoundsCheckLastTouchDownPos.x > -1) {
-                    ViewConfiguration viewConfig = ViewConfiguration.get(getContext());
-                    float dx = ev.getX() - mBoundsCheckLastTouchDownPos.x;
-                    float dy = ev.getY() - mBoundsCheckLastTouchDownPos.y;
-                    float distance = (float) Math.hypot(dx, dy);
-                    if (distance < viewConfig.getScaledTouchSlop()) {
-                        // The background was clicked, so just go home
-                        Launcher launcher = Launcher.getLauncher(getContext());
-                        launcher.showWorkspace(true);
-                        return true;
-                    }
-                }
-                // Fall through
-            case MotionEvent.ACTION_CANCEL:
-                mBoundsCheckLastTouchDownPos.set(-1, -1);
-                break;
-        }
-        return false;
-    }
-
-    @Override
     public void onSearchResult(String query, ArrayList<ComponentKey> apps) {
         if (apps != null) {
             if (mApps.setOrderedFilter(apps)) {
@@ -732,11 +470,29 @@
     }
 
     @Override
-    public void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent) {
+    public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
         targetParent.containerType = mAppsRecyclerView.getContainerType(v);
     }
 
     public boolean shouldRestoreImeState() {
         return !TextUtils.isEmpty(mSearchInput.getText());
     }
+
+    @Override
+    public void setInsets(Rect insets) {
+        DeviceProfile grid = mLauncher.getDeviceProfile();
+        if (grid.isVerticalBarLayout()) {
+            ViewGroup.MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
+            mlp.leftMargin = insets.left;
+            mlp.topMargin = insets.top;
+            mlp.rightMargin = insets.right;
+            setLayoutParams(mlp);
+        } else {
+            View navBarBg = findViewById(R.id.nav_bar_bg);
+            ViewGroup.LayoutParams navBarBgLp = navBarBg.getLayoutParams();
+            navBarBgLp.height = insets.bottom;
+            navBarBg.setLayoutParams(navBarBgLp);
+            navBarBg.setVisibility(View.VISIBLE);
+        }
+    }
 }
diff --git a/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java b/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
index 76934af..28b7685 100644
--- a/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
+++ b/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
@@ -29,12 +29,11 @@
 
     private static final int INITIAL_TOUCH_SETTLING_DURATION = 100;
     private static final int REPEAT_TOUCH_SETTLING_DURATION = 200;
-    private static final float FAST_SCROLL_TOUCH_VELOCITY_BARRIER = 1900f;
 
     private AllAppsRecyclerView mRv;
     private AlphabeticalAppsList mApps;
 
-    // Keeps track of the current and targetted fast scroll section (the section to scroll to after
+    // Keeps track of the current and targeted fast scroll section (the section to scroll to after
     // the initial delay)
     int mTargetFastScrollPosition = -1;
     @Thunk String mCurrentFastScrollSection;
@@ -187,9 +186,9 @@
     public void onBindView(AllAppsGridAdapter.ViewHolder holder) {
         // Update newly bound views to the current fast scroll state if we are fast scrolling
         if (mCurrentFastScrollSection != null || mTargetFastScrollSection != null) {
-            if (holder.mContent instanceof BaseRecyclerViewFastScrollBar.FastScrollFocusableView) {
+            if (holder.itemView instanceof BaseRecyclerViewFastScrollBar.FastScrollFocusableView) {
                 BaseRecyclerViewFastScrollBar.FastScrollFocusableView v =
-                        (BaseRecyclerViewFastScrollBar.FastScrollFocusableView) holder.mContent;
+                        (BaseRecyclerViewFastScrollBar.FastScrollFocusableView) holder.itemView;
                 updateViewFastScrollFocusState(v, holder.getPosition(), false /* animated */);
                 mTrackedFastScrollViews.add(v);
             }
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 7b6aef1..bd877f2 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -18,10 +18,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Paint;
 import android.graphics.Point;
-import android.graphics.PointF;
 import android.graphics.Rect;
 import android.support.v4.view.accessibility.AccessibilityEventCompat;
 import android.support.v4.view.accessibility.AccessibilityRecordCompat;
@@ -41,10 +38,6 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-
-import java.util.HashMap;
-import java.util.List;
 
 /**
  * The grid view adapter of all the apps.
@@ -52,10 +45,7 @@
 public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHolder> {
 
     public static final String TAG = "AppsGridAdapter";
-    private static final boolean DEBUG = false;
 
-    // A section break in the grid
-    public static final int VIEW_TYPE_SECTION_BREAK = 1 << 0;
     // A normal icon
     public static final int VIEW_TYPE_ICON = 1 << 1;
     // A prediction icon
@@ -78,25 +68,22 @@
     // Common view type masks
     public static final int VIEW_TYPE_MASK_DIVIDER = VIEW_TYPE_SEARCH_DIVIDER
             | VIEW_TYPE_SEARCH_MARKET_DIVIDER
-            | VIEW_TYPE_PREDICTION_DIVIDER
-            | VIEW_TYPE_SECTION_BREAK;
+            | VIEW_TYPE_PREDICTION_DIVIDER;
     public static final int VIEW_TYPE_MASK_ICON = VIEW_TYPE_ICON
             | VIEW_TYPE_PREDICTION_ICON;
 
 
     public interface BindViewCallback {
-        public void onBindView(ViewHolder holder);
+        void onBindView(ViewHolder holder);
     }
 
     /**
      * ViewHolder for each icon.
      */
     public static class ViewHolder extends RecyclerView.ViewHolder {
-        public View mContent;
 
         public ViewHolder(View v) {
             super(v);
-            mContent = v;
         }
     }
 
@@ -158,189 +145,14 @@
         }
     }
 
-    /**
-     * Helper class to draw the section headers
-     */
-    public class GridItemDecoration extends RecyclerView.ItemDecoration {
-
-        private static final boolean DEBUG_SECTION_MARGIN = false;
-        private static final boolean FADE_OUT_SECTIONS = false;
-
-        private HashMap<String, PointF> mCachedSectionBounds = new HashMap<>();
-        private Rect mTmpBounds = new Rect();
-
-        @Override
-        public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
-            if (mApps.hasFilter() || mAppsPerRow == 0) {
-                return;
-            }
-
-            if (DEBUG_SECTION_MARGIN) {
-                Paint p = new Paint();
-                p.setColor(0x33ff0000);
-                c.drawRect(mBackgroundPadding.left, 0, mBackgroundPadding.left + mSectionNamesMargin,
-                        parent.getMeasuredHeight(), p);
-            }
-
-            List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
-            boolean showSectionNames = mSectionNamesMargin > 0;
-            int childCount = parent.getChildCount();
-            int lastSectionTop = 0;
-            int lastSectionHeight = 0;
-            for (int i = 0; i < childCount; i++) {
-                View child = parent.getChildAt(i);
-                ViewHolder holder = (ViewHolder) parent.getChildViewHolder(child);
-                if (!isValidHolderAndChild(holder, child, items)) {
-                    continue;
-                }
-
-                if (showSectionNames && shouldDrawItemSection(holder, i, items)) {
-                    // At this point, we only draw sections for each section break;
-                    int viewTopOffset = (2 * child.getPaddingTop());
-                    int pos = holder.getPosition();
-                    AlphabeticalAppsList.AdapterItem item = items.get(pos);
-                    AlphabeticalAppsList.SectionInfo sectionInfo = item.sectionInfo;
-
-                    // Draw all the sections for this index
-                    String lastSectionName = item.sectionName;
-                    for (int j = item.sectionAppIndex; j < sectionInfo.numApps; j++, pos++) {
-                        AlphabeticalAppsList.AdapterItem nextItem = items.get(pos);
-                        String sectionName = nextItem.sectionName;
-                        if (nextItem.sectionInfo != sectionInfo) {
-                            break;
-                        }
-                        if (j > item.sectionAppIndex && sectionName.equals(lastSectionName)) {
-                            continue;
-                        }
-
-                        // Find the section name bounds
-                        PointF sectionBounds = getAndCacheSectionBounds(sectionName);
-
-                        // Calculate where to draw the section
-                        int sectionBaseline = (int) (viewTopOffset + sectionBounds.y);
-                        int x = mIsRtl ?
-                                parent.getWidth() - mBackgroundPadding.left - mSectionNamesMargin :
-                                mBackgroundPadding.left;
-                        x += (int) ((mSectionNamesMargin - sectionBounds.x) / 2f);
-                        int y = child.getTop() + sectionBaseline;
-
-                        // Determine whether this is the last row with apps in that section, if
-                        // so, then fix the section to the row allowing it to scroll past the
-                        // baseline, otherwise, bound it to the baseline so it's in the viewport
-                        int appIndexInSection = items.get(pos).sectionAppIndex;
-                        int nextRowPos = Math.min(items.size() - 1,
-                                pos + mAppsPerRow - (appIndexInSection % mAppsPerRow));
-                        AlphabeticalAppsList.AdapterItem nextRowItem = items.get(nextRowPos);
-                        boolean fixedToRow = !sectionName.equals(nextRowItem.sectionName);
-                        if (!fixedToRow) {
-                            y = Math.max(sectionBaseline, y);
-                        }
-
-                        // In addition, if it overlaps with the last section that was drawn, then
-                        // offset it so that it does not overlap
-                        if (lastSectionHeight > 0 && y <= (lastSectionTop + lastSectionHeight)) {
-                            y += lastSectionTop - y + lastSectionHeight;
-                        }
-
-                        // Draw the section header
-                        if (FADE_OUT_SECTIONS) {
-                            int alpha = 255;
-                            if (fixedToRow) {
-                                alpha = Math.min(255,
-                                        (int) (255 * (Math.max(0, y) / (float) sectionBaseline)));
-                            }
-                            mSectionTextPaint.setAlpha(alpha);
-                        }
-                        c.drawText(sectionName, x, y, mSectionTextPaint);
-
-                        lastSectionTop = y;
-                        lastSectionHeight = (int) (sectionBounds.y + mSectionHeaderOffset);
-                        lastSectionName = sectionName;
-                    }
-                    i += (sectionInfo.numApps - item.sectionAppIndex);
-                }
-            }
-        }
-
-        @Override
-        public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
-                RecyclerView.State state) {
-            // Do nothing
-        }
-
-        /**
-         * Given a section name, return the bounds of the given section name.
-         */
-        private PointF getAndCacheSectionBounds(String sectionName) {
-            PointF bounds = mCachedSectionBounds.get(sectionName);
-            if (bounds == null) {
-                mSectionTextPaint.getTextBounds(sectionName, 0, sectionName.length(), mTmpBounds);
-                bounds = new PointF(mSectionTextPaint.measureText(sectionName), mTmpBounds.height());
-                mCachedSectionBounds.put(sectionName, bounds);
-            }
-            return bounds;
-        }
-
-        /**
-         * Returns whether we consider this a valid view holder for us to draw a divider or section for.
-         */
-        private boolean isValidHolderAndChild(ViewHolder holder, View child,
-                List<AlphabeticalAppsList.AdapterItem> items) {
-            // Ensure item is not already removed
-            GridLayoutManager.LayoutParams lp = (GridLayoutManager.LayoutParams)
-                    child.getLayoutParams();
-            if (lp.isItemRemoved()) {
-                return false;
-            }
-            // Ensure we have a valid holder
-            if (holder == null) {
-                return false;
-            }
-            // Ensure we have a holder position
-            int pos = holder.getPosition();
-            if (pos < 0 || pos >= items.size()) {
-                return false;
-            }
-            return true;
-        }
-
-        /**
-         * Returns whether to draw the section for the given child.
-         */
-        private boolean shouldDrawItemSection(ViewHolder holder, int childIndex,
-                List<AlphabeticalAppsList.AdapterItem> items) {
-            int pos = holder.getPosition();
-            AlphabeticalAppsList.AdapterItem item = items.get(pos);
-
-            // Ensure it's an icon
-            if (item.viewType != AllAppsGridAdapter.VIEW_TYPE_ICON) {
-                return false;
-            }
-            // Draw the section header for the first item in each section
-            return (childIndex == 0) ||
-                    (items.get(pos - 1).viewType == AllAppsGridAdapter.VIEW_TYPE_SECTION_BREAK);
-        }
-    }
-
     private final Launcher mLauncher;
     private final LayoutInflater mLayoutInflater;
     private final AlphabeticalAppsList mApps;
     private final GridLayoutManager mGridLayoutMgr;
     private final GridSpanSizer mGridSizer;
-    private final GridItemDecoration mItemDecoration;
     private final View.OnClickListener mIconClickListener;
     private final View.OnLongClickListener mIconLongClickListener;
 
-    private final Rect mBackgroundPadding = new Rect();
-    private final boolean mIsRtl;
-
-    // Section drawing
-    @Deprecated
-    private final int mSectionNamesMargin;
-    @Deprecated
-    private final int mSectionHeaderOffset;
-    private final Paint mSectionTextPaint;
-
     private int mAppsPerRow;
 
     private BindViewCallback mBindViewCallback;
@@ -361,18 +173,9 @@
         mGridSizer = new GridSpanSizer();
         mGridLayoutMgr = new AppsGridLayoutManager(launcher);
         mGridLayoutMgr.setSpanSizeLookup(mGridSizer);
-        mItemDecoration = new GridItemDecoration();
         mLayoutInflater = LayoutInflater.from(launcher);
         mIconClickListener = iconClickListener;
         mIconLongClickListener = iconLongClickListener;
-        mSectionNamesMargin = res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin);
-        mSectionHeaderOffset = res.getDimensionPixelSize(R.dimen.all_apps_grid_section_y_offset);
-        mIsRtl = Utilities.isRtl(res);
-
-        mSectionTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-        mSectionTextPaint.setTextSize(res.getDimensionPixelSize(
-                R.dimen.all_apps_grid_section_text_size));
-        mSectionTextPaint.setColor(Utilities.getColorAccent(launcher));
     }
 
     public static boolean isDividerViewType(int viewType) {
@@ -421,33 +224,15 @@
     }
 
     /**
-     * Notifies the adapter of the background padding so that it can draw things correctly in the
-     * item decorator.
-     */
-    public void updateBackgroundPadding(Rect padding) {
-        mBackgroundPadding.set(padding);
-    }
-
-    /**
      * Returns the grid layout manager.
      */
     public GridLayoutManager getLayoutManager() {
         return mGridLayoutMgr;
     }
 
-    /**
-     * Returns the item decoration for the recycler view.
-     */
-    public RecyclerView.ItemDecoration getItemDecoration() {
-        // We don't draw any headers when we are uncomfortably dense
-        return mItemDecoration;
-    }
-
     @Override
     public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
         switch (viewType) {
-            case VIEW_TYPE_SECTION_BREAK:
-                return new ViewHolder(new View(parent.getContext()));
             case VIEW_TYPE_ICON:
                 /* falls through */
             case VIEW_TYPE_PREDICTION_ICON: {
@@ -499,26 +284,26 @@
         switch (holder.getItemViewType()) {
             case VIEW_TYPE_ICON: {
                 AppInfo info = mApps.getAdapterItems().get(position).appInfo;
-                BubbleTextView icon = (BubbleTextView) holder.mContent;
+                BubbleTextView icon = (BubbleTextView) holder.itemView;
                 icon.applyFromApplicationInfo(info);
                 icon.setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
                 break;
             }
             case VIEW_TYPE_PREDICTION_ICON: {
                 AppInfo info = mApps.getAdapterItems().get(position).appInfo;
-                BubbleTextView icon = (BubbleTextView) holder.mContent;
+                BubbleTextView icon = (BubbleTextView) holder.itemView;
                 icon.applyFromApplicationInfo(info);
                 icon.setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
                 break;
             }
             case VIEW_TYPE_EMPTY_SEARCH:
-                TextView emptyViewText = (TextView) holder.mContent;
+                TextView emptyViewText = (TextView) holder.itemView;
                 emptyViewText.setText(mEmptySearchMessage);
                 emptyViewText.setGravity(mApps.hasNoFilteredResults() ? Gravity.CENTER :
                         Gravity.START | Gravity.CENTER_VERTICAL);
                 break;
             case VIEW_TYPE_SEARCH_MARKET:
-                TextView searchView = (TextView) holder.mContent;
+                TextView searchView = (TextView) holder.itemView;
                 if (mMarketSearchIntent != null) {
                     searchView.setVisibility(View.VISIBLE);
                 } else {
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 0173847..ab34287 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -101,7 +101,6 @@
         pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_ICON, approxRows * mNumAppsPerRow);
         pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_PREDICTION_ICON, mNumAppsPerRow);
         pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_PREDICTION_DIVIDER, 1);
-        pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_SECTION_BREAK, approxRows);
     }
 
     /**
@@ -116,21 +115,21 @@
 
         // Icons
         BubbleTextView icon = (BubbleTextView) adapter.onCreateViewHolder(this,
-                AllAppsGridAdapter.VIEW_TYPE_ICON).mContent;
+                AllAppsGridAdapter.VIEW_TYPE_ICON).itemView;
         int iconHeight = icon.getLayoutParams().height;
         mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_ICON, iconHeight);
         mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_PREDICTION_ICON, iconHeight);
 
         // Search divider
         View searchDivider = adapter.onCreateViewHolder(this,
-                AllAppsGridAdapter.VIEW_TYPE_SEARCH_DIVIDER).mContent;
+                AllAppsGridAdapter.VIEW_TYPE_SEARCH_DIVIDER).itemView;
         searchDivider.measure(widthMeasureSpec, heightMeasureSpec);
         int searchDividerHeight = searchDivider.getMeasuredHeight();
         mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_SEARCH_DIVIDER, searchDividerHeight);
 
         // Generic dividers
         View divider = adapter.onCreateViewHolder(this,
-                AllAppsGridAdapter.VIEW_TYPE_PREDICTION_DIVIDER).mContent;
+                AllAppsGridAdapter.VIEW_TYPE_PREDICTION_DIVIDER).itemView;
         divider.measure(widthMeasureSpec, heightMeasureSpec);
         int dividerHeight = divider.getMeasuredHeight();
         mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_PREDICTION_DIVIDER, dividerHeight);
@@ -138,18 +137,15 @@
 
         // Search views
         View emptySearch = adapter.onCreateViewHolder(this,
-                AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH).mContent;
+                AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH).itemView;
         emptySearch.measure(widthMeasureSpec, heightMeasureSpec);
         mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH,
                 emptySearch.getMeasuredHeight());
         View searchMarket = adapter.onCreateViewHolder(this,
-                AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET).mContent;
+                AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET).itemView;
         searchMarket.measure(widthMeasureSpec, heightMeasureSpec);
         mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET,
                 searchMarket.getMeasuredHeight());
-
-        // Section breaks
-        mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_SECTION_BREAK, 0);
     }
 
     /**
@@ -166,27 +162,10 @@
         }
     }
 
-    /**
-     * We need to override the draw to ensure that we don't draw the overscroll effect beyond the
-     * background bounds.
-     */
-    @Override
-    protected void dispatchDraw(Canvas canvas) {
-        // Clip to ensure that we don't draw the overscroll effect beyond the background bounds
-        canvas.clipRect(mBackgroundPadding.left, mBackgroundPadding.top,
-                getWidth() - mBackgroundPadding.right,
-                getHeight() - mBackgroundPadding.bottom);
-        super.dispatchDraw(canvas);
-    }
-
     @Override
     public void onDraw(Canvas c) {
         // Draw the background
         if (mEmptySearchBackground != null && mEmptySearchBackground.getAlpha() > 0) {
-            c.clipRect(mBackgroundPadding.left, mBackgroundPadding.top,
-                    getWidth() - mBackgroundPadding.right,
-                    getHeight() - mBackgroundPadding.bottom);
-
             mEmptySearchBackground.draw(c);
         }
 
@@ -299,14 +278,14 @@
 
         // Skip early if there are no items or we haven't been measured
         if (items.isEmpty() || mNumAppsPerRow == 0) {
-            mScrollbar.setThumbOffset(-1, -1);
+            mScrollbar.setThumbOffsetY(-1);
             return;
         }
 
         // Skip early if, there no child laid out in the container.
         int scrollY = getCurrentScrollY();
         if (scrollY < 0) {
-            mScrollbar.setThumbOffset(-1, -1);
+            mScrollbar.setThumbOffsetY(-1);
             return;
         }
 
@@ -314,7 +293,7 @@
         int availableScrollBarHeight = getAvailableScrollBarHeight();
         int availableScrollHeight = getAvailableScrollHeight();
         if (availableScrollHeight <= 0) {
-            mScrollbar.setThumbOffset(-1, -1);
+            mScrollbar.setThumbOffsetY(-1);
             return;
         }
 
@@ -323,11 +302,10 @@
                 // Calculate the current scroll position, the scrollY of the recycler view accounts
                 // for the view padding, while the scrollBarY is drawn right up to the background
                 // padding (ignoring padding)
-                int scrollBarX = getScrollBarX();
-                int scrollBarY = mBackgroundPadding.top +
-                        (int) (((float) scrollY / availableScrollHeight) * availableScrollBarHeight);
+                int scrollBarY = (int)
+                        (((float) scrollY / availableScrollHeight) * availableScrollBarHeight);
 
-                int thumbScrollY = mScrollbar.getThumbOffset().y;
+                int thumbScrollY = mScrollbar.getThumbOffsetY();
                 int diffScrollY = scrollBarY - thumbScrollY;
                 if (diffScrollY * dy > 0f) {
                     // User is scrolling in the same direction the thumb needs to catch up to the
@@ -344,7 +322,7 @@
                         thumbScrollY += Math.min(offset, diffScrollY);
                     }
                     thumbScrollY = Math.max(0, Math.min(availableScrollBarHeight, thumbScrollY));
-                    mScrollbar.setThumbOffset(scrollBarX, thumbScrollY);
+                    mScrollbar.setThumbOffsetY(thumbScrollY);
                     if (scrollBarY == thumbScrollY) {
                         mScrollbar.reattachThumbToScroll();
                     }
@@ -352,7 +330,7 @@
                     // User is scrolling in an opposite direction to the direction that the thumb
                     // needs to catch up to the scroll position.  Do nothing except for updating
                     // the scroll bar x to match the thumb width.
-                    mScrollbar.setThumbOffset(scrollBarX, thumbScrollY);
+                    mScrollbar.setThumbOffsetY(thumbScrollY);
                 }
             }
         } else {
@@ -416,8 +394,8 @@
     }
 
     @Override
-    protected int getVisibleHeight() {
-        return super.getVisibleHeight()
+    protected int getScrollbarTrackHeight() {
+        return super.getScrollbarTrackHeight()
                 - Launcher.getLauncher(getContext()).getDragLayer().getInsets().bottom;
     }
 
@@ -429,7 +407,7 @@
     protected int getAvailableScrollHeight() {
         int paddedHeight = getCurrentScrollY(mApps.getAdapterItems().size(), 0);
         int totalHeight = paddedHeight + getPaddingBottom();
-        return totalHeight - getVisibleHeight();
+        return totalHeight - getScrollbarTrackHeight();
     }
 
     /**
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java
index b5afb2b..6587ad7 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java
@@ -21,6 +21,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
+import android.widget.RelativeLayout;
 
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.BubbleTextView.BubbleTextShadowHandler;
@@ -33,7 +34,7 @@
  * A container for RecyclerView to allow for the click shadow view to be shown behind an icon that
  * is launching.
  */
-public class AllAppsRecyclerViewContainerView extends FrameLayout
+public class AllAppsRecyclerViewContainerView extends RelativeLayout
         implements BubbleTextShadowHandler {
 
     private final ClickShadowView mTouchFeedbackView;
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index b129bb0..adfad08 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -108,7 +108,7 @@
     }
 
     @Override
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
+    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
             mNoIntercept = false;
             if (!mLauncher.isAllAppsVisible() && mLauncher.getWorkspace().workspaceInModalState()) {
@@ -174,7 +174,7 @@
     }
 
     @Override
-    public boolean onTouchEvent(MotionEvent ev) {
+    public boolean onControllerTouchEvent(MotionEvent ev) {
         return mDetector.onTouchEvent(ev);
     }
 
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 173065b..8b7a6ba 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -23,8 +23,8 @@
 import com.android.launcher3.compat.AlphabeticIndexCompat;
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.config.ProviderConfig;
-import com.android.launcher3.model.AppNameComparator;
 import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.LabelComparator;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -49,18 +49,6 @@
     private final int mFastScrollDistributionMode = FAST_SCROLL_FRACTION_DISTRIBUTE_BY_NUM_SECTIONS;
 
     /**
-     * Info about a section in the alphabetic list
-     */
-    public static class SectionInfo {
-        // The number of applications in this section
-        public int numApps;
-        // The section break AdapterItem for this section
-        public AdapterItem sectionBreakItem;
-        // The first app AdapterItem for this section
-        public AdapterItem firstAppItem;
-    }
-
-    /**
      * Info about a fast scroller section, depending if sections are merged, the fast scroller
      * sections will not be the same set as the section headers.
      */
@@ -87,16 +75,10 @@
         // The type of this item
         public int viewType;
 
-        /** Section & App properties */
-        // The section for this item
-        public SectionInfo sectionInfo;
-
         /** App-only properties */
         // The section name of this app.  Note that there can be multiple items with different
         // sectionNames in the same section
         public String sectionName = null;
-        // The index of this app in the section
-        public int sectionAppIndex = -1;
         // The row that this item shows up on
         public int rowIndex;
         // The index of this app in the row
@@ -106,30 +88,19 @@
         // The index of this app not including sections
         public int appIndex = -1;
 
-        public static AdapterItem asSectionBreak(int pos, SectionInfo section) {
-            AdapterItem item = new AdapterItem();
-            item.viewType = AllAppsGridAdapter.VIEW_TYPE_SECTION_BREAK;
-            item.position = pos;
-            item.sectionInfo = section;
-            section.sectionBreakItem = item;
-            return item;
-        }
-
-        public static AdapterItem asPredictedApp(int pos, SectionInfo section, String sectionName,
-                int sectionAppIndex, AppInfo appInfo, int appIndex) {
-            AdapterItem item = asApp(pos, section, sectionName, sectionAppIndex, appInfo, appIndex);
+        public static AdapterItem asPredictedApp(int pos, String sectionName, AppInfo appInfo,
+                int appIndex) {
+            AdapterItem item = asApp(pos, sectionName, appInfo, appIndex);
             item.viewType = AllAppsGridAdapter.VIEW_TYPE_PREDICTION_ICON;
             return item;
         }
 
-        public static AdapterItem asApp(int pos, SectionInfo section, String sectionName,
-                int sectionAppIndex, AppInfo appInfo, int appIndex) {
+        public static AdapterItem asApp(int pos, String sectionName, AppInfo appInfo,
+                int appIndex) {
             AdapterItem item = new AdapterItem();
             item.viewType = AllAppsGridAdapter.VIEW_TYPE_ICON;
             item.position = pos;
-            item.sectionInfo = section;
             item.sectionName = sectionName;
-            item.sectionAppIndex = sectionAppIndex;
             item.appInfo = appInfo;
             item.appIndex = appIndex;
             return item;
@@ -149,7 +120,7 @@
             return item;
         }
 
-        public static AdapterItem asSearchDivder(int pos) {
+        public static AdapterItem asSearchDivider(int pos) {
             AdapterItem item = new AdapterItem();
             item.viewType = AllAppsGridAdapter.VIEW_TYPE_SEARCH_DIVIDER;
             item.position = pos;
@@ -171,14 +142,6 @@
         }
     }
 
-    /**
-     * Common interface for different merging strategies.
-     */
-    public interface MergeAlgorithm {
-        boolean continueMerging(SectionInfo section, SectionInfo withSection,
-                int sectionAppCount, int numAppsPerRow, int mergeCount);
-    }
-
     private Launcher mLauncher;
 
     // The set of apps from the system not including predictions
@@ -189,8 +152,6 @@
     private List<AppInfo> mFilteredApps = new ArrayList<>();
     // The current set of adapter items
     private List<AdapterItem> mAdapterItems = new ArrayList<>();
-    // The set of sections for the apps with the current filter
-    private List<SectionInfo> mSections = new ArrayList<>();
     // The set of sections that we allow fast-scrolling to (includes non-merged sections)
     private List<FastScrollSectionInfo> mFastScrollerSections = new ArrayList<>();
     // The set of predicted app component names
@@ -202,8 +163,7 @@
     private HashMap<CharSequence, String> mCachedSectionNames = new HashMap<>();
     private AllAppsGridAdapter mAdapter;
     private AlphabeticIndexCompat mIndexer;
-    private AppNameComparator mAppNameComparator;
-    private MergeAlgorithm mMergeAlgorithm;
+    private AppInfoComparator mAppNameComparator;
     private int mNumAppsPerRow;
     private int mNumPredictedAppsPerRow;
     private int mNumAppRowsInAdapter;
@@ -211,17 +171,15 @@
     public AlphabeticalAppsList(Context context) {
         mLauncher = Launcher.getLauncher(context);
         mIndexer = new AlphabeticIndexCompat(context);
-        mAppNameComparator = new AppNameComparator(context);
+        mAppNameComparator = new AppInfoComparator(context);
     }
 
     /**
      * Sets the number of apps per row.
      */
-    public void setNumAppsPerRow(int numAppsPerRow, int numPredictedAppsPerRow,
-            MergeAlgorithm mergeAlgorithm) {
+    public void setNumAppsPerRow(int numAppsPerRow, int numPredictedAppsPerRow) {
         mNumAppsPerRow = numAppsPerRow;
         mNumPredictedAppsPerRow = numPredictedAppsPerRow;
-        mMergeAlgorithm = mergeAlgorithm;
 
         updateAdapterItems();
     }
@@ -241,13 +199,6 @@
     }
 
     /**
-     * Returns sections of all the current filtered applications.
-     */
-    public List<SectionInfo> getSections() {
-        return mSections;
-    }
-
-    /**
      * Returns fast scroller sections of all the current filtered applications.
      */
     public List<FastScrollSectionInfo> getFastScrollerSections() {
@@ -354,17 +305,16 @@
         // Sort the list of apps
         mApps.clear();
         mApps.addAll(mComponentToAppMap.values());
-        Collections.sort(mApps, mAppNameComparator.getAppInfoComparator());
+        Collections.sort(mApps, mAppNameComparator);
 
         // As a special case for some languages (currently only Simplified Chinese), we may need to
         // coalesce sections
         Locale curLocale = mLauncher.getResources().getConfiguration().locale;
-        TreeMap<String, ArrayList<AppInfo>> sectionMap = null;
         boolean localeRequiresSectionSorting = curLocale.equals(Locale.SIMPLIFIED_CHINESE);
         if (localeRequiresSectionSorting) {
-            // Compute the section headers.  We use a TreeMap with the section name comparator to
+            // Compute the section headers. We use a TreeMap with the section name comparator to
             // ensure that the sections are ordered when we iterate over it later
-            sectionMap = new TreeMap<>(mAppNameComparator.getSectionNameComparator());
+            TreeMap<String, ArrayList<AppInfo>> sectionMap = new TreeMap<>(new LabelComparator());
             for (AppInfo info : mApps) {
                 // Add the section to the cache
                 String sectionName = getAndUpdateCachedSectionName(info.title);
@@ -379,13 +329,10 @@
             }
 
             // Add each of the section apps to the list in order
-            List<AppInfo> allApps = new ArrayList<>(mApps.size());
-            for (Map.Entry<String, ArrayList<AppInfo>> entry : sectionMap.entrySet()) {
-                allApps.addAll(entry.getValue());
-            }
-
             mApps.clear();
-            mApps.addAll(allApps);
+            for (Map.Entry<String, ArrayList<AppInfo>> entry : sectionMap.entrySet()) {
+                mApps.addAll(entry.getValue());
+            }
         } else {
             // Just compute the section headers for use below
             for (AppInfo info : mApps) {
@@ -403,7 +350,6 @@
      * mCachedSectionNames to have been calculated for the set of all apps in mApps.
      */
     private void updateAdapterItems() {
-        SectionInfo lastSectionInfo = null;
         String lastSectionName = null;
         FastScrollSectionInfo lastFastScrollerSectionInfo = null;
         int position = 0;
@@ -413,7 +359,6 @@
         mFilteredApps.clear();
         mFastScrollerSections.clear();
         mAdapterItems.clear();
-        mSections.clear();
 
         if (DEBUG_PREDICTIONS) {
             if (mPredictedAppComponents.isEmpty() && !mApps.isEmpty()) {
@@ -429,7 +374,7 @@
         }
 
         // Add the search divider
-        mAdapterItems.add(AdapterItem.asSearchDivder(position++));
+        mAdapterItems.add(AdapterItem.asSearchDivider(position++));
 
         // Process the predicted app components
         mPredictedApps.clear();
@@ -451,19 +396,14 @@
 
             if (!mPredictedApps.isEmpty()) {
                 // Add a section for the predictions
-                lastSectionInfo = new SectionInfo();
                 lastFastScrollerSectionInfo = new FastScrollSectionInfo("");
-                AdapterItem sectionItem = AdapterItem.asSectionBreak(position++, lastSectionInfo);
-                mSections.add(lastSectionInfo);
                 mFastScrollerSections.add(lastFastScrollerSectionInfo);
-                mAdapterItems.add(sectionItem);
 
                 // Add the predicted app items
                 for (AppInfo info : mPredictedApps) {
-                    AdapterItem appItem = AdapterItem.asPredictedApp(position++, lastSectionInfo,
-                            "", lastSectionInfo.numApps++, info, appIndex++);
-                    if (lastSectionInfo.firstAppItem == null) {
-                        lastSectionInfo.firstAppItem = appItem;
+                    AdapterItem appItem = AdapterItem.asPredictedApp(position++, "", info,
+                            appIndex++);
+                    if (lastFastScrollerSectionInfo.fastScrollToItem == null) {
                         lastFastScrollerSectionInfo.fastScrollToItem = appItem;
                     }
                     mAdapterItems.add(appItem);
@@ -480,25 +420,15 @@
             String sectionName = getAndUpdateCachedSectionName(info.title);
 
             // Create a new section if the section names do not match
-            if (lastSectionInfo == null || !sectionName.equals(lastSectionName)) {
+            if (!sectionName.equals(lastSectionName)) {
                 lastSectionName = sectionName;
-                lastSectionInfo = new SectionInfo();
                 lastFastScrollerSectionInfo = new FastScrollSectionInfo(sectionName);
-                mSections.add(lastSectionInfo);
                 mFastScrollerSections.add(lastFastScrollerSectionInfo);
-
-                // Create a new section item to break the flow of items in the list
-                if (!hasFilter()) {
-                    AdapterItem sectionItem = AdapterItem.asSectionBreak(position++, lastSectionInfo);
-                    mAdapterItems.add(sectionItem);
-                }
             }
 
             // Create an app item
-            AdapterItem appItem = AdapterItem.asApp(position++, lastSectionInfo, sectionName,
-                    lastSectionInfo.numApps++, info, appIndex++);
-            if (lastSectionInfo.firstAppItem == null) {
-                lastSectionInfo.firstAppItem = appItem;
+            AdapterItem appItem = AdapterItem.asApp(position++, sectionName, info, appIndex++);
+            if (lastFastScrollerSectionInfo.fastScrollToItem == null) {
                 lastFastScrollerSectionInfo.fastScrollToItem = appItem;
             }
             mAdapterItems.add(appItem);
@@ -515,9 +445,6 @@
             mAdapterItems.add(AdapterItem.asMarketSearch(position++));
         }
 
-        // Merge multiple sections together as requested by the merge strategy for this device
-        mergeSections();
-
         if (mNumAppsPerRow != 0) {
             // Update the number of rows in the adapter after we do all the merging (otherwise, we
             // would have to shift the values again)
@@ -594,61 +521,6 @@
     }
 
     /**
-     * Merges multiple sections to reduce visual raggedness.
-     */
-    private void mergeSections() {
-        // Ignore merging until we have an algorithm and a valid row size
-        if (mMergeAlgorithm == null || mNumAppsPerRow == 0) {
-            return;
-        }
-
-        // Go through each section and try and merge some of the sections
-        if (!hasFilter()) {
-            int sectionAppCount = 0;
-            for (int i = 0; i < mSections.size() - 1; i++) {
-                SectionInfo section = mSections.get(i);
-                sectionAppCount = section.numApps;
-                int mergeCount = 1;
-
-                // Merge rows based on the current strategy
-                while (i < (mSections.size() - 1) &&
-                        mMergeAlgorithm.continueMerging(section, mSections.get(i + 1),
-                                sectionAppCount, mNumAppsPerRow, mergeCount)) {
-                    SectionInfo nextSection = mSections.remove(i + 1);
-
-                    // Remove the next section break
-                    mAdapterItems.remove(nextSection.sectionBreakItem);
-                    int pos = mAdapterItems.indexOf(section.firstAppItem);
-
-                    // Point the section for these new apps to the merged section
-                    int nextPos = pos + section.numApps;
-                    for (int j = nextPos; j < (nextPos + nextSection.numApps); j++) {
-                        AdapterItem item = mAdapterItems.get(j);
-                        item.sectionInfo = section;
-                        item.sectionAppIndex += section.numApps;
-                    }
-
-                    // Update the following adapter items of the removed section item
-                    pos = mAdapterItems.indexOf(nextSection.firstAppItem);
-                    for (int j = pos; j < mAdapterItems.size(); j++) {
-                        AdapterItem item = mAdapterItems.get(j);
-                        item.position--;
-                    }
-                    section.numApps += nextSection.numApps;
-                    sectionAppCount += nextSection.numApps;
-
-                    if (DEBUG) {
-                        Log.d(TAG, "Merging: " + nextSection.firstAppItem.sectionName +
-                                " to " + section.firstAppItem.sectionName +
-                                " mergedNumRows: " + (sectionAppCount / mNumAppsPerRow));
-                    }
-                    mergeCount++;
-                }
-            }
-        }
-    }
-
-    /**
      * Returns the cached section name for the given title, recomputing and updating the cache if
      * the title has no cached section name.
      */
diff --git a/src/com/android/launcher3/model/AbstractUserComparator.java b/src/com/android/launcher3/allapps/AppInfoComparator.java
similarity index 61%
rename from src/com/android/launcher3/model/AbstractUserComparator.java
rename to src/com/android/launcher3/allapps/AppInfoComparator.java
index bd28560..1f5fece 100644
--- a/src/com/android/launcher3/model/AbstractUserComparator.java
+++ b/src/com/android/launcher3/allapps/AppInfoComparator.java
@@ -13,36 +13,51 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.launcher3.model;
+package com.android.launcher3.allapps;
 
 import android.content.Context;
 
-import com.android.launcher3.ItemInfo;
+import com.android.launcher3.AppInfo;
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.util.LabelComparator;
 
 import java.util.Comparator;
 
 /**
  * A comparator to arrange items based on user profiles.
  */
-public abstract class AbstractUserComparator<T extends ItemInfo> implements Comparator<T> {
+public class AppInfoComparator implements Comparator<AppInfo> {
 
     private final UserManagerCompat mUserManager;
     private final UserHandleCompat mMyUser;
+    private final LabelComparator mLabelComparator;
 
-    public AbstractUserComparator(Context context) {
+    public AppInfoComparator(Context context) {
         mUserManager = UserManagerCompat.getInstance(context);
         mMyUser = UserHandleCompat.myUserHandle();
+        mLabelComparator = new LabelComparator();
     }
 
     @Override
-    public int compare(T lhs, T rhs) {
-        if (mMyUser.equals(lhs.user)) {
+    public int compare(AppInfo a, AppInfo b) {
+        // Order by the title in the current locale
+        int result = mLabelComparator.compare(a.title.toString(), b.title.toString());
+        if (result != 0) {
+            return result;
+        }
+
+        // If labels are same, compare component names
+        result = a.componentName.compareTo(b.componentName);
+        if (result != 0) {
+            return result;
+        }
+
+        if (mMyUser.equals(a.user)) {
             return -1;
         } else {
-            Long aUserSerial = mUserManager.getSerialNumberForUser(lhs.user);
-            Long bUserSerial = mUserManager.getSerialNumberForUser(rhs.user);
+            Long aUserSerial = mUserManager.getSerialNumberForUser(a.user);
+            Long bUserSerial = mUserManager.getSerialNumberForUser(b.user);
             return aUserSerial.compareTo(bUserSerial);
         }
     }
diff --git a/src/com/android/launcher3/allapps/HeaderElevationController.java b/src/com/android/launcher3/allapps/HeaderElevationController.java
index ce9837c..e79e5c7 100644
--- a/src/com/android/launcher3/allapps/HeaderElevationController.java
+++ b/src/com/android/launcher3/allapps/HeaderElevationController.java
@@ -14,6 +14,7 @@
 
 import com.android.launcher3.BaseRecyclerView;
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 
 /**
  * Helper class for controlling the header elevation in response to RecyclerView scroll.
@@ -84,7 +85,7 @@
 
         public ControllerVL(View header) {
             mHeader = header;
-            Resources res = mHeader.getContext().getResources();
+            final Resources res = mHeader.getContext().getResources();
             mMaxElevation = res.getDimension(R.dimen.all_apps_header_max_elevation);
             mScrollToElevation = res.getDimension(R.dimen.all_apps_header_scroll_to_elevation);
 
@@ -101,11 +102,8 @@
                     final int right = left + view.getWidth();
                     final int bottom = view.getBottom();
 
-                    outline.setRect(
-                            left - (int) mMaxElevation,
-                            top - (int) mMaxElevation,
-                            right + (int) mMaxElevation,
-                            bottom);
+                    final int offset = Utilities.pxFromDp(mMaxElevation, res.getDisplayMetrics());
+                    outline.setRect(left - offset, top - offset, right + offset, bottom);
                 }
             };
             mHeader.setOutlineProvider(vop);
diff --git a/src/com/android/launcher3/allapps/VerticalPullDetector.java b/src/com/android/launcher3/allapps/VerticalPullDetector.java
index ab2b6ed..96e1299 100644
--- a/src/com/android/launcher3/allapps/VerticalPullDetector.java
+++ b/src/com/android/launcher3/allapps/VerticalPullDetector.java
@@ -230,7 +230,7 @@
 
     private void reportDragEnd() {
         if (DBG) {
-            Log.d(TAG, String.format("onScrolEnd disp=%.1f, velocity=%.1f",
+            Log.d(TAG, String.format("onScrollEnd disp=%.1f, velocity=%.1f",
                     mDisplacementY, mVelocity));
         }
         mListener.onDragEnd(mVelocity, Math.abs(mVelocity) > RELEASE_VELOCITY_PX_MS);
diff --git a/src/com/android/launcher3/anim/AnimationLayerSet.java b/src/com/android/launcher3/anim/AnimationLayerSet.java
new file mode 100644
index 0000000..42706ff
--- /dev/null
+++ b/src/com/android/launcher3/anim/AnimationLayerSet.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.anim;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.view.View;
+
+import java.util.HashSet;
+
+/**
+ * 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<>();
+
+    public void addView(View v) {
+        mViews.add(v);
+    }
+
+    @Override
+    public void onAnimationStart(Animator animation) {
+        // Enable all necessary layers
+        for (View v : mViews) {
+            v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+            if (v.isAttachedToWindow() && v.getVisibility() == View.VISIBLE) {
+                v.buildLayer();
+            }
+        }
+    }
+
+    @Override
+    public void onAnimationEnd(Animator animation) {
+        for (View v : mViews) {
+            v.setLayerType(View.LAYER_TYPE_NONE, null);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
index c7a529d..84e82e3 100644
--- a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
+++ b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
@@ -23,7 +23,7 @@
         BaseIndex index = null;
 
         try {
-            if (Utilities.isNycOrAbove()) {
+            if (Utilities.ATLEAST_NOUGAT) {
                 index = new AlphabeticIndexVN(context);
             }
         } catch (Exception e) {
diff --git a/src/com/android/launcher3/compat/LauncherActivityInfoCompat.java b/src/com/android/launcher3/compat/LauncherActivityInfoCompat.java
index 0bc9588..65af4ea 100644
--- a/src/com/android/launcher3/compat/LauncherActivityInfoCompat.java
+++ b/src/com/android/launcher3/compat/LauncherActivityInfoCompat.java
@@ -17,9 +17,7 @@
 package com.android.launcher3.compat;
 
 import android.content.ComponentName;
-import android.content.Context;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.ResolveInfo;
 import android.graphics.drawable.Drawable;
 
 public abstract class LauncherActivityInfoCompat {
@@ -33,11 +31,4 @@
     public abstract Drawable getIcon(int density);
     public abstract ApplicationInfo getApplicationInfo();
     public abstract long getFirstInstallTime();
-
-    /**
-     * Creates a LauncherActivityInfoCompat for the primary user.
-     */
-    public static LauncherActivityInfoCompat fromResolveInfo(ResolveInfo info, Context context) {
-        return new LauncherActivityInfoCompatV16(context, info);
-    }
 }
diff --git a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
index 4aa667e..948471c 100644
--- a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
+++ b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
@@ -54,7 +54,7 @@
         HashMap<String, Integer> activePackages = new HashMap<>();
         UserHandleCompat user = UserHandleCompat.myUserHandle();
         for (SessionInfo info : mInstaller.getAllSessions()) {
-            addSessionInfoToCahce(info, user);
+            addSessionInfoToCache(info, user);
             if (info.getAppPackageName() != null) {
                 activePackages.put(info.getAppPackageName(), (int) (info.getProgress() * 100));
                 mActiveSessions.put(info.getSessionId(), info.getAppPackageName());
@@ -63,7 +63,7 @@
         return activePackages;
     }
 
-    @Thunk void addSessionInfoToCahce(SessionInfo info, UserHandleCompat user) {
+    @Thunk void addSessionInfoToCache(SessionInfo info, UserHandleCompat user) {
         String packageName = info.getAppPackageName();
         if (packageName != null) {
             mCache.cachePackageInstallInfo(packageName, user, info.getAppIcon(),
@@ -124,7 +124,7 @@
         private void pushSessionDisplayToLauncher(int sessionId) {
             SessionInfo session = mInstaller.getSessionInfo(sessionId);
             if (session != null && session.getAppPackageName() != null) {
-                addSessionInfoToCahce(session, UserHandleCompat.myUserHandle());
+                addSessionInfoToCache(session, UserHandleCompat.myUserHandle());
                 LauncherAppState app = LauncherAppState.getInstanceNoCreate();
 
                 if (app != null) {
diff --git a/src/com/android/launcher3/compat/UserManagerCompat.java b/src/com/android/launcher3/compat/UserManagerCompat.java
index a5f8dd2..b40eaa2 100644
--- a/src/com/android/launcher3/compat/UserManagerCompat.java
+++ b/src/com/android/launcher3/compat/UserManagerCompat.java
@@ -32,9 +32,9 @@
     public static UserManagerCompat getInstance(Context context) {
         synchronized (sInstanceLock) {
             if (sInstance == null) {
-                if (Utilities.isNycMR1OrAbove()) {
+                if (Utilities.ATLEAST_NOUGAT_MR1) {
                     sInstance = new UserManagerCompatVNMr1(context.getApplicationContext());
-                } else if (Utilities.isNycOrAbove()) {
+                } else if (Utilities.ATLEAST_NOUGAT) {
                     sInstance = new UserManagerCompatVN(context.getApplicationContext());
                 } else if (Utilities.ATLEAST_MARSHMALLOW) {
                     sInstance = new UserManagerCompatVM(context.getApplicationContext());
diff --git a/src/com/android/launcher3/dragndrop/AnotherWindowDragSource.java b/src/com/android/launcher3/dragndrop/AnotherWindowDragSource.java
index 156941a..e968f36 100644
--- a/src/com/android/launcher3/dragndrop/AnotherWindowDragSource.java
+++ b/src/com/android/launcher3/dragndrop/AnotherWindowDragSource.java
@@ -70,7 +70,7 @@
     }
 
     @Override
-    public void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent) {
+    public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
         // TODO: Probably log something
     }
 }
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 6eb7dcc..67ef5fc 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -131,7 +131,7 @@
     protected final int mFlingToDeleteThresholdVelocity;
     private VelocityTracker mVelocityTracker;
 
-    private boolean mIsDragDeferred;
+    private boolean mIsInPreDrag;
 
     /**
      * Interface to receive notifications when a drag starts or stops
@@ -230,13 +230,14 @@
 
         mDragObject = new DropTarget.DragObject();
 
-        mIsDragDeferred = !mOptions.deferDragCondition.shouldStartDeferredDrag(0);
+        mIsInPreDrag = mOptions.preDragCondition != null
+                && !mOptions.preDragCondition.shouldStartDrag(0);
 
         final Resources res = mLauncher.getResources();
         final float scaleDps = FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND
                 ? res.getDimensionPixelSize(R.dimen.dragViewScale)
-                : mIsDragDeferred
-                    ? res.getDimensionPixelSize(R.dimen.deferred_drag_view_scale)
+                : mIsInPreDrag
+                    ? res.getDimensionPixelSize(R.dimen.pre_drag_view_scale)
                     : 0f;
         final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX,
                 registrationY, initialDragViewScale, scaleDps);
@@ -271,10 +272,10 @@
         dragView.show(mMotionDownX, mMotionDownY);
         mDistanceSinceScroll = 0;
 
-        if (!mIsDragDeferred) {
-            startDeferredDrag();
-        } else {
-            mOptions.deferDragCondition.onDeferredDragStart();
+        if (!mIsInPreDrag) {
+            callOnDragStart();
+        } else if (mOptions.preDragCondition != null) {
+            mOptions.preDragCondition.onPreDragStart();
         }
 
         mLastTouch[0] = mMotionDownX;
@@ -284,16 +285,14 @@
         return dragView;
     }
 
-    public boolean isDeferringDrag() {
-        return mIsDragDeferred;
-    }
-
-    public void startDeferredDrag() {
+    private void callOnDragStart() {
         for (DragListener listener : new ArrayList<>(mListeners)) {
             listener.onDragStart(mDragObject, mOptions);
         }
-        mOptions.deferDragCondition.onDragStart();
-        mIsDragDeferred = false;
+        if (mOptions.preDragCondition != null) {
+            mOptions.preDragCondition.onPreDragEnd(true /* dragStarted*/);
+        }
+        mIsInPreDrag = false;
     }
 
     /**
@@ -329,7 +328,9 @@
             mDragObject.deferDragViewCleanupPostAnimation = false;
             mDragObject.cancelled = true;
             mDragObject.dragComplete = true;
-            mDragObject.dragSource.onDropCompleted(null, mDragObject, false, false);
+            if (!mIsInPreDrag) {
+                mDragObject.dragSource.onDropCompleted(null, mDragObject, false, false);
+            }
         }
         endDrag();
     }
@@ -350,28 +351,54 @@
     private void endDrag() {
         if (isDragging()) {
             mDragDriver = null;
-            mOptions = null;
             clearScrollRunnable();
             boolean isDeferred = false;
             if (mDragObject.dragView != null) {
                 isDeferred = mDragObject.deferDragViewCleanupPostAnimation;
                 if (!isDeferred) {
                     mDragObject.dragView.remove();
+                } else if (mIsInPreDrag) {
+                    animateDragViewToOriginalPosition(null, null, -1);
                 }
                 mDragObject.dragView = null;
             }
 
             // Only end the drag if we are not deferred
             if (!isDeferred) {
-                for (DragListener listener : new ArrayList<>(mListeners)) {
-                    listener.onDragEnd();
-                }
+                callOnDragEnd();
             }
         }
 
         releaseVelocityTracker();
     }
 
+    public void animateDragViewToOriginalPosition(final Runnable onComplete,
+            final View originalIcon, int duration) {
+        Runnable onCompleteRunnable = new Runnable() {
+            @Override
+            public void run() {
+                if (originalIcon != null) {
+                    originalIcon.setVisibility(View.VISIBLE);
+                }
+                if (onComplete != null) {
+                    onComplete.run();
+                }
+            }
+        };
+        mDragObject.dragView.animateTo(mMotionDownX, mMotionDownY, onCompleteRunnable, duration);
+    }
+
+    private void callOnDragEnd() {
+        if (mIsInPreDrag && mOptions.preDragCondition != null) {
+            mOptions.preDragCondition.onPreDragEnd(false /* dragStarted*/);
+        }
+        mIsInPreDrag = false;
+        mOptions = null;
+        for (DragListener listener : new ArrayList<>(mListeners)) {
+            listener.onDragEnd();
+        }
+    }
+
     /**
      * This only gets called as a result of drag view cleanup being deferred in endDrag();
      */
@@ -380,9 +407,7 @@
 
         if (mDragObject.deferDragViewCleanupPostAnimation) {
             // If we skipped calling onDragEnd() before, do it now
-            for (DragListener listener : new ArrayList<>(mListeners)) {
-                listener.onDragEnd();
-            }
+            callOnDragEnd();
         }
     }
 
@@ -456,7 +481,7 @@
     /**
      * Call this from a drag source view.
      */
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
+    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
         if (mOptions != null && mOptions.isAccessibleDrag) {
             return false;
         }
@@ -536,9 +561,9 @@
         mLastTouch[1] = y;
         checkScrollState(x, y);
 
-        if (mIsDragDeferred && mOptions.deferDragCondition.shouldStartDeferredDrag(
-                Math.hypot(x - mMotionDownX, y - mMotionDownY))) {
-            startDeferredDrag();
+        if (mIsInPreDrag && mOptions.preDragCondition != null
+                && mOptions.preDragCondition.shouldStartDrag(mDistanceSinceScroll)) {
+            callOnDragStart();
         }
     }
 
@@ -604,7 +629,7 @@
     /**
      * Call this from a drag source view.
      */
-    public boolean onTouchEvent(MotionEvent ev) {
+    public boolean onControllerTouchEvent(MotionEvent ev) {
         if (mDragDriver == null || mOptions == null || mOptions.isAccessibleDrag) {
             return false;
         }
@@ -701,7 +726,7 @@
                 (vec1.length() * vec2.length()));
     }
 
-    void drop(DropTarget dropTarget, float x, float y, PointF flingVel) {
+    private void drop(DropTarget dropTarget, float x, float y, PointF flingVel) {
         final int[] coordinates = mCoordinatesTemp;
 
         mDragObject.x = coordinates[0];
@@ -727,18 +752,17 @@
             if (dropTarget.acceptDrop(mDragObject)) {
                 if (flingVel != null) {
                     dropTarget.onFlingToDelete(mDragObject, flingVel);
-                } else {
+                } else if (!mIsInPreDrag) {
                     dropTarget.onDrop(mDragObject);
                 }
                 accepted = true;
             }
         }
         final View dropTargetAsView = dropTarget instanceof View ? (View) dropTarget : null;
-        mDragObject.dragSource.onDropCompleted(
-                dropTargetAsView, mDragObject, flingVel != null, accepted);
         mLauncher.getUserEventDispatcher().logDragNDrop(mDragObject, dropTargetAsView);
-        if (mIsDragDeferred) {
-            mOptions.deferDragCondition.onDropBeforeDeferredDrag();
+        if (!mIsInPreDrag) {
+            mDragObject.dragSource.onDropCompleted(
+                    dropTargetAsView, mDragObject, flingVel != null, accepted);
         }
     }
 
@@ -760,7 +784,7 @@
 
                 dropCoordinates[0] = x;
                 dropCoordinates[1] = y;
-                mLauncher.getDragLayer().mapCoordInSelfToDescendent((View) target, dropCoordinates);
+                mLauncher.getDragLayer().mapCoordInSelfToDescendant((View) target, dropCoordinates);
 
                 return target;
             }
@@ -768,7 +792,7 @@
         return null;
     }
 
-    public void setDragScoller(DragScroller scroller) {
+    public void setDragScroller(DragScroller scroller) {
         mDragScroller = scroller;
     }
 
@@ -777,7 +801,7 @@
     }
 
     /**
-     * Sets the drag listner which will be notified when a drag starts or ends.
+     * Sets the drag listener which will be notified when a drag starts or ends.
      */
     public void addDragListener(DragListener l) {
         mListeners.add(l);
diff --git a/src/com/android/launcher3/dragndrop/DragDriver.java b/src/com/android/launcher3/dragndrop/DragDriver.java
index 4db8c07..d0c8e16 100644
--- a/src/com/android/launcher3/dragndrop/DragDriver.java
+++ b/src/com/android/launcher3/dragndrop/DragDriver.java
@@ -92,7 +92,7 @@
 
     public static DragDriver create(Context context, DragController dragController,
             DragObject dragObject, DragOptions options) {
-        if (Utilities.isNycOrAbove() && options.systemDndStartPoint != null) {
+        if (Utilities.ATLEAST_NOUGAT && options.systemDndStartPoint != null) {
             return new SystemDragDriver(dragController, context, dragObject);
         } else {
             return new InternalDragDriver(dragController);
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 016347b..9de4452 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -36,9 +36,9 @@
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.view.DragEvent;
 import android.view.KeyEvent;
+import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
@@ -49,12 +49,12 @@
 import android.widget.FrameLayout;
 import android.widget.TextView;
 
+import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.AppWidgetResizeFrame;
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DropTargetBar;
+import com.android.launcher3.ExtendedEditText;
 import com.android.launcher3.InsettableFrameLayout;
-import com.android.launcher3.InstallShortcutReceiver;
-import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppWidgetHostView;
 import com.android.launcher3.PinchToOverviewListener;
@@ -68,11 +68,9 @@
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
-import com.android.launcher3.shortcuts.DeepShortcutsContainer;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.TouchController;
 
-import java.net.URISyntaxException;
 import java.util.ArrayList;
 
 /**
@@ -90,11 +88,9 @@
 
     @Thunk DragController mDragController;
 
-    private int mXDown, mYDown;
     private Launcher mLauncher;
 
     // Variables relating to resizing widgets
-    private final ArrayList<AppWidgetResizeFrame> mResizeFrames = new ArrayList<>();
     private final boolean mIsRtl;
     private AppWidgetResizeFrame mCurrentResizeFrame;
 
@@ -149,10 +145,12 @@
         setChildrenDrawingOrderEnabled(true);
 
         final Resources res = getResources();
-        mLeftHoverDrawable = res.getDrawable(R.drawable.page_hover_left);
-        mRightHoverDrawable = res.getDrawable(R.drawable.page_hover_right);
-        mLeftHoverDrawableActive = res.getDrawable(R.drawable.page_hover_left_active);
-        mRightHoverDrawableActive = res.getDrawable(R.drawable.page_hover_right_active);
+        if (FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND) {
+            mLeftHoverDrawable = res.getDrawable(R.drawable.page_hover_left);
+            mRightHoverDrawable = res.getDrawable(R.drawable.page_hover_right);
+            mLeftHoverDrawableActive = res.getDrawable(R.drawable.page_hover_left_active);
+            mRightHoverDrawableActive = res.getDrawable(R.drawable.page_hover_right_active);
+        }
         mIsRtl = Utilities.isRtl(res);
         mFocusIndicatorHelper = new ViewGroupFocusHelper(this);
     }
@@ -183,18 +181,13 @@
     }
 
     public boolean isEventOverPageIndicator(MotionEvent ev) {
-        getDescendantRectRelativeToSelf(mLauncher.getWorkspace().getPageIndicator(), mHitRect);
-        return mHitRect.contains((int) ev.getX(), (int) ev.getY());
+        return isEventOverView(mLauncher.getWorkspace().getPageIndicator(), ev);
     }
 
     public boolean isEventOverHotseat(MotionEvent ev) {
         return isEventOverView(mLauncher.getHotseat(), ev);
     }
 
-    private boolean isEventOverFolderTextRegion(Folder folder, MotionEvent ev) {
-        return isEventOverView(folder.getEditTextRegion(), ev);
-    }
-
     private boolean isEventOverFolder(Folder folder, MotionEvent ev) {
         return isEventOverView(folder, ev);
     }
@@ -209,62 +202,27 @@
     }
 
     private boolean handleTouchDown(MotionEvent ev, boolean intercept) {
-        Rect hitRect = new Rect();
-        int x = (int) ev.getX();
-        int y = (int) ev.getY();
-
-        for (AppWidgetResizeFrame child: mResizeFrames) {
-            child.getHitRect(hitRect);
-            if (hitRect.contains(x, y)) {
-                if (child.beginResizeIfPointInRegion(x - child.getLeft(), y - child.getTop())) {
-                    mCurrentResizeFrame = child;
-                    mXDown = x;
-                    mYDown = y;
-                    requestDisallowInterceptTouchEvent(true);
+        AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mLauncher);
+        if (topView != null && intercept) {
+            ExtendedEditText textView = topView.getActiveTextView();
+            if (textView != null) {
+                if (!isEventOverView(textView, ev)) {
+                    textView.dispatchBackKey();
                     return true;
                 }
-            }
-        }
-
-        // Remove the shortcuts container when touching outside of it.
-        DeepShortcutsContainer deepShortcutsContainer = mLauncher.getOpenShortcutsContainer();
-        if (deepShortcutsContainer != null) {
-            if (isEventOverView(deepShortcutsContainer, ev)) {
-                // Let the container handle the event.
-                return false;
-            } else {
+            } else if (!isEventOverView(topView, ev)) {
                 if (isInAccessibleDrag()) {
                     // Do not close the container if in drag and drop.
                     if (!isEventOverDropTargetBar(ev)) {
                         return true;
                     }
                 } else {
-                    mLauncher.closeShortcutsContainer();
+                    topView.close(true);
+
                     // We let touches on the original icon go through so that users can launch
                     // the app with one tap if they don't find a shortcut they want.
-                    return !isEventOverView(deepShortcutsContainer.getDeferredDragIcon(), ev);
-                }
-            }
-        }
-
-        Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
-        if (currentFolder != null && intercept) {
-            if (currentFolder.isEditingName()) {
-                if (!isEventOverFolderTextRegion(currentFolder, ev)) {
-                    currentFolder.dismissEditingName();
-                    return true;
-                }
-            }
-
-            if (!isEventOverFolder(currentFolder, ev)) {
-                if (isInAccessibleDrag()) {
-                    // Do not close the folder if in drag and drop.
-                    if (!isEventOverDropTargetBar(ev)) {
-                        return true;
-                    }
-                } else {
-                    mLauncher.closeFolder();
-                    return true;
+                    View extendedTouch = topView.getExtendedTouchView();
+                    return extendedTouch == null || !isEventOverView(extendedTouch, ev);
                 }
             }
         }
@@ -289,21 +247,27 @@
             }
             mTouchCompleteListener = null;
         }
-        clearAllResizeFrames();
-
         mActiveController = null;
 
-        if (mDragController.onInterceptTouchEvent(ev)) {
+        if (mCurrentResizeFrame != null
+                && mCurrentResizeFrame.onControllerInterceptTouchEvent(ev)) {
+            mActiveController = mCurrentResizeFrame;
+            return true;
+        } else {
+            clearResizeFrame();
+        }
+
+        if (mDragController.onControllerInterceptTouchEvent(ev)) {
             mActiveController = mDragController;
             return true;
         }
 
-        if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && mAllAppsController.onInterceptTouchEvent(ev)) {
+        if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && mAllAppsController.onControllerInterceptTouchEvent(ev)) {
             mActiveController = mAllAppsController;
             return true;
         }
 
-        if (mPinchListener != null && mPinchListener.onInterceptTouchEvent(ev)) {
+        if (mPinchListener != null && mPinchListener.onControllerInterceptTouchEvent(ev)) {
             // Stop listening for scrolling etc. (onTouchEvent() handles the rest of the pinch.)
             mActiveController = mPinchListener;
             return true;
@@ -316,7 +280,7 @@
         if (mLauncher == null || mLauncher.getWorkspace() == null) {
             return false;
         }
-        Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
+        Folder currentFolder = Folder.getOpen(mLauncher);
         if (currentFolder == null) {
             return false;
         } else {
@@ -366,7 +330,7 @@
     @Override
     public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
         // Shortcuts can appear above folder
-        View topView = mLauncher.getTopFloatingView();
+        View topView = AbstractFloatingView.getTopOpenView(mLauncher);
         if (topView != null) {
             if (child == topView) {
                 return super.onRequestSendAccessibilityEvent(child, event);
@@ -383,7 +347,7 @@
 
     @Override
     public void addChildrenForAccessibility(ArrayList<View> childrenForAccessibility) {
-        View topView = mLauncher.getTopFloatingView();
+        View topView = AbstractFloatingView.getTopOpenView(mLauncher);
         if (topView != null) {
             // Only add the top view as a child for accessibility when it is open
             childrenForAccessibility.add(topView);
@@ -405,12 +369,8 @@
 
     @Override
     public boolean onTouchEvent(MotionEvent ev) {
-        boolean handled = false;
         int action = ev.getAction();
 
-        int x = (int) ev.getX();
-        int y = (int) ev.getY();
-
         if (action == MotionEvent.ACTION_DOWN) {
             if (handleTouchDown(ev, false)) {
                 return true;
@@ -422,29 +382,15 @@
             mTouchCompleteListener = null;
         }
 
-        if (mCurrentResizeFrame != null) {
-            handled = true;
-            switch (action) {
-                case MotionEvent.ACTION_MOVE:
-                    mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown);
-                    break;
-                case MotionEvent.ACTION_CANCEL:
-                case MotionEvent.ACTION_UP:
-                    mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown);
-                    mCurrentResizeFrame.onTouchUp();
-                    mCurrentResizeFrame = null;
-            }
-        }
-        if (handled) return true;
         if (mActiveController != null) {
-            return mActiveController.onTouchEvent(ev);
+            return mActiveController.onControllerTouchEvent(ev);
         }
         return false;
     }
 
     @TargetApi(Build.VERSION_CODES.N)
     private void handleSystemDragStart(DragEvent event) {
-        if (!FeatureFlags.LAUNCHER3_USE_SYSTEM_DRAG_DRIVER || !Utilities.isNycOrAbove()) {
+        if (!FeatureFlags.LAUNCHER3_USE_SYSTEM_DRAG_DRIVER || !Utilities.ATLEAST_NOUGAT) {
             return;
         }
         if (mLauncher.isWorkspaceLocked()) {
@@ -534,8 +480,8 @@
     /**
      * Inverse of {@link #getDescendantCoordRelativeToSelf(View, int[])}.
      */
-    public float mapCoordInSelfToDescendent(View descendant, int[] coord) {
-        return Utilities.mapCoordInSelfToDescendent(descendant, this, coord);
+    public void mapCoordInSelfToDescendant(View descendant, int[] coord) {
+        Utilities.mapCoordInSelfToDescendant(descendant, this, coord);
     }
 
     public void getViewRectRelativeToSelf(View v, Rect r) {
@@ -556,7 +502,7 @@
     @Override
     public boolean dispatchUnhandledMove(View focused, int direction) {
         // Consume the unhandled move if a container is open, to avoid switching pages underneath.
-        boolean isContainerOpen = mLauncher.getTopFloatingView() != null;
+        boolean isContainerOpen = AbstractFloatingView.getTopOpenView(mLauncher) != null;
         return isContainerOpen || mDragController.dispatchUnhandledMove(focused, direction);
     }
 
@@ -645,36 +591,24 @@
         }
     }
 
-    public void clearAllResizeFrames() {
-        if (mResizeFrames.size() > 0) {
-            for (AppWidgetResizeFrame frame: mResizeFrames) {
-                frame.commitResize();
-                removeView(frame);
-            }
-            mResizeFrames.clear();
+    public void clearResizeFrame() {
+        if (mCurrentResizeFrame != null) {
+            mCurrentResizeFrame.commitResize();
+            removeView(mCurrentResizeFrame);
+            mCurrentResizeFrame = null;
         }
     }
 
-    public boolean hasResizeFrames() {
-        return mResizeFrames.size() > 0;
-    }
+    public void addResizeFrame(LauncherAppWidgetHostView widget, CellLayout cellLayout) {
+        clearResizeFrame();
 
-    public boolean isWidgetBeingResized() {
-        return mCurrentResizeFrame != null;
-    }
+        mCurrentResizeFrame = (AppWidgetResizeFrame) LayoutInflater.from(mLauncher)
+                .inflate(R.layout.app_widget_resize_frame, this, false);
+        mCurrentResizeFrame.setupForWidget(widget, cellLayout, this);
+        ((LayoutParams) mCurrentResizeFrame.getLayoutParams()).customPosition = true;
 
-    public void addResizeFrame(ItemInfo itemInfo, LauncherAppWidgetHostView widget,
-            CellLayout cellLayout) {
-        AppWidgetResizeFrame resizeFrame = new AppWidgetResizeFrame(getContext(),
-                widget, cellLayout, this);
-
-        LayoutParams lp = new LayoutParams(-1, -1);
-        lp.customPosition = true;
-
-        addView(resizeFrame, lp);
-        mResizeFrames.add(resizeFrame);
-
-        resizeFrame.snapToWidget(false);
+        addView(mCurrentResizeFrame);
+        mCurrentResizeFrame.snapToWidget(false);
     }
 
     public void animateViewIntoPosition(DragView dragView, final int[] pos, float alpha,
@@ -1077,7 +1011,7 @@
 
     @Override
     protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
-        View topView = mLauncher.getTopFloatingView();
+        View topView = AbstractFloatingView.getTopOpenView(mLauncher);
         if (topView != null) {
             return topView.requestFocus(direction, previouslyFocusedRect);
         } else {
@@ -1087,7 +1021,7 @@
 
     @Override
     public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
-        View topView = mLauncher.getTopFloatingView();
+        View topView = AbstractFloatingView.getTopOpenView(mLauncher);
         if (topView != null) {
             topView.addFocusables(views, direction);
         } else {
diff --git a/src/com/android/launcher3/dragndrop/DragOptions.java b/src/com/android/launcher3/dragndrop/DragOptions.java
index dbf46f3..906855a 100644
--- a/src/com/android/launcher3/dragndrop/DragOptions.java
+++ b/src/com/android/launcher3/dragndrop/DragOptions.java
@@ -17,6 +17,8 @@
 package com.android.launcher3.dragndrop;
 
 import android.graphics.Point;
+import android.support.annotation.CallSuper;
+import android.view.View;
 
 /**
  * Set of options to control the drag and drop behavior.
@@ -29,8 +31,8 @@
     /** Specifies the start location for the system DnD, null when using internal DnD */
     public Point systemDndStartPoint = null;
 
-    /** Determines when a deferred drag should start. By default, drags aren't deferred at all. */
-    public DeferDragCondition deferDragCondition = new DeferDragCondition();
+    /** Determines when a pre-drag should transition to a drag. By default, this is immediate. */
+    public PreDragCondition preDragCondition = null;
 
     /**
      * Specifies a condition that must be met before DragListener#onDragStart() is called.
@@ -38,34 +40,26 @@
      * DragController#startDrag().
      *
      * This condition can be overridden, and callbacks are provided for the following cases:
-     * - The drag starts, but onDragStart() is deferred (onDeferredDragStart()).
-     * - The drag ends before the condition is met (onDropBeforeDeferredDrag()).
-     * - The condition is met (onDragStart()).
+     * - The pre-drag starts, but onDragStart() is deferred (onPreDragStart()).
+     * - The pre-drag ends before the condition is met (onPreDragEnd(false)).
+     * - The actual drag starts when the condition is met (onPreDragEnd(true)).
      */
-    public static class DeferDragCondition {
-        public boolean shouldStartDeferredDrag(double distanceDragged) {
-            return true;
-        }
+    public interface PreDragCondition {
+
+        public boolean shouldStartDrag(double distanceDragged);
 
         /**
-         * The drag has started, but onDragStart() is deferred.
-         * This happens when shouldStartDeferredDrag() returns true.
+         * The pre-drag has started, but onDragStart() is
+         * deferred until shouldStartDrag() returns true.
          */
-        public void onDeferredDragStart() {
-            // Do nothing.
-        }
+        void onPreDragStart();
 
         /**
-         * User dropped before the deferred condition was met,
-         * i.e. before shouldStartDeferredDrag() returned true.
+         * The pre-drag has ended. This gets called at the same time as onDragStart()
+         * if the condition is met, otherwise at the same time as onDragEnd().
+         * @param dragStarted Whether the pre-drag ended because the actual drag started.
+         *                    This will be true if the condition was met, otherwise false.
          */
-        public void onDropBeforeDeferredDrag() {
-            // Do nothing
-        }
-
-        /** onDragStart() has been called, now we are in a normal drag. */
-        public void onDragStart() {
-            // Do nothing
-        }
+        void onPreDragEnd(boolean dragStarted);
     }
 }
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 8a2ae94..22e077b 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -23,7 +23,6 @@
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.annotation.SuppressLint;
 import android.annotation.TargetApi;
-import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
@@ -38,11 +37,9 @@
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAnimUtils;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.util.Thunk;
-
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.util.Thunk;
 
 import java.util.Arrays;
 
@@ -57,6 +54,8 @@
     @Thunk Paint mPaint;
     private final int mRegistrationX;
     private final int mRegistrationY;
+    private final float mInitialScale;
+    private final int[] mTempLoc = new int[2];
 
     private Point mDragVisualizeOffset = null;
     private Rect mDragRegion = null;
@@ -138,6 +137,8 @@
         mRegistrationX = registrationX;
         mRegistrationY = registrationY;
 
+        mInitialScale = initialScale;
+
         // Force a measure, because Workspace uses getMeasuredHeight() before the layout pass
         int ms = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
         measure(ms, ms);
@@ -356,6 +357,13 @@
         applyTranslation();
     }
 
+    public void animateTo(int toTouchX, int toTouchY, Runnable onCompleteRunnable, int duration) {
+        mTempLoc[0] = toTouchX - mRegistrationX;
+        mTempLoc[1] = toTouchY - mRegistrationY;
+        mDragLayer.animateViewIntoPosition(this, mTempLoc, 1f, mInitialScale, mInitialScale,
+                DragLayer.ANIMATION_END_DISAPPEAR, onCompleteRunnable, duration);
+    }
+
     public void animateShift(final int shiftX, final int shiftY) {
         if (mAnim.isStarted()) {
             return;
diff --git a/src/com/android/launcher3/dragndrop/ExternalDragPreviewProvider.java b/src/com/android/launcher3/dragndrop/ExternalDragPreviewProvider.java
index 6b14be7..ac50332 100644
--- a/src/com/android/launcher3/dragndrop/ExternalDragPreviewProvider.java
+++ b/src/com/android/launcher3/dragndrop/ExternalDragPreviewProvider.java
@@ -23,10 +23,10 @@
 import android.graphics.Rect;
 
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.HolographicOutlineHelper;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.graphics.DragPreviewProvider;
+import com.android.launcher3.graphics.HolographicOutlineHelper;
 
 /**
  * Extension of {@link DragPreviewProvider} which provides a dummy outline when drag starts from
@@ -72,7 +72,7 @@
         canvas.drawCircle(DRAG_BITMAP_PADDING / 2 + radius,
                 DRAG_BITMAP_PADDING / 2 + radius, radius * 0.9f, paint);
 
-        HolographicOutlineHelper.obtain(mLauncher).applyExpensiveOutlineWithBlur(b, canvas);
+        HolographicOutlineHelper.getInstance(mLauncher).applyExpensiveOutlineWithBlur(b, canvas);
         canvas.setBitmap(null);
         return b;
     }
diff --git a/src/com/android/launcher3/dynamicui/ColorExtractionService.java b/src/com/android/launcher3/dynamicui/ColorExtractionService.java
index 1369f60..1a127dc 100644
--- a/src/com/android/launcher3/dynamicui/ColorExtractionService.java
+++ b/src/com/android/launcher3/dynamicui/ColorExtractionService.java
@@ -16,24 +16,35 @@
 
 package com.android.launcher3.dynamicui;
 
+import android.annotation.TargetApi;
 import android.app.IntentService;
 import android.app.WallpaperManager;
 import android.content.Intent;
 import android.graphics.Bitmap;
+import android.graphics.BitmapRegionDecoder;
+import android.graphics.Rect;
 import android.graphics.drawable.BitmapDrawable;
+import android.os.Build;
 import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
 import android.support.v7.graphics.Palette;
+import android.util.Log;
 
 import com.android.launcher3.LauncherProvider;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
 
+import java.io.IOException;
+
 /**
  * Extracts colors from the wallpaper, and saves results to {@link LauncherProvider}.
  */
 public class ColorExtractionService extends IntentService {
 
+    private static final String TAG = "ColorExtractionService";
+
     /** The fraction of the wallpaper to extract colors for use on the hotseat. */
     private static final float HOTSEAT_FRACTION = 1f / 4;
 
@@ -45,32 +56,18 @@
     protected void onHandleIntent(Intent intent) {
         WallpaperManager wallpaperManager = WallpaperManager.getInstance(this);
         int wallpaperId = ExtractionUtils.getWallpaperId(wallpaperManager);
+
         ExtractedColors extractedColors = new ExtractedColors();
         if (wallpaperManager.getWallpaperInfo() != null) {
             // We can't extract colors from live wallpapers, so just use the default color always.
-            extractedColors.updatePalette(null);
             extractedColors.updateHotseatPalette(null);
         } else {
-            Bitmap wallpaper = ((BitmapDrawable) wallpaperManager.getDrawable()).getBitmap();
-            Palette palette = Palette.from(wallpaper).generate();
-            extractedColors.updatePalette(palette);
             // We extract colors for the hotseat and status bar separately,
             // since they only consider part of the wallpaper.
-            Palette hotseatPalette = Palette.from(wallpaper)
-                    .setRegion(0, (int) (wallpaper.getHeight() * (1f - HOTSEAT_FRACTION)),
-                            wallpaper.getWidth(), wallpaper.getHeight())
-                    .clearFilters()
-                    .generate();
-            extractedColors.updateHotseatPalette(hotseatPalette);
+            extractedColors.updateHotseatPalette(getHotseatPalette());
 
             if (FeatureFlags.LIGHT_STATUS_BAR) {
-                int statusBarHeight = getResources()
-                        .getDimensionPixelSize(R.dimen.status_bar_height);
-                Palette statusBarPalette = Palette.from(wallpaper)
-                        .setRegion(0, 0, wallpaper.getWidth(), statusBarHeight)
-                        .clearFilters()
-                        .generate();
-                extractedColors.updateStatusBarPalette(statusBarPalette);
+                extractedColors.updateStatusBarPalette(getStatusBarPalette());
             }
         }
 
@@ -84,4 +81,63 @@
                 LauncherSettings.Settings.METHOD_SET_EXTRACTED_COLORS_AND_WALLPAPER_ID,
                 null, extras);
     }
+
+    @TargetApi(Build.VERSION_CODES.N)
+    private Palette getHotseatPalette() {
+        WallpaperManager wallpaperManager = WallpaperManager.getInstance(this);
+        if (Utilities.ATLEAST_NOUGAT) {
+            try (ParcelFileDescriptor fd = wallpaperManager
+                    .getWallpaperFile(WallpaperManager.FLAG_SYSTEM)) {
+                BitmapRegionDecoder decoder = BitmapRegionDecoder
+                        .newInstance(fd.getFileDescriptor(), false);
+                int height = decoder.getHeight();
+                Rect decodeRegion = new Rect(0, (int) (height * (1f - HOTSEAT_FRACTION)),
+                        decoder.getWidth(), height);
+                Bitmap bitmap = decoder.decodeRegion(decodeRegion, null);
+                decoder.recycle();
+                if (bitmap != null) {
+                    return Palette.from(bitmap).clearFilters().generate();
+                }
+            } catch (IOException e) {
+                Log.e(TAG, "Fetching partial bitmap failed, trying old method", e);
+            }
+        }
+
+        Bitmap wallpaper = ((BitmapDrawable) wallpaperManager.getDrawable()).getBitmap();
+        return Palette.from(wallpaper)
+                .setRegion(0, (int) (wallpaper.getHeight() * (1f - HOTSEAT_FRACTION)),
+                        wallpaper.getWidth(), wallpaper.getHeight())
+                .clearFilters()
+                .generate();
+    }
+
+    @TargetApi(Build.VERSION_CODES.N)
+    private Palette getStatusBarPalette() {
+        WallpaperManager wallpaperManager = WallpaperManager.getInstance(this);
+        int statusBarHeight = getResources()
+                .getDimensionPixelSize(R.dimen.status_bar_height);
+
+        if (Utilities.ATLEAST_NOUGAT) {
+            try (ParcelFileDescriptor fd = wallpaperManager
+                    .getWallpaperFile(WallpaperManager.FLAG_SYSTEM)) {
+                BitmapRegionDecoder decoder = BitmapRegionDecoder
+                        .newInstance(fd.getFileDescriptor(), false);
+                Rect decodeRegion = new Rect(0, 0,
+                        decoder.getWidth(), statusBarHeight);
+                Bitmap bitmap = decoder.decodeRegion(decodeRegion, null);
+                decoder.recycle();
+                if (bitmap != null) {
+                    return Palette.from(bitmap).clearFilters().generate();
+                }
+            } catch (IOException e) {
+                Log.e(TAG, "Fetching partial bitmap failed, trying old method", e);
+            }
+        }
+
+        Bitmap wallpaper = ((BitmapDrawable) wallpaperManager.getDrawable()).getBitmap();
+        return Palette.from(wallpaper)
+                .setRegion(0, 0, wallpaper.getWidth(), statusBarHeight)
+                .clearFilters()
+                .generate();
+    }
 }
diff --git a/src/com/android/launcher3/dynamicui/ExtractedColors.java b/src/com/android/launcher3/dynamicui/ExtractedColors.java
index 6a3011d..711508e 100644
--- a/src/com/android/launcher3/dynamicui/ExtractedColors.java
+++ b/src/com/android/launcher3/dynamicui/ExtractedColors.java
@@ -113,34 +113,6 @@
     }
 
     /**
-     * Updates colors based on the palette.
-     * If the palette is null, the default color is used in all cases.
-     */
-    public void updatePalette(Palette palette) {
-        if (palette == null) {
-            for (int i = 0; i < NUM_COLOR_PROFILES; i++) {
-                setColorAtIndex(i, ExtractedColors.DEFAULT_COLOR);
-            }
-        } else {
-            // We currently don't use any of the colors defined by the Palette API,
-            // but this is how we would add them if we ever need them.
-
-            // setColorAtIndex(ExtractedColors.VIBRANT_INDEX,
-                // palette.getVibrantColor(ExtractedColors.DEFAULT_COLOR));
-            // setColorAtIndex(ExtractedColors.VIBRANT_DARK_INDEX,
-                // palette.getDarkVibrantColor(ExtractedColors.DEFAULT_DARK));
-            // setColorAtIndex(ExtractedColors.VIBRANT_LIGHT_INDEX,
-                // palette.getLightVibrantColor(ExtractedColors.DEFAULT_LIGHT));
-            // setColorAtIndex(ExtractedColors.MUTED_INDEX,
-                // palette.getMutedColor(DEFAULT_COLOR));
-            // setColorAtIndex(ExtractedColors.MUTED_DARK_INDEX,
-                // palette.getDarkMutedColor(ExtractedColors.DEFAULT_DARK));
-            // setColorAtIndex(ExtractedColors.MUTED_LIGHT_INDEX,
-                // palette.getLightVibrantColor(ExtractedColors.DEFAULT_LIGHT));
-        }
-    }
-
-    /**
      * The hotseat's color is defined as follows:
      * - 12% black for super light wallpaper
      * - 18% white for super dark
diff --git a/src/com/android/launcher3/dynamicui/ExtractionUtils.java b/src/com/android/launcher3/dynamicui/ExtractionUtils.java
index 6dc0035..1cf5d55 100644
--- a/src/com/android/launcher3/dynamicui/ExtractionUtils.java
+++ b/src/com/android/launcher3/dynamicui/ExtractionUtils.java
@@ -16,18 +16,18 @@
 
 package com.android.launcher3.dynamicui;
 
+import android.annotation.TargetApi;
 import android.app.WallpaperManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.graphics.Color;
+import android.os.Build;
 import android.support.v4.graphics.ColorUtils;
 import android.support.v7.graphics.Palette;
 
 import com.android.launcher3.Utilities;
 
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
 import java.util.List;
 
 /**
@@ -37,7 +37,6 @@
     public static final String EXTRACTED_COLORS_PREFERENCE_KEY = "pref_extractedColors";
     public static final String WALLPAPER_ID_PREFERENCE_KEY = "pref_wallpaperId";
 
-    private static final int FLAG_SET_SYSTEM = 1 << 0; // TODO: use WallpaperManager.FLAG_SET_SYSTEM
     private static final float MIN_CONTRAST_RATIO = 2f;
 
     /**
@@ -63,7 +62,7 @@
     }
 
     private static boolean hasWallpaperIdChanged(Context context) {
-        if (!Utilities.isNycOrAbove()) {
+        if (!Utilities.ATLEAST_NOUGAT) {
             // TODO: update an id in sharedprefs in onWallpaperChanged broadcast, and read it here.
             return false;
         }
@@ -73,14 +72,10 @@
         return wallpaperId != savedWallpaperId;
     }
 
+    @TargetApi(Build.VERSION_CODES.N)
     public static int getWallpaperId(WallpaperManager wallpaperManager) {
-        // TODO: use WallpaperManager#getWallpaperId(WallpaperManager.FLAG_SET_SYSTEM) directly.
-        try {
-            Method getWallpaperId = WallpaperManager.class.getMethod("getWallpaperId", int.class);
-            return (int) getWallpaperId.invoke(wallpaperManager, FLAG_SET_SYSTEM);
-        } catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException e) {
-            return -1;
-        }
+        return Utilities.ATLEAST_NOUGAT ?
+                wallpaperManager.getWallpaperId(WallpaperManager.FLAG_SYSTEM) : -1;
     }
 
     public static boolean isSuperLight(Palette p) {
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 698e5aa..53c12b5 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -46,9 +46,9 @@
 import android.view.animation.AnimationUtils;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodManager;
-import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Alarm;
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.BubbleTextView;
@@ -71,8 +71,9 @@
 import com.android.launcher3.UninstallDropTarget.DropTargetSource;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace.ItemOperator;
-import com.android.launcher3.accessibility.AccessibileDragListenerAdapter;
+import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.config.ProviderConfig;
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragController.DragListener;
 import com.android.launcher3.dragndrop.DragLayer;
@@ -91,9 +92,10 @@
 /**
  * Represents a set of icons chosen by the user or generated by the system.
  */
-public class Folder extends LinearLayout implements DragSource, View.OnClickListener,
+public class Folder extends AbstractFloatingView implements DragSource, View.OnClickListener,
         View.OnLongClickListener, DropTarget, FolderListener, TextView.OnEditorActionListener,
-        View.OnFocusChangeListener, DragListener, DropTargetSource {
+        View.OnFocusChangeListener, DragListener, DropTargetSource,
+        ExtendedEditText.OnBackKeyListener {
     private static final String TAG = "Launcher.Folder";
 
     /**
@@ -227,14 +229,7 @@
 
         mPageIndicator = (PageIndicatorDots) findViewById(R.id.folder_page_indicator);
         mFolderName = (ExtendedEditText) findViewById(R.id.folder_name);
-        mFolderName.setOnBackKeyListener(new ExtendedEditText.OnBackKeyListener() {
-            @Override
-            public boolean onBackKey() {
-                // Close the activity on back key press
-                doneEditingFolderName(true);
-                return false;
-            }
-        });
+        mFolderName.setOnBackKeyListener(this);
         mFolderName.setOnFocusChangeListener(this);
 
         if (!Utilities.ATLEAST_MARSHMALLOW) {
@@ -281,17 +276,7 @@
     public boolean onLongClick(View v) {
         // Return if global dragging is not enabled
         if (!mLauncher.isDraggingEnabled()) return true;
-        DragOptions dragOptions = new DragOptions();
-        if (v instanceof BubbleTextView) {
-            BubbleTextView icon = (BubbleTextView) v;
-            if (icon.hasDeepShortcuts()) {
-                DeepShortcutsContainer dsc = DeepShortcutsContainer.showForIcon(icon);
-                if (dsc != null) {
-                    dragOptions.deferDragCondition = dsc.createDeferDragCondition(null);
-                }
-            }
-        }
-        return startDrag(v, dragOptions);
+        return startDrag(v, new DragOptions());
     }
 
     public boolean startDrag(View v, DragOptions options) {
@@ -307,7 +292,7 @@
 
             mDragController.addDragListener(this);
             if (options.isAccessibleDrag) {
-                mDragController.addDragListener(new AccessibileDragListenerAdapter(
+                mDragController.addDragListener(new AccessibleDragListenerAdapter(
                         mContent, CellLayout.FOLDER_ACCESSIBILITY_DRAG) {
 
                     @Override
@@ -367,12 +352,9 @@
         });
     }
 
-    public void dismissEditingName() {
-        mInputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
-        doneEditingFolderName(true);
-    }
 
-    public void doneEditingFolderName(boolean commit) {
+    @Override
+    public boolean onBackKey() {
         mFolderName.setHint(sHintText);
         // Convert to a string here to ensure that no other state associated with the text field
         // gets saved.
@@ -380,30 +362,30 @@
         mInfo.setTitle(newTitle);
         LauncherModel.updateItemInDatabase(mLauncher, mInfo);
 
-        if (commit) {
-            Utilities.sendCustomAccessibilityEvent(
-                    this, AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
-                    getContext().getString(R.string.folder_renamed, newTitle));
-        }
+        Utilities.sendCustomAccessibilityEvent(
+                this, AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
+                getContext().getString(R.string.folder_renamed, newTitle));
 
         // This ensures that focus is gained every time the field is clicked, which selects all
         // the text and brings up the soft keyboard if necessary.
         mFolderName.clearFocus();
 
-        Selection.setSelection((Spannable) mFolderName.getText(), 0, 0);
+        Selection.setSelection(mFolderName.getText(), 0, 0);
         mIsEditingName = false;
+        return true;
     }
 
     public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
         if (actionId == EditorInfo.IME_ACTION_DONE) {
-            dismissEditingName();
+            mFolderName.dispatchBackKey();
             return true;
         }
         return false;
     }
 
-    public View getEditTextRegion() {
-        return mFolderName;
+    @Override
+    public ExtendedEditText getActiveTextView() {
+        return isEditingName() ? mFolderName : null;
     }
 
     /**
@@ -528,8 +510,33 @@
         mState = STATE_SMALL;
     }
 
+    /**
+     * Opens the user folder described by the specified tag. The opening of the folder
+     * is animated relative to the specified View. If the View is null, no animation
+     * is played.
+     */
     public void animateOpen() {
-        if (!(getParent() instanceof DragLayer)) return;
+        Folder openFolder = getOpen(mLauncher);
+        if (openFolder != null && openFolder != this) {
+            // Close any open folder before opening a folder.
+            openFolder.close(true);
+        }
+
+        DragLayer dragLayer = mLauncher.getDragLayer();
+        // Just verify that the folder hasn't already been added to the DragLayer.
+        // There was a one-off crash where the folder had a parent already.
+        if (getParent() == null) {
+            dragLayer.addView(this);
+            mDragController.addDropTarget(this);
+        } else {
+            if (ProviderConfig.IS_DOGFOOD_BUILD) {
+                Log.e(TAG, "Opening folder (" + this + ") which already has a parent:"
+                        + getParent());
+            }
+        }
+
+        mIsOpen = true;
+        mFolderIcon.growAndFadeOut();
 
         mContent.completePendingPageChanges();
         if (!mDragInProgress) {
@@ -542,83 +549,63 @@
         // dropping. One resulting issue is that replaceFolderWithFinalItem() can be called twice.
         mDeleteFolderOnDropCompleted = false;
 
-        Animator openFolderAnim = null;
         final Runnable onCompleteRunnable;
-        if (!Utilities.ATLEAST_LOLLIPOP) {
-            positionAndSizeAsIcon();
-            centerAboutIcon();
+        prepareReveal();
+        centerAboutIcon();
 
-            final ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(this, 1, 1, 1);
-            oa.setDuration(mExpandDuration);
-            openFolderAnim = oa;
+        AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
+        int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth();
+        int height = getFolderHeight();
 
-            setLayerType(LAYER_TYPE_HARDWARE, null);
-            onCompleteRunnable = new Runnable() {
-                @Override
-                public void run() {
-                    setLayerType(LAYER_TYPE_NONE, null);
-                }
-            };
-        } else {
-            prepareReveal();
-            centerAboutIcon();
+        float transX = - 0.075f * (width / 2 - getPivotX());
+        float transY = - 0.075f * (height / 2 - getPivotY());
+        setTranslationX(transX);
+        setTranslationY(transY);
+        PropertyValuesHolder tx = PropertyValuesHolder.ofFloat(TRANSLATION_X, transX, 0);
+        PropertyValuesHolder ty = PropertyValuesHolder.ofFloat(TRANSLATION_Y, transY, 0);
 
-            AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
-            int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth();
-            int height = getFolderHeight();
+        Animator drift = ObjectAnimator.ofPropertyValuesHolder(this, tx, ty);
+        drift.setDuration(mMaterialExpandDuration);
+        drift.setStartDelay(mMaterialExpandStagger);
+        drift.setInterpolator(new LogDecelerateInterpolator(100, 0));
 
-            float transX = - 0.075f * (width / 2 - getPivotX());
-            float transY = - 0.075f * (height / 2 - getPivotY());
-            setTranslationX(transX);
-            setTranslationY(transY);
-            PropertyValuesHolder tx = PropertyValuesHolder.ofFloat(TRANSLATION_X, transX, 0);
-            PropertyValuesHolder ty = PropertyValuesHolder.ofFloat(TRANSLATION_Y, transY, 0);
+        int rx = (int) Math.max(Math.max(width - getPivotX(), 0), getPivotX());
+        int ry = (int) Math.max(Math.max(height - getPivotY(), 0), getPivotY());
+        float radius = (float) Math.hypot(rx, ry);
 
-            Animator drift = ObjectAnimator.ofPropertyValuesHolder(this, tx, ty);
-            drift.setDuration(mMaterialExpandDuration);
-            drift.setStartDelay(mMaterialExpandStagger);
-            drift.setInterpolator(new LogDecelerateInterpolator(100, 0));
+        Animator reveal = new CircleRevealOutlineProvider((int) getPivotX(),
+                (int) getPivotY(), 0, radius).createRevealAnimator(this);
+        reveal.setDuration(mMaterialExpandDuration);
+        reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
 
-            int rx = (int) Math.max(Math.max(width - getPivotX(), 0), getPivotX());
-            int ry = (int) Math.max(Math.max(height - getPivotY(), 0), getPivotY());
-            float radius = (float) Math.hypot(rx, ry);
+        mContent.setAlpha(0f);
+        Animator iconsAlpha = ObjectAnimator.ofFloat(mContent, "alpha", 0f, 1f);
+        iconsAlpha.setDuration(mMaterialExpandDuration);
+        iconsAlpha.setStartDelay(mMaterialExpandStagger);
+        iconsAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
 
-            Animator reveal = new CircleRevealOutlineProvider((int) getPivotX(),
-                    (int) getPivotY(), 0, radius).createRevealAnimator(this);
-            reveal.setDuration(mMaterialExpandDuration);
-            reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
+        mFooter.setAlpha(0f);
+        Animator textAlpha = ObjectAnimator.ofFloat(mFooter, "alpha", 0f, 1f);
+        textAlpha.setDuration(mMaterialExpandDuration);
+        textAlpha.setStartDelay(mMaterialExpandStagger);
+        textAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
 
-            mContent.setAlpha(0f);
-            Animator iconsAlpha = ObjectAnimator.ofFloat(mContent, "alpha", 0f, 1f);
-            iconsAlpha.setDuration(mMaterialExpandDuration);
-            iconsAlpha.setStartDelay(mMaterialExpandStagger);
-            iconsAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
+        anim.play(drift);
+        anim.play(iconsAlpha);
+        anim.play(textAlpha);
+        anim.play(reveal);
 
-            mFooter.setAlpha(0f);
-            Animator textAlpha = ObjectAnimator.ofFloat(mFooter, "alpha", 0f, 1f);
-            textAlpha.setDuration(mMaterialExpandDuration);
-            textAlpha.setStartDelay(mMaterialExpandStagger);
-            textAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
-
-            anim.play(drift);
-            anim.play(iconsAlpha);
-            anim.play(textAlpha);
-            anim.play(reveal);
-
-            openFolderAnim = anim;
-
-            mContent.setLayerType(LAYER_TYPE_HARDWARE, null);
-            mFooter.setLayerType(LAYER_TYPE_HARDWARE, null);
-            onCompleteRunnable = new Runnable() {
-                @Override
-                public void run() {
-                    mContent.setLayerType(LAYER_TYPE_NONE, null);
-                    mFooter.setLayerType(LAYER_TYPE_NONE, null);
-                    mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
-                }
-            };
-        }
-        openFolderAnim.addListener(new AnimatorListenerAdapter() {
+        mContent.setLayerType(LAYER_TYPE_HARDWARE, null);
+        mFooter.setLayerType(LAYER_TYPE_HARDWARE, null);
+        onCompleteRunnable = new Runnable() {
+            @Override
+            public void run() {
+                mContent.setLayerType(LAYER_TYPE_NONE, null);
+                mFooter.setLayerType(LAYER_TYPE_NONE, null);
+                mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
+            }
+        };
+        anim.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(Animator animation) {
                 Utilities.sendCustomAccessibilityEvent(
@@ -649,7 +636,7 @@
             // Do not update the flag if we are in drag mode. The flag will be updated, when we
             // actually drop the icon.
             final boolean updateAnimationFlag = !mDragInProgress;
-            openFolderAnim.addListener(new AnimatorListenerAdapter() {
+            anim.addListener(new AnimatorListenerAdapter() {
 
                 @SuppressLint("InlinedApi")
                 @Override
@@ -672,7 +659,7 @@
         }
 
         mPageIndicator.stopAllAnimations();
-        openFolderAnim.start();
+        anim.start();
 
         // Make sure the folder picks up the last drag move even if the finger doesn't move.
         if (mDragController.isDragging()) {
@@ -680,6 +667,11 @@
         }
 
         mContent.verifyVisibleHighResIcons(mContent.getNextPage());
+
+        // Notify the accessibility manager that this folder "window" has appeared and occluded
+        // the workspace items
+        sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+        dragLayer.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
     }
 
     public void beginExternalDrag() {
@@ -692,14 +684,44 @@
         mDragController.addDragListener(this);
     }
 
-    public void animateClosed() {
+    @Override
+    protected boolean isOfType(int type) {
+        return (type & TYPE_FOLDER) != 0;
+    }
+
+    @Override
+    protected void handleClose(boolean animate) {
+        mIsOpen = false;
+
+        if (isEditingName()) {
+            mFolderName.dispatchBackKey();
+        }
+
+        if (mFolderIcon != null) {
+            mFolderIcon.shrinkAndFadeIn(animate);
+        }
+
         if (!(getParent() instanceof DragLayer)) return;
+        DragLayer parent = (DragLayer) getParent();
+
+        if (animate) {
+            animateClosed();
+        } else {
+            closeComplete(false);
+        }
+
+        // Notify the accessibility manager that this folder "window" has disappeared and no
+        // longer occludes the workspace items
+        parent.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+    }
+
+    private void animateClosed() {
         final ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(this, 0, 0.9f, 0.9f);
         oa.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
                 setLayerType(LAYER_TYPE_NONE, null);
-                close(true);
+                closeComplete(true);
             }
             @Override
             public void onAnimationStart(Animator animation) {
@@ -715,7 +737,7 @@
         oa.start();
     }
 
-    public void close(boolean wasAnimated) {
+    private void closeComplete(boolean wasAnimated) {
         // TODO: Clear all active animations.
         DragLayer parent = (DragLayer) getParent();
         if (parent != null) {
@@ -850,8 +872,8 @@
     };
 
     public void completeDragExit() {
-        if (mInfo.opened) {
-            mLauncher.closeFolder();
+        if (mIsOpen) {
+            close(true);
             mRearrangeOnClose = true;
         } else if (mState == STATE_ANIMATING) {
             mRearrangeOnClose = true;
@@ -916,7 +938,7 @@
             if (mDeleteFolderOnDropCompleted && !mItemAddedBackToSelfViaIcon && target != this) {
                 replaceFolderWithFinalItem();
             }
-        } else if (!mDragController.isDeferringDrag()) {
+        } else {
             // The drag failed, we need to return the item to the folder
             ShortcutInfo info = (ShortcutInfo) d.dragInfo;
             View icon = (mCurrentDragView != null && mCurrentDragView.getTag() == info)
@@ -1309,9 +1331,7 @@
             mIsExternalDrag = false;
         } else {
             currentDragView = mCurrentDragView;
-            if (!mDragController.isDeferringDrag()) {
-                mContent.addViewForRank(currentDragView, si, mEmptyCellRank);
-            }
+            mContent.addViewForRank(currentDragView, si, mEmptyCellRank);
         }
 
         if (d.dragView.hasDrawn()) {
@@ -1332,11 +1352,9 @@
         mItemsInvalidated = true;
         rearrangeChildren();
 
-        if (!mDragController.isDeferringDrag()) {
-            // Temporarily suppress the listener, as we did all the work already here.
-            try (SuppressInfoChanges s = new SuppressInfoChanges()) {
-                mInfo.add(si, false);
-            }
+        // Temporarily suppress the listener, as we did all the work already here.
+        try (SuppressInfoChanges s = new SuppressInfoChanges()) {
+            mInfo.add(si, false);
         }
 
         // Clear the drag info, as it is no longer being dragged.
@@ -1382,8 +1400,8 @@
             rearrangeChildren();
         }
         if (getItemCount() <= 1) {
-            if (mInfo.opened) {
-                mLauncher.closeFolder(this, true);
+            if (mIsOpen) {
+                close(true);
             } else {
                 replaceFolderWithFinalItem();
             }
@@ -1429,7 +1447,7 @@
             if (hasFocus) {
                 startEditingFolderName();
             } else {
-                dismissEditingName();
+                mFolderName.dispatchBackKey();
             }
         }
     }
@@ -1442,7 +1460,7 @@
     }
 
     @Override
-    public void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent) {
+    public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
         target.gridX = info.cellX;
         target.gridY = info.cellY;
         target.pageIndex = mContent.getCurrentPage();
@@ -1528,4 +1546,11 @@
             updateTextViewFocus();
         }
     }
+
+    /**
+     * Returns a folder which is already open or null
+     */
+    public static Folder getOpen(Launcher launcher) {
+        return getOpenView(launcher, TYPE_FOLDER);
+    }
 }
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 69c2b0f..a29a946 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -18,6 +18,7 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.content.Context;
@@ -56,7 +57,6 @@
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAnimUtils;
-import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.OnAlarmListener;
 import com.android.launcher3.PreloadIconDrawable;
@@ -142,6 +142,7 @@
         mPreviewLayoutRule = FeatureFlags.LAUNCHER3_LEGACY_FOLDER_ICON ?
                 new StackFolderIconLayoutRule() :
                 new ClippedFolderIconLayoutRule();
+        mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
     }
 
     public static FolderIcon fromXml(int resId, Launcher launcher, ViewGroup group,
@@ -202,16 +203,12 @@
         updateItemDrawingParams(false);
     }
 
-    public FolderInfo getFolderInfo() {
-        return mInfo;
-    }
-
     private boolean willAcceptItem(ItemInfo item) {
         final int itemType = item.itemType;
         return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
                 itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT ||
                 itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) &&
-                !mFolder.isFull() && item != mInfo && !mInfo.opened);
+                !mFolder.isFull() && item != mInfo && !mFolder.isOpen());
     }
 
     public boolean acceptDrop(ItemInfo dragInfo) {
@@ -243,7 +240,7 @@
     OnAlarmListener mOnOpenListener = new OnAlarmListener() {
         public void onAlarm(Alarm alarm) {
             mFolder.beginExternalDrag();
-            mLauncher.openFolder(FolderIcon.this);
+            mFolder.animateOpen();
         }
     };
 
@@ -974,12 +971,6 @@
     }
 
     @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
-    }
-
-    @Override
     public void cancelLongPress() {
         super.cancelLongPress();
         mLongPressHelper.cancelLongPress();
@@ -990,13 +981,76 @@
         mInfo.removeListener(mFolder);
     }
 
+    public void shrinkAndFadeIn(boolean animate) {
+        final CellLayout cl = (CellLayout) getParent().getParent();
+        ((CellLayout.LayoutParams) getLayoutParams()).canReorder = true;
+
+        // We remove and re-draw the FolderIcon in-case it has changed
+        final PreviewImageView previewImage = PreviewImageView.get(getContext());
+        previewImage.removeFromParent();
+        copyToPreview(previewImage);
+
+        if (cl != null) {
+            cl.clearFolderLeaveBehind();
+        }
+
+        ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(previewImage, 1, 1, 1);
+        oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
+        oa.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (cl != null) {
+                    // Remove the ImageView copy of the FolderIcon and make the original visible.
+                    previewImage.removeFromParent();
+                    setVisibility(View.VISIBLE);
+                }
+            }
+        });
+        oa.start();
+        if (!animate) {
+            oa.end();
+        }
+    }
+
+    public void growAndFadeOut() {
+        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
+        // While the folder is open, the position of the icon cannot change.
+        lp.canReorder = false;
+        if (mInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+            CellLayout cl = (CellLayout) getParent().getParent();
+            cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY);
+        }
+
+        // Push an ImageView copy of the FolderIcon into the DragLayer and hide the original
+        PreviewImageView previewImage = PreviewImageView.get(getContext());
+        copyToPreview(previewImage);
+        setVisibility(View.INVISIBLE);
+
+        ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(previewImage, 0, 1.5f, 1.5f);
+        oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
+        oa.start();
+    }
+
+    /**
+     * This method draws the FolderIcon to an ImageView and then adds and positions that ImageView
+     * in the DragLayer in the exact absolute location of the original FolderIcon.
+     */
+    private void copyToPreview(PreviewImageView previewImageView) {
+        previewImageView.copy(this);
+        if (mFolder != null) {
+            previewImageView.setPivotX(mFolder.getPivotXForIconAnimation());
+            previewImageView.setPivotY(mFolder.getPivotYForIconAnimation());
+            mFolder.bringToFront();
+        }
+    }
+
     public interface PreviewLayoutRule {
-        public PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems,
+        PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems,
             PreviewItemDrawingParams params);
 
-        public void init(int availableSpace, int intrinsicIconSize, boolean rtl);
+        void init(int availableSpace, int intrinsicIconSize, boolean rtl);
 
-        public int numItems();
-        public boolean clipToBackground();
+        int numItems();
+        boolean clipToBackground();
     }
 }
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index 7e7ee34..e71c5e9 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -256,6 +256,7 @@
         CellLayout page = new CellLayout(getContext());
         page.setCellDimensions(grid.folderCellWidthPx, grid.folderCellHeightPx);
         page.getShortcutsAndWidgets().setMotionEventSplittingEnabled(false);
+        page.getShortcutsAndWidgets().setContainerType(ShortcutAndWidgetContainer.FOLDER);
         page.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
         page.setInvertIfRtl(true);
         page.setGridSize(mGridCountX, mGridCountY);
@@ -524,12 +525,11 @@
     }
 
     @Override
-    protected void onPageBeginMoving() {
-        super.onPageBeginMoving();
-        getVisiblePages(sTempPosArray);
-        for (int i = sTempPosArray[0]; i <= sTempPosArray[1]; i++) {
-            verifyVisibleHighResIcons(i);
-        }
+    protected void onPageBeginTransition() {
+        super.onPageBeginTransition();
+        // Ensure that adjacent pages have high resolution icons
+        verifyVisibleHighResIcons(getCurrentPage() - 1);
+        verifyVisibleHighResIcons(getCurrentPage() + 1);
     }
 
     /**
@@ -679,7 +679,7 @@
     }
 
     @Override
-    protected void getEdgeVerticalPostion(int[] pos) {
+    protected void getEdgeVerticalPosition(int[] pos) {
         pos[0] = 0;
         pos[1] = getViewportHeight();
     }
diff --git a/src/com/android/launcher3/folder/PreviewImageView.java b/src/com/android/launcher3/folder/PreviewImageView.java
new file mode 100644
index 0000000..c4f3ee1
--- /dev/null
+++ b/src/com/android/launcher3/folder/PreviewImageView.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.folder;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.dragndrop.DragLayer;
+
+/**
+ * A temporary view which displays the a bitmap (used for folder icon animation)
+ */
+public class PreviewImageView extends ImageView {
+
+    private final Rect mTempRect = new Rect();
+    private final DragLayer mParent;
+
+    private Bitmap mBitmap;
+    private Canvas mCanvas;
+
+    public PreviewImageView(DragLayer parent) {
+        super(parent.getContext());
+        mParent = parent;
+    }
+
+    public void copy(View view) {
+        final int width = view.getMeasuredWidth();
+        final int height = view.getMeasuredHeight();
+
+        if (mBitmap == null || mBitmap.getWidth() != width || mBitmap.getHeight() != height) {
+            mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+            mCanvas = new Canvas(mBitmap);
+        }
+
+        DragLayer.LayoutParams lp;
+        if (getLayoutParams() instanceof DragLayer.LayoutParams) {
+            lp = (DragLayer.LayoutParams) getLayoutParams();
+        } else {
+            lp = new DragLayer.LayoutParams(width, height);
+        }
+
+        // The layout from which the folder is being opened may be scaled, adjust the starting
+        // view size by this scale factor.
+        float scale = mParent.getDescendantRectRelativeToSelf(view, mTempRect);
+        lp.customPosition = true;
+        lp.x = mTempRect.left;
+        lp.y = mTempRect.top;
+        lp.width = (int) (scale * width);
+        lp.height = (int) (scale * height);
+
+        mCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
+        view.draw(mCanvas);
+        setImageBitmap(mBitmap);
+
+        // Just in case this image view is still in the drag layer from a previous animation,
+        // we remove it and re-add it.
+        removeFromParent();
+        mParent.addView(this, lp);
+    }
+
+    public void removeFromParent() {
+        if (mParent.indexOfChild(this) != -1) {
+            mParent.removeView(this);
+        }
+    }
+
+    public static PreviewImageView get(Context context) {
+        DragLayer dragLayer = Launcher.getLauncher(context).getDragLayer();
+        PreviewImageView view = (PreviewImageView) dragLayer.getTag(R.id.preview_image_id);
+        if (view == null) {
+            view = new PreviewImageView(dragLayer);
+            dragLayer.setTag(R.id.preview_image_id, view);
+        }
+        return view;
+    }
+}
diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java
index bc91c15..a7d4c63 100644
--- a/src/com/android/launcher3/graphics/DragPreviewProvider.java
+++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java
@@ -24,7 +24,6 @@
 import android.view.View;
 import android.widget.TextView;
 
-import com.android.launcher3.HolographicOutlineHelper;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.PreloadIconDrawable;
 import com.android.launcher3.Workspace;
@@ -45,7 +44,7 @@
     // The padding added to the drag view during the preview generation.
     public final int previewPadding;
 
-    public Bitmap gerenatedDragOutline;
+    public Bitmap generatedDragOutline;
 
     public DragPreviewProvider(View view) {
         mView = view;
@@ -121,11 +120,11 @@
     }
 
     public final void generateDragOutline(Canvas canvas) {
-        if (ProviderConfig.IS_DOGFOOD_BUILD && gerenatedDragOutline != null) {
+        if (ProviderConfig.IS_DOGFOOD_BUILD && generatedDragOutline != null) {
             throw new RuntimeException("Drag outline generated twice");
         }
 
-        gerenatedDragOutline = createDragOutline(canvas);
+        generatedDragOutline = createDragOutline(canvas);
     }
 
     /**
@@ -137,7 +136,7 @@
                 mView.getHeight() + DRAG_BITMAP_PADDING, Bitmap.Config.ALPHA_8);
         canvas.setBitmap(b);
         drawDragView(canvas);
-        HolographicOutlineHelper.obtain(mView.getContext())
+        HolographicOutlineHelper.getInstance(mView.getContext())
                 .applyExpensiveOutlineWithBlur(b, canvas);
         canvas.setBitmap(null);
         return b;
diff --git a/src/com/android/launcher3/HolographicOutlineHelper.java b/src/com/android/launcher3/graphics/HolographicOutlineHelper.java
similarity index 90%
rename from src/com/android/launcher3/HolographicOutlineHelper.java
rename to src/com/android/launcher3/graphics/HolographicOutlineHelper.java
index 9dec7d9..9c39721 100644
--- a/src/com/android/launcher3/HolographicOutlineHelper.java
+++ b/src/com/android/launcher3/graphics/HolographicOutlineHelper.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.launcher3;
+package com.android.launcher3.graphics;
 
 import android.content.Context;
 import android.content.res.Resources;
@@ -29,6 +29,8 @@
 import android.graphics.drawable.Drawable;
 import android.util.SparseArray;
 
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.R;
 import com.android.launcher3.config.ProviderConfig;
 
 import java.nio.ByteBuffer;
@@ -72,9 +74,9 @@
         mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
     }
 
-    public static HolographicOutlineHelper obtain(Context context) {
+    public static HolographicOutlineHelper getInstance(Context context) {
         if (sInstance == null) {
-            sInstance = new HolographicOutlineHelper(context);
+            sInstance = new HolographicOutlineHelper(context.getApplicationContext());
         }
         return sInstance;
     }
@@ -155,19 +157,14 @@
         thickInnerBlur.recycle();
     }
 
-    Bitmap createMediumDropShadow(BubbleTextView view) {
-        return createMediumDropShadow(view.getIcon(), view.getScaleX(), view.getScaleY(), true);
-    }
-
-    Bitmap createMediumDropShadow(Drawable drawable, boolean shouldCache) {
-        return createMediumDropShadow(drawable, 1f, 1f, shouldCache);
-    }
-
-    Bitmap createMediumDropShadow(Drawable drawable, float scaleX, float scaleY,
-                boolean shouldCache) {
+    public Bitmap createMediumDropShadow(BubbleTextView view) {
+        Drawable drawable = view.getIcon();
         if (drawable == null) {
             return null;
         }
+
+        float scaleX = view.getScaleX();
+        float scaleY = view.getScaleY();
         Rect rect = drawable.getBounds();
 
         int bitmapWidth = (int) (rect.width() * scaleX);
@@ -177,14 +174,11 @@
         }
 
         int key = (bitmapWidth << 16) | bitmapHeight;
-        Bitmap cache = shouldCache ? mBitmapCache.get(key) : null;
+        Bitmap cache = mBitmapCache.get(key);
         if (cache == null) {
             cache = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ALPHA_8);
             mCanvas.setBitmap(cache);
-
-            if (shouldCache) {
-                mBitmapCache.put(key, cache);
-            }
+            mBitmapCache.put(key, cache);
         } else {
             mCanvas.setBitmap(cache);
             mCanvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
@@ -204,7 +198,7 @@
         int resultWidth = bitmapWidth + extraSize;
         int resultHeight = bitmapHeight + extraSize;
         key = (resultWidth << 16) | resultHeight;
-        Bitmap result = shouldCache ? mBitmapCache.get(key) : null;
+        Bitmap result = mBitmapCache.get(key);
         if (result == null) {
             result = Bitmap.createBitmap(resultWidth, resultHeight, Bitmap.Config.ALPHA_8);
             mCanvas.setBitmap(result);
diff --git a/src/com/android/launcher3/util/IconNormalizer.java b/src/com/android/launcher3/graphics/IconNormalizer.java
similarity index 92%
rename from src/com/android/launcher3/util/IconNormalizer.java
rename to src/com/android/launcher3/graphics/IconNormalizer.java
index 040a1b5..1410917 100644
--- a/src/com/android/launcher3/util/IconNormalizer.java
+++ b/src/com/android/launcher3/graphics/IconNormalizer.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.launcher3.util;
+package com.android.launcher3.graphics;
 
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -186,16 +186,16 @@
     }
 
     /**
-     * Modifies {@param xCordinates} to represent a convex border. Fills in all missing values
+     * Modifies {@param xCoordinates} to represent a convex border. Fills in all missing values
      * (except on either ends) with appropriate values.
-     * @param xCordinates map of x coordinate per y.
+     * @param xCoordinates map of x coordinate per y.
      * @param direction 1 for left border and -1 for right border.
      * @param topY the first Y position (inclusive) with a valid value.
      * @param bottomY the last Y position (inclusive) with a valid value.
      */
     private static void convertToConvexArray(
-            float[] xCordinates, int direction, int topY, int bottomY) {
-        int total = xCordinates.length;
+            float[] xCoordinates, int direction, int topY, int bottomY) {
+        int total = xCoordinates.length;
         // The tangent at each pixel.
         float[] angles = new float[total - 1];
 
@@ -205,7 +205,7 @@
         float lastAngle = Float.MAX_VALUE;
 
         for (int i = topY + 1; i <= bottomY; i++) {
-            if (xCordinates[i] <= -1) {
+            if (xCoordinates[i] <= -1) {
                 continue;
             }
             int start;
@@ -213,14 +213,14 @@
             if (lastAngle == Float.MAX_VALUE) {
                 start = first;
             } else {
-                float currentAngle = (xCordinates[i] - xCordinates[last]) / (i - last);
+                float currentAngle = (xCoordinates[i] - xCoordinates[last]) / (i - last);
                 start = last;
                 // If this position creates a concave angle, keep moving up until we find a
                 // position which creates a convex angle.
                 if ((currentAngle - lastAngle) * direction < 0) {
                     while (start > first) {
                         start --;
-                        currentAngle = (xCordinates[i] - xCordinates[start]) / (i - start);
+                        currentAngle = (xCoordinates[i] - xCoordinates[start]) / (i - start);
                         if ((currentAngle - angles[start]) * direction >= 0) {
                             break;
                         }
@@ -229,11 +229,11 @@
             }
 
             // Reset from last check
-            lastAngle = (xCordinates[i] - xCordinates[start]) / (i - start);
+            lastAngle = (xCoordinates[i] - xCoordinates[start]) / (i - start);
             // Update all the points from start.
             for (int j = start; j < i; j++) {
                 angles[j] = lastAngle;
-                xCordinates[j] = xCordinates[start] + lastAngle * (j - start);
+                xCoordinates[j] = xCoordinates[start] + lastAngle * (j - start);
             }
             last = i;
         }
diff --git a/src/com/android/launcher3/graphics/LauncherIcons.java b/src/com/android/launcher3/graphics/LauncherIcons.java
new file mode 100644
index 0000000..9f3f357
--- /dev/null
+++ b/src/com/android/launcher3/graphics/LauncherIcons.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.graphics;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PaintFlagsDrawFilter;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.PaintDrawable;
+import android.os.Build;
+
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.config.FeatureFlags;
+
+/**
+ * Helper methods for generating various launcher icons
+ */
+public class LauncherIcons {
+
+    private static final Rect sOldBounds = new Rect();
+    private static final Canvas sCanvas = new Canvas();
+
+    static {
+        sCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
+                Paint.FILTER_BITMAP_FLAG));
+    }
+
+
+    public static Bitmap createIconBitmap(Cursor c, int iconIndex, Context context) {
+        byte[] data = c.getBlob(iconIndex);
+        try {
+            return createIconBitmap(BitmapFactory.decodeByteArray(data, 0, data.length), context);
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    /**
+     * Returns a bitmap suitable for the all apps view. If the package or the resource do not
+     * exist, it returns null.
+     */
+    public static Bitmap createIconBitmap(String packageName, String resourceName,
+            Context context) {
+        PackageManager packageManager = context.getPackageManager();
+        // the resource
+        try {
+            Resources resources = packageManager.getResourcesForApplication(packageName);
+            if (resources != null) {
+                final int id = resources.getIdentifier(resourceName, null, null);
+                return createIconBitmap(
+                        resources.getDrawableForDensity(id, LauncherAppState.getInstance()
+                                .getInvariantDeviceProfile().fillResIconDpi), context);
+            }
+        } catch (Exception e) {
+            // Icon not found.
+        }
+        return null;
+    }
+
+    private static int getIconBitmapSize() {
+        return LauncherAppState.getInstance().getInvariantDeviceProfile().iconBitmapSize;
+    }
+
+    /**
+     * Returns a bitmap which is of the appropriate size to be displayed as an icon
+     */
+    public static Bitmap createIconBitmap(Bitmap icon, Context context) {
+        final int iconBitmapSize = getIconBitmapSize();
+        if (iconBitmapSize == icon.getWidth() && iconBitmapSize == icon.getHeight()) {
+            return icon;
+        }
+        return createIconBitmap(new BitmapDrawable(context.getResources(), icon), context);
+    }
+
+    /**
+     * Returns a bitmap suitable for the all apps view. The icon is badged for {@param user}.
+     * The bitmap is also visually normalized with other icons.
+     */
+    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+    public static Bitmap createBadgedIconBitmap(
+            Drawable icon, UserHandleCompat user, Context context) {
+        float scale = FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION ?
+                1 : IconNormalizer.getInstance().getScale(icon, null);
+        Bitmap bitmap = createIconBitmap(icon, context, scale);
+        return badgeIconForUser(bitmap, user, context);
+    }
+
+    /**
+     * Badges the provided icon with the user badge if required.
+     */
+    public static Bitmap badgeIconForUser(Bitmap icon,  UserHandleCompat user, Context context) {
+        if (Utilities.ATLEAST_LOLLIPOP && user != null
+                && !UserHandleCompat.myUserHandle().equals(user)) {
+            BitmapDrawable drawable = new FixedSizeBitmapDrawable(icon);
+            Drawable badged = context.getPackageManager().getUserBadgedIcon(
+                    drawable, user.getUser());
+            if (badged instanceof BitmapDrawable) {
+                return ((BitmapDrawable) badged).getBitmap();
+            } else {
+                return createIconBitmap(badged, context);
+            }
+        } else {
+            return icon;
+        }
+    }
+
+    /**
+     * Creates a normalized bitmap suitable for the all apps view. The bitmap is also visually
+     * normalized with other icons and has enough spacing to add shadow.
+     */
+    public static Bitmap createScaledBitmapWithoutShadow(Drawable icon, Context context) {
+        RectF iconBounds = new RectF();
+        float scale = FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION ?
+                1 : IconNormalizer.getInstance().getScale(icon, iconBounds);
+        scale = Math.min(scale, ShadowGenerator.getScaleForBounds(iconBounds));
+        return createIconBitmap(icon, context, scale);
+    }
+
+    /**
+     * Adds a shadow to the provided icon. It assumes that the icon has already been scaled using
+     * {@link #createScaledBitmapWithoutShadow(Drawable, Context)}
+     */
+    public static Bitmap addShadowToIcon(Bitmap icon) {
+        return ShadowGenerator.getInstance().recreateIcon(icon);
+    }
+
+    /**
+     * Adds the {@param badge} on top of {@param srcTgt} using the badge dimensions.
+     */
+    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+    public static Bitmap badgeWithBitmap(Bitmap srcTgt, Bitmap badge, Context context) {
+        int badgeSize = context.getResources().getDimensionPixelSize(R.dimen.profile_badge_size);
+        synchronized (sCanvas) {
+            sCanvas.setBitmap(srcTgt);
+            sCanvas.drawBitmap(badge, new Rect(0, 0, badge.getWidth(), badge.getHeight()),
+                    new Rect(srcTgt.getWidth() - badgeSize,
+                            srcTgt.getHeight() - badgeSize, srcTgt.getWidth(), srcTgt.getHeight()),
+                    new Paint(Paint.FILTER_BITMAP_FLAG));
+            sCanvas.setBitmap(null);
+        }
+        return srcTgt;
+    }
+
+    /**
+     * Returns a bitmap suitable for the all apps view.
+     */
+    public static Bitmap createIconBitmap(Drawable icon, Context context) {
+        return createIconBitmap(icon, context, 1.0f /* scale */);
+    }
+
+    /**
+     * @param scale the scale to apply before drawing {@param icon} on the canvas
+     */
+    public static Bitmap createIconBitmap(Drawable icon, Context context, float scale) {
+        synchronized (sCanvas) {
+            final int iconBitmapSize = getIconBitmapSize();
+
+            int width = iconBitmapSize;
+            int height = iconBitmapSize;
+
+            if (icon instanceof PaintDrawable) {
+                PaintDrawable painter = (PaintDrawable) icon;
+                painter.setIntrinsicWidth(width);
+                painter.setIntrinsicHeight(height);
+            } else if (icon instanceof BitmapDrawable) {
+                // Ensure the bitmap has a density.
+                BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
+                Bitmap bitmap = bitmapDrawable.getBitmap();
+                if (bitmap != null && bitmap.getDensity() == Bitmap.DENSITY_NONE) {
+                    bitmapDrawable.setTargetDensity(context.getResources().getDisplayMetrics());
+                }
+            }
+            int sourceWidth = icon.getIntrinsicWidth();
+            int sourceHeight = icon.getIntrinsicHeight();
+            if (sourceWidth > 0 && sourceHeight > 0) {
+                // Scale the icon proportionally to the icon dimensions
+                final float ratio = (float) sourceWidth / sourceHeight;
+                if (sourceWidth > sourceHeight) {
+                    height = (int) (width / ratio);
+                } else if (sourceHeight > sourceWidth) {
+                    width = (int) (height * ratio);
+                }
+            }
+
+            // no intrinsic size --> use default size
+            int textureWidth = iconBitmapSize;
+            int textureHeight = iconBitmapSize;
+
+            final Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight,
+                    Bitmap.Config.ARGB_8888);
+            final Canvas canvas = sCanvas;
+            canvas.setBitmap(bitmap);
+
+            final int left = (textureWidth-width) / 2;
+            final int top = (textureHeight-height) / 2;
+
+            sOldBounds.set(icon.getBounds());
+            icon.setBounds(left, top, left+width, top+height);
+            canvas.save(Canvas.MATRIX_SAVE_FLAG);
+            canvas.scale(scale, scale, textureWidth / 2, textureHeight / 2);
+            icon.draw(canvas);
+            canvas.restore();
+            icon.setBounds(sOldBounds);
+            canvas.setBitmap(null);
+
+            return bitmap;
+        }
+    }
+
+    /**
+     * An extension of {@link BitmapDrawable} which returns the bitmap pixel size as intrinsic size.
+     * This allows the badging to be done based on the action bitmap size rather than
+     * the scaled bitmap size.
+     */
+    private static class FixedSizeBitmapDrawable extends BitmapDrawable {
+
+        public FixedSizeBitmapDrawable(Bitmap bitmap) {
+            super(null, bitmap);
+        }
+
+        @Override
+        public int getIntrinsicHeight() {
+            return getBitmap().getWidth();
+        }
+
+        @Override
+        public int getIntrinsicWidth() {
+            return getBitmap().getWidth();
+        }
+    }
+}
diff --git a/src/com/android/launcher3/keyboard/CustomActionsPopup.java b/src/com/android/launcher3/keyboard/CustomActionsPopup.java
new file mode 100644
index 0000000..6603e93
--- /dev/null
+++ b/src/com/android/launcher3/keyboard/CustomActionsPopup.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.keyboard;
+
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+import android.widget.PopupMenu;
+import android.widget.PopupMenu.OnMenuItemClickListener;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
+import com.android.launcher3.shortcuts.DeepShortcutsContainer;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Handles showing a popup menu with available custom actions for a launcher icon.
+ * This allows exposing various custom actions using keyboard shortcuts.
+ */
+public class CustomActionsPopup implements OnMenuItemClickListener {
+
+    private final Launcher mLauncher;
+    private final LauncherAccessibilityDelegate mDelegate;
+    private final View mIcon;
+
+    public CustomActionsPopup(Launcher launcher, View icon) {
+        mLauncher = launcher;
+        mIcon = icon;
+        DeepShortcutsContainer container = DeepShortcutsContainer.getOpen(launcher);
+        if (container != null) {
+            mDelegate = container.getAccessibilityDelegate();
+        } else {
+            mDelegate = launcher.getAccessibilityDelegate();
+        }
+    }
+
+    private List<AccessibilityAction> getActionList() {
+        if (mIcon == null || !(mIcon.getTag() instanceof ItemInfo)) {
+            return Collections.EMPTY_LIST;
+        }
+
+        AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
+        mDelegate.addSupportedActions(mIcon, info, true);
+        List<AccessibilityAction> result = new ArrayList<>(info.getActionList());
+        info.recycle();
+        return result;
+    }
+
+    public boolean canShow() {
+        return !getActionList().isEmpty();
+    }
+
+    public boolean show() {
+        List<AccessibilityAction> actions = getActionList();
+        if (actions.isEmpty()) {
+            return false;
+        }
+
+        PopupMenu popup = new PopupMenu(mLauncher, mIcon);
+        popup.setOnMenuItemClickListener(this);
+        Menu menu = popup.getMenu();
+        for (AccessibilityAction action : actions) {
+            menu.add(Menu.NONE, action.getId(), Menu.NONE, action.getLabel());
+        }
+        popup.show();
+        return true;
+    }
+
+    @Override
+    public boolean onMenuItemClick(MenuItem menuItem) {
+        return mDelegate.performAction(mIcon, (ItemInfo) mIcon.getTag(), menuItem.getItemId());
+    }
+}
diff --git a/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java b/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java
index 7672f5a..b0d6b2d 100644
--- a/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java
+++ b/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java
@@ -143,7 +143,7 @@
     }
 
     private Rect getDrawRect() {
-        if (mCurrentView != null) {
+        if (mCurrentView != null && mCurrentView.isAttachedToWindow()) {
             viewToRect(mCurrentView, sTempRect1);
 
             if (mShift > 0 && mTargetView != null) {
diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java
index 53a2f5d..abe8f42 100644
--- a/src/com/android/launcher3/logging/UserEventDispatcher.java
+++ b/src/com/android/launcher3/logging/UserEventDispatcher.java
@@ -38,7 +38,7 @@
 
 /**
  * Manages the creation of {@link LauncherEvent}.
- * To debug this class, execute following command before sideloading a new apk.
+ * To debug this class, execute following command before side loading a new apk.
  *
  * $ adb shell setprop log.tag.UserEvent VERBOSE
  */
@@ -49,13 +49,9 @@
     private final boolean mIsVerbose;
 
     /**
-     * TODO: change the name of this interface to LogContainerProvider
-     * and the method name to fillInLogContainerData. Not changed to minimize CL diff
-     * in this branch.
-     *
-     * Implemented by containers to provide a launch source for a given child.
+     * Implemented by containers to provide a container source for a given child.
      */
-    public interface LaunchSourceProvider {
+    public interface LogContainerProvider {
 
         /**
          * Copies data from the source to the destination proto.
@@ -65,13 +61,13 @@
          * @param target       dest of the data
          * @param targetParent dest of the data
          */
-        void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent);
+        void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent);
     }
 
     /**
      * Recursively finds the parent of the given child which implements IconLogInfoProvider
      */
-    public static LaunchSourceProvider getLaunchProviderRecursive(View v) {
+    public static LogContainerProvider getLaunchProviderRecursive(View v) {
         ViewParent parent = null;
 
         if (v != null) {
@@ -83,8 +79,8 @@
         // Optimization to only check up to 5 parents.
         int count = MAXIMUM_VIEW_HIERARCHY_LEVEL;
         while (parent != null && count-- > 0) {
-            if (parent instanceof LaunchSourceProvider) {
-                return (LaunchSourceProvider) parent;
+            if (parent instanceof LogContainerProvider) {
+                return (LogContainerProvider) parent;
             } else {
                 parent = parent.getParent();
             }
@@ -124,12 +120,12 @@
         // Fill in grid(x,y), pageIndex of the child and container type of the parent
         // TODO: make this percolate up the view hierarchy if needed.
         int idx = 0;
-        LaunchSourceProvider provider = getLaunchProviderRecursive(v);
+        LogContainerProvider provider = getLaunchProviderRecursive(v);
         if (v == null || !(v.getTag() instanceof ItemInfo) || provider == null) {
             return null;
         }
         ItemInfo itemInfo = (ItemInfo) v.getTag();
-        provider.fillInLaunchSourceData(v, itemInfo, event.srcTarget[idx], event.srcTarget[idx + 1]);
+        provider.fillInLogContainerData(v, itemInfo, event.srcTarget[idx], event.srcTarget[idx + 1]);
 
         event.srcTarget[idx].intentHash = intent.hashCode();
         ComponentName cn = intent.getComponent();
@@ -167,24 +163,31 @@
     }
 
     public void logActionOnContainer(int action, int dir, int containerType) {
+        logActionOnContainer(action, dir, containerType, 0);
+    }
+
+    public void logActionOnContainer(int action, int dir, int containerType, int pageIndex) {
         LauncherEvent event = LoggerUtils.initLauncherEvent(Action.TOUCH, Target.CONTAINER);
         event.action.touch = action;
         event.action.dir = dir;
         event.srcTarget[0].containerType = containerType;
+        event.srcTarget[0].pageIndex = pageIndex;
         dispatchUserEvent(event, null);
     }
 
     public void logDeepShortcutsOpen(View icon) {
         LauncherEvent event = LoggerUtils.initLauncherEvent(
                 Action.TOUCH, icon, Target.CONTAINER);
-        LaunchSourceProvider provider = getLaunchProviderRecursive(icon);
+        LogContainerProvider provider = getLaunchProviderRecursive(icon);
         if (icon == null && !(icon.getTag() instanceof ItemInfo)) {
             return;
         }
         ItemInfo info = (ItemInfo) icon.getTag();
-        provider.fillInLaunchSourceData(icon, info, event.srcTarget[0], event.srcTarget[1]);
+        provider.fillInLogContainerData(icon, info, event.srcTarget[0], event.srcTarget[1]);
         event.action.touch = Action.LONGPRESS;
         dispatchUserEvent(event, null);
+
+        resetElapsedContainerMillis();
     }
 
     public void setPredictedApps(List<ComponentKey> predictedApps) {
@@ -218,11 +221,11 @@
                 dropTargetAsView);
         event.action.touch = Action.DRAGDROP;
 
-        dragObj.dragSource.fillInLaunchSourceData(null, dragObj.originalDragInfo,
+        dragObj.dragSource.fillInLogContainerData(null, dragObj.originalDragInfo,
                 event.srcTarget[0], event.srcTarget[1]);
 
-        if (dropTargetAsView instanceof LaunchSourceProvider) {
-            ((LaunchSourceProvider) dropTargetAsView).fillInLaunchSourceData(null,
+        if (dropTargetAsView instanceof LogContainerProvider) {
+            ((LogContainerProvider) dropTargetAsView).fillInLogContainerData(null,
                     dragObj.dragInfo, event.destTarget[0], event.destTarget[1]);
 
         }
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
new file mode 100644
index 0000000..986e163
--- /dev/null
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.model;
+
+import android.content.Context;
+import android.content.Intent;
+import android.util.LongSparseArray;
+import android.util.Pair;
+
+import com.android.launcher3.AllAppsList;
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.FolderInfo;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherModel.CallbackTask;
+import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.util.GridOccupancy;
+
+import java.util.ArrayList;
+
+/**
+ * Task to add auto-created workspace items.
+ */
+public class AddWorkspaceItemsTask extends ExtendedModelTask {
+
+    private final ArrayList<? extends ItemInfo> mWorkspaceApps;
+
+    /**
+     * @param workspaceApps items to add on the workspace
+     */
+    public AddWorkspaceItemsTask(ArrayList<? extends ItemInfo> workspaceApps) {
+        mWorkspaceApps = workspaceApps;
+    }
+
+    @Override
+    public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+        if (mWorkspaceApps.isEmpty()) {
+            return;
+        }
+        Context context = app.getContext();
+
+        final ArrayList<ItemInfo> addedShortcutsFinal = new ArrayList<ItemInfo>();
+        final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<Long>();
+
+        // Get the list of workspace screens.  We need to append to this list and
+        // can not use sBgWorkspaceScreens because loadWorkspace() may not have been
+        // called.
+        ArrayList<Long> workspaceScreens = LauncherModel.loadWorkspaceScreensDb(context);
+        synchronized(dataModel) {
+            for (ItemInfo item : mWorkspaceApps) {
+                if (item instanceof ShortcutInfo) {
+                    // Short-circuit this logic if the icon exists somewhere on the workspace
+                    if (shortcutExists(dataModel, item.getIntent(), item.user)) {
+                        continue;
+                    }
+                }
+
+                // Find appropriate space for the item.
+                Pair<Long, int[]> coords = findSpaceForItem(
+                        app, dataModel, workspaceScreens, addedWorkspaceScreensFinal, 1, 1);
+                long screenId = coords.first;
+                int[] cordinates = coords.second;
+
+                ItemInfo itemInfo;
+                if (item instanceof ShortcutInfo || item instanceof FolderInfo) {
+                    itemInfo = item;
+                } else if (item instanceof AppInfo) {
+                    itemInfo = ((AppInfo) item).makeShortcut();
+                } else {
+                    throw new RuntimeException("Unexpected info type");
+                }
+
+                // Add the shortcut to the db
+                addItemToDatabase(context, itemInfo, screenId, cordinates);
+
+                // Save the ShortcutInfo for binding in the workspace
+                addedShortcutsFinal.add(itemInfo);
+            }
+        }
+
+        // Update the workspace screens
+        updateScreens(context, workspaceScreens);
+
+        if (!addedShortcutsFinal.isEmpty()) {
+            scheduleCallbackTask(new CallbackTask() {
+                @Override
+                public void execute(Callbacks callbacks) {
+                    final ArrayList<ItemInfo> addAnimated = new ArrayList<ItemInfo>();
+                    final ArrayList<ItemInfo> addNotAnimated = new ArrayList<ItemInfo>();
+                    if (!addedShortcutsFinal.isEmpty()) {
+                        ItemInfo info = addedShortcutsFinal.get(addedShortcutsFinal.size() - 1);
+                        long lastScreenId = info.screenId;
+                        for (ItemInfo i : addedShortcutsFinal) {
+                            if (i.screenId == lastScreenId) {
+                                addAnimated.add(i);
+                            } else {
+                                addNotAnimated.add(i);
+                            }
+                        }
+                    }
+                    callbacks.bindAppsAdded(addedWorkspaceScreensFinal,
+                            addNotAnimated, addAnimated, null);
+                }
+            });
+        }
+    }
+
+    protected void addItemToDatabase(Context context, ItemInfo item, long screenId, int[] pos) {
+        LauncherModel.addItemToDatabase(context, item,
+                LauncherSettings.Favorites.CONTAINER_DESKTOP, screenId, pos[0], pos[1]);
+    }
+
+    protected void updateScreens(Context context, ArrayList<Long> workspaceScreens) {
+        LauncherModel.updateWorkspaceScreenOrder(context, workspaceScreens);
+    }
+
+    /**
+     * Returns true if the shortcuts already exists on the workspace. This must be called after
+     * the workspace has been loaded. We identify a shortcut by its intent.
+     */
+    protected boolean shortcutExists(BgDataModel dataModel, Intent intent, UserHandleCompat user) {
+        final String intentWithPkg, intentWithoutPkg;
+        if (intent.getComponent() != null) {
+            // If component is not null, an intent with null package will produce
+            // the same result and should also be a match.
+            String packageName = intent.getComponent().getPackageName();
+            if (intent.getPackage() != null) {
+                intentWithPkg = intent.toUri(0);
+                intentWithoutPkg = new Intent(intent).setPackage(null).toUri(0);
+            } else {
+                intentWithPkg = new Intent(intent).setPackage(packageName).toUri(0);
+                intentWithoutPkg = intent.toUri(0);
+            }
+        } else {
+            intentWithPkg = intent.toUri(0);
+            intentWithoutPkg = intent.toUri(0);
+        }
+
+        synchronized (dataModel) {
+            for (ItemInfo item : dataModel.itemsIdMap) {
+                if (item instanceof ShortcutInfo) {
+                    ShortcutInfo info = (ShortcutInfo) item;
+                    Intent targetIntent = info.promisedIntent == null
+                            ? info.intent : info.promisedIntent;
+                    if (targetIntent != null && info.user.equals(user)) {
+                        Intent copyIntent = new Intent(targetIntent);
+                        copyIntent.setSourceBounds(intent.getSourceBounds());
+                        String s = copyIntent.toUri(0);
+                        if (intentWithPkg.equals(s) || intentWithoutPkg.equals(s)) {
+                            return true;
+                        }
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Find a position on the screen for the given size or adds a new screen.
+     * @return screenId and the coordinates for the item.
+     */
+    protected Pair<Long, int[]> findSpaceForItem(
+            LauncherAppState app, BgDataModel dataModel,
+            ArrayList<Long> workspaceScreens,
+            ArrayList<Long> addedWorkspaceScreensFinal,
+            int spanX, int spanY) {
+        LongSparseArray<ArrayList<ItemInfo>> screenItems = new LongSparseArray<>();
+
+        // Use sBgItemsIdMap as all the items are already loaded.
+        synchronized (dataModel) {
+            for (ItemInfo info : dataModel.itemsIdMap) {
+                if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+                    ArrayList<ItemInfo> items = screenItems.get(info.screenId);
+                    if (items == null) {
+                        items = new ArrayList<>();
+                        screenItems.put(info.screenId, items);
+                    }
+                    items.add(info);
+                }
+            }
+        }
+
+        // Find appropriate space for the item.
+        long screenId = 0;
+        int[] cordinates = new int[2];
+        boolean found = false;
+
+        int screenCount = workspaceScreens.size();
+        // First check the preferred screen.
+        int preferredScreenIndex = workspaceScreens.isEmpty() ? 0 : 1;
+        if (preferredScreenIndex < screenCount) {
+            screenId = workspaceScreens.get(preferredScreenIndex);
+            found = findNextAvailableIconSpaceInScreen(
+                    app, screenItems.get(screenId), cordinates, spanX, spanY);
+        }
+
+        if (!found) {
+            // Search on any of the screens starting from the first screen.
+            for (int screen = 1; screen < screenCount; screen++) {
+                screenId = workspaceScreens.get(screen);
+                if (findNextAvailableIconSpaceInScreen(
+                        app, screenItems.get(screenId), cordinates, spanX, spanY)) {
+                    // We found a space for it
+                    found = true;
+                    break;
+                }
+            }
+        }
+
+        if (!found) {
+            // Still no position found. Add a new screen to the end.
+            screenId = LauncherSettings.Settings.call(app.getContext().getContentResolver(),
+                    LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
+                    .getLong(LauncherSettings.Settings.EXTRA_VALUE);
+
+            // Save the screen id for binding in the workspace
+            workspaceScreens.add(screenId);
+            addedWorkspaceScreensFinal.add(screenId);
+
+            // If we still can't find an empty space, then God help us all!!!
+            if (!findNextAvailableIconSpaceInScreen(
+                    app, screenItems.get(screenId), cordinates, spanX, spanY)) {
+                throw new RuntimeException("Can't find space to add the item");
+            }
+        }
+        return Pair.create(screenId, cordinates);
+    }
+
+    private boolean findNextAvailableIconSpaceInScreen(
+            LauncherAppState app, ArrayList<ItemInfo> occupiedPos,
+            int[] xy, int spanX, int spanY) {
+        InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
+
+        GridOccupancy occupied = new GridOccupancy(profile.numColumns, profile.numRows);
+        if (occupiedPos != null) {
+            for (ItemInfo r : occupiedPos) {
+                occupied.markCells(r, true);
+            }
+        }
+        return occupied.findVacantCell(xy, spanX, spanY);
+    }
+
+}
diff --git a/src/com/android/launcher3/model/AppNameComparator.java b/src/com/android/launcher3/model/AppNameComparator.java
deleted file mode 100644
index 5f80037..0000000
--- a/src/com/android/launcher3/model/AppNameComparator.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * 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.
- */
-package com.android.launcher3.model;
-
-import android.content.Context;
-
-import com.android.launcher3.AppInfo;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.util.Thunk;
-
-import java.text.Collator;
-import java.util.Comparator;
-
-/**
- * Class to manage access to an app name comparator.
- * <p>
- * Used to sort application name in all apps view and widget tray view.
- */
-public class AppNameComparator {
-    private final Collator mCollator;
-    private final AbstractUserComparator<ItemInfo> mAppInfoComparator;
-    private final Comparator<String> mSectionNameComparator;
-
-    public AppNameComparator(Context context) {
-        mCollator = Collator.getInstance();
-        mAppInfoComparator = new AbstractUserComparator<ItemInfo>(context) {
-
-            @Override
-            public final int compare(ItemInfo a, ItemInfo b) {
-                // Order by the title in the current locale
-                int result = compareTitles(a.title.toString(), b.title.toString());
-                if (result == 0 && a instanceof AppInfo && b instanceof AppInfo) {
-                    AppInfo aAppInfo = (AppInfo) a;
-                    AppInfo bAppInfo = (AppInfo) b;
-                    // If two apps have the same title, then order by the component name
-                    result = aAppInfo.componentName.compareTo(bAppInfo.componentName);
-                    if (result == 0) {
-                        // If the two apps are the same component, then prioritize by the order that
-                        // the app user was created (prioritizing the main user's apps)
-                        return super.compare(a, b);
-                    }
-                }
-                return result;
-            }
-        };
-        mSectionNameComparator = new Comparator<String>() {
-            @Override
-            public int compare(String o1, String o2) {
-                return compareTitles(o1, o2);
-            }
-        };
-    }
-
-    /**
-     * Returns a locale-aware comparator that will alphabetically order a list of applications.
-     */
-    public Comparator<ItemInfo> getAppInfoComparator() {
-        return mAppInfoComparator;
-    }
-
-    /**
-     * Returns a locale-aware comparator that will alphabetically order a list of section names.
-     */
-    public Comparator<String> getSectionNameComparator() {
-        return mSectionNameComparator;
-    }
-
-    /**
-     * Compares two titles with the same return value semantics as Comparator.
-     */
-    @Thunk int compareTitles(String titleA, String titleB) {
-        // Ensure that we de-prioritize any titles that don't start with a linguistic letter or digit
-        boolean aStartsWithLetter = (titleA.length() > 0) &&
-                Character.isLetterOrDigit(titleA.codePointAt(0));
-        boolean bStartsWithLetter = (titleB.length() > 0) &&
-                Character.isLetterOrDigit(titleB.codePointAt(0));
-        if (aStartsWithLetter && !bStartsWithLetter) {
-            return -1;
-        } else if (!aStartsWithLetter && bStartsWithLetter) {
-            return 1;
-        }
-
-        // Order by the title in the current locale
-        return mCollator.compare(titleA, titleB);
-    }
-}
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
new file mode 100644
index 0000000..29defdd
--- /dev/null
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.model;
+
+import android.util.Log;
+import android.util.MutableInt;
+
+import com.android.launcher3.FolderInfo;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.LongArrayMap;
+import com.android.launcher3.util.MultiHashMap;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * All the data stored in-memory and managed by the LauncherModel
+ */
+public class BgDataModel {
+
+    private static final String TAG = "BgDataModel";
+
+    /**
+     * Map of all the ItemInfos (shortcuts, folders, and widgets) created by
+     * LauncherModel to their ids
+     */
+    public final LongArrayMap<ItemInfo> itemsIdMap = new LongArrayMap<>();
+
+    /**
+     * List of all the folders and shortcuts directly on the home screen (no widgets
+     * or shortcuts within folders).
+     */
+    public final ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
+
+    /**
+     * All LauncherAppWidgetInfo created by LauncherModel.
+     */
+    public final ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
+
+    /**
+     * Map of id to FolderInfos of all the folders created by LauncherModel
+     */
+    public final LongArrayMap<FolderInfo> folders = new LongArrayMap<>();
+
+    /**
+     * Ordered list of workspace screens ids.
+     */
+    public final ArrayList<Long> workspaceScreens = new ArrayList<>();
+
+    /**
+     * Map of ShortcutKey to the number of times it is pinned.
+     */
+    public final Map<ShortcutKey, MutableInt> pinnedShortcutCounts = new HashMap<>();
+
+    /**
+     * Maps all launcher activities to the id's of their shortcuts (if they have any).
+     */
+    public final MultiHashMap<ComponentKey, String> deepShortcutMap = new MultiHashMap<>();
+
+    /**
+     * Clears all the data
+     */
+    public synchronized void clear() {
+        workspaceItems.clear();
+        appWidgets.clear();
+        folders.clear();
+        itemsIdMap.clear();
+        workspaceScreens.clear();
+        pinnedShortcutCounts.clear();
+        deepShortcutMap.clear();
+    }
+
+    public synchronized void removeItem(ItemInfo... items) {
+        removeItem(Arrays.asList(items));
+    }
+
+    public synchronized void removeItem(Iterable<? extends ItemInfo> items) {
+        for (ItemInfo item : items) {
+            switch (item.itemType) {
+                case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+                    folders.remove(item.id);
+                    if (ProviderConfig.IS_DOGFOOD_BUILD) {
+                        for (ItemInfo info : itemsIdMap) {
+                            if (info.container == item.id) {
+                                // We are deleting a folder which still contains items that
+                                // think they are contained by that folder.
+                                String msg = "deleting a folder (" + item + ") which still " +
+                                        "contains items (" + info + ")";
+                                Log.e(TAG, msg);
+                            }
+                        }
+                    }
+                    workspaceItems.remove(item);
+                    break;
+                case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
+                    // Decrement pinned shortcut count
+                    ShortcutKey pinnedShortcut = ShortcutKey.fromShortcutInfo((ShortcutInfo) item);
+                    MutableInt count = pinnedShortcutCounts.get(pinnedShortcut);
+                    if (count == null || --count.value == 0) {
+                        DeepShortcutManager.getInstance(LauncherAppState.getInstance().getContext())
+                                .unpinShortcut(pinnedShortcut);
+                    }
+                    // Fall through.
+                }
+                case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
+                case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+                    workspaceItems.remove(item);
+                    break;
+                case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
+                case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
+                    appWidgets.remove(item);
+                    break;
+            }
+            itemsIdMap.remove(item.id);
+        }
+    }
+
+    public synchronized void addItem(ItemInfo item, boolean newItem) {
+        itemsIdMap.put(item.id, item);
+        switch (item.itemType) {
+            case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+                folders.put(item.id, (FolderInfo) item);
+                workspaceItems.add(item);
+                break;
+            case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
+                // Increment the count for the given shortcut
+                ShortcutKey pinnedShortcut = ShortcutKey.fromShortcutInfo((ShortcutInfo) item);
+                MutableInt count = pinnedShortcutCounts.get(pinnedShortcut);
+                if (count == null) {
+                    count = new MutableInt(1);
+                    pinnedShortcutCounts.put(pinnedShortcut, count);
+                } else {
+                    count.value++;
+                }
+
+                // Since this is a new item, pin the shortcut in the system server.
+                if (newItem && count.value == 1) {
+                    DeepShortcutManager.getInstance(LauncherAppState.getInstance().getContext())
+                            .pinShortcut(pinnedShortcut);
+                }
+                // Fall through
+            }
+            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
+            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+                if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
+                        item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+                    workspaceItems.add(item);
+                } else {
+                    if (newItem) {
+                        if (!folders.containsKey(item.container)) {
+                            // Adding an item to a folder that doesn't exist.
+                            String msg = "adding item: " + item + " to a folder that " +
+                                    " doesn't exist";
+                            Log.e(TAG, msg);
+                        }
+                    } else {
+                        findOrMakeFolder(item.container).add((ShortcutInfo) item, false);
+                    }
+
+                }
+                break;
+            case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
+            case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
+                appWidgets.add((LauncherAppWidgetInfo) item);
+                break;
+        }
+    }
+
+    /**
+     * Return an existing FolderInfo object if we have encountered this ID previously,
+     * or make a new one.
+     */
+    public synchronized FolderInfo findOrMakeFolder(long id) {
+        // See if a placeholder was created for us already
+        FolderInfo folderInfo = folders.get(id);
+        if (folderInfo == null) {
+            // No placeholder -- create a new instance
+            folderInfo = new FolderInfo();
+            folders.put(id, folderInfo);
+        }
+        return folderInfo;
+    }
+
+    /**
+     * Clear all the deep shortcuts for the given package, and re-add the new shortcuts.
+     */
+    public synchronized void updateDeepShortcutMap(
+            String packageName, UserHandleCompat user, List<ShortcutInfoCompat> shortcuts) {
+        if (packageName != null) {
+            Iterator<ComponentKey> keysIter = deepShortcutMap.keySet().iterator();
+            while (keysIter.hasNext()) {
+                ComponentKey next = keysIter.next();
+                if (next.componentName.getPackageName().equals(packageName)
+                        && next.user.equals(user)) {
+                    keysIter.remove();
+                }
+            }
+        }
+
+        // Now add the new shortcuts to the map.
+        for (ShortcutInfoCompat shortcut : shortcuts) {
+            boolean shouldShowInContainer = shortcut.isEnabled()
+                    && (shortcut.isDeclaredInManifest() || shortcut.isDynamic());
+            if (shouldShowInContainer) {
+                ComponentKey targetComponent
+                        = new ComponentKey(shortcut.getActivity(), shortcut.getUserHandle());
+                deepShortcutMap.addToList(targetComponent, shortcut.getId());
+            }
+        }
+    }
+}
diff --git a/src/com/android/launcher3/model/CacheDataUpdatedTask.java b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
new file mode 100644
index 0000000..9f24e90
--- /dev/null
+++ b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.model;
+
+import android.content.ComponentName;
+
+import com.android.launcher3.AllAppsList;
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.IconCache;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel.CallbackTask;
+import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.compat.UserHandleCompat;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+
+/**
+ * Handles changes due to cache updates.
+ */
+public class CacheDataUpdatedTask extends ExtendedModelTask {
+
+    public static final int OP_CACHE_UPDATE = 1;
+    public static final int OP_SESSION_UPDATE = 2;
+
+    private final int mOp;
+    private final UserHandleCompat mUser;
+    private final HashSet<String> mPackages;
+
+    public CacheDataUpdatedTask(int op, UserHandleCompat user, HashSet<String> packages) {
+        mOp = op;
+        mUser = user;
+        mPackages = packages;
+    }
+
+    @Override
+    public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+        IconCache iconCache = app.getIconCache();
+
+        final ArrayList<AppInfo> updatedApps = new ArrayList<>();
+
+        ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<>();
+        synchronized (dataModel) {
+            for (ItemInfo info : dataModel.itemsIdMap) {
+                if (info instanceof ShortcutInfo && mUser.equals(info.user)) {
+                    ShortcutInfo si = (ShortcutInfo) info;
+                    ComponentName cn = si.getTargetComponent();
+                    if (isValidShortcut(si) &&
+                            cn != null && mPackages.contains(cn.getPackageName())) {
+                        si.updateIcon(iconCache);
+                        updatedShortcuts.add(si);
+                    }
+                }
+            }
+            apps.updateIconsAndLabels(mPackages, mUser, updatedApps);
+        }
+        bindUpdatedShortcuts(updatedShortcuts, mUser);
+
+        if (!updatedApps.isEmpty()) {
+            scheduleCallbackTask(new CallbackTask() {
+                @Override
+                public void execute(Callbacks callbacks) {
+                    callbacks.bindAppsUpdated(updatedApps);
+                }
+            });
+        }
+    }
+
+    public boolean isValidShortcut(ShortcutInfo si) {
+        switch (mOp) {
+            case OP_CACHE_UPDATE:
+                return si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+            case OP_SESSION_UPDATE:
+                return si.isPromise();
+            default:
+                return false;
+        }
+    }
+}
diff --git a/src/com/android/launcher3/model/ExtendedModelTask.java b/src/com/android/launcher3/model/ExtendedModelTask.java
new file mode 100644
index 0000000..ccc6007
--- /dev/null
+++ b/src/com/android/launcher3/model/ExtendedModelTask.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.model;
+
+import com.android.launcher3.LauncherModel.CallbackTask;
+import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.LauncherModel.BaseModelUpdateTask;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.MultiHashMap;
+
+import java.util.ArrayList;
+
+/**
+ * Extension of {@link BaseModelUpdateTask} with some utility methods
+ */
+public abstract class ExtendedModelTask extends BaseModelUpdateTask {
+
+    public void bindUpdatedShortcuts(
+            ArrayList<ShortcutInfo> updatedShortcuts, UserHandleCompat user) {
+        bindUpdatedShortcuts(updatedShortcuts, new ArrayList<ShortcutInfo>(), user);
+    }
+
+    public void bindUpdatedShortcuts(
+            final ArrayList<ShortcutInfo> updatedShortcuts,
+            final ArrayList<ShortcutInfo> removedShortcuts,
+            final UserHandleCompat user) {
+        if (!updatedShortcuts.isEmpty() || !removedShortcuts.isEmpty()) {
+            scheduleCallbackTask(new CallbackTask() {
+                @Override
+                public void execute(Callbacks callbacks) {
+                    callbacks.bindShortcutsChanged(updatedShortcuts, removedShortcuts, user);
+                }
+            });
+        }
+    }
+
+    public void bindDeepShortcuts(BgDataModel dataModel) {
+        final MultiHashMap<ComponentKey, String> shortcutMapCopy = dataModel.deepShortcutMap.clone();
+        scheduleCallbackTask(new CallbackTask() {
+            @Override
+            public void execute(Callbacks callbacks) {
+                callbacks.bindDeepShortcutMap(shortcutMapCopy);
+            }
+        });
+    }
+}
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTask.java b/src/com/android/launcher3/model/GridSizeMigrationTask.java
index fd647c7..599dcd0 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTask.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTask.java
@@ -42,7 +42,7 @@
  */
 public class GridSizeMigrationTask {
 
-    public static boolean ENABLED = Utilities.isNycOrAbove();
+    public static boolean ENABLED = Utilities.ATLEAST_NOUGAT;
 
     private static final String TAG = "GridSizeMigrationTask";
     private static final boolean DEBUG = true;
diff --git a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
new file mode 100644
index 0000000..5d04325
--- /dev/null
+++ b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.model;
+
+import android.content.ComponentName;
+
+import com.android.launcher3.AllAppsList;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.LauncherModel.CallbackTask;
+import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.compat.PackageInstallerCompat;
+import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
+
+import java.util.HashSet;
+
+/**
+ * Handles changes due to a sessions updates for a currently installing app.
+ */
+public class PackageInstallStateChangedTask extends ExtendedModelTask {
+
+    private final PackageInstallInfo mInstallInfo;
+
+    public PackageInstallStateChangedTask(PackageInstallInfo installInfo) {
+        mInstallInfo = installInfo;
+    }
+
+    @Override
+    public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+        if (mInstallInfo.state == PackageInstallerCompat.STATUS_INSTALLED) {
+            // Ignore install success events as they are handled by Package add events.
+            return;
+        }
+
+        synchronized (dataModel) {
+            final HashSet<ItemInfo> updates = new HashSet<>();
+            for (ItemInfo info : dataModel.itemsIdMap) {
+                if (info instanceof ShortcutInfo) {
+                    ShortcutInfo si = (ShortcutInfo) info;
+                    ComponentName cn = si.getTargetComponent();
+                    if (si.isPromise() && (cn != null)
+                            && mInstallInfo.packageName.equals(cn.getPackageName())) {
+                        si.setInstallProgress(mInstallInfo.progress);
+
+                        if (mInstallInfo.state == PackageInstallerCompat.STATUS_FAILED) {
+                            // Mark this info as broken.
+                            si.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
+                        }
+                        updates.add(si);
+                    }
+                }
+            }
+
+            for (LauncherAppWidgetInfo widget : dataModel.appWidgets) {
+                if (widget.providerName.getPackageName().equals(mInstallInfo.packageName)) {
+                    widget.installProgress = mInstallInfo.progress;
+                    updates.add(widget);
+                }
+            }
+
+            if (!updates.isEmpty()) {
+                scheduleCallbackTask(new CallbackTask() {
+                    @Override
+                    public void execute(Callbacks callbacks) {
+                        callbacks.bindRestoreItemsChange(updates);
+                    }
+                });
+            }
+        }
+    }
+}
diff --git a/src/com/android/launcher3/model/PackageItemInfo.java b/src/com/android/launcher3/model/PackageItemInfo.java
index c86ba86..00470e1 100644
--- a/src/com/android/launcher3/model/PackageItemInfo.java
+++ b/src/com/android/launcher3/model/PackageItemInfo.java
@@ -40,12 +40,6 @@
      */
     public String packageName;
 
-    /**
-     * Character that is used as a section name for the {@link ItemInfo#title}.
-     * (e.g., "G" will be stored if title is "Google")
-     */
-    public String titleSectionName;
-
     PackageItemInfo(String packageName) {
         this.packageName = packageName;
     }
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
new file mode 100644
index 0000000..176e8ea
--- /dev/null
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -0,0 +1,378 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.model;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.graphics.Bitmap;
+import android.util.Log;
+
+import com.android.launcher3.AllAppsList;
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.IconCache;
+import com.android.launcher3.InstallShortcutReceiver;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherModel.CallbackTask;
+import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.graphics.LauncherIcons;
+import com.android.launcher3.util.FlagOp;
+import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.launcher3.util.ManagedProfileHeuristic;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+
+/**
+ * Handles updates due to changes in package manager (app installed/updated/removed)
+ * or when a user availability changes.
+ */
+public class PackageUpdatedTask extends ExtendedModelTask {
+
+    private static final boolean DEBUG = false;
+    private static final String TAG = "PackageUpdatedTask";
+
+    public static final int OP_NONE = 0;
+    public static final int OP_ADD = 1;
+    public static final int OP_UPDATE = 2;
+    public static final int OP_REMOVE = 3; // uninstalled
+    public static final int OP_UNAVAILABLE = 4; // external media unmounted
+    public static final int OP_SUSPEND = 5; // package suspended
+    public static final int OP_UNSUSPEND = 6; // package unsuspended
+    public static final int OP_USER_AVAILABILITY_CHANGE = 7; // user available/unavailable
+
+    private final int mOp;
+    private final UserHandleCompat mUser;
+    private final String[] mPackages;
+
+    public PackageUpdatedTask(int op, UserHandleCompat user, String... packages) {
+        mOp = op;
+        mUser = user;
+        mPackages = packages;
+    }
+
+    @Override
+    public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList appsList) {
+        final Context context = app.getContext();
+        final IconCache iconCache = app.getIconCache();
+
+        final String[] packages = mPackages;
+        final int N = packages.length;
+        FlagOp flagOp = FlagOp.NO_OP;
+        final HashSet<String> packageSet = new HashSet<>(Arrays.asList(packages));
+        ItemInfoMatcher matcher = ItemInfoMatcher.ofPackages(packageSet, mUser);
+        switch (mOp) {
+            case OP_ADD: {
+                for (int i = 0; i < N; i++) {
+                    if (DEBUG) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
+                    iconCache.updateIconsForPkg(packages[i], mUser);
+                    appsList.addPackage(context, packages[i], mUser);
+                }
+
+                ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser);
+                if (heuristic != null) {
+                    heuristic.processPackageAdd(mPackages);
+                }
+                break;
+            }
+            case OP_UPDATE:
+                for (int i = 0; i < N; i++) {
+                    if (DEBUG) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
+                    iconCache.updateIconsForPkg(packages[i], mUser);
+                    appsList.updatePackage(context, packages[i], mUser);
+                    app.getWidgetCache().removePackage(packages[i], mUser);
+                }
+                // Since package was just updated, the target must be available now.
+                flagOp = FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE);
+                break;
+            case OP_REMOVE: {
+                ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser);
+                if (heuristic != null) {
+                    heuristic.processPackageRemoved(mPackages);
+                }
+                for (int i = 0; i < N; i++) {
+                    iconCache.removeIconsForPkg(packages[i], mUser);
+                }
+                // Fall through
+            }
+            case OP_UNAVAILABLE:
+                for (int i = 0; i < N; i++) {
+                    if (DEBUG) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
+                    appsList.removePackage(packages[i], mUser);
+                    app.getWidgetCache().removePackage(packages[i], mUser);
+                }
+                flagOp = FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE);
+                break;
+            case OP_SUSPEND:
+            case OP_UNSUSPEND:
+                flagOp = mOp == OP_SUSPEND ?
+                        FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_SUSPENDED) :
+                        FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_SUSPENDED);
+                if (DEBUG) Log.d(TAG, "mAllAppsList.(un)suspend " + N);
+                appsList.updateDisabledFlags(matcher, flagOp);
+                break;
+            case OP_USER_AVAILABILITY_CHANGE:
+                flagOp = UserManagerCompat.getInstance(context).isQuietModeEnabled(mUser)
+                        ? FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_QUIET_USER)
+                        : FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_QUIET_USER);
+                // We want to update all packages for this user.
+                matcher = ItemInfoMatcher.ofUser(mUser);
+                appsList.updateDisabledFlags(matcher, flagOp);
+                break;
+        }
+
+        ArrayList<AppInfo> added = null;
+        ArrayList<AppInfo> modified = null;
+        final ArrayList<AppInfo> removedApps = new ArrayList<AppInfo>();
+
+        if (appsList.added.size() > 0) {
+            added = new ArrayList<>(appsList.added);
+            appsList.added.clear();
+        }
+        if (appsList.modified.size() > 0) {
+            modified = new ArrayList<>(appsList.modified);
+            appsList.modified.clear();
+        }
+        if (appsList.removed.size() > 0) {
+            removedApps.addAll(appsList.removed);
+            appsList.removed.clear();
+        }
+
+        final HashMap<ComponentName, AppInfo> addedOrUpdatedApps = new HashMap<>();
+
+        if (added != null) {
+            final ArrayList<AppInfo> addedApps = added;
+            scheduleCallbackTask(new CallbackTask() {
+                @Override
+                public void execute(Callbacks callbacks) {
+                    callbacks.bindAppsAdded(null, null, null, addedApps);
+                }
+            });
+            for (AppInfo ai : added) {
+                addedOrUpdatedApps.put(ai.componentName, ai);
+            }
+        }
+
+        if (modified != null) {
+            final ArrayList<AppInfo> modifiedFinal = modified;
+            for (AppInfo ai : modified) {
+                addedOrUpdatedApps.put(ai.componentName, ai);
+            }
+            scheduleCallbackTask(new CallbackTask() {
+                @Override
+                public void execute(Callbacks callbacks) {
+                    callbacks.bindAppsUpdated(modifiedFinal);
+                }
+            });
+        }
+
+        // Update shortcut infos
+        if (mOp == OP_ADD || flagOp != FlagOp.NO_OP) {
+            final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<>();
+            final ArrayList<ShortcutInfo> removedShortcuts = new ArrayList<>();
+            final ArrayList<LauncherAppWidgetInfo> widgets = new ArrayList<>();
+
+            synchronized (dataModel) {
+                for (ItemInfo info : dataModel.itemsIdMap) {
+                    if (info instanceof ShortcutInfo && mUser.equals(info.user)) {
+                        ShortcutInfo si = (ShortcutInfo) info;
+                        boolean infoUpdated = false;
+                        boolean shortcutUpdated = false;
+
+                        // Update shortcuts which use iconResource.
+                        if ((si.iconResource != null)
+                                && packageSet.contains(si.iconResource.packageName)) {
+                            Bitmap icon = LauncherIcons.createIconBitmap(
+                                    si.iconResource.packageName,
+                                    si.iconResource.resourceName, context);
+                            if (icon != null) {
+                                si.setIcon(icon);
+                                infoUpdated = true;
+                            }
+                        }
+
+                        ComponentName cn = si.getTargetComponent();
+                        if (cn != null && matcher.matches(si, cn)) {
+                            AppInfo appInfo = addedOrUpdatedApps.get(cn);
+
+                            if (si.isPromise() && mOp == OP_ADD) {
+                                if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
+                                    // Auto install icon
+                                    PackageManager pm = context.getPackageManager();
+                                    ResolveInfo matched = pm.resolveActivity(
+                                            new Intent(Intent.ACTION_MAIN)
+                                                    .setComponent(cn).addCategory(Intent.CATEGORY_LAUNCHER),
+                                            PackageManager.MATCH_DEFAULT_ONLY);
+                                    if (matched == null) {
+                                        // Try to find the best match activity.
+                                        Intent intent = pm.getLaunchIntentForPackage(
+                                                cn.getPackageName());
+                                        if (intent != null) {
+                                            cn = intent.getComponent();
+                                            appInfo = addedOrUpdatedApps.get(cn);
+                                        }
+
+                                        if ((intent == null) || (appInfo == null)) {
+                                            removedShortcuts.add(si);
+                                            continue;
+                                        }
+                                        si.promisedIntent = intent;
+                                    }
+                                }
+
+                                si.intent = si.promisedIntent;
+                                si.promisedIntent = null;
+                                si.status = ShortcutInfo.DEFAULT;
+                                infoUpdated = true;
+                                si.updateIcon(iconCache);
+                            }
+
+                            if (appInfo != null && Intent.ACTION_MAIN.equals(si.intent.getAction())
+                                    && si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
+                                si.updateIcon(iconCache);
+                                si.title = Utilities.trim(appInfo.title);
+                                si.contentDescription = appInfo.contentDescription;
+                                infoUpdated = true;
+                            }
+
+                            int oldDisabledFlags = si.isDisabled;
+                            si.isDisabled = flagOp.apply(si.isDisabled);
+                            if (si.isDisabled != oldDisabledFlags) {
+                                shortcutUpdated = true;
+                            }
+                        }
+
+                        if (infoUpdated || shortcutUpdated) {
+                            updatedShortcuts.add(si);
+                        }
+                        if (infoUpdated) {
+                            LauncherModel.updateItemInDatabase(context, si);
+                        }
+                    } else if (info instanceof LauncherAppWidgetInfo && mOp == OP_ADD) {
+                        LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) info;
+                        if (mUser.equals(widgetInfo.user)
+                                && widgetInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)
+                                && packageSet.contains(widgetInfo.providerName.getPackageName())) {
+                            widgetInfo.restoreStatus &=
+                                    ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY &
+                                            ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
+
+                            // adding this flag ensures that launcher shows 'click to setup'
+                            // if the widget has a config activity. In case there is no config
+                            // activity, it will be marked as 'restored' during bind.
+                            widgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
+
+                            widgets.add(widgetInfo);
+                            LauncherModel.updateItemInDatabase(context, widgetInfo);
+                        }
+                    }
+                }
+            }
+
+            bindUpdatedShortcuts(updatedShortcuts, removedShortcuts, mUser);
+            if (!removedShortcuts.isEmpty()) {
+                LauncherModel.deleteItemsFromDatabase(context, removedShortcuts);
+            }
+
+            if (!widgets.isEmpty()) {
+                scheduleCallbackTask(new CallbackTask() {
+                    @Override
+                    public void execute(Callbacks callbacks) {
+                        callbacks.bindWidgetsRestored(widgets);
+                    }
+                });
+            }
+        }
+
+        final HashSet<String> removedPackages = new HashSet<>();
+        final HashSet<ComponentName> removedComponents = new HashSet<>();
+        if (mOp == OP_REMOVE) {
+            // Mark all packages in the broadcast to be removed
+            Collections.addAll(removedPackages, packages);
+
+            // No need to update the removedComponents as
+            // removedPackages is a super-set of removedComponents
+        } else if (mOp == OP_UPDATE) {
+            // Mark disabled packages in the broadcast to be removed
+            final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
+            for (int i=0; i<N; i++) {
+                if (!launcherApps.isPackageEnabledForProfile(packages[i], mUser)) {
+                    removedPackages.add(packages[i]);
+                }
+            }
+
+            // Update removedComponents as some components can get removed during package update
+            for (AppInfo info : removedApps) {
+                removedComponents.add(info.componentName);
+            }
+        }
+
+        if (!removedPackages.isEmpty() || !removedComponents.isEmpty()) {
+            LauncherModel.deleteItemsFromDatabase(
+                    context, ItemInfoMatcher.ofPackages(removedPackages, mUser));
+            LauncherModel.deleteItemsFromDatabase(
+                    context, ItemInfoMatcher.ofComponents(removedComponents, mUser));
+
+            // Remove any queued items from the install queue
+            InstallShortcutReceiver.removeFromInstallQueue(context, removedPackages, mUser);
+
+            // Call the components-removed callback
+            scheduleCallbackTask(new CallbackTask() {
+                @Override
+                public void execute(Callbacks callbacks) {
+                    callbacks.bindWorkspaceComponentsRemoved(
+                            removedPackages, removedComponents, mUser);
+                }
+            });
+        }
+
+        if (!removedApps.isEmpty()) {
+            // Remove corresponding apps from All-Apps
+            scheduleCallbackTask(new CallbackTask() {
+                @Override
+                public void execute(Callbacks callbacks) {
+                    callbacks.bindAppInfosRemoved(removedApps);
+                }
+            });
+        }
+
+        // Notify launcher of widget update. From marshmallow onwards we use AppWidgetHost to
+        // get widget update signals.
+        if (!Utilities.ATLEAST_MARSHMALLOW &&
+                (mOp == OP_ADD || mOp == OP_REMOVE || mOp == OP_UPDATE)) {
+            scheduleCallbackTask(new CallbackTask() {
+                @Override
+                public void execute(Callbacks callbacks) {
+                    callbacks.notifyWidgetProvidersChanged();
+                }
+            });
+        }
+    }
+}
diff --git a/src/com/android/launcher3/model/SdCardAvailableReceiver.java b/src/com/android/launcher3/model/SdCardAvailableReceiver.java
new file mode 100644
index 0000000..54260c9
--- /dev/null
+++ b/src/com/android/launcher3/model/SdCardAvailableReceiver.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.model;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.util.MultiHashMap;
+import com.android.launcher3.util.PackageManagerHelper;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Map.Entry;
+
+/**
+ * Helper class to re-query app status when SD-card becomes available.
+ *
+ * During first load, just after reboot, some apps on sdcard might not be available immediately due
+ * to some race conditions in the system. We wait for ACTION_BOOT_COMPLETED and process such
+ * apps again.
+ */
+public class SdCardAvailableReceiver extends BroadcastReceiver {
+
+    private final LauncherModel mModel;
+    private final Context mContext;
+    private final MultiHashMap<UserHandleCompat, String> mPackages;
+
+    public SdCardAvailableReceiver(LauncherModel model, Context context,
+            MultiHashMap<UserHandleCompat, String> packages) {
+        mModel = model;
+        mContext = context;
+        mPackages = packages;
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
+        final PackageManager manager = context.getPackageManager();
+        for (Entry<UserHandleCompat, ArrayList<String>> entry : mPackages.entrySet()) {
+            UserHandleCompat user = entry.getKey();
+
+            final ArrayList<String> packagesRemoved = new ArrayList<>();
+            final ArrayList<String> packagesUnavailable = new ArrayList<>();
+
+            for (String pkg : new HashSet<>(entry.getValue())) {
+                if (!launcherApps.isPackageEnabledForProfile(pkg, user)) {
+                    if (PackageManagerHelper.isAppOnSdcard(manager, pkg)) {
+                        packagesUnavailable.add(pkg);
+                    } else {
+                        packagesRemoved.add(pkg);
+                    }
+                }
+            }
+            if (!packagesRemoved.isEmpty()) {
+                mModel.onPackagesRemoved(user,
+                        packagesRemoved.toArray(new String[packagesRemoved.size()]));
+            }
+            if (!packagesUnavailable.isEmpty()) {
+                mModel.onPackagesUnavailable(
+                        packagesUnavailable.toArray(new String[packagesUnavailable.size()]),
+                        user, false);
+            }
+        }
+
+        // Unregister the broadcast receiver, just in case
+        mContext.unregisterReceiver(this);
+    }
+}
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java
new file mode 100644
index 0000000..3314353
--- /dev/null
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.model;
+
+import android.content.Context;
+
+import com.android.launcher3.AllAppsList;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+import com.android.launcher3.util.MultiHashMap;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Handles changes due to shortcut manager updates (deep shortcut changes)
+ */
+public class ShortcutsChangedTask extends ExtendedModelTask {
+
+    private final String mPackageName;
+    private final List<ShortcutInfoCompat> mShortcuts;
+    private final UserHandleCompat mUser;
+    private final boolean mUpdateIdMap;
+
+    public ShortcutsChangedTask(String packageName, List<ShortcutInfoCompat> shortcuts,
+            UserHandleCompat user, boolean updateIdMap) {
+        mPackageName = packageName;
+        mShortcuts = shortcuts;
+        mUser = user;
+        mUpdateIdMap = updateIdMap;
+    }
+
+    @Override
+    public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+        final Context context = app.getContext();
+        DeepShortcutManager deepShortcutManager = DeepShortcutManager.getInstance(context);
+        deepShortcutManager.onShortcutsChanged(mShortcuts);
+
+        // Find ShortcutInfo's that have changed on the workspace.
+        final ArrayList<ShortcutInfo> removedShortcutInfos = new ArrayList<>();
+        MultiHashMap<String, ShortcutInfo> idsToWorkspaceShortcutInfos = new MultiHashMap<>();
+        for (ItemInfo itemInfo : dataModel.itemsIdMap) {
+            if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+                ShortcutInfo si = (ShortcutInfo) itemInfo;
+                if (si.getPromisedIntent().getPackage().equals(mPackageName)
+                        && si.user.equals(mUser)) {
+                    idsToWorkspaceShortcutInfos.addToList(si.getDeepShortcutId(), si);
+                }
+            }
+        }
+
+        final ArrayList<ShortcutInfo> updatedShortcutInfos = new ArrayList<>();
+        if (!idsToWorkspaceShortcutInfos.isEmpty()) {
+            // Update the workspace to reflect the changes to updated shortcuts residing on it.
+            List<ShortcutInfoCompat> shortcuts = deepShortcutManager.queryForFullDetails(
+                    mPackageName, new ArrayList<>(idsToWorkspaceShortcutInfos.keySet()), mUser);
+            for (ShortcutInfoCompat fullDetails : shortcuts) {
+                List<ShortcutInfo> shortcutInfos = idsToWorkspaceShortcutInfos
+                        .remove(fullDetails.getId());
+                if (!fullDetails.isPinned()) {
+                    // The shortcut was previously pinned but is no longer, so remove it from
+                    // the workspace and our pinned shortcut counts.
+                    // Note that we put this check here, after querying for full details,
+                    // because there's a possible race condition between pinning and
+                    // receiving this callback.
+                    removedShortcutInfos.addAll(shortcutInfos);
+                    continue;
+                }
+                for (ShortcutInfo shortcutInfo : shortcutInfos) {
+                    shortcutInfo.updateFromDeepShortcutInfo(fullDetails, context);
+                    updatedShortcutInfos.add(shortcutInfo);
+                }
+            }
+        }
+
+        // If there are still entries in idsToWorkspaceShortcutInfos, that means that
+        // the corresponding shortcuts weren't passed in onShortcutsChanged(). This
+        // means they were cleared, so we remove and unpin them now.
+        for (String id : idsToWorkspaceShortcutInfos.keySet()) {
+            removedShortcutInfos.addAll(idsToWorkspaceShortcutInfos.get(id));
+        }
+
+        bindUpdatedShortcuts(updatedShortcutInfos, removedShortcutInfos, mUser);
+        if (!removedShortcutInfos.isEmpty()) {
+            LauncherModel.deleteItemsFromDatabase(context, removedShortcutInfos);
+        }
+
+        if (mUpdateIdMap) {
+            // Update the deep shortcut map if the list of ids has changed for an activity.
+            dataModel.updateDeepShortcutMap(mPackageName, mUser, mShortcuts);
+            bindDeepShortcuts(dataModel);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java
new file mode 100644
index 0000000..a89fe0b
--- /dev/null
+++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.model;
+
+import android.content.Context;
+
+import com.android.launcher3.AllAppsList;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.util.ComponentKey;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Task to handle changing of lock state of the user
+ */
+public class UserLockStateChangedTask extends ExtendedModelTask {
+
+    private final UserHandleCompat mUser;
+
+    public UserLockStateChangedTask(UserHandleCompat user) {
+        mUser = user;
+    }
+
+    @Override
+    public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+        Context context = app.getContext();
+        boolean isUserUnlocked = UserManagerCompat.getInstance(context).isUserUnlocked(mUser);
+        DeepShortcutManager deepShortcutManager = DeepShortcutManager.getInstance(context);
+
+        HashMap<ShortcutKey, ShortcutInfoCompat> pinnedShortcuts = new HashMap<>();
+        if (isUserUnlocked) {
+            List<ShortcutInfoCompat> shortcuts =
+                    deepShortcutManager.queryForPinnedShortcuts(null, mUser);
+            if (deepShortcutManager.wasLastCallSuccess()) {
+                for (ShortcutInfoCompat shortcut : shortcuts) {
+                    pinnedShortcuts.put(ShortcutKey.fromInfo(shortcut), shortcut);
+                }
+            } else {
+                // Shortcut manager can fail due to some race condition when the lock state
+                // changes too frequently. For the purpose of the update,
+                // consider it as still locked.
+                isUserUnlocked = false;
+            }
+        }
+
+        // Update the workspace to reflect the changes to updated shortcuts residing on it.
+        ArrayList<ShortcutInfo> updatedShortcutInfos = new ArrayList<>();
+        ArrayList<ShortcutInfo> deletedShortcutInfos = new ArrayList<>();
+        for (ItemInfo itemInfo : dataModel.itemsIdMap) {
+            if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
+                    && mUser.equals(itemInfo.user)) {
+                ShortcutInfo si = (ShortcutInfo) itemInfo;
+                if (isUserUnlocked) {
+                    ShortcutInfoCompat shortcut =
+                            pinnedShortcuts.get(ShortcutKey.fromShortcutInfo(si));
+                    // We couldn't verify the shortcut during loader. If its no longer available
+                    // (probably due to clear data), delete the workspace item as well
+                    if (shortcut == null) {
+                        deletedShortcutInfos.add(si);
+                        continue;
+                    }
+                    si.isDisabled &= ~ShortcutInfo.FLAG_DISABLED_LOCKED_USER;
+                    si.updateFromDeepShortcutInfo(shortcut, context);
+                } else {
+                    si.isDisabled |= ShortcutInfo.FLAG_DISABLED_LOCKED_USER;
+                }
+                updatedShortcutInfos.add(si);
+            }
+        }
+        bindUpdatedShortcuts(updatedShortcutInfos, deletedShortcutInfos, mUser);
+        if (!deletedShortcutInfos.isEmpty()) {
+            LauncherModel.deleteItemsFromDatabase(context, deletedShortcutInfos);
+        }
+
+        // Remove shortcut id map for that user
+        Iterator<ComponentKey> keysIter = dataModel.deepShortcutMap.keySet().iterator();
+        while (keysIter.hasNext()) {
+            if (keysIter.next().user.equals(mUser)) {
+                keysIter.remove();
+            }
+        }
+
+        if (isUserUnlocked) {
+            dataModel.updateDeepShortcutMap(
+                    null, mUser, deepShortcutManager.queryForAllShortcuts(mUser));
+        }
+        bindDeepShortcuts(dataModel);
+    }
+}
diff --git a/src/com/android/launcher3/model/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java
index b2a94bb..64043f4 100644
--- a/src/com/android/launcher3/model/WidgetsModel.java
+++ b/src/com/android/launcher3/model/WidgetsModel.java
@@ -6,26 +6,22 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
-import android.os.DeadObjectException;
-import android.os.TransactionTooLargeException;
 import android.util.Log;
 
 import com.android.launcher3.AppFilter;
 import com.android.launcher3.IconCache;
 import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.compat.AlphabeticIndexCompat;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
+import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.Preconditions;
 
 import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
 import java.util.HashMap;
-import java.util.List;
 
 /**
  * Widgets data model that is used by the adapters of the widget views and controllers.
@@ -37,74 +33,31 @@
     private static final String TAG = "WidgetsModel";
     private static final boolean DEBUG = false;
 
-    /* List of packages that is tracked by this model. */
-    private final ArrayList<PackageItemInfo> mPackageItemInfos;
-
     /* Map of widgets and shortcuts that are tracked per package. */
-    private final HashMap<PackageItemInfo, ArrayList<WidgetItem>> mWidgetsList;
+    private final MultiHashMap<PackageItemInfo, WidgetItem> mWidgetsList;
 
-    private final AppWidgetManagerCompat mAppWidgetMgr;
-    private final Comparator<ItemInfo> mAppNameComparator;
     private final IconCache mIconCache;
     private final AppFilter mAppFilter;
-    private final AlphabeticIndexCompat mIndexer;
 
-    private ArrayList<WidgetItem> mRawList;
-
-    public WidgetsModel(Context context,  IconCache iconCache, AppFilter appFilter) {
-        mAppWidgetMgr = AppWidgetManagerCompat.getInstance(context);
-        mAppNameComparator = (new AppNameComparator(context)).getAppInfoComparator();
+    public WidgetsModel(IconCache iconCache, AppFilter appFilter) {
         mIconCache = iconCache;
         mAppFilter = appFilter;
-        mIndexer = new AlphabeticIndexCompat(context);
-        mPackageItemInfos = new ArrayList<>();
-        mWidgetsList = new HashMap<>();
-
-        mRawList = new ArrayList<>();
+        mWidgetsList = new MultiHashMap<>();
     }
 
-    @SuppressWarnings("unchecked")
-    private WidgetsModel(WidgetsModel model) {
-        mAppWidgetMgr = model.mAppWidgetMgr;
-        mPackageItemInfos = (ArrayList<PackageItemInfo>) model.mPackageItemInfos.clone();
-        mWidgetsList = (HashMap<PackageItemInfo, ArrayList<WidgetItem>>) model.mWidgetsList.clone();
-        mAppNameComparator = model.mAppNameComparator;
-        mIconCache = model.mIconCache;
-        mAppFilter = model.mAppFilter;
-        mIndexer = model.mIndexer;
-        mRawList = (ArrayList<WidgetItem>) model.mRawList.clone();
-    }
-
-    // Access methods that may be deleted if the private fields are made package-private.
-    public int getPackageSize() {
-        return mPackageItemInfos.size();
-    }
-
-    // Access methods that may be deleted if the private fields are made package-private.
-    public PackageItemInfo getPackageItemInfo(int pos) {
-        if (pos >= mPackageItemInfos.size() || pos < 0) {
-            return null;
-        }
-        return mPackageItemInfos.get(pos);
-    }
-
-    public List<WidgetItem> getSortedWidgets(int pos) {
-        return mWidgetsList.get(mPackageItemInfos.get(pos));
-    }
-
-    public ArrayList<WidgetItem> getRawList() {
-        return mRawList;
+    public MultiHashMap<PackageItemInfo, WidgetItem> getWidgetsMap() {
+        return mWidgetsList;
     }
 
     public boolean isEmpty() {
-        return mRawList.isEmpty();
+        return mWidgetsList.isEmpty();
     }
 
-    public WidgetsModel updateAndClone(Context context) {
+    public ArrayList<WidgetItem> update(Context context) {
         Preconditions.assertWorkerThread();
 
+        final ArrayList<WidgetItem> widgetsAndShortcuts = new ArrayList<>();
         try {
-            final ArrayList<WidgetItem> widgetsAndShortcuts = new ArrayList<>();
             // Widgets
             AppWidgetManagerCompat widgetManager = AppWidgetManagerCompat.getInstance(context);
             for (AppWidgetProviderInfo widgetInfo : widgetManager.getAllProviders()) {
@@ -121,9 +74,7 @@
             }
             setWidgetsAndShortcuts(widgetsAndShortcuts);
         } catch (Exception e) {
-            if (!ProviderConfig.IS_DOGFOOD_BUILD &&
-                    (e.getCause() instanceof TransactionTooLargeException ||
-                            e.getCause() instanceof DeadObjectException)) {
+            if (!ProviderConfig.IS_DOGFOOD_BUILD && Utilities.isBinderSizeError(e)) {
                 // the returned value may be incomplete and will not be refreshed until the next
                 // time Launcher starts.
                 // TODO: after figuring out a repro step, introduce a dirty bit to check when
@@ -132,11 +83,10 @@
                 throw e;
             }
         }
-        return clone();
+        return widgetsAndShortcuts;
     }
 
     private void setWidgetsAndShortcuts(ArrayList<WidgetItem> rawWidgetsShortcuts) {
-        mRawList = rawWidgetsShortcuts;
         if (DEBUG) {
             Log.d(TAG, "addWidgetsAndShortcuts, widgetsShortcuts#=" + rawWidgetsShortcuts.size());
         }
@@ -147,9 +97,9 @@
 
         // clear the lists.
         mWidgetsList.clear();
-        mPackageItemInfos.clear();
 
         InvariantDeviceProfile idp = LauncherAppState.getInstance().getInvariantDeviceProfile();
+        UserHandleCompat myUser = UserHandleCompat.myUserHandle();
 
         // add and update.
         for (WidgetItem item: rawWidgetsShortcuts) {
@@ -177,43 +127,20 @@
 
             String packageName = item.componentName.getPackageName();
             PackageItemInfo pInfo = tmpPackageItemInfos.get(packageName);
-            ArrayList<WidgetItem> widgetsShortcutsList = mWidgetsList.get(pInfo);
-
-            if (widgetsShortcutsList == null) {
-                widgetsShortcutsList = new ArrayList<>();
-
+            if (pInfo == null) {
                 pInfo = new PackageItemInfo(packageName);
+                pInfo.user = item.user;
                 tmpPackageItemInfos.put(packageName,  pInfo);
-
-                mPackageItemInfos.add(pInfo);
-                mWidgetsList.put(pInfo, widgetsShortcutsList);
+            } else if (!myUser.equals(pInfo.user)) {
+                // Keep updating the user, until we get the primary user.
+                pInfo.user = item.user;
             }
-
-            widgetsShortcutsList.add(item);
+            mWidgetsList.addToList(pInfo, item);
         }
 
         // Update each package entry
-        for (PackageItemInfo p : mPackageItemInfos) {
-            ArrayList<WidgetItem> widgetsShortcutsList = mWidgetsList.get(p);
-            Collections.sort(widgetsShortcutsList);
-
-            // Update the package entry based on the first item.
-            p.user = widgetsShortcutsList.get(0).user;
+        for (PackageItemInfo p : tmpPackageItemInfos.values()) {
             mIconCache.getTitleAndIconForApp(p, true /* userLowResIcon */);
-            p.titleSectionName = mIndexer.computeSectionName(p.title);
         }
-
-        // sort the package entries.
-        Collections.sort(mPackageItemInfos, mAppNameComparator);
-    }
-
-    /**
-     * Create a snapshot of the widgets model.
-     * <p>
-     * Usage case: view binding without being modified from package updates.
-     */
-    @Override
-    public WidgetsModel clone(){
-        return new WidgetsModel(this);
     }
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
index fb9d2f7..12a6701 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
@@ -80,7 +80,7 @@
         @Override
         public void onAnimationEnd(Animator animation) {
             mAnimator = null;
-            animateToPostion(mFinalPosition);
+            animateToPosition(mFinalPosition);
         }
     };
 
@@ -136,22 +136,25 @@
                 currentScroll = totalScroll - currentScroll;
             }
             int scrollPerPage = totalScroll / (mNumPages - 1);
-            int absScroll = mActivePage * scrollPerPage;
-            float scrollThreshold = SHIFT_THRESHOLD * scrollPerPage;
+            int pageToLeft = currentScroll / scrollPerPage;
+            int pageToLeftScroll = pageToLeft * scrollPerPage;
+            int pageToRightScroll = pageToLeftScroll + scrollPerPage;
 
-            if ((absScroll - currentScroll) > scrollThreshold) {
-                // current scroll is before absolute scroll
-                animateToPostion(mActivePage - SHIFT_PER_ANIMATION);
-            } else if ((currentScroll - absScroll) > scrollThreshold) {
-                // current scroll is ahead of absolute scroll
-                animateToPostion(mActivePage + SHIFT_PER_ANIMATION);
+            float scrollThreshold = SHIFT_THRESHOLD * scrollPerPage;
+            if (currentScroll < pageToLeftScroll + scrollThreshold) {
+                // scroll is within the left page's threshold
+                animateToPosition(pageToLeft);
+            } else if (currentScroll > pageToRightScroll - scrollThreshold) {
+                // scroll is far enough from left page to go to the right page
+                animateToPosition(pageToLeft + 1);
             } else {
-                animateToPostion(mActivePage);
+                // scroll is between left and right page
+                animateToPosition(pageToLeft + SHIFT_PER_ANIMATION);
             }
         }
     }
 
-    private void animateToPostion(float position) {
+    private void animateToPosition(float position) {
         mFinalPosition = position;
         if (Math.abs(mCurrentPosition - mFinalPosition) < SHIFT_THRESHOLD) {
             mCurrentPosition = mFinalPosition;
diff --git a/src/com/android/launcher3/provider/LauncherDbUtils.java b/src/com/android/launcher3/provider/LauncherDbUtils.java
index faa5fad..2f82419 100644
--- a/src/com/android/launcher3/provider/LauncherDbUtils.java
+++ b/src/com/android/launcher3/provider/LauncherDbUtils.java
@@ -56,7 +56,7 @@
             if (screenIds.get(0) != 0) {
                 // First screen is not 0, we need to rename screens
                 if (screenIds.indexOf(0L) > -1) {
-                    // There is already a screen 0. First rename it to a differen screen.
+                    // There is already a screen 0. First rename it to a different screen.
                     long newScreenId = 1;
                     while (screenIds.indexOf(newScreenId) > -1) newScreenId++;
                     renameScreen(db, 0, newScreenId);
diff --git a/src/com/android/launcher3/QsbBlockerView.java b/src/com/android/launcher3/qsb/QsbBlockerView.java
similarity index 91%
rename from src/com/android/launcher3/QsbBlockerView.java
rename to src/com/android/launcher3/qsb/QsbBlockerView.java
index 6a2bce0..5379336 100644
--- a/src/com/android/launcher3/QsbBlockerView.java
+++ b/src/com/android/launcher3/qsb/QsbBlockerView.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.launcher3;
+package com.android.launcher3.qsb;
 
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
@@ -27,12 +27,15 @@
 import android.util.AttributeSet;
 import android.view.View;
 
+import com.android.launcher3.Launcher;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.Workspace.OnStateChangeListener;
 import com.android.launcher3.Workspace.State;
 
 /**
  * A simple view used to show the region blocked by QSB during drag and drop.
  */
-public class QsbBlockerView extends View implements Workspace.OnStateChangeListener {
+public class QsbBlockerView extends View implements OnStateChangeListener {
 
     private static final int VISIBLE_ALPHA = 100;
 
diff --git a/src/com/android/launcher3/QsbContainerView.java b/src/com/android/launcher3/qsb/QsbContainerView.java
similarity index 61%
rename from src/com/android/launcher3/QsbContainerView.java
rename to src/com/android/launcher3/qsb/QsbContainerView.java
index 02d8a13..c83143b 100644
--- a/src/com/android/launcher3/QsbContainerView.java
+++ b/src/com/android/launcher3/qsb/QsbContainerView.java
@@ -14,19 +14,18 @@
  * limitations under the License.
  */
 
-package com.android.launcher3;
+package com.android.launcher3.qsb;
 
 import android.app.Activity;
 import android.app.Fragment;
 import android.app.SearchManager;
+import android.appwidget.AppWidgetHost;
+import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetProviderInfo;
-import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.SharedPreferences;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.util.AttributeSet;
@@ -35,6 +34,11 @@
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
 
+import com.android.launcher3.AppWidgetResizeFrame;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 
 /**
@@ -68,25 +72,14 @@
         private static final int REQUEST_BIND_QSB = 1;
         private static final String QSB_WIDGET_ID = "qsb_widget_id";
 
-        private static int sSavedWidgetId = -1;
-
+        private QsbWidgetHost mQsbWidgetHost;
         private AppWidgetProviderInfo mWidgetInfo;
-        private LauncherAppWidgetHostView mQsb;
-
-        private BroadcastReceiver mRebindReceiver = new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                rebindFragment();
-            }
-        };
+        private QsbWidgetHostView mQsb;
 
         @Override
         public void onCreate(Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
-
-            IntentFilter filter = new IntentFilter(Launcher.ACTION_APPWIDGET_HOST_RESET);
-            filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
-            getActivity().registerReceiver(mRebindReceiver, filter);
+            mQsbWidgetHost = new QsbWidgetHost(getActivity());
         }
 
         private FrameLayout mWrapper;
@@ -95,108 +88,96 @@
         public View onCreateView(
                 LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
 
-            if (savedInstanceState != null) {
-                sSavedWidgetId = savedInstanceState.getInt(QSB_WIDGET_ID, -1);
-            }
             mWrapper = new FrameLayout(getActivity());
-            mWrapper.addView(createQsb(inflater, mWrapper));
+            mWrapper.addView(createQsb(mWrapper));
             return mWrapper;
         }
 
-        private View createQsb(LayoutInflater inflater, ViewGroup container) {
-            Launcher launcher = Launcher.getLauncher(getActivity());
-            mWidgetInfo = getSearchWidgetProvider(launcher);
+        private View createQsb(ViewGroup container) {
+            Activity activity = getActivity();
+            mWidgetInfo = getSearchWidgetProvider(activity);
             if (mWidgetInfo == null) {
                 // There is no search provider, just show the default widget.
-                return getDefaultView(inflater, container, false);
+                return QsbWidgetHostView.getDefaultView(container);
             }
 
-            SharedPreferences prefs = Utilities.getPrefs(launcher);
-            AppWidgetManagerCompat widgetManager = AppWidgetManagerCompat.getInstance(launcher);
-            LauncherAppWidgetHost widgetHost = launcher.getAppWidgetHost();
+            AppWidgetManagerCompat widgetManager = AppWidgetManagerCompat.getInstance(activity);
             InvariantDeviceProfile idp = LauncherAppState.getInstance().getInvariantDeviceProfile();
 
             Bundle opts = new Bundle();
-            Rect size = AppWidgetResizeFrame.getWidgetSizeRanges(launcher, idp.numColumns, 1, null);
+            Rect size = AppWidgetResizeFrame.getWidgetSizeRanges(activity, idp.numColumns, 1, null);
             opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, size.left);
             opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, size.top);
             opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, size.right);
             opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, size.bottom);
 
-            int widgetId = prefs.getInt(QSB_WIDGET_ID, -1);
+            int widgetId = Utilities.getPrefs(activity).getInt(QSB_WIDGET_ID, -1);
             AppWidgetProviderInfo widgetInfo = widgetManager.getAppWidgetInfo(widgetId);
             boolean isWidgetBound = (widgetInfo != null) &&
                     widgetInfo.provider.equals(mWidgetInfo.provider);
 
+            int oldWidgetId = widgetId;
             if (!isWidgetBound) {
-                // widgetId is already bound and its not the correct provider.
-                // Delete the widget id.
                 if (widgetId > -1) {
-                    widgetHost.deleteAppWidgetId(widgetId);
+                    // widgetId is already bound and its not the correct provider. reset host.
+                    mQsbWidgetHost.deleteHost();
+                }
+
+                widgetId = mQsbWidgetHost.allocateAppWidgetId();
+                isWidgetBound = widgetManager.bindAppWidgetIdIfAllowed(widgetId, mWidgetInfo, opts);
+                if (!isWidgetBound) {
+                    mQsbWidgetHost.deleteAppWidgetId(widgetId);
                     widgetId = -1;
                 }
 
-                widgetId = widgetHost.allocateAppWidgetId();
-                isWidgetBound = widgetManager.bindAppWidgetIdIfAllowed(widgetId, mWidgetInfo, opts);
-                if (!isWidgetBound) {
-                    widgetHost.deleteAppWidgetId(widgetId);
-                    widgetId = -1;
+                if (oldWidgetId != widgetId) {
+                    saveWidgetId(widgetId);
                 }
             }
 
             if (isWidgetBound) {
-                mQsb = (LauncherAppWidgetHostView)
-                        widgetHost.createView(launcher, widgetId, mWidgetInfo);
+                mQsb = (QsbWidgetHostView) mQsbWidgetHost.createView(activity, widgetId, mWidgetInfo);
                 mQsb.setId(R.id.qsb_widget);
-                mQsb.mErrorViewId = R.layout.qsb_default_view;
 
-                if (!Utilities.containsAll(AppWidgetManager.getInstance(launcher)
+                if (!Utilities.containsAll(AppWidgetManager.getInstance(activity)
                         .getAppWidgetOptions(widgetId), opts)) {
                     mQsb.updateAppWidgetOptions(opts);
                 }
                 mQsb.setPadding(0, 0, 0, 0);
+                mQsbWidgetHost.startListening();
                 return mQsb;
             }
 
             // Return a default widget with setup icon.
-            return getDefaultView(inflater, container, true);
+            View v = QsbWidgetHostView.getDefaultView(container);
+            View setupButton = v.findViewById(R.id.btn_qsb_setup);
+            setupButton.setVisibility(View.VISIBLE);
+            setupButton.setOnClickListener(this);
+            return v;
+        }
+
+        private void saveWidgetId(int widgetId) {
+            Utilities.getPrefs(getActivity()).edit().putInt(QSB_WIDGET_ID, widgetId).apply();
         }
 
         @Override
         public void onClick(View view) {
-            if (view.getId() == R.id.btn_qsb_search) {
-                getActivity().startSearch("", false, null, true);
-            } else if (view.getId() == R.id.btn_qsb_setup) {
-                // Allocate a new widget id for QSB
-                sSavedWidgetId = Launcher.getLauncher(getActivity())
-                        .getAppWidgetHost().allocateAppWidgetId();
-                // Start intent for bind the widget
-                Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
-                intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, sSavedWidgetId);
-                intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, mWidgetInfo.provider);
-                startActivityForResult(intent, REQUEST_BIND_QSB);
-            }
-        }
-
-        @Override
-        public void onSaveInstanceState(Bundle outState) {
-            super.onSaveInstanceState(outState);
-            outState.putInt(QSB_WIDGET_ID, sSavedWidgetId);
+            // Start intent for bind the widget
+            Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
+            // Allocate a new widget id for QSB
+            intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mQsbWidgetHost.allocateAppWidgetId());
+            intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, mWidgetInfo.provider);
+            startActivityForResult(intent, REQUEST_BIND_QSB);
         }
 
         @Override
         public void onActivityResult(int requestCode, int resultCode, Intent data) {
             if (requestCode == REQUEST_BIND_QSB) {
                 if (resultCode == Activity.RESULT_OK) {
-                    int widgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
-                            sSavedWidgetId);
-                    Utilities.getPrefs(getActivity()).edit().putInt(QSB_WIDGET_ID, widgetId).apply();
-                    sSavedWidgetId = -1;
+                    saveWidgetId(data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1));
                     rebindFragment();
-                } else if (sSavedWidgetId != -1) {
-                    Launcher.getLauncher(getActivity()).getAppWidgetHost()
-                            .deleteAppWidgetId(sSavedWidgetId);
-                    sSavedWidgetId = -1;
+                } else {
+                    mQsbWidgetHost.deleteHost();
                 }
             }
         }
@@ -211,27 +192,16 @@
 
         @Override
         public void onDestroy() {
-            getActivity().unregisterReceiver(mRebindReceiver);
+            mQsbWidgetHost.stopListening();
             super.onDestroy();
         }
 
         private void rebindFragment() {
             if (mWrapper != null && getActivity() != null) {
                 mWrapper.removeAllViews();
-                mWrapper.addView(createQsb(getActivity().getLayoutInflater(), mWrapper));
+                mWrapper.addView(createQsb(mWrapper));
             }
         }
-
-        private View getDefaultView(LayoutInflater inflater, ViewGroup parent, boolean showSetup) {
-            View v = inflater.inflate(R.layout.qsb_default_view, parent, false);
-            if (showSetup) {
-                View setupButton = v.findViewById(R.id.btn_qsb_setup);
-                setupButton.setVisibility(View.VISIBLE);
-                setupButton.setOnClickListener(this);
-            }
-            v.findViewById(R.id.btn_qsb_search).setOnClickListener(this);
-            return v;
-        }
     }
 
     /**
@@ -261,4 +231,19 @@
         }
         return defaultWidgetForSearchPackage;
     }
+
+    private static class QsbWidgetHost extends AppWidgetHost {
+
+        private static final int QSB_WIDGET_HOST_ID = 1026;
+
+        public QsbWidgetHost(Context context) {
+            super(context, QSB_WIDGET_HOST_ID);
+        }
+
+        @Override
+        protected AppWidgetHostView onCreateView(
+                Context context, int appWidgetId, AppWidgetProviderInfo appWidget) {
+            return new QsbWidgetHostView(context);
+        }
+    }
 }
diff --git a/src/com/android/launcher3/qsb/QsbWidgetHostView.java b/src/com/android/launcher3/qsb/QsbWidgetHostView.java
new file mode 100644
index 0000000..8b6fa16
--- /dev/null
+++ b/src/com/android/launcher3/qsb/QsbWidgetHostView.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.qsb;
+
+import android.appwidget.AppWidgetHostView;
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewDebug;
+import android.view.ViewGroup;
+import android.widget.RemoteViews;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+
+/**
+ * Appwidget host view with QSB specific logic.
+ */
+public class QsbWidgetHostView extends AppWidgetHostView {
+
+    @ViewDebug.ExportedProperty(category = "launcher")
+    private int mPreviousOrientation;
+
+    public QsbWidgetHostView(Context context) {
+        super(context);
+    }
+
+    @Override
+    public void updateAppWidget(RemoteViews remoteViews) {
+        // Store the orientation in which the widget was inflated
+        mPreviousOrientation = getResources().getConfiguration().orientation;
+        super.updateAppWidget(remoteViews);
+    }
+
+
+    public boolean isReinflateRequired() {
+        // Re-inflate is required if the orientation has changed since last inflation.
+        return mPreviousOrientation != getResources().getConfiguration().orientation;
+    }
+
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        try {
+            super.onLayout(changed, left, top, right, bottom);
+        } catch (final RuntimeException e) {
+            post(new Runnable() {
+                @Override
+                public void run() {
+                    // Update the widget with 0 Layout id, to reset the view to error view.
+                    updateAppWidget(new RemoteViews(getAppWidgetInfo().provider.getPackageName(), 0));
+                }
+            });
+        }
+    }
+
+    @Override
+    protected View getErrorView() {
+        return getDefaultView(this);
+    }
+
+    public static View getDefaultView(ViewGroup parent) {
+        View v = LayoutInflater.from(parent.getContext())
+                .inflate(R.layout.qsb_default_view, parent, false);
+        v.findViewById(R.id.btn_qsb_search).setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                Launcher.getLauncher(view.getContext()).startSearch("", false, null, true);
+            }
+        });
+        return v;
+    }
+}
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutManager.java b/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
index 49d6fa9..41f1a47 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
@@ -42,18 +42,25 @@
 public class DeepShortcutManager {
     private static final String TAG = "DeepShortcutManager";
 
-    // TODO: Replace this with platform constants when the new sdk is available.
-    public static final int FLAG_MATCH_DYNAMIC = 1 << 0;
-    public static final int FLAG_MATCH_MANIFEST = 1 << 3;
-    public static final int FLAG_MATCH_PINNED = 1 << 1;
+    private static final int FLAG_GET_ALL = ShortcutQuery.FLAG_MATCH_DYNAMIC
+            | ShortcutQuery.FLAG_MATCH_MANIFEST | ShortcutQuery.FLAG_MATCH_PINNED;
 
-    private static final int FLAG_GET_ALL =
-            FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST;
+    private static DeepShortcutManager sInstance;
+    private static final Object sInstanceLock = new Object();
+
+    public static DeepShortcutManager getInstance(Context context) {
+        synchronized (sInstanceLock) {
+            if (sInstance == null) {
+                sInstance = new DeepShortcutManager(context.getApplicationContext());
+            }
+            return sInstance;
+        }
+    }
 
     private final LauncherApps mLauncherApps;
     private boolean mWasLastCallSuccess;
 
-    public DeepShortcutManager(Context context, ShortcutCache shortcutCache) {
+    private DeepShortcutManager(Context context) {
         mLauncherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
     }
 
@@ -86,7 +93,7 @@
      */
     public List<ShortcutInfoCompat> queryForShortcutsContainer(ComponentName activity,
             List<String> ids, UserHandleCompat user) {
-        return query(FLAG_MATCH_MANIFEST | FLAG_MATCH_DYNAMIC,
+        return query(ShortcutQuery.FLAG_MATCH_MANIFEST | ShortcutQuery.FLAG_MATCH_DYNAMIC,
                 activity.getPackageName(), activity, ids, user);
     }
 
@@ -96,7 +103,7 @@
      */
     @TargetApi(25)
     public void unpinShortcut(final ShortcutKey key) {
-        if (Utilities.isNycMR1OrAbove()) {
+        if (Utilities.ATLEAST_NOUGAT_MR1) {
             String packageName = key.componentName.getPackageName();
             String id = key.getId();
             UserHandleCompat user = key.user;
@@ -118,7 +125,7 @@
      */
     @TargetApi(25)
     public void pinShortcut(final ShortcutKey key) {
-        if (Utilities.isNycMR1OrAbove()) {
+        if (Utilities.ATLEAST_NOUGAT_MR1) {
             String packageName = key.componentName.getPackageName();
             String id = key.getId();
             UserHandleCompat user = key.user;
@@ -137,7 +144,7 @@
     @TargetApi(25)
     public void startShortcut(String packageName, String id, Rect sourceBounds,
           Bundle startActivityOptions, UserHandleCompat user) {
-        if (Utilities.isNycMR1OrAbove()) {
+        if (Utilities.ATLEAST_NOUGAT_MR1) {
             try {
                 mLauncherApps.startShortcut(packageName, id, sourceBounds,
                         startActivityOptions, user.getUser());
@@ -151,7 +158,7 @@
 
     @TargetApi(25)
     public Drawable getShortcutIconDrawable(ShortcutInfoCompat shortcutInfo, int density) {
-        if (Utilities.isNycMR1OrAbove()) {
+        if (Utilities.ATLEAST_NOUGAT_MR1) {
             try {
                 Drawable icon = mLauncherApps.getShortcutIconDrawable(
                         shortcutInfo.getShortcutInfo(), density);
@@ -172,7 +179,7 @@
      */
     public List<ShortcutInfoCompat> queryForPinnedShortcuts(String packageName,
             UserHandleCompat user) {
-        return query(FLAG_MATCH_PINNED, packageName, null, null, user);
+        return query(ShortcutQuery.FLAG_MATCH_PINNED, packageName, null, null, user);
     }
 
     public List<ShortcutInfoCompat> queryForAllShortcuts(UserHandleCompat user) {
@@ -196,7 +203,7 @@
     @TargetApi(25)
     private List<ShortcutInfoCompat> query(int flags, String packageName,
             ComponentName activity, List<String> shortcutIds, UserHandleCompat user) {
-        if (Utilities.isNycMR1OrAbove()) {
+        if (Utilities.ATLEAST_NOUGAT_MR1) {
             ShortcutQuery q = new ShortcutQuery();
             q.setQueryFlags(flags);
             if (packageName != null) {
@@ -227,7 +234,7 @@
 
     @TargetApi(25)
     public boolean hasHostPermission() {
-        if (Utilities.isNycMR1OrAbove()) {
+        if (Utilities.ATLEAST_NOUGAT_MR1) {
             try {
                 return mLauncherApps.hasShortcutHostPermission();
             } catch (SecurityException|IllegalStateException e) {
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java b/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
index 2702d4e..314a862 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
@@ -20,6 +20,7 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.TimeInterpolator;
+import android.annotation.SuppressLint;
 import android.annotation.TargetApi;
 import android.content.ComponentName;
 import android.content.Context;
@@ -27,6 +28,7 @@
 import android.graphics.Bitmap;
 import android.graphics.Color;
 import android.graphics.Point;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.drawable.ShapeDrawable;
 import android.os.Build;
@@ -37,10 +39,12 @@
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewConfiguration;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.DecelerateInterpolator;
 import android.widget.LinearLayout;
 
+import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.DragSource;
 import com.android.launcher3.DropTarget;
@@ -48,7 +52,6 @@
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAnimUtils;
-import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherViewPropertyAnimator;
@@ -73,9 +76,9 @@
  * A container for shortcuts to deep links within apps.
  */
 @TargetApi(Build.VERSION_CODES.N)
-public class DeepShortcutsContainer extends LinearLayout implements View.OnLongClickListener,
+public class DeepShortcutsContainer extends AbstractFloatingView
+        implements View.OnLongClickListener,
         View.OnTouchListener, DragSource, DragController.DragListener {
-    private static final String TAG = "ShortcutsContainer";
 
     private final Point mIconShift = new Point();
 
@@ -85,8 +88,9 @@
     private final ShortcutMenuAccessibilityDelegate mAccessibilityDelegate;
     private final boolean mIsRtl;
 
-    private BubbleTextView mDeferredDragIcon;
+    private BubbleTextView mOriginalIcon;
     private final Rect mTempRect = new Rect();
+    private PointF mInterceptTouchDown = new PointF();
     private Point mIconLastTouchPos = new Point();
     private boolean mIsLeftAligned;
     private boolean mIsAboveIcon;
@@ -94,12 +98,11 @@
 
     private Animator mOpenCloseAnimator;
     private boolean mDeferContainerRemoval;
-    private boolean mIsOpen;
 
     public DeepShortcutsContainer(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
         mLauncher = Launcher.getLauncher(context);
-        mDeepShortcutsManager = LauncherAppState.getInstance().getShortcutManager();
+        mDeepShortcutsManager = DeepShortcutManager.getInstance(context);
 
         mStartDragThreshold = getResources().getDimensionPixelSize(
                 R.dimen.deep_shortcuts_start_drag_threshold);
@@ -150,7 +153,8 @@
 
         animateOpen();
 
-        deferDrag(originalIcon);
+        mOriginalIcon = originalIcon;
+        mLauncher.getDragController().addDragListener(this);
 
         // Load the shortcuts on a background thread and update the container as it animates.
         final Looper workerLooper = LauncherModel.getWorkerLooper();
@@ -375,13 +379,9 @@
         return arrowView;
     }
 
-    private void deferDrag(BubbleTextView originalIcon) {
-        mDeferredDragIcon = originalIcon;
-        mLauncher.getDragController().addDragListener(this);
-    }
-
-    public BubbleTextView getDeferredDragIcon() {
-        return mDeferredDragIcon;
+    @Override
+    public View getExtendedTouchView() {
+        return mOriginalIcon;
     }
 
     /**
@@ -390,36 +390,52 @@
      * Current behavior:
      * - Start the drag if the touch passes a certain distance from the original touch down.
      */
-    public DragOptions.DeferDragCondition createDeferDragCondition(final Runnable onDragStart) {
-        return new DragOptions.DeferDragCondition() {
+    public DragOptions.PreDragCondition createPreDragCondition() {
+        return new DragOptions.PreDragCondition() {
             @Override
-            public boolean shouldStartDeferredDrag(double distanceDragged) {
+            public boolean shouldStartDrag(double distanceDragged) {
                 return distanceDragged > mStartDragThreshold;
             }
 
             @Override
-            public void onDeferredDragStart() {
-                mDeferredDragIcon.setVisibility(INVISIBLE);
+            public void onPreDragStart() {
+                mOriginalIcon.setVisibility(INVISIBLE);
             }
 
             @Override
-            public void onDropBeforeDeferredDrag() {
-                mLauncher.getUserEventDispatcher().logDeepShortcutsOpen(mDeferredDragIcon);
-                if (!mIsAboveIcon) {
-                    mDeferredDragIcon.setTextVisibility(false);
-                }
-            }
-
-            @Override
-            public void onDragStart() {
-                if (onDragStart != null) {
-                    onDragStart.run();
+            public void onPreDragEnd(boolean dragStarted) {
+                if (!dragStarted) {
+                    mOriginalIcon.setVisibility(VISIBLE);
+                    mLauncher.getUserEventDispatcher().logDeepShortcutsOpen(mOriginalIcon);
+                    if (!mIsAboveIcon) {
+                        mOriginalIcon.setTextVisibility(false);
+                    }
                 }
             }
         };
     }
 
     @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            mInterceptTouchDown.set(ev.getX(), ev.getY());
+            return false;
+        }
+        // Stop sending touch events to deep shortcut views if user moved beyond touch slop.
+        return Math.hypot(mInterceptTouchDown.x - ev.getX(), mInterceptTouchDown.y - ev.getY())
+                > ViewConfiguration.get(getContext()).getScaledTouchSlop();
+    }
+
+    /**
+     * We need to handle touch events to prevent them from falling through to the workspace below.
+     */
+    @SuppressLint("ClickableViewAccessibility")
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        return true;
+    }
+
+    @Override
     public boolean onTouch(View v, MotionEvent ev) {
         // Touched a shortcut, update where it was touched so we can drag from there on long click.
         switch (ev.getAction()) {
@@ -434,8 +450,10 @@
     public boolean onLongClick(View v) {
         // Return early if this is not initiated from a touch or not the correct view
         if (!v.isInTouchMode() || !(v.getParent() instanceof DeepShortcutView)) return false;
-        // Return if global dragging is not enabled
+        // Return early if global dragging is not enabled
         if (!mLauncher.isDraggingEnabled()) return false;
+        // Return early if an item is already being dragged (e.g. when long-pressing two shortcuts)
+        if (mLauncher.getDragController().isDragging()) return false;
 
         // Long clicked on a shortcut.
         mDeferContainerRemoval = true;
@@ -452,7 +470,7 @@
         dv.animateShift(-mIconShift.x, -mIconShift.y);
 
         // TODO: support dragging from within folder without having to close it
-        mLauncher.closeFolder();
+        AbstractFloatingView.closeOpenContainer(mLauncher, AbstractFloatingView.TYPE_FOLDER);
         return false;
     }
 
@@ -507,21 +525,29 @@
             } else {
                 // Close animation is not running.
                 if (mDeferContainerRemoval) {
-                    close();
+                    closeComplete();
                 }
             }
         }
-        mDeferredDragIcon.setVisibility(VISIBLE);
     }
 
     @Override
-    public void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent) {
+    public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
         target.itemType = LauncherLogProto.DEEPSHORTCUT;
         // TODO: add target.rank
         targetParent.containerType = LauncherLogProto.DEEPSHORTCUTS;
     }
 
-    public void animateClose() {
+    @Override
+    protected void handleClose(boolean animate) {
+        if (animate) {
+            animateClose();
+        } else {
+            closeComplete();
+        }
+    }
+
+    private void animateClose() {
         if (!mIsOpen) {
             return;
         }
@@ -601,7 +627,7 @@
                 if (mDeferContainerRemoval) {
                     setVisibility(INVISIBLE);
                 } else {
-                    close();
+                    closeComplete();
                 }
             }
         });
@@ -609,25 +635,30 @@
         shortcutAnims.start();
     }
 
+    public ShortcutMenuAccessibilityDelegate getAccessibilityDelegate() {
+        return mAccessibilityDelegate;
+    }
+
     /**
      * Closes the folder without animation.
      */
-    public void close() {
+    private void closeComplete() {
         if (mOpenCloseAnimator != null) {
             mOpenCloseAnimator.cancel();
             mOpenCloseAnimator = null;
         }
         mIsOpen = false;
         mDeferContainerRemoval = false;
-        boolean isInHotseat = ((ItemInfo) mDeferredDragIcon.getTag()).container
+        boolean isInHotseat = ((ItemInfo) mOriginalIcon.getTag()).container
                 == LauncherSettings.Favorites.CONTAINER_HOTSEAT;
-        mDeferredDragIcon.setTextVisibility(!isInHotseat);
+        mOriginalIcon.setTextVisibility(!isInHotseat);
         mLauncher.getDragController().removeDragListener(this);
         mLauncher.getDragLayer().removeView(this);
     }
 
-    public boolean isOpen() {
-        return mIsOpen;
+    @Override
+    protected boolean isOfType(int type) {
+        return (type & TYPE_DEEPSHORTCUT_CONTAINER) != 0;
     }
 
     /**
@@ -636,14 +667,13 @@
      */
     public static DeepShortcutsContainer showForIcon(BubbleTextView icon) {
         Launcher launcher = Launcher.getLauncher(icon.getContext());
-        if (launcher.getOpenShortcutsContainer() != null) {
+        if (getOpen(launcher) != null) {
             // There is already a shortcuts container open, so don't open this one.
             icon.clearFocus();
             return null;
         }
         List<String> ids = launcher.getShortcutIdsForItem((ItemInfo) icon.getTag());
         if (!ids.isEmpty()) {
-            // There are shortcuts associated with the app, so defer its drag.
             final DeepShortcutsContainer container =
                     (DeepShortcutsContainer) launcher.getLayoutInflater().inflate(
                             R.layout.deep_shortcuts_container, launcher.getDragLayer(), false);
@@ -672,4 +702,11 @@
             return unbadgedBitmap;
         }
     }
+
+    /**
+     * Returns a DeepShortcutsContainer which is already open or null
+     */
+    public static DeepShortcutsContainer getOpen(Launcher launcher) {
+        return getOpenView(launcher, TYPE_DEEPSHORTCUT_CONTAINER);
+    }
 }
diff --git a/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
index 2adb82e..fc474f5 100644
--- a/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
+++ b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
@@ -23,7 +23,7 @@
 import android.graphics.drawable.Drawable;
 import android.view.View;
 
-import com.android.launcher3.HolographicOutlineHelper;
+import com.android.launcher3.graphics.HolographicOutlineHelper;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.graphics.DragPreviewProvider;
@@ -44,7 +44,7 @@
     public Bitmap createDragOutline(Canvas canvas) {
         Bitmap b = drawScaledPreview(canvas, Bitmap.Config.ALPHA_8);
 
-        HolographicOutlineHelper.obtain(mView.getContext())
+        HolographicOutlineHelper.getInstance(mView.getContext())
                 .applyExpensiveOutlineWithBlur(b, canvas);
         canvas.setBitmap(null);
         return b;
diff --git a/src/com/android/launcher3/util/ContentWriter.java b/src/com/android/launcher3/util/ContentWriter.java
new file mode 100644
index 0000000..33d979c
--- /dev/null
+++ b/src/com/android/launcher3/util/ContentWriter.java
@@ -0,0 +1,98 @@
+package com.android.launcher3.util;
+
+/**
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.compat.UserManagerCompat;
+
+/**
+ * A wrapper around {@link ContentValues} with some utility methods.
+ */
+public class ContentWriter {
+
+    private final ContentValues mValues;
+    private final Context mContext;
+
+    private Bitmap mIcon;
+    private UserHandleCompat mUser;
+
+    public ContentWriter(Context context) {
+        this(new ContentValues(), context);
+    }
+
+    public ContentWriter(ContentValues values, Context context) {
+        mValues = values;
+        mContext = context;
+    }
+
+    public ContentWriter put(String key, Integer value) {
+        mValues.put(key, value);
+        return this;
+    }
+
+    public ContentWriter put(String key, Long value) {
+        mValues.put(key, value);
+        return this;
+    }
+
+    public ContentWriter put(String key, String value) {
+        mValues.put(key, value);
+        return this;
+    }
+
+    public ContentWriter put(String key, CharSequence value) {
+        mValues.put(key, value == null ? null : value.toString());
+        return this;
+    }
+
+    public ContentWriter put(String key, Intent value) {
+        mValues.put(key, value == null ? null : value.toUri(0));
+        return this;
+    }
+
+    public ContentWriter putIcon(Bitmap value, UserHandleCompat user) {
+        mIcon = value;
+        mUser = user;
+        return this;
+    }
+
+    public ContentWriter put(String key, UserHandleCompat user) {
+        return put(key, UserManagerCompat.getInstance(mContext).getSerialNumberForUser(user));
+    }
+
+    /**
+     * Commits any pending validation and returns the final values.
+     * Must not be called on UI thread.
+     */
+    public ContentValues getValues() {
+        Preconditions.assertNonUiThread();
+        if (mIcon != null && !LauncherAppState.getInstance().getIconCache()
+                .isDefaultIcon(mIcon, mUser)) {
+            mValues.put(LauncherSettings.Favorites.ICON, Utilities.flattenBitmap(mIcon));
+            mIcon = null;
+        }
+        return mValues;
+    }
+}
diff --git a/src/com/android/launcher3/util/CursorIconInfo.java b/src/com/android/launcher3/util/CursorIconInfo.java
index 4fefa98..6603ee7 100644
--- a/src/com/android/launcher3/util/CursorIconInfo.java
+++ b/src/com/android/launcher3/util/CursorIconInfo.java
@@ -25,6 +25,7 @@
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.graphics.LauncherIcons;
 
 /**
  * Utility class to load icon from a cursor.
@@ -59,7 +60,7 @@
             info.iconResource = new ShortcutIconResource();
             info.iconResource.packageName = packageName;
             info.iconResource.resourceName = resourceName;
-            icon = Utilities.createIconBitmap(packageName, resourceName, mContext);
+            icon = LauncherIcons.createIconBitmap(packageName, resourceName, mContext);
         }
         if (icon == null) {
             // Failed to load from resource, try loading from DB.
@@ -72,7 +73,7 @@
      * Loads the fixed bitmap from the icon if available.
      */
     public Bitmap loadIcon(Cursor c) {
-        return Utilities.createIconBitmap(c, iconIndex, mContext);
+        return LauncherIcons.createIconBitmap(c, iconIndex, mContext);
     }
 
     /**
diff --git a/src/com/android/launcher3/util/FocusLogic.java b/src/com/android/launcher3/util/FocusLogic.java
index 163c953..afc45fe 100644
--- a/src/com/android/launcher3/util/FocusLogic.java
+++ b/src/com/android/launcher3/util/FocusLogic.java
@@ -79,8 +79,7 @@
         return (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT ||
                 keyCode == KeyEvent.KEYCODE_DPAD_UP || keyCode == KeyEvent.KEYCODE_DPAD_DOWN ||
                 keyCode == KeyEvent.KEYCODE_MOVE_HOME || keyCode == KeyEvent.KEYCODE_MOVE_END ||
-                keyCode == KeyEvent.KEYCODE_PAGE_UP || keyCode == KeyEvent.KEYCODE_PAGE_DOWN ||
-                keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_FORWARD_DEL);
+                keyCode == KeyEvent.KEYCODE_PAGE_UP || keyCode == KeyEvent.KEYCODE_PAGE_DOWN);
     }
 
     public static int handleKeyEvent(int keyCode, int [][] map, int iconIdx, int pageIndex,
diff --git a/src/com/android/launcher3/util/ItemInfoMatcher.java b/src/com/android/launcher3/util/ItemInfoMatcher.java
index 46e9184..8f985c3 100644
--- a/src/com/android/launcher3/util/ItemInfoMatcher.java
+++ b/src/com/android/launcher3/util/ItemInfoMatcher.java
@@ -18,7 +18,9 @@
 
 import android.content.ComponentName;
 
+import com.android.launcher3.FolderInfo;
 import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.compat.UserHandleCompat;
@@ -33,6 +35,46 @@
 
     public abstract boolean matches(ItemInfo info, ComponentName cn);
 
+    /**
+     * Filters {@param infos} to those satisfying the {@link #matches(ItemInfo, ComponentName)}.
+     */
+    public final HashSet<ItemInfo> filterItemInfos(Iterable<ItemInfo> infos) {
+        HashSet<ItemInfo> filtered = new HashSet<>();
+        for (ItemInfo i : infos) {
+            if (i instanceof ShortcutInfo) {
+                ShortcutInfo info = (ShortcutInfo) i;
+                ComponentName cn = info.getTargetComponent();
+                if (cn != null && matches(info, cn)) {
+                    filtered.add(info);
+                }
+            } else if (i instanceof FolderInfo) {
+                FolderInfo info = (FolderInfo) i;
+                for (ShortcutInfo s : info.contents) {
+                    ComponentName cn = s.getTargetComponent();
+                    if (cn != null && matches(s, cn)) {
+                        filtered.add(s);
+                    }
+                }
+            } else if (i instanceof LauncherAppWidgetInfo) {
+                LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) i;
+                ComponentName cn = info.providerName;
+                if (cn != null && matches(info, cn)) {
+                    filtered.add(info);
+                }
+            }
+        }
+        return filtered;
+    }
+
+    public static ItemInfoMatcher ofUser(final UserHandleCompat user) {
+        return new ItemInfoMatcher() {
+            @Override
+            public boolean matches(ItemInfo info, ComponentName cn) {
+                return info.user.equals(user);
+            }
+        };
+    }
+
     public static ItemInfoMatcher ofComponents(
             final HashSet<ComponentName> components, final UserHandleCompat user) {
         return new ItemInfoMatcher() {
diff --git a/src/com/android/launcher3/util/LabelComparator.java b/src/com/android/launcher3/util/LabelComparator.java
new file mode 100644
index 0000000..5da9ddf
--- /dev/null
+++ b/src/com/android/launcher3/util/LabelComparator.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util;
+
+import java.text.Collator;
+import java.util.Comparator;
+
+/**
+ * Extension of {@link java.text.Collator} with special handling for digits. Used for comparing
+ * user visible labels.
+ */
+public class LabelComparator implements Comparator<String> {
+
+    private final Collator mCollator = Collator.getInstance();
+
+    @Override
+    public int compare(String titleA, String titleB) {
+        // Ensure that we de-prioritize any titles that don't start with a
+        // linguistic letter or digit
+        boolean aStartsWithLetter = (titleA.length() > 0) &&
+                Character.isLetterOrDigit(titleA.codePointAt(0));
+        boolean bStartsWithLetter = (titleB.length() > 0) &&
+                Character.isLetterOrDigit(titleB.codePointAt(0));
+        if (aStartsWithLetter && !bStartsWithLetter) {
+            return -1;
+        } else if (!aStartsWithLetter && bStartsWithLetter) {
+            return 1;
+        }
+
+        // Order by the title in the current locale
+        return mCollator.compare(titleA, titleB);
+    }
+}
diff --git a/src/com/android/launcher3/util/ManagedProfileHeuristic.java b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
index 6661429..78b7a3e 100644
--- a/src/com/android/launcher3/util/ManagedProfileHeuristic.java
+++ b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
@@ -121,7 +121,7 @@
             // getting filled with the managed user apps, when it start with a fresh DB (or after
             // a very long time).
             if (userAppsExisted && !homescreenApps.isEmpty()) {
-                mModel.addAndBindAddedWorkspaceItems(mContext, homescreenApps);
+                mModel.addAndBindAddedWorkspaceItems(homescreenApps);
             }
         }
 
@@ -175,7 +175,7 @@
                 // Add the item to home screen and DB. This also generates an item id synchronously.
                 ArrayList<ItemInfo> itemList = new ArrayList<ItemInfo>(1);
                 itemList.add(workFolder);
-                mModel.addAndBindAddedWorkspaceItems(mContext, itemList);
+                mModel.addAndBindAddedWorkspaceItems(itemList);
                 mPrefs.edit().putLong(folderIdKey, workFolder.id).apply();
 
                 saveWorkFolderShortcuts(workFolder.id, 0, workFolderApps);
@@ -200,7 +200,6 @@
         }
     }
 
-
     /**
      * Verifies that entries corresponding to {@param users} exist and removes all invalid entries.
      */
diff --git a/src/com/android/launcher3/util/NoLocaleSqliteContext.java b/src/com/android/launcher3/util/NoLocaleSqliteContext.java
index 3b258e4..c8a5ffb 100644
--- a/src/com/android/launcher3/util/NoLocaleSqliteContext.java
+++ b/src/com/android/launcher3/util/NoLocaleSqliteContext.java
@@ -11,9 +11,6 @@
  */
 public class NoLocaleSqliteContext extends ContextWrapper {
 
-    // TODO: Use the flag defined in Context when the new SDK is available
-    private static final int MODE_NO_LOCALIZED_COLLATORS = 0x0010;
-
     public NoLocaleSqliteContext(Context context) {
         super(context);
     }
@@ -22,6 +19,6 @@
     public SQLiteDatabase openOrCreateDatabase(
             String name, int mode, CursorFactory factory, DatabaseErrorHandler errorHandler) {
         return super.openOrCreateDatabase(
-                name, mode | MODE_NO_LOCALIZED_COLLATORS, factory, errorHandler);
+                name, mode | Context.MODE_NO_LOCALIZED_COLLATORS, factory, errorHandler);
     }
 }
diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index 3e15d05..b61d609 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -28,15 +28,12 @@
 
 import com.android.launcher3.Utilities;
 
-import java.util.ArrayList;
-
 /**
  * Utility methods using package manager
  */
 public class PackageManagerHelper {
 
     private static final int FLAG_SUSPENDED = 1<<30;
-    private static final String LIVE_WALLPAPER_PICKER_PKG = "com.android.wallpaper.livepicker";
 
     /**
      * Returns true if the app can possibly be on the SDCard. This is just a workaround and doesn't
@@ -72,7 +69,7 @@
         // The value of FLAG_SUSPENDED was reused by a hidden constant
         // ApplicationInfo.FLAG_PRIVILEGED prior to N, so only check for suspended flag on N
         // or later.
-        if (Utilities.isNycOrAbove()) {
+        if (Utilities.ATLEAST_NOUGAT) {
             return (info.flags & FLAG_SUSPENDED) != 0;
         } else {
             return false;
@@ -80,29 +77,6 @@
     }
 
     /**
-     * Returns the package for a wallpaper picker system app giving preference to a app which
-     * is not as image picker.
-     */
-    public static String getWallpaperPickerPackage(PackageManager pm) {
-        ArrayList<String> excludePackages = new ArrayList<>();
-        // Exclude packages which contain an image picker
-        for (ResolveInfo info : pm.queryIntentActivities(
-                new Intent(Intent.ACTION_GET_CONTENT).setType("image/*"), 0)) {
-            excludePackages.add(info.activityInfo.packageName);
-        }
-        excludePackages.add(LIVE_WALLPAPER_PICKER_PKG);
-
-        for (ResolveInfo info : pm.queryIntentActivities(
-                new Intent(Intent.ACTION_SET_WALLPAPER), 0)) {
-            if (!excludePackages.contains(info.activityInfo.packageName) &&
-                    (info.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
-                return info.activityInfo.packageName;
-            }
-        }
-        return excludePackages.get(0);
-    }
-
-    /**
      * Returns true if {@param srcPackage} has the permission required to start the activity from
      * {@param intent}. If {@param srcPackage} is null, then the activity should not need
      * any permissions
diff --git a/src/com/android/launcher3/util/PendingRequestArgs.java b/src/com/android/launcher3/util/PendingRequestArgs.java
index bade967..8eea28b 100644
--- a/src/com/android/launcher3/util/PendingRequestArgs.java
+++ b/src/com/android/launcher3/util/PendingRequestArgs.java
@@ -73,7 +73,7 @@
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         ContentValues itemValues = new ContentValues();
-        writeToValues(itemValues);
+        writeToValues(new ContentWriter(itemValues, null));
         itemValues.writeToParcel(dest, flags);
 
         dest.writeInt(mArg1);
diff --git a/src/com/android/launcher3/util/StringFilter.java b/src/com/android/launcher3/util/StringFilter.java
deleted file mode 100644
index f539ad1..0000000
--- a/src/com/android/launcher3/util/StringFilter.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.android.launcher3.util;
-
-import java.util.Set;
-
-/**
- * Abstract class to filter a set of strings.
- */
-public abstract class StringFilter {
-
-    private StringFilter() { }
-
-    public abstract boolean matches(String str);
-
-    public static StringFilter matchesAll() {
-        return new StringFilter() {
-            @Override
-            public boolean matches(String str) {
-                return true;
-            }
-        };
-    }
-
-    public static StringFilter of(final Set<String> validEntries) {
-        return new StringFilter() {
-            @Override
-            public boolean matches(String str) {
-                return validEntries.contains(str);
-            }
-        };
-    }
-}
diff --git a/src/com/android/launcher3/util/TouchController.java b/src/com/android/launcher3/util/TouchController.java
index d1409c8..3cca215 100644
--- a/src/com/android/launcher3/util/TouchController.java
+++ b/src/com/android/launcher3/util/TouchController.java
@@ -1,8 +1,32 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.launcher3.util;
 
 import android.view.MotionEvent;
 
 public interface TouchController {
-    boolean onTouchEvent(MotionEvent ev);
-    boolean onInterceptTouchEvent(MotionEvent ev);
+
+    /**
+     * Called when the draglayer receives touch event.
+     */
+    boolean onControllerTouchEvent(MotionEvent ev);
+
+    /**
+     * Called when the draglayer receives a intercept touch event.
+     */
+    boolean onControllerInterceptTouchEvent(MotionEvent ev);
 }
diff --git a/src/com/android/launcher3/widget/PendingItemPreviewProvider.java b/src/com/android/launcher3/widget/PendingItemPreviewProvider.java
index eaa0bb3..7c06701 100644
--- a/src/com/android/launcher3/widget/PendingItemPreviewProvider.java
+++ b/src/com/android/launcher3/widget/PendingItemPreviewProvider.java
@@ -21,7 +21,7 @@
 import android.graphics.Rect;
 import android.view.View;
 
-import com.android.launcher3.HolographicOutlineHelper;
+import com.android.launcher3.graphics.HolographicOutlineHelper;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.PendingAddItemInfo;
 import com.android.launcher3.Workspace;
@@ -67,7 +67,7 @@
         // 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.obtain(mView.getContext())
+        HolographicOutlineHelper.getInstance(mView.getContext())
                 .applyExpensiveOutlineWithBlur(b, canvas, clipAlpha);
         canvas.setBitmap(null);
 
diff --git a/src/com/android/launcher3/widget/WidgetItemComparator.java b/src/com/android/launcher3/widget/WidgetItemComparator.java
new file mode 100644
index 0000000..b5aaeb9
--- /dev/null
+++ b/src/com/android/launcher3/widget/WidgetItemComparator.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.widget;
+
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.model.WidgetItem;
+
+import java.text.Collator;
+import java.util.Comparator;
+
+/**
+ * Comparator for sorting WidgetItem based on their user, title and size.
+ */
+public class WidgetItemComparator implements Comparator<WidgetItem> {
+
+    private final UserHandleCompat mMyUserHandle = UserHandleCompat.myUserHandle();
+    private final Collator mCollator = Collator.getInstance();
+
+    @Override
+    public int compare(WidgetItem a, WidgetItem b) {
+        // Independent of how the labels compare, if only one of the two widget info belongs to
+        // work profile, put that one in the back.
+        boolean thisWorkProfile = !mMyUserHandle.equals(a.user);
+        boolean otherWorkProfile = !mMyUserHandle.equals(b.user);
+        if (thisWorkProfile ^ otherWorkProfile) {
+            return thisWorkProfile ? 1 : -1;
+        }
+
+        int labelCompare = mCollator.compare(a.label, b.label);
+        if (labelCompare != 0) {
+            return labelCompare;
+        }
+
+        // If the label is same, put the smaller widget before the larger widget. If the area is
+        // also same, put the widget with smaller height before.
+        int thisArea = a.spanX * a.spanY;
+        int otherArea = b.spanX * b.spanY;
+        return thisArea == otherArea
+                ? Integer.compare(a.spanY, b.spanY)
+                : Integer.compare(thisArea, otherArea);
+    }
+}
diff --git a/src/com/android/launcher3/widget/WidgetListRowEntry.java b/src/com/android/launcher3/widget/WidgetListRowEntry.java
new file mode 100644
index 0000000..3e89eeb
--- /dev/null
+++ b/src/com/android/launcher3/widget/WidgetListRowEntry.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.widget;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.model.PackageItemInfo;
+import com.android.launcher3.model.WidgetItem;
+
+import java.util.ArrayList;
+
+/**
+ * Holder class to store all the information related to a single row in the widget list
+ */
+public class WidgetListRowEntry {
+
+    public final PackageItemInfo pkgItem;
+
+    public final ArrayList<WidgetItem> widgets;
+
+    /**
+     * Character that is used as a section name for the {@link ItemInfo#title}.
+     * (e.g., "G" will be stored if title is "Google")
+     */
+    public String titleSectionName;
+
+    public WidgetListRowEntry(PackageItemInfo pkgItem, ArrayList<WidgetItem> items) {
+        this.pkgItem = pkgItem;
+        this.widgets = items;
+    }
+
+}
diff --git a/src/com/android/launcher3/widget/WidgetsContainerView.java b/src/com/android/launcher3/widget/WidgetsContainerView.java
index 89c44c8..2e12942 100644
--- a/src/com/android/launcher3/widget/WidgetsContainerView.java
+++ b/src/com/android/launcher3/widget/WidgetsContainerView.java
@@ -21,7 +21,6 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView.State;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
@@ -29,7 +28,6 @@
 import android.widget.Toast;
 
 import com.android.launcher3.BaseContainerView;
-import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeleteDropTarget;
 import com.android.launcher3.DragSource;
 import com.android.launcher3.DropTarget.DragObject;
@@ -43,13 +41,15 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.WidgetPreviewLoader;
-import com.android.launcher3.Workspace;
 import com.android.launcher3.dragndrop.DragController;
+import com.android.launcher3.graphics.LauncherIcons;
+import com.android.launcher3.model.PackageItemInfo;
+import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.Thunk;
-import com.android.launcher3.util.TransformingTouchDelegate;
 
 /**
  * The widgets list view container.
@@ -64,12 +64,9 @@
     private DragController mDragController;
     private IconCache mIconCache;
 
-    private final Rect mTmpBgPaddingRect = new Rect();
-
     /* Recycler view related member variables */
     private WidgetsRecyclerView mRecyclerView;
     private WidgetsListAdapter mAdapter;
-    private TransformingTouchDelegate mRecyclerViewTouchDelegate;
 
     /* Touch handling related member variables. */
     private Toast mWidgetInstructionToast;
@@ -97,14 +94,8 @@
     }
 
     @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        super.onLayout(changed, left, top, right, bottom);
-        getRevealView().getBackground().getPadding(mTmpBgPaddingRect);
-        mRecyclerViewTouchDelegate.setBounds(
-                mRecyclerView.getLeft() - mTmpBgPaddingRect.left,
-                mRecyclerView.getTop() - mTmpBgPaddingRect.top,
-                mRecyclerView.getRight() + mTmpBgPaddingRect.right,
-                mRecyclerView.getBottom() + mTmpBgPaddingRect.bottom);
+    public View getTouchDelegateTargetView() {
+        return mRecyclerView;
     }
 
     @Override
@@ -113,13 +104,6 @@
         mRecyclerView = (WidgetsRecyclerView) getContentView().findViewById(R.id.widgets_list_view);
         mRecyclerView.setAdapter(mAdapter);
         mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
-        mRecyclerViewTouchDelegate = new TransformingTouchDelegate(mRecyclerView);
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        ((View) mRecyclerView.getParent()).setTouchDelegate(mRecyclerViewTouchDelegate);
     }
 
     //
@@ -156,7 +140,7 @@
     @Override
     public boolean onLongClick(View v) {
         if (LOGD) {
-            Log.d(TAG, String.format("onLonglick [v=%s]", v));
+            Log.d(TAG, String.format("onLongClick [v=%s]", v));
         }
         // Return early if this is not initiated from a touch
         if (!v.isInTouchMode()) return false;
@@ -241,7 +225,7 @@
         } else {
             PendingAddShortcutInfo createShortcutInfo = (PendingAddShortcutInfo) v.getTag();
             Drawable icon = mIconCache.getFullResIcon(createShortcutInfo.activityInfo);
-            preview = Utilities.createIconBitmap(icon, mLauncher);
+            preview = LauncherIcons.createIconBitmap(icon, mLauncher);
             createItemInfo.spanX = createItemInfo.spanY = 1;
             scale = ((float) mLauncher.getDeviceProfile().iconSizePx) / preview.getWidth();
         }
@@ -308,23 +292,7 @@
         }
         mLauncher.unlockScreenOrientation(false);
 
-        // Display an error message if the drag failed due to there not being enough space on the
-        // target layout we were dropping on.
         if (!success) {
-            boolean showOutOfSpaceMessage = false;
-            if (target instanceof Workspace) {
-                int currentScreen = mLauncher.getCurrentWorkspaceScreen();
-                Workspace workspace = (Workspace) target;
-                CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen);
-                ItemInfo itemInfo = d.dragInfo;
-                if (layout != null) {
-                    showOutOfSpaceMessage =
-                            !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY);
-                }
-            }
-            if (showOutOfSpaceMessage) {
-                mLauncher.showOutOfSpaceMessage(false);
-            }
             d.deferDragViewCleanupPostAnimation = false;
         }
     }
@@ -332,9 +300,8 @@
     /**
      * Initialize the widget data model.
      */
-    public void addWidgets(WidgetsModel model) {
-        mRecyclerView.setWidgets(model);
-        mAdapter.setWidgetsModel(model);
+    public void setWidgets(MultiHashMap<PackageItemInfo, WidgetItem> model) {
+        mAdapter.setWidgets(model);
         mAdapter.notifyDataSetChanged();
 
         View loader = getContentView().findViewById(R.id.loader);
@@ -355,7 +322,7 @@
     }
 
     @Override
-    public void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent) {
+    public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
         targetParent.containerType = LauncherLogProto.WIDGETS;
     }
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/widget/WidgetsListAdapter.java b/src/com/android/launcher3/widget/WidgetsListAdapter.java
index 6b8ea49..a5846ec 100644
--- a/src/com/android/launcher3/widget/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/WidgetsListAdapter.java
@@ -27,16 +27,21 @@
 import android.view.ViewGroup.LayoutParams;
 import android.widget.LinearLayout;
 
-import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.WidgetPreviewLoader;
+import com.android.launcher3.compat.AlphabeticIndexCompat;
 import com.android.launcher3.model.PackageItemInfo;
 import com.android.launcher3.model.WidgetItem;
-import com.android.launcher3.model.WidgetsModel;
+import com.android.launcher3.util.LabelComparator;
+import com.android.launcher3.util.MultiHashMap;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
+import java.util.Map;
 
 /**
  * List view adapter for the widget tray.
@@ -57,7 +62,8 @@
     private final View.OnClickListener mIconClickListener;
     private final View.OnLongClickListener mIconLongClickListener;
 
-    private WidgetsModel mWidgetsModel;
+    private final ArrayList<WidgetListRowEntry> mEntries = new ArrayList<>();
+    private final AlphabeticIndexCompat mIndexer;
 
     private final int mIndent;
 
@@ -67,26 +73,40 @@
         mLayoutInflater = LayoutInflater.from(context);
         mWidgetPreviewLoader = LauncherAppState.getInstance().getWidgetCache();
 
+        mIndexer = new AlphabeticIndexCompat(context);
+
         mIconClickListener = iconClickListener;
         mIconLongClickListener = iconLongClickListener;
         mIndent = context.getResources().getDimensionPixelSize(R.dimen.widget_section_indent);
     }
 
-    public void setWidgetsModel(WidgetsModel w) {
-        mWidgetsModel = w;
+    public void setWidgets(MultiHashMap<PackageItemInfo, WidgetItem> widgets) {
+        mEntries.clear();
+        WidgetItemComparator widgetComparator = new WidgetItemComparator();
+
+        for (Map.Entry<PackageItemInfo, ArrayList<WidgetItem>> entry : widgets.entrySet()) {
+            WidgetListRowEntry row = new WidgetListRowEntry(entry.getKey(), entry.getValue());
+            row.titleSectionName = mIndexer.computeSectionName(row.pkgItem.title);
+            Collections.sort(row.widgets, widgetComparator);
+            mEntries.add(row);
+        }
+
+        Collections.sort(mEntries, new WidgetListRowEntryComparator());
     }
 
     @Override
     public int getItemCount() {
-        if (mWidgetsModel == null) {
-            return 0;
-        }
-        return mWidgetsModel.getPackageSize();
+        return mEntries.size();
+    }
+
+    public String getSectionName(int pos) {
+        return mEntries.get(pos).titleSectionName;
     }
 
     @Override
     public void onBindViewHolder(WidgetsRowViewHolder holder, int pos) {
-        List<WidgetItem> infoList = mWidgetsModel.getSortedWidgets(pos);
+        WidgetListRowEntry entry = mEntries.get(pos);
+        List<WidgetItem> infoList = entry.widgets;
 
         ViewGroup row = holder.cellContainer;
         if (DEBUG) {
@@ -121,7 +141,7 @@
         }
 
         // Bind the views in the application info section.
-        holder.title.applyFromPackageItemInfo(mWidgetsModel.getPackageItemInfo(pos));
+        holder.title.applyFromPackageItemInfo(entry.pkgItem);
 
         // Bind the view in the widget horizontal tray region.
         for (int i=0; i < infoList.size(); i++) {
@@ -175,4 +195,18 @@
     public long getItemId(int pos) {
         return pos;
     }
+
+    /**
+     * Comparator for sorting WidgetListRowEntry based on package title
+     */
+    public static class WidgetListRowEntryComparator implements Comparator<WidgetListRowEntry> {
+
+        private final LabelComparator mComparator = new LabelComparator();
+
+        @Override
+        public int compare(WidgetListRowEntry a, WidgetListRowEntry b) {
+            return mComparator.compare(a.pkgItem.title.toString(), b.pkgItem.title.toString());
+        }
+    }
+
 }
diff --git a/src/com/android/launcher3/widget/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
index 2560661..e0a80c6 100644
--- a/src/com/android/launcher3/widget/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
@@ -17,7 +17,6 @@
 package com.android.launcher3.widget;
 
 import android.content.Context;
-import android.graphics.Canvas;
 import android.graphics.Color;
 import android.support.v7.widget.LinearLayoutManager;
 import android.util.AttributeSet;
@@ -32,7 +31,7 @@
 public class WidgetsRecyclerView extends BaseRecyclerView {
 
     private static final String TAG = "WidgetsRecyclerView";
-    private WidgetsModel mWidgets;
+    private WidgetsListAdapter mAdapter;
 
     public WidgetsRecyclerView(Context context) {
         this(context, null);
@@ -65,23 +64,10 @@
         return Color.WHITE;
     }
 
-    /**
-     * Sets the widget model in this view, used to determine the fast scroll position.
-     */
-    public void setWidgets(WidgetsModel widgets) {
-        mWidgets = widgets;
-    }
-    
-    /**
-     * We need to override the draw to ensure that we don't draw the overscroll effect beyond the
-     * background bounds.
-     */
     @Override
-    protected void dispatchDraw(Canvas canvas) {
-        canvas.clipRect(mBackgroundPadding.left, mBackgroundPadding.top,
-                getWidth() - mBackgroundPadding.right,
-                getHeight() - mBackgroundPadding.bottom);
-        super.dispatchDraw(canvas);
+    public void setAdapter(Adapter adapter) {
+        super.setAdapter(adapter);
+        mAdapter = (WidgetsListAdapter) adapter;
     }
 
     /**
@@ -97,15 +83,14 @@
         // Stop the scroller if it is scrolling
         stopScroll();
 
-        int rowCount = mWidgets.getPackageSize();
+        int rowCount = mAdapter.getItemCount();
         float pos = rowCount * touchFraction;
         int availableScrollHeight = getAvailableScrollHeight();
         LinearLayoutManager layoutManager = ((LinearLayoutManager) getLayoutManager());
         layoutManager.scrollToPositionWithOffset(0, (int) -(availableScrollHeight * touchFraction));
 
         int posInt = (int) ((touchFraction == 1)? pos -1 : pos);
-        PackageItemInfo p = mWidgets.getPackageItemInfo(posInt);
-        return p.titleSectionName;
+        return mAdapter.getSectionName(posInt);
     }
 
     /**
@@ -121,7 +106,7 @@
         // Skip early if, there no child laid out in the container.
         int scrollY = getCurrentScrollY();
         if (scrollY < 0) {
-            mScrollbar.setThumbOffset(-1, -1);
+            mScrollbar.setThumbOffsetY(-1);
             return;
         }
 
@@ -150,13 +135,13 @@
     @Override
     protected int getAvailableScrollHeight() {
         View child = getChildAt(0);
-        int height = child.getMeasuredHeight() * mWidgets.getPackageSize();
+        int height = child.getMeasuredHeight() * mAdapter.getItemCount();
         int totalHeight = getPaddingTop() + height + getPaddingBottom();
-        int availableScrollHeight = totalHeight - getVisibleHeight();
+        int availableScrollHeight = totalHeight - getScrollbarTrackHeight();
         return availableScrollHeight;
     }
 
     private boolean isModelNotReady() {
-        return mWidgets == null || mWidgets.getPackageSize() == 0;
+        return mAdapter.getItemCount() == 0;
     }
 }
\ No newline at end of file
diff --git a/tests/Android.mk b/tests/Android.mk
index 61ee220..5103ced 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -17,11 +17,12 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE_TAGS := tests
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test ub-uiautomator
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test ub-uiautomator mockito-target-minus-junit4
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 21
 
 LOCAL_PACKAGE_NAME := Launcher3Tests
 
diff --git a/tests/res/raw/cache_data_updated_task_data.txt b/tests/res/raw/cache_data_updated_task_data.txt
new file mode 100644
index 0000000..9095476
--- /dev/null
+++ b/tests/res/raw/cache_data_updated_task_data.txt
@@ -0,0 +1,28 @@
+# Model data used by CacheDataUpdatedTaskTest
+
+classMap s com.android.launcher3.ShortcutInfo
+
+# Items for the BgDataModel
+
+# App shortcuts
+bgItem s itemType=0 title=app1-class1 intent=component=app1/class1 id=1
+bgItem s itemType=0 title=app1-class2 intent=component=app1/class2 id=2
+bgItem s itemType=0 title=app2-class1 intent=component=app2/class1 id=3
+bgItem s itemType=0 title=app2-class2 intent=component=app2/class2 id=4
+
+# Auto install app shortcut
+bgItem s itemType=0 status=2 title=app3-class1 intent=component=app3/class1 id=5
+bgItem s itemType=0 status=2 title=app3-class2 intent=component=app3/class2 id=6
+
+# Custom shortcuts
+bgItem s itemType=1 title=app1-shrt intent=component=app1/class3 id=7
+bgItem s itemType=1 title=app4-shrt intent=component=app4/class1 id=8
+
+# Restored custom shortcut
+bgItem s itemType=1 status=1 title=app3-shrt intent=component=app3/class3 id=9
+bgItem s itemType=1 status=1 title=app5-shrt intent=component=app5/class1 id=10
+
+allApps componentName=app1/class1
+allApps componentName=app1/class2
+allApps componentName=app2/class1
+allApps componentName=app2/class2
\ No newline at end of file
diff --git a/tests/res/raw/package_install_state_change_task_data.txt b/tests/res/raw/package_install_state_change_task_data.txt
new file mode 100644
index 0000000..84f9c16
--- /dev/null
+++ b/tests/res/raw/package_install_state_change_task_data.txt
@@ -0,0 +1,24 @@
+# Model data used by PackageInstallStateChangeTaskTest
+
+classMap s com.android.launcher3.ShortcutInfo
+classMap w com.android.launcher3.LauncherAppWidgetInfo
+
+# Items for the BgDataModel
+
+# App shortcuts
+bgItem s itemType=0 title=app1-class1 intent=component=app1/class1 id=1
+bgItem s itemType=0 title=app1-class2 intent=component=app1/class2 id=2
+bgItem s itemType=0 title=app2-class1 intent=component=app2/class1 id=3
+bgItem s itemType=0 title=app2-class2 intent=component=app2/class2 id=4
+
+# Promise icons for app3
+bgItem s itemType=0 status=2 title=app3-class1 intent=component=app3/class1 id=5
+bgItem s itemType=0 status=2 title=app3-class2 intent=component=app3/class2 id=6
+bgItem s itemType=1 status=1 title=app3-shrt intent=component=app3/class3 id=7
+
+# Promise icon for app4
+bgItem s itemType=1 status=1 title=app4-shrt intent=component=app4/class1 id=8
+
+# Widget
+bgItem w providerName=app4/provider1 id=9
+bgItem w providerName=app5/provider1 id=10
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/BindWidgetTest.java b/tests/src/com/android/launcher3/BindWidgetTest.java
index 5c5069f..6be2522 100644
--- a/tests/src/com/android/launcher3/BindWidgetTest.java
+++ b/tests/src/com/android/launcher3/BindWidgetTest.java
@@ -17,6 +17,7 @@
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.PackageInstallerCompat;
 import com.android.launcher3.ui.LauncherInstrumentationTestCase;
+import com.android.launcher3.util.ContentWriter;
 import com.android.launcher3.util.ManagedProfileHeuristic;
 import com.android.launcher3.widget.PendingAddWidgetInfo;
 import com.android.launcher3.widget.WidgetHostViewLoader;
@@ -218,21 +219,20 @@
         mResolver.insert(LauncherSettings.WorkspaceScreens.CONTENT_URI, v);
 
         // Insert the item
-        v = new ContentValues();
+        ContentWriter writer = new ContentWriter(mTargetContext);
         item.id = LauncherSettings.Settings.call(
                 mResolver, LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
                 .getLong(LauncherSettings.Settings.EXTRA_VALUE);
         item.screenId = screenId;
-        item.onAddToDatabase(mTargetContext, v);
-        v.put(LauncherSettings.Favorites._ID, item.id);
-        mResolver.insert(LauncherSettings.Favorites.CONTENT_URI, v);
+        item.onAddToDatabase(writer);
+        writer.put(LauncherSettings.Favorites._ID, item.id);
+        mResolver.insert(LauncherSettings.Favorites.CONTENT_URI, writer.getValues());
 
         // Reset loader
         try {
             runTestOnUiThread(new Runnable() {
                 @Override
                 public void run() {
-                    LauncherClings.markFirstRunClingDismissed(mTargetContext);
                     ManagedProfileHeuristic.markExistingUsersForNoFolderCreation(mTargetContext);
                     LauncherAppState.getInstance().getModel().resetLoadedState(true, true);
                 }
diff --git a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
new file mode 100644
index 0000000..ecb3782
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
@@ -0,0 +1,190 @@
+package com.android.launcher3.model;
+
+import android.content.ComponentName;
+import android.content.ContentProviderOperation;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.util.Pair;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.util.GridOccupancy;
+import com.android.launcher3.util.LongArrayMap;
+
+import org.mockito.ArgumentCaptor;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for {@link AddWorkspaceItemsTask}
+ */
+public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase {
+
+    private final ComponentName mComponent1 = new ComponentName("a", "b");
+    private final ComponentName mComponent2 = new ComponentName("b", "b");
+
+    private ArrayList<Long> existingScreens;
+    private ArrayList<Long> newScreens;
+    private LongArrayMap<GridOccupancy> screenOccupancy;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        existingScreens = new ArrayList<>();
+        screenOccupancy = new LongArrayMap<>();
+        newScreens = new ArrayList<>();
+
+        idp.numColumns = 5;
+        idp.numRows = 5;
+    }
+
+    private <T extends ItemInfo> AddWorkspaceItemsTask newTask(T... items) {
+        return new AddWorkspaceItemsTask(new ArrayList<>(Arrays.asList(items))) {
+
+            @Override
+            protected void addItemToDatabase(Context context, ItemInfo item,
+                    long screenId, int[] pos) {
+                item.screenId = screenId;
+                item.cellX = pos[0];
+                item.cellY = pos[1];
+            }
+
+            @Override
+            protected void updateScreens(Context context, ArrayList<Long> workspaceScreens) { }
+        };
+    }
+
+    public void testFindSpaceForItem_prefers_second() {
+        // First screen has only one hole of size 1
+        int nextId = setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
+
+        // Second screen has 2 holes of sizes 3x2 and 2x3
+        setupWorkspaceWithHoles(nextId, 2, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5));
+
+        Pair<Long, int[]> spaceFound = newTask()
+                .findSpaceForItem(appState, bgDataModel, existingScreens, newScreens, 1, 1);
+        assertEquals(2L, (long) spaceFound.first);
+        assertTrue(screenOccupancy.get(spaceFound.first)
+                .isRegionVacant(spaceFound.second[0], spaceFound.second[1], 1, 1));
+
+        // Find a larger space
+        spaceFound = newTask()
+                .findSpaceForItem(appState, bgDataModel, existingScreens, newScreens, 2, 3);
+        assertEquals(2L, (long) spaceFound.first);
+        assertTrue(screenOccupancy.get(spaceFound.first)
+                .isRegionVacant(spaceFound.second[0], spaceFound.second[1], 2, 3));
+    }
+
+    public void testFindSpaceForItem_adds_new_screen() throws Exception {
+        // First screen has 2 holes of sizes 3x2 and 2x3
+        setupWorkspaceWithHoles(1, 1, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5));
+        commitScreensToDb();
+
+        when(appState.getContext()).thenReturn(getMockContext());
+
+        ArrayList<Long> oldScreens = new ArrayList<>(existingScreens);
+        Pair<Long, int[]> spaceFound = newTask()
+                .findSpaceForItem(appState, bgDataModel, existingScreens, newScreens, 3, 3);
+        assertFalse(oldScreens.contains(spaceFound.first));
+        assertTrue(newScreens.contains(spaceFound.first));
+    }
+
+    public void testAddItem_existing_item_ignored() throws Exception {
+        ShortcutInfo info = new ShortcutInfo();
+        info.intent = new Intent().setComponent(mComponent1);
+
+        // Setup a screen with a hole
+        setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
+        commitScreensToDb();
+
+        when(appState.getContext()).thenReturn(getMockContext());
+
+        // Nothing was added
+        assertTrue(executeTaskForTest(newTask(info)).isEmpty());
+    }
+
+    public void testAddItem_some_items_added() throws Exception {
+        ShortcutInfo info = new ShortcutInfo();
+        info.intent = new Intent().setComponent(mComponent1);
+
+        ShortcutInfo info2 = new ShortcutInfo();
+        info2.intent = new Intent().setComponent(mComponent2);
+
+        // Setup a screen with a hole
+        setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
+        commitScreensToDb();
+
+        when(appState.getContext()).thenReturn(getMockContext());
+
+        executeTaskForTest(newTask(info, info2)).get(0).run();
+        ArgumentCaptor<ArrayList> notAnimated = ArgumentCaptor.forClass(ArrayList.class);
+        ArgumentCaptor<ArrayList> animated = ArgumentCaptor.forClass(ArrayList.class);
+
+        // only info2 should be added because info was already added to the workspace
+        // in setupWorkspaceWithHoles()
+        verify(callbacks).bindAppsAdded(any(ArrayList.class), notAnimated.capture(),
+                animated.capture(), any(ArrayList.class));
+        assertTrue(notAnimated.getValue().isEmpty());
+
+        assertEquals(1, animated.getValue().size());
+        assertTrue(animated.getValue().contains(info2));
+    }
+
+    private int setupWorkspaceWithHoles(int startId, long screenId, Rect... holes) {
+        GridOccupancy occupancy = new GridOccupancy(idp.numColumns, idp.numRows);
+        occupancy.markCells(0, 0, idp.numColumns, idp.numRows, true);
+        for (Rect r : holes) {
+            occupancy.markCells(r, false);
+        }
+
+        existingScreens.add(screenId);
+        screenOccupancy.append(screenId, occupancy);
+
+        for (int x = 0; x < idp.numColumns; x++) {
+            for (int y = 0; y < idp.numRows; y++) {
+                if (!occupancy.cells[x][y]) {
+                    continue;
+                }
+
+                ShortcutInfo info = new ShortcutInfo();
+                info.intent = new Intent().setComponent(mComponent1);
+                info.id = startId++;
+                info.screenId = screenId;
+                info.cellX = x;
+                info.cellY = y;
+                info.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
+                bgDataModel.addItem(info, false);
+            }
+        }
+        return startId;
+    }
+
+    private void commitScreensToDb() throws Exception {
+        LauncherSettings.Settings.call(getMockContentResolver(),
+                LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
+
+        Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
+        ArrayList<ContentProviderOperation> ops = new ArrayList<>();
+        // Clear the table
+        ops.add(ContentProviderOperation.newDelete(uri).build());
+        int count = existingScreens.size();
+        for (int i = 0; i < count; i++) {
+            ContentValues v = new ContentValues();
+            long screenId = existingScreens.get(i);
+            v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
+            v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
+            ops.add(ContentProviderOperation.newInsert(uri).withValues(v).build());
+        }
+        getMockContentResolver().applyBatch(ProviderConfig.AUTHORITY, ops);
+    }
+}
diff --git a/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
new file mode 100644
index 0000000..5628e82
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
@@ -0,0 +1,208 @@
+package com.android.launcher3.model;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.support.test.InstrumentationRegistry;
+import android.test.ProviderTestCase2;
+
+import com.android.launcher3.AllAppsList;
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.DeferredHandler;
+import com.android.launcher3.IconCache;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.LauncherModel.BaseModelUpdateTask;
+import com.android.launcher3.compat.LauncherActivityInfoCompat;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.TestLauncherProvider;
+
+import org.mockito.ArgumentCaptor;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.List;
+
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Base class for writing tests for Model update tasks.
+ */
+public class BaseModelUpdateTaskTestCase extends ProviderTestCase2<TestLauncherProvider> {
+
+    public final HashMap<Class, HashMap<String, Field>> fieldCache = new HashMap<>();
+
+    public Context targetContext;
+    public UserHandleCompat myUser;
+
+    public InvariantDeviceProfile idp;
+    public LauncherAppState appState;
+    public MyIconCache iconCache;
+
+    public BgDataModel bgDataModel;
+    public AllAppsList allAppsList;
+    public Callbacks callbacks;
+
+    public BaseModelUpdateTaskTestCase() {
+        super(TestLauncherProvider.class, ProviderConfig.AUTHORITY);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        callbacks = mock(Callbacks.class);
+        appState = mock(LauncherAppState.class);
+        myUser = UserHandleCompat.myUserHandle();
+
+        bgDataModel = new BgDataModel();
+        targetContext = InstrumentationRegistry.getTargetContext();
+        idp = new InvariantDeviceProfile();
+        iconCache = new MyIconCache(targetContext, idp);
+
+        allAppsList = new AllAppsList(iconCache, null);
+
+        when(appState.getIconCache()).thenReturn(iconCache);
+        when(appState.getInvariantDeviceProfile()).thenReturn(idp);
+    }
+
+    /**
+     * Synchronously executes the task and returns all the UI callbacks posted.
+     */
+    public List<Runnable> executeTaskForTest(BaseModelUpdateTask task) throws Exception {
+        LauncherModel mockModel = mock(LauncherModel.class);
+        when(mockModel.getCallback()).thenReturn(callbacks);
+
+        Field f = BaseModelUpdateTask.class.getDeclaredField("mModel");
+        f.setAccessible(true);
+        f.set(task, mockModel);
+
+        DeferredHandler mockHandler = mock(DeferredHandler.class);
+        f = BaseModelUpdateTask.class.getDeclaredField("mUiHandler");
+        f.setAccessible(true);
+        f.set(task, mockHandler);
+
+        task.execute(appState, bgDataModel, allAppsList);
+        ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
+        verify(mockHandler, atLeast(0)).post(captor.capture());
+
+        return captor.getAllValues();
+    }
+
+    /**
+     * Initializes mock data for the test.
+     */
+    public void initializeData(String resourceName) throws Exception {
+        Context myContext = InstrumentationRegistry.getContext();
+        Resources res = myContext.getResources();
+        int id = res.getIdentifier(resourceName, "raw", myContext.getPackageName());
+        try (BufferedReader reader =
+                     new BufferedReader(new InputStreamReader(res.openRawResource(id)))) {
+            String line;
+            HashMap<String, Class> classMap = new HashMap<>();
+            while((line = reader.readLine()) != null) {
+                line = line.trim();
+                if (line.startsWith("#") || line.isEmpty()) {
+                    continue;
+                }
+                String[] commands = line.split(" ");
+                switch (commands[0]) {
+                    case "classMap":
+                        classMap.put(commands[1], Class.forName(commands[2]));
+                        break;
+                    case "bgItem":
+                        bgDataModel.addItem(
+                                (ItemInfo) initItem(classMap.get(commands[1]), commands, 2), false);
+                        break;
+                    case "allApps":
+                        allAppsList.add((AppInfo) initItem(AppInfo.class, commands, 1));
+                        break;
+                }
+            }
+        }
+    }
+
+    private Object initItem(Class clazz, String[] fieldDef, int startIndex) throws Exception {
+        HashMap<String, Field> cache = fieldCache.get(clazz);
+        if (cache == null) {
+            cache = new HashMap<>();
+            Class c = clazz;
+            while (c != null) {
+                for (Field f : c.getDeclaredFields()) {
+                    f.setAccessible(true);
+                    cache.put(f.getName(), f);
+                }
+                c = c.getSuperclass();
+            }
+            fieldCache.put(clazz, cache);
+        }
+
+        Object item = clazz.newInstance();
+        for (int i = startIndex; i < fieldDef.length; i++) {
+            String[] fieldData = fieldDef[i].split("=", 2);
+            Field f = cache.get(fieldData[0]);
+            Class type = f.getType();
+            if (type == int.class || type == long.class) {
+                f.set(item, Integer.parseInt(fieldData[1]));
+            } else if (type == CharSequence.class || type == String.class) {
+                f.set(item, fieldData[1]);
+            } else if (type == Intent.class) {
+                if (!fieldData[1].startsWith("#Intent")) {
+                    fieldData[1] = "#Intent;" + fieldData[1] + ";end";
+                }
+                f.set(item, Intent.parseUri(fieldData[1], 0));
+            } else if (type == ComponentName.class) {
+                f.set(item, ComponentName.unflattenFromString(fieldData[1]));
+            } else {
+                throw new Exception("Added parsing logic for "
+                        + f.getName() + " of type " + f.getType());
+            }
+        }
+        return item;
+    }
+
+    public static class MyIconCache extends IconCache {
+
+        private final HashMap<ComponentKey, CacheEntry> mCache = new HashMap<>();
+
+        public MyIconCache(Context context, InvariantDeviceProfile idp) {
+            super(context, idp);
+        }
+
+        @Override
+        protected CacheEntry cacheLocked(ComponentName componentName,
+                LauncherActivityInfoCompat info, UserHandleCompat user,
+                boolean usePackageIcon, boolean useLowResIcon) {
+            CacheEntry entry = mCache.get(new ComponentKey(componentName, user));
+            if (entry == null) {
+                entry = new CacheEntry();
+                entry.icon = getDefaultIcon(user);
+            }
+            return entry;
+        }
+
+        public void addCache(ComponentName key, String title) {
+            CacheEntry entry = new CacheEntry();
+            entry.icon = newIcon();
+            entry.title = title;
+            mCache.put(new ComponentKey(key, UserHandleCompat.myUserHandle()), entry);
+        }
+
+        public Bitmap newIcon() {
+            return Bitmap.createBitmap(1, 1, Config.ARGB_8888);
+        }
+    }
+}
diff --git a/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java b/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
new file mode 100644
index 0000000..25b8df9
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
@@ -0,0 +1,81 @@
+package com.android.launcher3.model;
+
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.IconCache;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.ShortcutInfo;
+
+import java.util.Arrays;
+import java.util.HashSet;
+
+import static org.mockito.Mockito.mock;
+
+/**
+ * Tests for {@link CacheDataUpdatedTask}
+ */
+public class CacheDataUpdatedTaskTest extends BaseModelUpdateTaskTestCase {
+
+    private static final String NEW_LABEL_PREFIX = "new-label-";
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        initializeData("cache_data_updated_task_data");
+        // Add dummy entries in the cache to simulate update
+        for (ItemInfo info : bgDataModel.itemsIdMap) {
+            iconCache.addCache(info.getTargetComponent(), NEW_LABEL_PREFIX + info.id);
+        }
+    }
+
+    private CacheDataUpdatedTask newTask(int op, String... pkg) {
+        return new CacheDataUpdatedTask(op, myUser, new HashSet<>(Arrays.asList(pkg)));
+    }
+
+    public void testCacheUpdate_update_apps() throws Exception {
+        executeTaskForTest(newTask(CacheDataUpdatedTask.OP_CACHE_UPDATE, "app1"));
+
+        // Verify that only the app icons of app1 (id 1 & 2) are updated. Custom shortcut (id 7)
+        // is not updated
+        verifyUpdate(1L, 2L);
+
+        // Verify that only app1 var updated in allAppsList
+        assertFalse(allAppsList.data.isEmpty());
+        for (AppInfo info : allAppsList.data) {
+            if (info.componentName.getPackageName().equals("app1")) {
+                assertNotNull(info.iconBitmap);
+            } else {
+                assertNull(info.iconBitmap);
+            }
+        }
+    }
+
+    public void testSessionUpdate_ignores_normal_apps() throws Exception {
+        executeTaskForTest(newTask(CacheDataUpdatedTask.OP_SESSION_UPDATE, "app1"));
+
+        // app1 has no restored shortcuts. Verify that nothing was updated.
+        verifyUpdate();
+    }
+
+    public void testSessionUpdate_updates_pending_apps() throws Exception {
+        executeTaskForTest(newTask(CacheDataUpdatedTask.OP_SESSION_UPDATE, "app3"));
+
+        // app3 has only restored apps (id 5, 6) and shortcuts (id 9). Verify that only apps were
+        // were updated
+        verifyUpdate(5L, 6L);
+    }
+
+    private void verifyUpdate(Long... idsUpdated) {
+        HashSet<Long> updates = new HashSet<>(Arrays.asList(idsUpdated));
+        IconCache noOpIconCache = mock(IconCache.class);
+        for (ItemInfo info : bgDataModel.itemsIdMap) {
+            if (updates.contains(info.id)) {
+                assertEquals(NEW_LABEL_PREFIX + info.id, info.title);
+                assertNotNull(((ShortcutInfo) info).getIcon(noOpIconCache));
+            } else {
+                assertNotSame(NEW_LABEL_PREFIX + info.id, info.title);
+                assertNull(((ShortcutInfo) info).getIcon(noOpIconCache));
+            }
+        }
+    }
+}
diff --git a/tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java b/tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
new file mode 100644
index 0000000..d655562
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
@@ -0,0 +1,61 @@
+package com.android.launcher3.model;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.compat.PackageInstallerCompat;
+import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
+
+import java.util.Arrays;
+import java.util.HashSet;
+
+/**
+ * Tests for {@link PackageInstallStateChangedTask}
+ */
+public class PackageInstallStateChangedTaskTest extends BaseModelUpdateTaskTestCase {
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        initializeData("package_install_state_change_task_data");
+    }
+
+    private PackageInstallStateChangedTask newTask(String pkg, int progress) {
+        PackageInstallInfo installInfo = new PackageInstallInfo(pkg);
+        installInfo.progress = progress;
+        installInfo.state = PackageInstallerCompat.STATUS_INSTALLING;
+        return new PackageInstallStateChangedTask(installInfo);
+    }
+
+    public void testSessionUpdate_ignore_installed() throws Exception {
+        executeTaskForTest(newTask("app1", 30));
+
+        // No shortcuts were updated
+        verifyProgressUpdate(0);
+    }
+
+    public void testSessionUpdate_shortcuts_updated() throws Exception {
+        executeTaskForTest(newTask("app3", 30));
+
+        verifyProgressUpdate(30, 5L, 6L, 7L);
+    }
+
+    public void testSessionUpdate_widgets_updated() throws Exception {
+        executeTaskForTest(newTask("app4", 30));
+
+        verifyProgressUpdate(30, 8L, 9L);
+    }
+
+    private void verifyProgressUpdate(int progress, Long... idsUpdated) {
+        HashSet<Long> updates = new HashSet<>(Arrays.asList(idsUpdated));
+        for (ItemInfo info : bgDataModel.itemsIdMap) {
+            if (info instanceof ShortcutInfo) {
+                assertEquals(updates.contains(info.id) ? progress: 0,
+                        ((ShortcutInfo) info).getInstallProgress());
+            } else {
+                assertEquals(updates.contains(info.id) ? progress: -1,
+                        ((LauncherAppWidgetInfo) info).installProgress);
+            }
+        }
+    }
+}
diff --git a/tests/src/com/android/launcher3/ui/AddWidgetTest.java b/tests/src/com/android/launcher3/ui/AddWidgetTest.java
index a0ca60c..d536af2 100644
--- a/tests/src/com/android/launcher3/ui/AddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/AddWidgetTest.java
@@ -5,7 +5,6 @@
 import android.test.suitebuilder.annotation.LargeTest;
 import android.view.View;
 
-import com.android.launcher3.CellLayout;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppWidgetInfo;
@@ -51,7 +50,7 @@
         // Drag widget to homescreen
         UiObject2 widget = scrollAndFind(widgetContainer, By.clazz(WidgetCell.class)
                 .hasDescendant(By.text(widgetInfo.getLabel(mTargetContext.getPackageManager()))));
-        dragToWorkspace(widget);
+        dragToWorkspace(widget, false);
 
         assertNotNull(launcher.getWorkspace().getFirstMatch(new ItemOperator() {
             @Override
diff --git a/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java b/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java
index 56fc90a..f45710c 100644
--- a/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java
+++ b/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java
@@ -47,7 +47,7 @@
 
         // Drag icon to homescreen.
         UiObject2 icon = scrollAndFind(appsContainer, By.text(mSettingsApp.getLabel().toString()));
-        dragToWorkspace(icon);
+        dragToWorkspace(icon, true);
 
         // Verify that the icon works on homescreen.
         mDevice.findObject(By.text(mSettingsApp.getLabel().toString())).click();
diff --git a/tests/src/com/android/launcher3/ui/LauncherInstrumentationTestCase.java b/tests/src/com/android/launcher3/ui/LauncherInstrumentationTestCase.java
index e858d17..fcf7122 100644
--- a/tests/src/com/android/launcher3/ui/LauncherInstrumentationTestCase.java
+++ b/tests/src/com/android/launcher3/ui/LauncherInstrumentationTestCase.java
@@ -23,7 +23,6 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.LauncherClings;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
@@ -141,29 +140,52 @@
     /**
      * Drags an icon to the center of homescreen.
      */
-    protected void dragToWorkspace(UiObject2 icon) {
+    protected void dragToWorkspace(UiObject2 icon, boolean expectedToShowShortcuts) {
         Point center = icon.getVisibleCenter();
 
         // Action Down
         sendPointer(MotionEvent.ACTION_DOWN, center);
 
-        // Wait until "Remove/Delete target is visible
+        UiObject2 dragLayer = findViewById(R.id.drag_layer);
+
+        if (expectedToShowShortcuts) {
+            // Make sure shortcuts show up, and then move a bit to hide them.
+            assertNotNull(findViewById(R.id.deep_shortcuts_container));
+
+            Point moveLocation = new Point(center);
+            int distanceToMove = mTargetContext.getResources().getDimensionPixelSize(
+                    R.dimen.deep_shortcuts_start_drag_threshold) + 50;
+            if (moveLocation.y - distanceToMove >= dragLayer.getVisibleBounds().top) {
+                moveLocation.y -= distanceToMove;
+            } else {
+                moveLocation.y += distanceToMove;
+            }
+            movePointer(center, moveLocation);
+
+            assertNull(findViewById(R.id.deep_shortcuts_container));
+        }
+
+        // Wait until Remove/Delete target is visible
         assertNotNull(findViewById(R.id.delete_target_text));
 
-        Point moveLocation = findViewById(R.id.drag_layer).getVisibleCenter();
+        Point moveLocation = dragLayer.getVisibleCenter();
 
         // Move to center
-        while(!moveLocation.equals(center)) {
-            center.x = getNextMoveValue(moveLocation.x, center.x);
-            center.y = getNextMoveValue(moveLocation.y, center.y);
-            sendPointer(MotionEvent.ACTION_MOVE, center);
-        }
+        movePointer(center, moveLocation);
         sendPointer(MotionEvent.ACTION_UP, center);
 
         // Wait until remove target is gone.
         mDevice.wait(Until.gone(getSelectorForId(R.id.delete_target_text)), DEFAULT_UI_TIMEOUT);
     }
 
+    private void movePointer(Point from, Point to) {
+        while(!from.equals(to)) {
+            from.x = getNextMoveValue(to.x, from.x);
+            from.y = getNextMoveValue(to.y, from.y);
+            sendPointer(MotionEvent.ACTION_MOVE, from);
+        }
+    }
+
     private int getNextMoveValue(int targetValue, int oldValue) {
         if (targetValue - oldValue > 10) {
             return oldValue + 10;
@@ -174,7 +196,7 @@
         }
     }
 
-    private void sendPointer(int action, Point point) {
+    protected void sendPointer(int action, Point point) {
         MotionEvent event = MotionEvent.obtain(SystemClock.uptimeMillis(),
                 SystemClock.uptimeMillis(), action, point.x, point.y, 0);
         getInstrumentation().sendPointerSync(event);
@@ -189,7 +211,6 @@
                 LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
         LauncherSettings.Settings.call(mTargetContext.getContentResolver(),
                 LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG);
-        LauncherClings.markFirstRunClingDismissed(mTargetContext);
         ManagedProfileHeuristic.markExistingUsersForNoFolderCreation(mTargetContext);
 
         runTestOnUiThread(new Runnable() {
diff --git a/tests/src/com/android/launcher3/ui/ShortcutsLaunchTest.java b/tests/src/com/android/launcher3/ui/ShortcutsLaunchTest.java
new file mode 100644
index 0000000..f892e63
--- /dev/null
+++ b/tests/src/com/android/launcher3/ui/ShortcutsLaunchTest.java
@@ -0,0 +1,68 @@
+package com.android.launcher3.ui;
+
+import android.graphics.Point;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.view.MotionEvent;
+
+import com.android.launcher3.R;
+import com.android.launcher3.compat.LauncherActivityInfoCompat;
+import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.util.Condition;
+import com.android.launcher3.util.Wait;
+
+/**
+ * Test for verifying that shortcuts are shown and can be launched after long pressing an app
+ */
+@LargeTest
+public class ShortcutsLaunchTest extends LauncherInstrumentationTestCase {
+
+    private LauncherActivityInfoCompat mSettingsApp;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mSettingsApp = LauncherAppsCompat.getInstance(mTargetContext)
+                .getActivityList("com.android.settings", UserHandleCompat.myUserHandle()).get(0);
+    }
+
+    public void testAppLauncher_portrait() throws Exception {
+        lockRotation(true);
+        performTest();
+    }
+
+    public void testAppLauncher_landscape() throws Exception {
+        lockRotation(false);
+        performTest();
+    }
+
+    private void performTest() throws Exception {
+        startLauncher();
+
+        // Open all apps and wait for load complete
+        final UiObject2 appsContainer = openAllApps();
+        assertTrue(Wait.atMost(Condition.minChildCount(appsContainer, 2), DEFAULT_UI_TIMEOUT));
+
+        // Find settings app and verify shortcuts appear when long pressed
+        UiObject2 icon = scrollAndFind(appsContainer, By.text(mSettingsApp.getLabel().toString()));
+        // Press icon center until shortcuts appear
+        Point iconCenter = icon.getVisibleCenter();
+        sendPointer(MotionEvent.ACTION_DOWN, iconCenter);
+        UiObject2 deepShortcutsContainer = findViewById(R.id.deep_shortcuts_container);
+        assertNotNull(deepShortcutsContainer);
+        sendPointer(MotionEvent.ACTION_UP, iconCenter);
+
+        // Verify that launching a shortcut opens a page with the same text
+        assertTrue(deepShortcutsContainer.getChildCount() > 0);
+        UiObject2 shortcut = deepShortcutsContainer.getChildren().get(0)
+                .findObject(getSelectorForId(R.id.deep_shortcut));
+        shortcut.click();
+        assertTrue(mDevice.wait(Until.hasObject(By.pkg(
+                mSettingsApp.getComponentName().getPackageName())
+                .text(shortcut.getText())), DEFAULT_UI_TIMEOUT));
+    }
+}
diff --git a/tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java b/tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java
new file mode 100644
index 0000000..f9a0e6e
--- /dev/null
+++ b/tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java
@@ -0,0 +1,74 @@
+package com.android.launcher3.ui;
+
+import android.graphics.Point;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.view.MotionEvent;
+
+import com.android.launcher3.R;
+import com.android.launcher3.compat.LauncherActivityInfoCompat;
+import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.util.Condition;
+import com.android.launcher3.util.Wait;
+
+/**
+ * Test for dragging a deep shortcut to the home screen.
+ */
+@LargeTest
+public class ShortcutsToHomeTest extends LauncherInstrumentationTestCase {
+
+    private LauncherActivityInfoCompat mSettingsApp;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mSettingsApp = LauncherAppsCompat.getInstance(mTargetContext)
+                .getActivityList("com.android.settings", UserHandleCompat.myUserHandle()).get(0);
+    }
+
+    public void testDragIcon_portrait() throws Throwable {
+        lockRotation(true);
+        performTest();
+    }
+
+    public void testDragIcon_landscape() throws Throwable {
+        lockRotation(false);
+        performTest();
+    }
+
+    private void performTest() throws Throwable {
+        clearHomescreen();
+        startLauncher();
+
+        // Open all apps and wait for load complete.
+        final UiObject2 appsContainer = openAllApps();
+        assertTrue(Wait.atMost(Condition.minChildCount(appsContainer, 2), DEFAULT_UI_TIMEOUT));
+
+        // Find the app and long press it to show shortcuts.
+        UiObject2 icon = scrollAndFind(appsContainer, By.text(mSettingsApp.getLabel().toString()));
+        // Press icon center until shortcuts appear
+        Point iconCenter = icon.getVisibleCenter();
+        sendPointer(MotionEvent.ACTION_DOWN, iconCenter);
+        UiObject2 deepShortcutsContainer = findViewById(R.id.deep_shortcuts_container);
+        assertNotNull(deepShortcutsContainer);
+        sendPointer(MotionEvent.ACTION_UP, iconCenter);
+
+        // Drag the first shortcut to the home screen.
+        assertTrue(deepShortcutsContainer.getChildCount() > 0);
+        UiObject2 shortcut = deepShortcutsContainer.getChildren().get(0)
+                .findObject(getSelectorForId(R.id.deep_shortcut));
+        String shortcutName = shortcut.getText();
+        dragToWorkspace(shortcut, false);
+
+        // Verify that the shortcut works on home screen
+        // (the app opens and has the same text as the shortcut).
+        mDevice.findObject(By.text(shortcutName)).click();
+        assertTrue(mDevice.wait(Until.hasObject(By.pkg(
+                mSettingsApp.getComponentName().getPackageName())
+                .text(shortcutName)), DEFAULT_UI_TIMEOUT));
+    }
+}
diff --git a/tests/src/com/android/launcher3/util/FocusLogicTest.java b/tests/src/com/android/launcher3/util/FocusLogicTest.java
index eee567f..79aed80 100644
--- a/tests/src/com/android/launcher3/util/FocusLogicTest.java
+++ b/tests/src/com/android/launcher3/util/FocusLogicTest.java
@@ -49,8 +49,6 @@
          assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_MOVE_END));
          assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_PAGE_UP));
          assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_PAGE_DOWN));
-         assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_DEL));
-         assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_FORWARD_DEL));
     }
 
     public void testCreateSparseMatrix() {