merged ub-launcher3-dorval, and resolved conflicts
Bug: 36904684
Bug: 37929893
Bug: 36068989

Test: make -j 32 dist checkbuild
Change-Id: If9b11b212852cb1048d54db2224dab4acf2d93e0
diff --git a/Android.mk b/Android.mk
index ad8ce02..713d082 100644
--- a/Android.mk
+++ b/Android.mk
@@ -35,12 +35,14 @@
 
 LOCAL_RESOURCE_DIR := \
     $(LOCAL_PATH)/res \
-    frameworks/support/v7/recyclerview/res
+    prebuilts/sdk/current/support/v7/recyclerview/res \
 
 LOCAL_PROGUARD_FLAG_FILES := proguard.flags
 
 LOCAL_PROTOC_OPTIMIZE_TYPE := nano
 LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/protos/
+LOCAL_PROTO_JAVA_OUTPUT_PARAMS := enum_style=java
+
 LOCAL_AAPT_FLAGS := \
     --auto-add-overlay \
     --extra-packages android.support.v7.recyclerview \
@@ -48,6 +50,7 @@
 LOCAL_SDK_VERSION := current
 LOCAL_MIN_SDK_VERSION := 21
 LOCAL_PACKAGE_NAME := Launcher3
+LOCAL_PRIVILEGED_MODULE := true
 LOCAL_OVERRIDES_PACKAGES := Home Launcher2
 
 LOCAL_FULL_LIBS_MANIFEST_FILES := $(LOCAL_PATH)/AndroidManifest-common.xml
@@ -65,6 +68,7 @@
 
 LOCAL_PROTOC_OPTIMIZE_TYPE := nano
 LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/protos/
+LOCAL_PROTO_JAVA_OUTPUT_PARAMS := enum_style=java
 
 LOCAL_MODULE_TAGS := optional
 LOCAL_MODULE := launcher_proto_lib
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index bbe1f4a..3a60a98 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -49,7 +49,7 @@
         android:fullBackupOnly="true"
         android:fullBackupContent="@xml/backupscheme"
         android:hardwareAccelerated="true"
-        android:icon="@mipmap/ic_launcher_home"
+        android:icon="@drawable/ic_launcher_home"
         android:label="@string/derived_app_name"
         android:largeHeap="@bool/config_largeHeap"
         android:restoreAnyVersion="true"
@@ -58,12 +58,21 @@
         <!-- Intent received used to install shortcuts from other applications -->
         <receiver
             android:name="com.android.launcher3.InstallShortcutReceiver"
-            android:permission="com.android.launcher.permission.INSTALL_SHORTCUT">
+            android:permission="com.android.launcher.permission.INSTALL_SHORTCUT"
+            android:enabled="@bool/enable_install_shortcut_api" >
             <intent-filter>
                 <action android:name="com.android.launcher.action.INSTALL_SHORTCUT" />
             </intent-filter>
         </receiver>
 
+        <!-- Intent received when a session is committed -->
+        <receiver
+            android:name="com.android.launcher3.SessionCommitReceiver" >
+            <intent-filter>
+                <action android:name="android.content.pm.action.SESSION_COMMITTED" />
+            </intent-filter>
+        </receiver>
+
         <!-- Intent received used to initialize a restored widget -->
         <receiver android:name="com.android.launcher3.AppWidgetsRestoredReceiver" >
             <intent-filter>
@@ -76,8 +85,35 @@
             android:process=":wallpaper_chooser">
         </service>
 
+        <service android:name="com.android.launcher3.notification.NotificationListener"
+                 android:enabled="@bool/notification_badging_enabled"
+                 android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
+            <intent-filter>
+                <action android:name="android.service.notification.NotificationListenerService" />
+            </intent-filter>
+        </service>
+
         <meta-data android:name="android.nfc.disable_beam_default"
                        android:value="true" />
 
+        <activity android:name="com.android.launcher3.dragndrop.AddItemActivity"
+            android:theme="@android:style/Theme.DeviceDefault.Light.Dialog.Alert"
+            android:excludeFromRecents="true"
+            android:autoRemoveFromRecents="true"
+            android:label="@string/action_add_to_workspace" >
+            <intent-filter>
+                <action android:name="android.content.pm.action.CONFIRM_PIN_SHORTCUT" />
+                <action android:name="android.content.pm.action.CONFIRM_PIN_APPWIDGET" />
+            </intent-filter>
+        </activity>
+
+        <!--
+        Should point to the content provider which can be used to dump Launcher3 compatible
+        worspace configuration to the dump's file descriptor by using launcher_dump.proto
+        -->
+        <meta-data
+            android:name="com.android.launcher3.launcher_dump_provider"
+            android:value="com.android.launcher3.LauncherProvider" />
+
     </application>
 </manifest>
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 6c5990d..bcb522b 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -54,8 +54,9 @@
         android:fullBackupOnly="true"
         android:fullBackupContent="@xml/backupscheme"
         android:hardwareAccelerated="true"
-        android:icon="@mipmap/ic_launcher_home"
+        android:icon="@drawable/ic_launcher_home"
         android:label="@string/derived_app_name"
+        android:theme="@style/LauncherTheme"
         android:largeHeap="@bool/config_largeHeap"
         android:restoreAnyVersion="true"
         android:supportsRtl="true" >
@@ -69,10 +70,10 @@
             android:launchMode="singleTask"
             android:clearTaskOnLaunch="true"
             android:stateNotNeeded="true"
-            android:theme="@style/LauncherTheme"
-            android:windowSoftInputMode="adjustPan"
+            android:windowSoftInputMode="adjustPan|stateUnchanged"
             android:screenOrientation="nosensor"
             android:configChanges="keyboard|keyboardHidden|navigation"
+            android:resizeableActivity="true"
             android:resumeWhilePausing="true"
             android:taskAffinity=""
             android:enabled="true">
@@ -90,6 +91,7 @@
         <activity
             android:name="com.android.launcher3.SettingsActivity"
             android:label="@string/settings_button_text"
+            android:theme="@android:style/Theme.DeviceDefault.Settings"
             android:autoRemoveFromRecents="true">
             <intent-filter>
                 <action android:name="android.intent.action.APPLICATION_PREFERENCES" />
@@ -133,7 +135,7 @@
             android:theme="@android:style/Theme.NoDisplay"
             android:label="* HPROF"
             android:excludeFromRecents="true"
-            android:icon="@mipmap/ic_launcher_home"
+            android:icon="@drawable/ic_launcher_home"
             >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -145,7 +147,7 @@
         <activity
             android:name="com.android.launcher3.testing.ToggleWeightWatcher"
             android:label="Show Mem"
-            android:icon="@mipmap/ic_launcher_home">
+            android:icon="@drawable/ic_launcher_home">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.DEFAULT" />
diff --git a/build.gradle b/build.gradle
index e103d79..9c71693 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,9 +1,10 @@
 buildscript {
     repositories {
         mavenCentral()
+        jcenter()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:2.1.3'
+        classpath 'com.android.tools.build:gradle:2.3.1'
         classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.0'
     }
 }
@@ -12,12 +13,12 @@
 apply plugin: 'com.google.protobuf'
 
 android {
-    compileSdkVersion 25
-    buildToolsVersion '24.0.0'
+    compileSdkVersion 26
+    buildToolsVersion '26.0.0'
 
     defaultConfig {
         minSdkVersion 21
-        targetSdkVersion 25
+        targetSdkVersion 26
         versionCode 1
         versionName "1.0"
 
@@ -45,29 +46,39 @@
 
         androidTest {
             java.srcDirs = ['tests/src']
-            manifest.srcFile "tests/AndroidManifest.xml"
+            res.srcDirs = ['tests/res']
+            manifest.srcFile "tests/AndroidManifest-common.xml"
         }
 
         aosp {
             manifest.srcFile "AndroidManifest.xml"
         }
+
+        aospAndroidTest {
+            manifest.srcFile "tests/AndroidManifest.xml"
+        }
     }
 }
 
 repositories {
     mavenCentral()
+    jcenter()
 }
 
+final String SUPPORT_LIBS_VERSION = '26.0.0-SNAPSHOT'
 dependencies {
-    compile 'com.android.support:support-v4:23.1.1'
-    compile 'com.android.support:recyclerview-v7:23.1.1'
-    compile 'com.android.support:palette-v7:23.2.0'
+    compile "com.android.support:support-v4:${SUPPORT_LIBS_VERSION}"
+    compile "com.android.support:recyclerview-v7:${SUPPORT_LIBS_VERSION}"
+    compile "com.android.support:palette-v7:${SUPPORT_LIBS_VERSION}"
     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'
+    androidTestCompile "com.android.support:support-annotations:${SUPPORT_LIBS_VERSION}"
 }
 
 protobuf {
@@ -80,7 +91,8 @@
                 task.builtins {
                     remove java
                     javanano {
-                        option 'ignore_services=false'
+                        option "java_package=launcher_log.proto|com.android.launcher3.userevent.nano"
+                        option "enum_style=java"
                     }
                 }
             }
diff --git a/proguard.flags b/proguard.flags
index c5e9db1..6cbab08 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -82,3 +82,15 @@
   *;
 }
 
+# Proguard will strip methods required for talkback to properly scroll to
+# 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 class android.support.v7.widget.RecyclerView { *; }
+
+-keep interface com.android.launcher3.userevent.nano.LauncherLogProto.** {
+  *;
+}
diff --git a/protos/launcher_dump.proto b/protos/launcher_dump.proto
new file mode 100644
index 0000000..dc8fbda
--- /dev/null
+++ b/protos/launcher_dump.proto
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+syntax = "proto2";
+
+option java_package = "com.android.launcher3.model";
+option java_outer_classname = "LauncherDumpProto";
+
+package model;
+
+message DumpTarget {
+  enum Type {
+    NONE = 0;
+    ITEM = 1;
+    CONTAINER = 2;
+  }
+
+  optional Type type = 1;
+  optional int32 page_id = 2;
+  optional int32 grid_x = 3;
+  optional int32 grid_y = 4;
+
+  // For container types only
+  optional ContainerType container_type = 5;
+
+  // For item types only
+  optional ItemType item_type = 6;
+
+  optional string package_name = 7; // All ItemTypes except UNKNOWN type
+  optional string component = 8;   // All ItemTypes except UNKNOWN type
+  optional string item_id = 9; // For Pinned Shortcuts and appWidgetId
+
+  optional int32 span_x = 10 [default = 1];// Used for ItemType.WIDGET
+  optional int32 span_y = 11 [default = 1];// Used for ItemType.WIDGET
+  optional UserType user_type = 12;
+}
+
+// Used to define what type of item a Target would represent.
+enum ItemType {
+  UNKNOWN_ITEMTYPE = 0;  // Launcher specific items
+  APP_ICON = 1; // Regular app icons
+  WIDGET = 2;   // Elements from AppWidgetManager
+  SHORTCUT = 3; // ShortcutManager
+}
+
+// Used to define what type of container a Target would represent.
+enum ContainerType {
+  UNKNOWN_CONTAINERTYPE = 0;
+  WORKSPACE = 1;
+  HOTSEAT = 2;
+  FOLDER = 3;
+}
+
+// Used to define what type of control a Target would represent.
+enum UserType {
+  DEFAULT = 0;
+  WORK = 1;
+}
+
+// Main message;
+message LauncherImpression {
+  repeated DumpTarget targets = 1;
+}
diff --git a/protos/launcher_log.proto b/protos/launcher_log.proto
index 6b27559..909a429 100644
--- a/protos/launcher_log.proto
+++ b/protos/launcher_log.proto
@@ -63,6 +63,8 @@
   FOLDER_ICON = 4;
   DEEPSHORTCUT = 5;
   SEARCHBOX = 6;
+  EDITTEXT = 7;
+  NOTIFICATION = 8;
 }
 
 // Used to define what type of container a Target would represent.
@@ -77,6 +79,7 @@
   PREDICTION = 7;
   SEARCHRESULT = 8;
   DEEPSHORTCUTS = 9;
+  PINITEM = 10;    // confirmation screen
 }
 
 // Used to define what type of control a Target would represent.
@@ -91,7 +94,9 @@
   APPINFO_TARGET = 7;
   RESIZE_HANDLE = 8;
   VERTICAL_SCROLL = 9;
-  // HOME, BACK, GO_TO_PLAYSTORE
+  HOME_INTENT = 10; // Deprecated, use enum Command instead
+  BACK_BUTTON = 11; // Deprecated, use enum Command instead
+  // GO_TO_PLAYSTORE
 }
 
 // Used to define the action component of the LauncherEvent.
@@ -99,6 +104,7 @@
   enum Type {
     TOUCH = 0;
     AUTOMATED = 1;
+    COMMAND = 2;
     // SOFT_KEYBOARD, HARD_KEYBOARD, ASSIST
   }
   enum Touch {
@@ -116,9 +122,20 @@
     LEFT = 3;
     RIGHT = 4;
   }
+  enum Command {
+    HOME_INTENT = 0;
+    BACK = 1;
+    ENTRY = 2;    // Indicates entry to one of Launcher container type target
+                  // not using the HOME_INTENT
+    CANCEL = 3;   // Indicates that a confirmation screen was cancelled
+    CONFIRM = 4;  // Indicates thata confirmation screen was accepted
+  }
   optional Type type = 1;
   optional Touch touch = 2;
   optional Direction dir = 3;
+  optional Command command = 4;
+  // Log if the action was performed on outside of the container
+  optional bool is_outside = 5;
 }
 
 //
@@ -137,4 +154,6 @@
   optional int64 action_duration_millis = 4;
   optional int64 elapsed_container_millis = 5;
   optional int64 elapsed_session_millis = 6;
+
+  optional bool is_in_multi_window_mode = 7;
 }
diff --git a/res/animator/all_apps_fastscroll_icon_anim.xml b/res/animator/all_apps_fastscroll_icon_anim.xml
new file mode 100644
index 0000000..380b009
--- /dev/null
+++ b/res/animator/all_apps_fastscroll_icon_anim.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2017, The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:state_activated="true">
+        <set>
+            <objectAnimator
+                android:duration="225"
+                android:propertyName="scaleX"
+                android:valueTo="1.15"
+                android:valueType="floatType" />
+            <objectAnimator
+                android:duration="225"
+                android:propertyName="scaleY"
+                android:valueTo="1.15"
+                android:valueType="floatType" />
+        </set>
+    </item>
+
+    <item>
+        <set>
+            <objectAnimator
+                android:duration="275"
+                android:propertyName="scaleX"
+                android:valueTo="1"
+                android:valueType="floatType" />
+            <objectAnimator
+                android:duration="275"
+                android:propertyName="scaleY"
+                android:valueTo="1"
+                android:valueType="floatType" />
+        </set>
+    </item>
+
+</selector>
\ No newline at end of file
diff --git a/res/animator-v21/overview_button_anim.xml b/res/animator/overview_button_anim.xml
similarity index 81%
rename from res/animator-v21/overview_button_anim.xml
rename to res/animator/overview_button_anim.xml
index aac3d26..585f165 100644
--- a/res/animator-v21/overview_button_anim.xml
+++ b/res/animator/overview_button_anim.xml
@@ -24,13 +24,6 @@
             android:valueType="floatType" />
     </item>
 
-    <item android:state_focused="true">
-        <objectAnimator
-            android:duration="@android:integer/config_shortAnimTime"
-            android:propertyName="alpha"
-            android:valueTo="0.5"
-            android:valueType="floatType" />
-    </item>
     <item>
         <objectAnimator
             android:duration="@android:integer/config_shortAnimTime"
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/page_hover_left.9.png b/res/drawable-hdpi/page_hover_left.9.png
deleted file mode 100644
index 3f11d0b..0000000
--- a/res/drawable-hdpi/page_hover_left.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/page_hover_left_active.9.png b/res/drawable-hdpi/page_hover_left_active.9.png
deleted file mode 100644
index abe4c31..0000000
--- a/res/drawable-hdpi/page_hover_left_active.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/page_hover_right.9.png b/res/drawable-hdpi/page_hover_right.9.png
deleted file mode 100644
index 3bcf191..0000000
--- a/res/drawable-hdpi/page_hover_right.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/page_hover_right_active.9.png b/res/drawable-hdpi/page_hover_right_active.9.png
deleted file mode 100644
index 101e4dc..0000000
--- a/res/drawable-hdpi/page_hover_right_active.9.png
+++ /dev/null
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-hdpi/screenpanel.9.png b/res/drawable-hdpi/screenpanel.9.png
deleted file mode 100644
index 5bccd33..0000000
--- a/res/drawable-hdpi/screenpanel.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/screenpanel_hover.9.png b/res/drawable-hdpi/screenpanel_hover.9.png
deleted file mode 100644
index f6b8c62..0000000
--- a/res/drawable-hdpi/screenpanel_hover.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/virtual_preload.9.png b/res/drawable-hdpi/virtual_preload.9.png
deleted file mode 100644
index 670088f..0000000
--- a/res/drawable-hdpi/virtual_preload.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/virtual_preload_folder.9.png b/res/drawable-hdpi/virtual_preload_folder.9.png
deleted file mode 100644
index 68e2afe..0000000
--- a/res/drawable-hdpi/virtual_preload_folder.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/widget_tile.png b/res/drawable-hdpi/widget_tile.png
deleted file mode 100644
index 572bf6f..0000000
--- a/res/drawable-hdpi/widget_tile.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/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/page_hover_left.9.png b/res/drawable-mdpi/page_hover_left.9.png
deleted file mode 100644
index 2b6094c..0000000
--- a/res/drawable-mdpi/page_hover_left.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/page_hover_left_active.9.png b/res/drawable-mdpi/page_hover_left_active.9.png
deleted file mode 100644
index 9eb00a2..0000000
--- a/res/drawable-mdpi/page_hover_left_active.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/page_hover_right.9.png b/res/drawable-mdpi/page_hover_right.9.png
deleted file mode 100644
index c2e59835..0000000
--- a/res/drawable-mdpi/page_hover_right.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/page_hover_right_active.9.png b/res/drawable-mdpi/page_hover_right_active.9.png
deleted file mode 100644
index d2771a1..0000000
--- a/res/drawable-mdpi/page_hover_right_active.9.png
+++ /dev/null
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-mdpi/screenpanel.9.png b/res/drawable-mdpi/screenpanel.9.png
deleted file mode 100644
index 9603c12..0000000
--- a/res/drawable-mdpi/screenpanel.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/screenpanel_hover.9.png b/res/drawable-mdpi/screenpanel_hover.9.png
deleted file mode 100644
index 7f28ce0..0000000
--- a/res/drawable-mdpi/screenpanel_hover.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/virtual_preload.9.png b/res/drawable-mdpi/virtual_preload.9.png
deleted file mode 100644
index c4a01fe..0000000
--- a/res/drawable-mdpi/virtual_preload.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/virtual_preload_folder.9.png b/res/drawable-mdpi/virtual_preload_folder.9.png
deleted file mode 100644
index 2f3e420..0000000
--- a/res/drawable-mdpi/virtual_preload_folder.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/widget_tile.png b/res/drawable-mdpi/widget_tile.png
deleted file mode 100644
index 9652ace..0000000
--- a/res/drawable-mdpi/widget_tile.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-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/quantum_panel_shape_dark.xml b/res/drawable-v26/adaptive_icon_drawable_wrapper.xml
similarity index 66%
copy from res/drawable/quantum_panel_shape_dark.xml
copy to res/drawable-v26/adaptive_icon_drawable_wrapper.xml
index b299eb8..2d78b69 100644
--- a/res/drawable/quantum_panel_shape_dark.xml
+++ b/res/drawable-v26/adaptive_icon_drawable_wrapper.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-     Copyright (C) 2015 The Android Open Source Project
+     Copyright (C) 2017 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -14,9 +14,9 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-       android:shape="rectangle">
-    <solid android:color="?attr/colorSecondary" />
-    <corners
-        android:radius="2dp" />
-</shape>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@color/legacy_icon_background"/>
+    <foreground>
+        <com.android.launcher3.graphics.FixedScaleDrawable />
+    </foreground>
+</adaptive-icon>
diff --git a/res/values-sw720dp-port/dimens.xml b/res/drawable-v26/ic_launcher_home.xml
similarity index 60%
copy from res/values-sw720dp-port/dimens.xml
copy to res/drawable-v26/ic_launcher_home.xml
index 6f594d5..7038775 100644
--- a/res/values-sw720dp-port/dimens.xml
+++ b/res/drawable-v26/ic_launcher_home.xml
@@ -1,11 +1,11 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 The Android Open Source Project
+<!-- Copyright (C) 2017 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
      You may obtain a copy of the License at
 
-          http://www.apache.org/licenses/LICENSE-2.0
+        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,
@@ -13,10 +13,9 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-
-<resources>
-<!-- Workspace -->
-    <!-- the area at the edge of the screen that makes the workspace go left
-         or right while you're dragging. -->
-    <dimen name="scroll_zone">40dp</dimen>
-</resources>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@color/icon_background" />
+    <foreground>
+        <bitmap android:src="@mipmap/ic_launcher_home_foreground"/>
+    </foreground>
+</adaptive-icon>
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/page_hover_left.9.png b/res/drawable-xhdpi/page_hover_left.9.png
deleted file mode 100644
index dbcc0ab..0000000
--- a/res/drawable-xhdpi/page_hover_left.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/page_hover_left_active.9.png b/res/drawable-xhdpi/page_hover_left_active.9.png
deleted file mode 100644
index 3233efe..0000000
--- a/res/drawable-xhdpi/page_hover_left_active.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/page_hover_right.9.png b/res/drawable-xhdpi/page_hover_right.9.png
deleted file mode 100644
index d82f809..0000000
--- a/res/drawable-xhdpi/page_hover_right.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/page_hover_right_active.9.png b/res/drawable-xhdpi/page_hover_right_active.9.png
deleted file mode 100644
index 819ea19..0000000
--- a/res/drawable-xhdpi/page_hover_right_active.9.png
+++ /dev/null
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-xhdpi/screenpanel.9.png b/res/drawable-xhdpi/screenpanel.9.png
deleted file mode 100644
index 75343f7..0000000
--- a/res/drawable-xhdpi/screenpanel.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/screenpanel_hover.9.png b/res/drawable-xhdpi/screenpanel_hover.9.png
deleted file mode 100644
index 55b4d6e..0000000
--- a/res/drawable-xhdpi/screenpanel_hover.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/virtual_preload.9.png b/res/drawable-xhdpi/virtual_preload.9.png
deleted file mode 100644
index 2afade1..0000000
--- a/res/drawable-xhdpi/virtual_preload.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/virtual_preload_folder.9.png b/res/drawable-xhdpi/virtual_preload_folder.9.png
deleted file mode 100644
index cb3fdca..0000000
--- a/res/drawable-xhdpi/virtual_preload_folder.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/widget_tile.png b/res/drawable-xhdpi/widget_tile.png
deleted file mode 100644
index be1748d..0000000
--- a/res/drawable-xhdpi/widget_tile.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/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/page_hover_left.9.png b/res/drawable-xxhdpi/page_hover_left.9.png
deleted file mode 100644
index c81f86c..0000000
--- a/res/drawable-xxhdpi/page_hover_left.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/page_hover_left_active.9.png b/res/drawable-xxhdpi/page_hover_left_active.9.png
deleted file mode 100644
index 858a3b2..0000000
--- a/res/drawable-xxhdpi/page_hover_left_active.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/page_hover_right.9.png b/res/drawable-xxhdpi/page_hover_right.9.png
deleted file mode 100644
index c529770..0000000
--- a/res/drawable-xxhdpi/page_hover_right.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/page_hover_right_active.9.png b/res/drawable-xxhdpi/page_hover_right_active.9.png
deleted file mode 100644
index 9900553..0000000
--- a/res/drawable-xxhdpi/page_hover_right_active.9.png
+++ /dev/null
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-xxhdpi/screenpanel.9.png b/res/drawable-xxhdpi/screenpanel.9.png
deleted file mode 100644
index b221b37..0000000
--- a/res/drawable-xxhdpi/screenpanel.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/screenpanel_hover.9.png b/res/drawable-xxhdpi/screenpanel_hover.9.png
deleted file mode 100644
index 418cf0a..0000000
--- a/res/drawable-xxhdpi/screenpanel_hover.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/virtual_preload.9.png b/res/drawable-xxhdpi/virtual_preload.9.png
deleted file mode 100644
index 03c6e7f..0000000
--- a/res/drawable-xxhdpi/virtual_preload.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/virtual_preload_folder.9.png b/res/drawable-xxhdpi/virtual_preload_folder.9.png
deleted file mode 100644
index 052a72e..0000000
--- a/res/drawable-xxhdpi/virtual_preload_folder.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/widget_tile.png b/res/drawable-xxhdpi/widget_tile.png
deleted file mode 100644
index c6237db..0000000
--- a/res/drawable-xxhdpi/widget_tile.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-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/all_apps_divider.xml b/res/drawable/all_apps_divider.xml
index 3fe5295..4bd274d 100644
--- a/res/drawable/all_apps_divider.xml
+++ b/res/drawable/all_apps_divider.xml
@@ -15,6 +15,6 @@
 -->
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
-    <solid android:color="@color/all_apps_divider_color" />
+    <solid android:color="?android:attr/colorControlHighlight" />
     <size android:height="1dp" />
 </shape>
\ No newline at end of file
diff --git a/res/drawable/bg_celllayout.xml b/res/drawable/bg_celllayout.xml
index d2219b3..b81b37f 100644
--- a/res/drawable/bg_celllayout.xml
+++ b/res/drawable/bg_celllayout.xml
@@ -18,20 +18,18 @@
 */
 -->
 
-<transition xmlns:android="http://schemas.android.com/apk/res/android" >
-
-    <item>
-        <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_active="true" >
+        <shape android:shape="rectangle" >
+            <stroke
+                android:width="@dimen/spring_loaded_panel_border"
+                android:color="@color/spring_loaded_highlighted_panel_border_color" />
             <solid android:color="@color/spring_loaded_panel_color" />
         </shape>
     </item>
     <item>
-        <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
-            <stroke
-                android:width="@dimen/spring_loaded_panel_border"
-                android:color="@color/spring_loaded_highlighted_panel_border_color" />
-            <solid android:color="@android:color/transparent" />
+        <shape android:shape="rectangle" >
+            <solid android:color="@color/spring_loaded_panel_color" />
         </shape>
     </item>
-
-</transition>
\ No newline at end of file
+</selector>
diff --git a/res/drawable/bg_pill_focused.xml b/res/drawable/bg_pill_focused.xml
deleted file mode 100644
index 37afad0..0000000
--- a/res/drawable/bg_pill_focused.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_focused="true">
-        <shape xmlns:android="http://schemas.android.com/apk/res/android"
-               android:shape="rectangle">
-            <stroke android:color="#616161" android:width="2dp"/>
-            <corners android:radius="@dimen/bg_pill_radius" />
-        </shape>
-    </item>
-</selector>
\ No newline at end of file
diff --git a/res/drawable/bg_white_pill.xml b/res/drawable/bg_white_round_rect.xml
similarity index 86%
rename from res/drawable/bg_white_pill.xml
rename to res/drawable/bg_white_round_rect.xml
index f92f739..c7f786f 100644
--- a/res/drawable/bg_white_pill.xml
+++ b/res/drawable/bg_white_round_rect.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
+<!-- Copyright (C) 2017 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -17,5 +17,5 @@
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
     <solid android:color="#FFFFFF" />
-    <corners android:radius="@dimen/bg_pill_radius" />
+    <corners android:radius="@dimen/bg_round_rect_radius" />
 </shape>
\ No newline at end of file
diff --git a/res/drawable/deep_shortcuts_drag_handle.xml b/res/drawable/deep_shortcuts_drag_handle.xml
index 99d2b07..82e844d 100644
--- a/res/drawable/deep_shortcuts_drag_handle.xml
+++ b/res/drawable/deep_shortcuts_drag_handle.xml
@@ -18,9 +18,10 @@
         android:width="@dimen/deep_shortcut_drag_handle_size"
         android:height="@dimen/deep_shortcut_drag_handle_size"
         android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/textColorHint" >
 
     <path
         android:pathData="M20 9H4v2h16V9zM4 15h16v-2H4v2z"
-        android:fillColor="#4D000000"/>
+        android:fillColor="@android:color/white" />
 </vector>
\ No newline at end of file
diff --git a/res/drawable/horizontal_ellipsis.xml b/res/drawable/horizontal_ellipsis.xml
new file mode 100644
index 0000000..ad08529
--- /dev/null
+++ b/res/drawable/horizontal_ellipsis.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="@dimen/horizontal_ellipsis_size"
+        android:height="@dimen/horizontal_ellipsis_size"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/textColorSecondary" >
+
+    <path
+        android:pathData="M6 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm12 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-6 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"
+        android:fillColor="@android:color/white" />
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_info_no_shadow.xml b/res/drawable/ic_info_no_shadow.xml
new file mode 100644
index 0000000..3d0c6d6
--- /dev/null
+++ b/res/drawable/ic_info_no_shadow.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="@color/workspace_icon_text_color"
+        android:pathData="M11 17h2v-6h-2v6zm1-15C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zM11 9h2V7h-2v2z"/>
+</vector>
diff --git a/res/drawable/quantum_panel.xml b/res/drawable/ic_launcher_home.xml
similarity index 70%
copy from res/drawable/quantum_panel.xml
copy to res/drawable/ic_launcher_home.xml
index 1f4fb71..a6f2519 100644
--- a/res/drawable/quantum_panel.xml
+++ b/res/drawable/ic_launcher_home.xml
@@ -1,12 +1,11 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2015 The Android Open Source Project
+<!-- Copyright (C) 2017 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
      You may obtain a copy of the License at
 
-          http://www.apache.org/licenses/LICENSE-2.0
+        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,
@@ -14,5 +13,6 @@
      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" />
+<bitmap
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:src="@mipmap/ic_launcher_home" />
diff --git a/res/drawable/ic_setting.xml b/res/drawable/ic_setting.xml
index 8a50c0c..e89c158 100644
--- a/res/drawable/ic_setting.xml
+++ b/res/drawable/ic_setting.xml
@@ -19,6 +19,6 @@
         android:viewportWidth="48.0"
         android:viewportHeight="48.0">
     <path
-        android:fillColor="#FFFFFFFF"
+        android:fillColor="@color/workspace_icon_text_color"
         android:pathData="M38.86 25.95c.08-.64 .14-1.29 .14-1.95s-.06-1.31-.14-1.95l4.23-3.31c.38-.3 .49-.84 .24-1.28l-4-6.93c-.25-.43-.77-.61-1.22-.43l-4.98 2.01c-1.03-.79-2.16-1.46-3.38-1.97L29 4.84c-.09-.47-.5-.84-1-.84h-8c-.5 0-.91 .37-.99 .84l-.75 5.3c-1.22 .51-2.35 1.17-3.38 1.97L9.9 10.1c-.45-.17-.97 0-1.22 .43l-4 6.93c-.25 .43-.14 .97 .24 1.28l4.22 3.31C9.06 22.69 9 23.34 9 24s.06 1.31 .14 1.95l-4.22 3.31c-.38 .3-.49 .84-.24 1.28l4 6.93c.25 .43 .77 .61 1.22 .43l4.98-2.01c1.03 .79 2.16 1.46 3.38 1.97l.75 5.3c.08 .47 .49 .84 .99 .84h8c.5 0 .91-.37 .99-.84l.75-5.3c1.22-.51 2.35-1.17 3.38-1.97l4.98 2.01c.45 .17 .97 0 1.22-.43l4-6.93c.25-.43 .14-.97-.24-1.28l-4.22-3.31zM24 31c-3.87 0-7-3.13-7-7s3.13-7 7-7 7 3.13 7 7-3.13 7-7 7z"/>
 </vector>
diff --git a/res/drawable/ic_star_rating.xml b/res/drawable/ic_star_rating.xml
new file mode 100644
index 0000000..4e34fa3
--- /dev/null
+++ b/res/drawable/ic_star_rating.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="12dp"
+    android:height="12dp"
+    android:viewportWidth="12"
+    android:viewportHeight="12">
+
+    <path
+        android:fillColor="#FB8C00"
+        android:fillType="evenOdd"
+        android:strokeWidth="1"
+        android:pathData="M 9.76511755 11.9348136 L 8.33665684 7.16088817 L 12.080006 4.41656311 L 7.49967039 4.41856896 L 6.03138903 0 L 4.57932894 4.41856896 L -1.34115008e-16 4.41656311 L 3.72612122 7.16088817 L 2.29967385 11.9348136 L 6.03138903 8.82574452 Z" />
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_wallpaper.xml b/res/drawable/ic_wallpaper.xml
index 7af4b2a..b7fcfbf 100644
--- a/res/drawable/ic_wallpaper.xml
+++ b/res/drawable/ic_wallpaper.xml
@@ -19,6 +19,6 @@
         android:viewportWidth="48.0"
         android:viewportHeight="48.0">
     <path
-        android:fillColor="#FFFFFFFF"
+        android:fillColor="@color/workspace_icon_text_color"
         android:pathData="M8 8h14V4H8C5.79 4 4 5.79 4 8v14h4V8zm12 18l-8 10h24l-6-8-4.06 5.42L20 26zm14-9c0-1.66-1.34-3-3-3s-3 1.34-3 3 1.34 3 3 3 3-1.34 3-3zm6-13H26v4h14v14h4V8c0-2.21-1.79-4-4-4zm0 36H26v4h14c2.21 0 4-1.79 4-4V26h-4v14zM8 26H4v14c0 2.21 1.79 4 4 4h14v-4H8V26z"/>
 </vector>
diff --git a/res/drawable/ic_widget.xml b/res/drawable/ic_widget.xml
index 3e7bd7b..97706e3 100644
--- a/res/drawable/ic_widget.xml
+++ b/res/drawable/ic_widget.xml
@@ -19,6 +19,6 @@
         android:viewportWidth="48.0"
         android:viewportHeight="48.0">
     <path
-        android:fillColor="#FFFFFFFF"
+        android:fillColor="@color/workspace_icon_text_color"
         android:pathData="M26 26v16h16V26H26zM6 42h16V26H6v16zM6 6v16h16V6H6zm27.31-2.63L22 14.69 33.31 26l11.31-11.31L33.31 3.37z"/>
 </vector>
diff --git a/res/drawable/quantum_panel.xml b/res/drawable/pending_widget_bg.xml
similarity index 72%
rename from res/drawable/quantum_panel.xml
rename to res/drawable/pending_widget_bg.xml
index 1f4fb71..cf29f90 100644
--- a/res/drawable/quantum_panel.xml
+++ b/res/drawable/pending_widget_bg.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-     Copyright (C) 2015 The Android Open Source Project
+     Copyright (C) 2017 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -14,5 +14,6 @@
      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/round_rect_primary"
+    android:inset="@dimen/pending_widget_min_padding" />
diff --git a/res/drawable/quantum_panel_dark.xml b/res/drawable/quantum_panel_dark.xml
deleted file mode 100644
index 6642e78..0000000
--- a/res/drawable/quantum_panel_dark.xml
+++ /dev/null
@@ -1,18 +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.
--->
-<nine-patch xmlns:android="http://schemas.android.com/apk/res/android"
-    android:src="@drawable/quantum_panel_dark_bitmap" />
diff --git a/res/drawable/quantum_panel_shape.xml b/res/drawable/quantum_panel_shape.xml
deleted file mode 100644
index 1083615..0000000
--- a/res/drawable/quantum_panel_shape.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.
--->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-       android:shape="rectangle">
-    <solid android:color="@color/quantum_panel_bg_color" />
-    <corners
-        android:radius="2dp" />
-</shape>
\ No newline at end of file
diff --git a/res/drawable/quantum_panel_shape_dark.xml b/res/drawable/round_rect_primary.xml
similarity index 88%
rename from res/drawable/quantum_panel_shape_dark.xml
rename to res/drawable/round_rect_primary.xml
index b299eb8..2c47e06 100644
--- a/res/drawable/quantum_panel_shape_dark.xml
+++ b/res/drawable/round_rect_primary.xml
@@ -16,7 +16,6 @@
 -->
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
-    <solid android:color="?attr/colorSecondary" />
-    <corners
-        android:radius="2dp" />
+    <solid android:color="?android:attr/colorPrimary" />
+    <corners android:radius="2dp" />
 </shape>
diff --git a/res/drawable/widgets_row_divider.xml b/res/drawable/widgets_row_divider.xml
deleted file mode 100644
index 2c3c7a2..0000000
--- a/res/drawable/widgets_row_divider.xml
+++ /dev/null
@@ -1,19 +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.
--->
-<shape xmlns:android="http://schemas.android.com/apk/res/android" >
-    <size android:width="@dimen/widget_row_divider" />
-    <solid android:color="?attr/colorSecondary" />
-</shape>
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/add_item_confirmation_activity.xml b/res/layout/add_item_confirmation_activity.xml
new file mode 100644
index 0000000..35bcac3
--- /dev/null
+++ b/res/layout/add_item_confirmation_activity.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2017, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <TextView
+        android:paddingLeft="24dp"
+        android:paddingRight="24dp"
+        android:paddingTop="4dp"
+        android:paddingBottom="20dp"
+        android:text="@string/add_item_request_drag_hint"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" />
+
+    <FrameLayout
+        android:theme="@style/WidgetContainerTheme"
+        android:layout_width="match_parent"
+        android:background="?android:attr/colorPrimaryDark"
+        android:layout_height="wrap_content">
+
+        <com.android.launcher3.dragndrop.LivePreviewWidgetCell
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:orientation="vertical"
+            android:focusable="true"
+            android:background="?android:attr/colorPrimaryDark"
+            android:gravity="center_horizontal"
+            android:id="@+id/widget_cell"
+            android:layout_gravity="center_horizontal" >
+
+            <include layout="@layout/widget_cell_content"  />
+
+        </com.android.launcher3.dragndrop.LivePreviewWidgetCell>
+    </FrameLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        style="?android:attr/buttonBarStyle"
+        android:gravity="end" >
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@android:string/cancel"
+            android:onClick="onCancelClick"
+            style="?android:attr/buttonBarButtonStyle" />
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/place_automatically"
+            android:onClick="onPlaceAutomaticallyClick"
+            style="?android:attr/buttonBarButtonStyle" />
+    </LinearLayout>
+</LinearLayout>
diff --git a/res/layout/all_apps.xml b/res/layout/all_apps.xml
index 4909eb3..d6bdac2 100644
--- a/res/layout/all_apps.xml
+++ b/res/layout/all_apps.xml
@@ -22,7 +22,7 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="vertical"
-    launcher:revealBackground="@drawable/quantum_panel_shape">
+    launcher:revealBackground="@drawable/round_rect_primary">
 
     <View
         android:id="@+id/reveal_view"
@@ -46,21 +46,29 @@
         <!-- 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:theme="@style/CustomOverscroll.Light" />
+            android:paddingStart="@dimen/container_fastscroll_thumb_max_width"
+            android:paddingEnd="@dimen/container_fastscroll_thumb_max_width" />
+
+        <!-- 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 +76,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"
@@ -77,7 +86,7 @@
                 android:maxLines="1"
                 android:scrollHorizontally="true"
                 android:singleLine="true"
-                android:textColor="#4c4c4c"
+                android:textColor="?android:attr/textColorSecondary"
                 android:hint="@string/all_apps_search_bar_hint"
                 android:textColorHint="@drawable/all_apps_search_hint"
                 android:textSize="16sp" />
@@ -85,11 +94,11 @@
 
     </com.android.launcher3.allapps.AllAppsRecyclerViewContainerView>
     <View
+        style="@style/AllAppsNavBarProtection"
         android:id="@+id/nav_bar_bg"
         android:layout_width="match_parent"
         android:layout_height="0dp"
         android:layout_gravity="bottom"
-        android:background="@color/all_apps_navbar_color"
         android:focusable="false"
         android:visibility="invisible" />
 </com.android.launcher3.allapps.AllAppsContainerView>
\ No newline at end of file
diff --git a/res/layout/all_apps_button.xml b/res/layout/all_apps_button.xml
index 68cc109..4bc780a 100644
--- a/res/layout/all_apps_button.xml
+++ b/res/layout/all_apps_button.xml
@@ -14,6 +14,4 @@
      limitations under the License.
 -->
 
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
-   style="@style/Icon"
-   android:focusable="true" />
+<TextView style="@style/BaseIcon" />
diff --git a/res/layout/all_apps_discovery_item.xml b/res/layout/all_apps_discovery_item.xml
new file mode 100644
index 0000000..1a7eaa7
--- /dev/null
+++ b/res/layout/all_apps_discovery_item.xml
@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<com.android.launcher3.discovery.AppDiscoveryItemView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:clickable="true"
+    android:background="?android:selectableItemBackground">
+
+    <ImageView
+        android:id="@+id/image"
+        android:layout_width="56dp"
+        android:layout_height="56dp"
+        android:padding="8dp"
+        android:scaleType="fitCenter"/>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:layout_centerVertical="true"
+        android:layout_toRightOf="@id/image">
+
+        <TextView
+            android:id="@+id/title"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textColor="?android:textColorSecondary"
+            android:textSize="15sp"/>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+            <TextView
+                android:id="@+id/rating"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textColor="?android:textColorSecondary"
+                android:textSize="14sp"
+                android:layout_gravity="center_vertical"/>
+
+            <com.android.launcher3.discovery.RatingView
+                android:id="@+id/rating_view"
+                android:layout_width="80dp"
+                android:layout_height="16dp"
+                android:layout_marginLeft="5dp"
+                android:layout_marginRight="5dp"
+                android:layout_gravity="center_vertical"/>
+
+            <TextView
+                android:id="@+id/review_count"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="5dp"
+                android:textColor="?android:textColorHint"
+                android:textSize="14sp"
+                android:layout_gravity="center_vertical"/>
+
+            <Space
+                android:layout_width="0dp"
+                android:layout_height="0dp"
+                android:layout_weight="1"/>
+
+            <TextView
+                android:id="@+id/price"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textColor="?android:textColorHint"
+                android:textSize="14sp"
+                android:layout_marginRight="12dp"
+                android:textAllCaps="true"/>
+        </LinearLayout>
+    </LinearLayout>
+
+    <ImageView
+        android:importantForAccessibility="no"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:paddingLeft="@dimen/container_fastscroll_thumb_max_width"
+        android:paddingRight="@dimen/container_fastscroll_thumb_max_width"
+        android:src="@drawable/all_apps_divider"
+        android:scaleType="fitXY"
+        android:focusable="false" />
+</com.android.launcher3.discovery.AppDiscoveryItemView>
\ No newline at end of file
diff --git a/res/layout/all_apps_discovery_loading_divider.xml b/res/layout/all_apps_discovery_loading_divider.xml
new file mode 100644
index 0000000..c7b5ad2
--- /dev/null
+++ b/res/layout/all_apps_discovery_loading_divider.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="20dp"
+    android:paddingLeft="@dimen/container_fastscroll_thumb_max_width"
+    android:paddingRight="@dimen/container_fastscroll_thumb_max_width">
+
+    <ProgressBar
+        android:id="@+id/loadingProgressBar"
+        style="@android:style/Widget.Material.ProgressBar.Horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="6dp"
+        android:maxHeight="6dp"
+        android:indeterminate="true"
+        android:layout_gravity="center"/>
+
+    <View
+        android:id="@+id/loadedDivider"
+        android:layout_width="match_parent"
+        android:layout_height="1dp"
+        android:background="@drawable/all_apps_divider"
+        android:layout_gravity="center"
+        android:visibility="invisible"/>
+
+</FrameLayout>
\ No newline at end of file
diff --git a/res/layout/all_apps_empty_search.xml b/res/layout/all_apps_empty_search.xml
index e1635d6..463adac 100644
--- a/res/layout/all_apps_empty_search.xml
+++ b/res/layout/all_apps_empty_search.xml
@@ -25,7 +25,6 @@
     android:paddingRight="16dp"
     android:fontFamily="sans-serif-medium"
     android:textSize="14sp"
-    android:textColor="#212121"
-    android:alpha="0.56"
+    android:textColor="?android:attr/textColorTertiary"
     android:focusable="false" />
 
diff --git a/res/layout/all_apps_icon.xml b/res/layout/all_apps_icon.xml
index 3d4bef7..ca0cbcc 100644
--- a/res/layout/all_apps_icon.xml
+++ b/res/layout/all_apps_icon.xml
@@ -16,12 +16,14 @@
 <com.android.launcher3.BubbleTextView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:launcher="http://schemas.android.com/apk/res-auto"
-    style="@style/Icon.AllApps"
+    style="@style/BaseIcon"
     android:id="@+id/icon"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:layout_gravity="center"
-    android:focusable="true"
+    android:stateListAnimator="@animator/all_apps_fastscroll_icon_anim"
     launcher:iconDisplay="all_apps"
-    launcher:centerVertically="true" />
+    launcher:centerVertically="true"
+    android:paddingLeft="4dp"
+    android:paddingRight="4dp"
+    android:drawablePadding="@dimen/dynamic_grid_icon_drawable_padding" />
 
diff --git a/res/layout/app_icon.xml b/res/layout/app_icon.xml
index 831cee5..fa6eb89 100644
--- a/res/layout/app_icon.xml
+++ b/res/layout/app_icon.xml
@@ -14,6 +14,4 @@
      limitations under the License.
 -->
 
-<com.android.launcher3.BubbleTextView xmlns:android="http://schemas.android.com/apk/res/android"
-   style="@style/Icon"
-   android:focusable="true" />
+<com.android.launcher3.BubbleTextView style="@style/BaseIcon.Workspace" />
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..6a459c3 100644
--- a/res/layout/appwidget_error.xml
+++ b/res/layout/appwidget_error.xml
@@ -19,8 +19,10 @@
     android:layout_height="match_parent"
     android:gravity="center"
     android:elevation="2dp"
-    android:background="@drawable/quantum_panel_dark"
+    android:padding="4dp"
+    android:theme="@style/WidgetContainerTheme"
+    android:background="@drawable/round_rect_primary"
     android:textAppearance="?android:attr/textAppearanceMediumInverse"
-    android:textColor="@color/widgets_view_item_text_color"
+    android:textColor="?android:attr/textColorSecondary"
     android:text="@string/gadget_error_text"
     />
diff --git a/res/layout/deep_shortcut.xml b/res/layout/deep_shortcut.xml
index 7b42ec7..85caba4 100644
--- a/res/layout/deep_shortcut.xml
+++ b/res/layout/deep_shortcut.xml
@@ -16,24 +16,40 @@
 
 <com.android.launcher3.shortcuts.DeepShortcutView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="@dimen/bg_pill_width"
-    android:layout_height="@dimen/bg_pill_height"
-    android:elevation="@dimen/deep_shortcuts_elevation"
-    android:background="@drawable/bg_white_pill">
+    xmlns:launcher="http://schemas.android.com/apk/res-auto"
+    android:layout_width="@dimen/bg_popup_item_width"
+    android:layout_height="@dimen/bg_popup_item_height" >
 
-<com.android.launcher3.shortcuts.DeepShortcutTextView
-        android:id="@+id/deep_shortcut"
-        style="@style/Icon.DeepShortcut"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:focusable="true"
-        android:background="@drawable/bg_pill_focused" />
+    <com.android.launcher3.shortcuts.DeepShortcutTextView
+        style="@style/BaseIcon"
+        android:id="@+id/bubble_text"
+        android:background="?android:attr/selectableItemBackground"
+        android:gravity="start|center_vertical"
+        android:textAlignment="viewStart"
+        android:paddingStart="@dimen/deep_shortcuts_text_padding_start"
+        android:paddingEnd="@dimen/popup_padding_end"
+        android:drawableEnd="@drawable/deep_shortcuts_drag_handle"
+        android:drawablePadding="@dimen/deep_shortcut_drawable_padding"
+        android:textSize="14sp"
+        android:textColor="?android:attr/textColorPrimary"
+        android:fontFamily="sans-serif"
+        launcher:layoutHorizontal="true"
+        launcher:iconDisplay="shortcut_popup"
+        launcher:iconSizeOverride="@dimen/deep_shortcut_icon_size" />
 
     <View
-        android:id="@+id/deep_shortcut_icon"
+        android:id="@+id/icon"
         android:layout_width="@dimen/deep_shortcut_icon_size"
         android:layout_height="@dimen/deep_shortcut_icon_size"
-        android:layout_margin="@dimen/deep_shortcut_padding_start"
-        android:layout_gravity="start" />
+        android:layout_marginStart="@dimen/popup_padding_start"
+        android:layout_gravity="start|center_vertical" />
+
+    <View
+        android:id="@+id/divider"
+        android:layout_width="@dimen/deep_shortcuts_divider_width"
+        android:layout_height="@dimen/popup_item_divider_height"
+        android:layout_gravity="end|bottom"
+        android:visibility="gone"
+        android:background="?android:attr/listDivider" />
 
 </com.android.launcher3.shortcuts.DeepShortcutView>
diff --git a/res/layout/folder_application.xml b/res/layout/folder_application.xml
index 4d00331..de861a0 100644
--- a/res/layout/folder_application.xml
+++ b/res/layout/folder_application.xml
@@ -14,6 +14,9 @@
      limitations under the License.
 -->
 
-<com.android.launcher3.BubbleTextView xmlns:android="http://schemas.android.com/apk/res/android"
-   style="@style/Icon.Folder"
-   android:focusable="true" />
+<com.android.launcher3.BubbleTextView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto"
+    style="@style/BaseIcon"
+    android:includeFontPadding="false"
+    launcher:iconDisplay="folder" />
diff --git a/res/layout/folder_icon.xml b/res/layout/folder_icon.xml
index 9eb8c9a..ccc6b01 100644
--- a/res/layout/folder_icon.xml
+++ b/res/layout/folder_icon.xml
@@ -21,8 +21,9 @@
     android:orientation="vertical"
     android:focusable="true" >
     <com.android.launcher3.BubbleTextView
-        style="@style/Icon"
+        style="@style/BaseIcon.Workspace"
         android:id="@+id/folder_icon_name"
+        android:focusable="false"
         android:layout_gravity="top"
         android:layout_width="match_parent"
         android:layout_height="match_parent" />
diff --git a/res/layout/deep_shortcuts_container.xml b/res/layout/folder_page.xml
similarity index 68%
copy from res/layout/deep_shortcuts_container.xml
copy to res/layout/folder_page.xml
index 68bb60f..084e8fd 100644
--- a/res/layout/deep_shortcuts_container.xml
+++ b/res/layout/folder_page.xml
@@ -14,16 +14,11 @@
      limitations under the License.
 -->
 
-<com.android.launcher3.shortcuts.DeepShortcutsContainer
+<com.android.launcher3.CellLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/deep_shortcuts_container"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
-    android:paddingTop="8dp"
-    android:paddingBottom="8dp"
-    android:clipToPadding="false"
-    android:clipChildren="false"
-    android:elevation="@dimen/deep_shortcuts_elevation"
-    android:orientation="vertical">
-
-</com.android.launcher3.shortcuts.DeepShortcutsContainer>
\ No newline at end of file
+    android:hapticFeedbackEnabled="false"
+    android:importantForAccessibility="no"
+    launcher:containerType="folder" />
diff --git a/res/layout/hotseat.xml b/res/layout/hotseat.xml
index 7bef889..f5b5bbf 100644
--- a/res/layout/hotseat.xml
+++ b/res/layout/hotseat.xml
@@ -20,5 +20,6 @@
         android:id="@+id/layout"
         android:layout_width="wrap_content"
         android:layout_height="match_parent"
-        android:layout_gravity="center" />
+        android:layout_gravity="center"
+        launcher:containerType="hotseat" />
 </com.android.launcher3.Hotseat>
diff --git a/res/layout/notification.xml b/res/layout/notification.xml
new file mode 100644
index 0000000..f955c6b
--- /dev/null
+++ b/res/layout/notification.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<com.android.launcher3.notification.NotificationItemView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/notification_view"
+    android:layout_width="@dimen/bg_popup_item_width"
+    android:layout_height="wrap_content"
+    android:elevation="@dimen/deep_shortcuts_elevation"
+    android:background="@drawable/bg_white_round_rect"
+    android:backgroundTint="@color/notification_color_beneath">
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical"
+        android:clipChildren="false">
+
+        <FrameLayout
+            android:id="@+id/header"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/notification_header_height"
+            android:paddingStart="@dimen/notification_padding_start"
+            android:paddingEnd="@dimen/notification_padding_end"
+            android:background="@color/popup_header_background_color"
+            android:elevation="@dimen/notification_elevation">
+            <TextView
+                android:id="@+id/notification_text"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:layout_gravity="start"
+                android:gravity="center_vertical"
+                android:text="@string/notifications_header"
+                android:textSize="@dimen/notification_header_text_size"
+                android:textColor="?android:attr/textColorSecondary" />
+            <TextView
+                android:id="@+id/notification_count"
+                android:layout_width="@dimen/notification_icon_size"
+                android:layout_height="match_parent"
+                android:layout_gravity="end"
+                android:gravity="center"
+                android:textSize="@dimen/notification_header_count_text_size"
+                android:fontFamily="sans-serif-medium"
+                android:textColor="?android:attr/textColorPrimary" />
+        </FrameLayout>
+
+        <include layout="@layout/notification_main"
+            android:id="@+id/main_view"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/notification_main_height"
+            android:layout_below="@id/header" />
+
+        <View
+            android:id="@+id/divider"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/popup_item_divider_height"
+            android:background="?android:attr/listDivider"
+            android:layout_below="@id/main_view"/>
+
+        <include layout="@layout/notification_footer"
+            android:id="@+id/footer"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/notification_footer_height"
+            android:layout_below="@id/divider" />
+
+    </RelativeLayout>
+
+</com.android.launcher3.notification.NotificationItemView>
diff --git a/res/layout/notification_footer.xml b/res/layout/notification_footer.xml
new file mode 100644
index 0000000..ed2212b
--- /dev/null
+++ b/res/layout/notification_footer.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+
+<com.android.launcher3.notification.NotificationFooterLayout
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:elevation="@dimen/notification_elevation"
+    android:clipChildren="false"
+    android:layout_gravity="center_vertical"
+    android:background="@color/popup_background_color">
+
+    <LinearLayout
+        android:id="@+id/icon_row"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="horizontal"
+        android:gravity="end|center_vertical"
+        android:padding="@dimen/notification_footer_icon_row_padding"
+        android:clipToPadding="false"
+        android:clipChildren="false"/>
+
+    <View
+        android:id="@+id/overflow"
+        android:layout_width="@dimen/horizontal_ellipsis_size"
+        android:layout_height="@dimen/horizontal_ellipsis_size"
+        android:background="@drawable/horizontal_ellipsis"
+        android:layout_marginStart="@dimen/horizontal_ellipsis_offset"
+        android:layout_gravity="start|center_vertical" />
+
+</com.android.launcher3.notification.NotificationFooterLayout>
+
diff --git a/res/layout/notification_main.xml b/res/layout/notification_main.xml
new file mode 100644
index 0000000..ce4e137
--- /dev/null
+++ b/res/layout/notification_main.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+
+<com.android.launcher3.notification.NotificationMainView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:focusable="true"
+    android:elevation="@dimen/notification_elevation" >
+
+    <LinearLayout
+        android:id="@+id/text_and_background"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical"
+        android:gravity="center_vertical"
+        android:background="@color/popup_background_color"
+        android:paddingStart="@dimen/notification_padding_start"
+        android:paddingEnd="@dimen/notification_main_text_padding_end">
+        <TextView
+            android:id="@+id/title"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textAlignment="viewStart"
+            android:fontFamily="sans-serif"
+            android:textSize="@dimen/notification_main_text_size"
+            android:textColor="?android:attr/textColorPrimary"
+            android:lines="1"
+            android:ellipsize="end" />
+
+        <TextView
+            android:id="@+id/text"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:fontFamily="sans-serif"
+            android:textSize="@dimen/notification_main_text_size"
+            android:textColor="?android:attr/textColorSecondary"
+            android:lines="1"
+            android:ellipsize="end" />
+    </LinearLayout>
+
+    <View
+        android:id="@+id/popup_item_icon"
+        android:layout_width="@dimen/notification_icon_size"
+        android:layout_height="@dimen/notification_icon_size"
+        android:layout_marginEnd="@dimen/notification_padding_end"
+        android:layout_gravity="center_vertical|end" />
+
+</com.android.launcher3.notification.NotificationMainView>
+
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/deep_shortcuts_container.xml b/res/layout/popup_container.xml
similarity index 90%
rename from res/layout/deep_shortcuts_container.xml
rename to res/layout/popup_container.xml
index 68bb60f..e9cfe24 100644
--- a/res/layout/deep_shortcuts_container.xml
+++ b/res/layout/popup_container.xml
@@ -14,7 +14,7 @@
      limitations under the License.
 -->
 
-<com.android.launcher3.shortcuts.DeepShortcutsContainer
+<com.android.launcher3.popup.PopupContainerWithArrow
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/deep_shortcuts_container"
     android:layout_width="wrap_content"
@@ -26,4 +26,4 @@
     android:elevation="@dimen/deep_shortcuts_elevation"
     android:orientation="vertical">
 
-</com.android.launcher3.shortcuts.DeepShortcutsContainer>
\ No newline at end of file
+</com.android.launcher3.popup.PopupContainerWithArrow>
\ No newline at end of file
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/qsb_default_view.xml b/res/layout/qsb_default_view.xml
index 82bdea5..3075f80 100644
--- a/res/layout/qsb_default_view.xml
+++ b/res/layout/qsb_default_view.xml
@@ -23,7 +23,7 @@
         android:layout_height="48dp"
         android:layout_margin="16dp"
         android:layout_gravity="center_vertical"
-        android:background="@drawable/quantum_panel_shape"
+        android:background="@drawable/round_rect_primary"
         android:elevation="2dp"
         android:orientation="horizontal">
 
@@ -35,7 +35,7 @@
             android:gravity="center_vertical"
             android:paddingStart="16dp"
             android:text="@string/abandoned_search"
-            android:textColor="@color/quantum_panel_text_color"
+            android:textColor="?android:attr/textColorSecondary"
             android:textAppearance="?android:textAppearanceMedium"
             android:clickable="true"
             android:background="?android:attr/selectableItemBackground" />
@@ -46,7 +46,7 @@
             android:visibility="gone"
             android:layout_height="match_parent"
             android:src="@drawable/ic_setting"
-            android:tint="@color/quantum_panel_text_color"
+            android:tint="?android:attr/textColorSecondary"
             android:contentDescription="@string/gadget_setup_text"
             android:padding="8dp"
             android:background="?android:attr/selectableItemBackground" />
diff --git a/res/layout/deep_shortcuts_container.xml b/res/layout/shortcuts_item.xml
similarity index 60%
copy from res/layout/deep_shortcuts_container.xml
copy to res/layout/shortcuts_item.xml
index 68bb60f..8b20bcb 100644
--- a/res/layout/deep_shortcuts_container.xml
+++ b/res/layout/shortcuts_item.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
+<!-- Copyright (C) 2017 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -14,16 +14,19 @@
      limitations under the License.
 -->
 
-<com.android.launcher3.shortcuts.DeepShortcutsContainer
+<com.android.launcher3.shortcuts.ShortcutsItemView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/deep_shortcuts_container"
-    android:layout_width="wrap_content"
+    android:id="@+id/shortcuts_view"
+    android:layout_width="@dimen/bg_popup_item_width"
     android:layout_height="wrap_content"
-    android:paddingTop="8dp"
-    android:paddingBottom="8dp"
-    android:clipToPadding="false"
-    android:clipChildren="false"
     android:elevation="@dimen/deep_shortcuts_elevation"
-    android:orientation="vertical">
+    android:background="@drawable/bg_white_round_rect">
 
-</com.android.launcher3.shortcuts.DeepShortcutsContainer>
\ No newline at end of file
+    <LinearLayout
+        android:id="@+id/deep_shortcuts"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical">
+    </LinearLayout>
+
+</com.android.launcher3.shortcuts.ShortcutsItemView>
diff --git a/res/layout/system_shortcut.xml b/res/layout/system_shortcut.xml
new file mode 100644
index 0000000..83ad9f2
--- /dev/null
+++ b/res/layout/system_shortcut.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<com.android.launcher3.shortcuts.DeepShortcutView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto"
+    android:layout_width="@dimen/bg_popup_item_width"
+    android:layout_height="@dimen/bg_popup_item_height" >
+
+    <com.android.launcher3.BubbleTextView
+        style="@style/BaseIcon"
+        android:id="@+id/bubble_text"
+        android:background="?android:attr/selectableItemBackground"
+        android:gravity="start|center_vertical"
+        android:textAlignment="viewStart"
+        android:paddingStart="@dimen/deep_shortcuts_text_padding_start"
+        android:paddingEnd="@dimen/popup_padding_end"
+        android:textSize="14sp"
+        android:textColor="?android:attr/textColorPrimary"
+        android:fontFamily="sans-serif"
+        launcher:iconDisplay="shortcut_popup"
+        launcher:layoutHorizontal="true" />
+
+    <View
+        android:id="@+id/icon"
+        android:layout_width="@dimen/system_shortcut_icon_size"
+        android:layout_height="@dimen/system_shortcut_icon_size"
+        android:layout_marginStart="@dimen/system_shortcut_margin_start"
+        android:layout_gravity="start|center_vertical" />
+
+    <View
+        android:id="@+id/divider"
+        android:layout_width="@dimen/deep_shortcuts_divider_width"
+        android:layout_height="@dimen/popup_item_divider_height"
+        android:layout_gravity="end|bottom"
+        android:visibility="gone"
+        android:background="?android:attr/listDivider" />
+
+</com.android.launcher3.shortcuts.DeepShortcutView>
diff --git a/res/layout/system_shortcut_icon_only.xml b/res/layout/system_shortcut_icon_only.xml
new file mode 100644
index 0000000..313c69c
--- /dev/null
+++ b/res/layout/system_shortcut_icon_only.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<ImageView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="@dimen/system_shortcut_header_icon_touch_size"
+    android:layout_height="@dimen/system_shortcut_header_icon_touch_size"
+    android:background="?android:attr/selectableItemBackgroundBorderless"
+    android:padding="@dimen/system_shortcut_header_icon_padding" />
diff --git a/res/layout/system_shortcut_icons.xml b/res/layout/system_shortcut_icons.xml
new file mode 100644
index 0000000..676be8e
--- /dev/null
+++ b/res/layout/system_shortcut_icons.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/system_shortcut_icons"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/system_shortcut_header_height"
+    android:orientation="horizontal"
+    android:gravity="end|center_vertical"
+    android:background="@color/popup_header_background_color" />
diff --git a/res/layout/user_folder.xml b/res/layout/user_folder.xml
index da2a861..cde6540 100644
--- a/res/layout/user_folder.xml
+++ b/res/layout/user_folder.xml
@@ -18,7 +18,7 @@
     xmlns:launcher="http://schemas.android.com/apk/res-auto"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
-    android:background="@drawable/quantum_panel"
+    android:background="@drawable/round_rect_primary"
     android:elevation="5dp"
     android:orientation="vertical" >
 
@@ -46,7 +46,7 @@
             android:layout_height="wrap_content"
             android:layout_gravity="center_vertical"
             android:layout_weight="1"
-            android:background="#00000000"
+            android:background="@android:color/transparent"
             android:fontFamily="sans-serif-condensed"
             android:gravity="center_horizontal"
             android:hint="@string/folder_hint_text"
@@ -54,10 +54,10 @@
             android:paddingBottom="@dimen/folder_label_padding_bottom"
             android:paddingTop="@dimen/folder_label_padding_top"
             android:singleLine="true"
-            android:textColor="#ff777777"
+            android:textColor="?android:attr/textColorTertiary"
             android:includeFontPadding="false"
-            android:textColorHighlight="#ffCCCCCC"
-            android:textColorHint="#ff808080"
+            android:textColorHighlight="?android:attr/colorControlHighlight"
+            android:textColorHint="?android:attr/textColorHint"
             android:textSize="@dimen/folder_label_text_size" />
 
         <com.android.launcher3.pageindicators.PageIndicatorDots
diff --git a/res/layout/user_folder_icon_normalized.xml b/res/layout/user_folder_icon_normalized.xml
index a8af201..2063f32 100644
--- a/res/layout/user_folder_icon_normalized.xml
+++ b/res/layout/user_folder_icon_normalized.xml
@@ -18,7 +18,7 @@
     xmlns:launcher="http://schemas.android.com/apk/res-auto"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
-    android:background="@drawable/quantum_panel"
+    android:background="@drawable/round_rect_primary"
     android:elevation="5dp"
     android:orientation="vertical" >
 
@@ -46,7 +46,7 @@
             android:layout_height="wrap_content"
             android:layout_gravity="center_vertical"
             android:layout_weight="1"
-            android:background="#00000000"
+            android:background="@android:color/transparent"
             android:fontFamily="sans-serif-condensed"
             android:textStyle="bold"
             android:gravity="center_horizontal"
@@ -55,9 +55,9 @@
             android:paddingBottom="@dimen/folder_label_padding_bottom"
             android:paddingTop="@dimen/folder_label_padding_top"
             android:singleLine="true"
-            android:textColor="#EE777777"
-            android:textColorHighlight="#ffCCCCCC"
-            android:textColorHint="#ff808080"
+            android:textColor="?android:attr/textColorTertiary"
+            android:textColorHighlight="?android:attr/colorControlHighlight"
+            android:textColorHint="?android:attr/textColorHint"
             android:textSize="@dimen/folder_label_text_size" />
 
         <com.android.launcher3.pageindicators.PageIndicatorDots
diff --git a/res/layout/widget_cell.xml b/res/layout/widget_cell.xml
index 15f369f..148a99b 100644
--- a/res/layout/widget_cell.xml
+++ b/res/layout/widget_cell.xml
@@ -15,60 +15,14 @@
 -->
 <com.android.launcher3.widget.WidgetCell
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:launcher="http://schemas.android.com/apk/res-auto"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:layout_weight="1"
     android:orientation="vertical"
     android:focusable="true"
-    android:background="?android:attr/colorPrimary"
+    android:background="?android:attr/colorPrimaryDark"
     android:gravity="center_horizontal">
 
-    <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:paddingTop="@dimen/widget_preview_label_vertical_padding"
-        android:paddingBottom="@dimen/widget_preview_label_vertical_padding"
-        android:paddingLeft="@dimen/widget_preview_label_horizontal_padding"
-        android:paddingRight="@dimen/widget_preview_label_horizontal_padding"
-        android:orientation="horizontal">
+    <include layout="@layout/widget_cell_content"  />
 
-        <!-- The name of the widget. -->
-        <TextView
-            android:id="@+id/widget_name"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_weight="1"
-            android:ellipsize="end"
-            android:fadingEdge="horizontal"
-            android:fontFamily="sans-serif-condensed"
-            android:gravity="start"
-            android:shadowColor="#B0000000"
-            android:shadowRadius="2.0"
-            android:singleLine="true"
-            android:textColor="@color/widgets_view_item_text_color"
-            android:textSize="14sp" />
-
-        <!-- The original dimensions of the widget (can't be the same text as above due to different
-             style. -->
-        <TextView
-            android:id="@+id/widget_dims"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="5dp"
-            android:layout_marginLeft="5dp"
-            android:textColor="@color/widgets_view_item_text_color"
-            android:textSize="14sp"
-            android:fontFamily="sans-serif-condensed"
-            android:shadowRadius="2.0"
-            android:shadowColor="#B0000000" />
-    </LinearLayout>
-
-    <!-- The image of the widget. This view does not support padding. Any placement adjustment
-         should be done using margins. -->
-    <com.android.launcher3.widget.WidgetImageView
-        android:id="@+id/widget_preview"
-        android:layout_width="match_parent"
-        android:layout_height="0dp"
-        android:layout_weight="1" />
 </com.android.launcher3.widget.WidgetCell>
\ No newline at end of file
diff --git a/res/layout/widget_cell_content.xml b/res/layout/widget_cell_content.xml
new file mode 100644
index 0000000..c77b0b9
--- /dev/null
+++ b/res/layout/widget_cell_content.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingTop="@dimen/widget_preview_label_vertical_padding"
+        android:paddingBottom="@dimen/widget_preview_label_vertical_padding"
+        android:paddingLeft="@dimen/widget_preview_label_horizontal_padding"
+        android:paddingRight="@dimen/widget_preview_label_horizontal_padding"
+        android:orientation="horizontal">
+
+        <!-- The name of the widget. -->
+        <TextView
+            android:id="@+id/widget_name"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:ellipsize="end"
+            android:fadingEdge="horizontal"
+            android:fontFamily="sans-serif-condensed"
+            android:gravity="start"
+            android:singleLine="true"
+            android:maxLines="1"
+            android:textColor="?android:attr/textColorSecondary"
+            android:textSize="14sp" />
+
+        <!-- The original dimensions of the widget (can't be the same text as above due to different
+             style. -->
+        <TextView
+            android:id="@+id/widget_dims"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="5dp"
+            android:layout_marginLeft="5dp"
+            android:textColor="?android:attr/textColorSecondary"
+            android:textSize="14sp"
+            android:fontFamily="sans-serif-condensed"
+            android:alpha="0.8" />
+    </LinearLayout>
+
+    <!-- The image of the widget. This view does not support padding. Any placement adjustment
+         should be done using margins. -->
+    <com.android.launcher3.widget.WidgetImageView
+        android:id="@+id/widget_preview"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1" />
+</merge>
\ No newline at end of file
diff --git a/res/values-sw720dp-port/dimens.xml b/res/layout/widget_list_divider.xml
similarity index 69%
rename from res/values-sw720dp-port/dimens.xml
rename to res/layout/widget_list_divider.xml
index 6f594d5..68c9a45 100644
--- a/res/values-sw720dp-port/dimens.xml
+++ b/res/layout/widget_list_divider.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 The Android Open Source Project
+<!-- Copyright (C) 2017 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -13,10 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-
-<resources>
-<!-- Workspace -->
-    <!-- the area at the edge of the screen that makes the workspace go left
-         or right while you're dragging. -->
-    <dimen name="scroll_zone">40dp</dimen>
-</resources>
+<View xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="@dimen/widget_row_divider"
+    android:layout_height="match_parent"
+    android:background="?android:attr/colorPrimary" />
\ No newline at end of file
diff --git a/res/layout/widgets_bottom_sheet.xml b/res/layout/widgets_bottom_sheet.xml
new file mode 100644
index 0000000..c2270d2
--- /dev/null
+++ b/res/layout/widgets_bottom_sheet.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<com.android.launcher3.widget.WidgetsBottomSheet
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingTop="28dp"
+    android:background="?android:attr/colorPrimary"
+    android:elevation="@dimen/deep_shortcuts_elevation"
+    android:layout_gravity="bottom">
+
+    <TextView
+        android:id="@+id/title"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center_horizontal"
+        android:fontFamily="sans-serif"
+        android:textColor="?android:attr/textColorPrimary"
+        android:textSize="24sp"/>
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center_horizontal"
+        android:paddingTop="4dp"
+        android:fontFamily="sans-serif"
+        android:textColor="?android:attr/textColorTertiary"
+        android:textSize="14sp"
+        android:text="@string/long_press_widget_to_add"/>
+
+    <include layout="@layout/widgets_scroll_container"
+         android:id="@+id/widgets"
+         android:layout_width="wrap_content"
+         android:layout_height="wrap_content"
+         android:layout_marginTop="45dp"
+         android:layout_marginBottom="40dp"/>
+
+</com.android.launcher3.widget.WidgetsBottomSheet>
\ No newline at end of file
diff --git a/res/layout/widgets_list_row_view.xml b/res/layout/widgets_list_row_view.xml
index 30a34d4..b6e0a0b 100644
--- a/res/layout/widgets_list_row_view.xml
+++ b/res/layout/widgets_list_row_view.xml
@@ -19,7 +19,6 @@
     android:id="@+id/widgets_cell_list_container"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:background="?android:attr/colorPrimary"
     android:orientation="vertical"
     android:focusable="true"
     android:descendantFocusability="afterDescendants">
@@ -30,18 +29,17 @@
         android:id="@+id/section"
         android:layout_width="match_parent"
         android:layout_height="@dimen/widget_section_height"
-        android:background="?attr/colorSecondary"
+        android:background="?android:attr/colorPrimary"
         android:drawablePadding="@dimen/widget_section_horizontal_padding"
         android:ellipsize="end"
         android:focusable="true"
         android:gravity="start|center_vertical"
-        android:importantForAccessibility="no"
         android:paddingBottom="@dimen/widget_section_vertical_padding"
         android:paddingLeft="@dimen/widget_section_horizontal_padding"
         android:paddingRight="@dimen/widget_section_horizontal_padding"
         android:paddingTop="@dimen/widget_section_vertical_padding"
         android:singleLine="true"
-        android:textColor="@color/widgets_view_section_text_color"
+        android:textColor="?android:attr/textColorPrimary"
         android:textSize="16sp"
         launcher:customShadows="false"
         launcher:deferShadowGeneration="true"
@@ -49,20 +47,5 @@
         launcher:iconSizeOverride="@dimen/widget_section_icon_size"
         launcher:layoutHorizontal="true" />
 
-    <HorizontalScrollView
-        android:id="@+id/widgets_scroll_container"
-        android:theme="@style/CustomOverscroll.Dark"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:scrollbars="none">
-        <LinearLayout
-            android:id="@+id/widgets_cell_list"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:paddingStart="@dimen/widget_row_padding"
-            android:paddingEnd="0dp"
-            android:orientation="horizontal"
-            android:divider="@drawable/widgets_row_divider"
-            android:showDividers="middle"/>
-    </HorizontalScrollView>
+    <include layout="@layout/widgets_scroll_container" />
 </LinearLayout>
diff --git a/res/layout/widgets_scroll_container.xml b/res/layout/widgets_scroll_container.xml
new file mode 100644
index 0000000..33c981a
--- /dev/null
+++ b/res/layout/widgets_scroll_container.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<HorizontalScrollView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/widgets_scroll_container"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="?android:attr/colorPrimaryDark"
+    android:scrollbars="none">
+    <LinearLayout
+        android:id="@+id/widgets_cell_list"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:paddingStart="0dp"
+        android:paddingEnd="0dp"
+        android:orientation="horizontal"
+        android:showDividers="none"/>
+</HorizontalScrollView>
\ No newline at end of file
diff --git a/res/layout/widgets_view.xml b/res/layout/widgets_view.xml
index c4431be..2f11c28 100644
--- a/res/layout/widgets_view.xml
+++ b/res/layout/widgets_view.xml
@@ -23,7 +23,7 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:descendantFocusability="afterDescendants"
-    launcher:revealBackground="@drawable/quantum_panel_shape_dark"
+    launcher:revealBackground="@drawable/round_rect_primary"
     android:theme="@style/WidgetContainerTheme">
 
     <View
@@ -45,10 +45,17 @@
 
         <com.android.launcher3.widget.WidgetsRecyclerView
             android:id="@+id/widgets_list_view"
-            android:theme="@style/CustomOverscroll.Dark"
             android:layout_width="match_parent"
             android:layout_height="match_parent" />
 
+        <!-- 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/layout/workspace_screen.xml b/res/layout/workspace_screen.xml
index faf6885..94bdedb 100644
--- a/res/layout/workspace_screen.xml
+++ b/res/layout/workspace_screen.xml
@@ -19,4 +19,5 @@
     xmlns:launcher="http://schemas.android.com/apk/res-auto"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
-    android:hapticFeedbackEnabled="false" />
+    android:hapticFeedbackEnabled="false"
+    launcher:containerType="workspace" />
diff --git a/res/mipmap-hdpi/ic_launcher_home_foreground.png b/res/mipmap-hdpi/ic_launcher_home_foreground.png
new file mode 100644
index 0000000..d068d92
--- /dev/null
+++ b/res/mipmap-hdpi/ic_launcher_home_foreground.png
Binary files differ
diff --git a/res/mipmap-mdpi/ic_launcher_home_foreground.png b/res/mipmap-mdpi/ic_launcher_home_foreground.png
new file mode 100644
index 0000000..0ed9f4d
--- /dev/null
+++ b/res/mipmap-mdpi/ic_launcher_home_foreground.png
Binary files differ
diff --git a/res/mipmap-xhdpi/ic_launcher_home_foreground.png b/res/mipmap-xhdpi/ic_launcher_home_foreground.png
new file mode 100644
index 0000000..7a9daf5
--- /dev/null
+++ b/res/mipmap-xhdpi/ic_launcher_home_foreground.png
Binary files differ
diff --git a/res/mipmap-xxhdpi/ic_launcher_home_foreground.png b/res/mipmap-xxhdpi/ic_launcher_home_foreground.png
new file mode 100644
index 0000000..03b493e
--- /dev/null
+++ b/res/mipmap-xxhdpi/ic_launcher_home_foreground.png
Binary files differ
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 9152ea5..cfc2787 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d breed by %2$d hoog"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Raak en hou om self te plaas"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Voeg outomaties by"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Deursoek programme"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Laai tans programme …"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Geen programme gevind wat met \"<xliff:g id="QUERY">%1$s</xliff:g>\" ooreenstem nie"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Soek meer programme"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Kennisgewings"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Niks meer spasie op die tuisskerm nie."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Geen plek meer in die Gunstelinge-laai nie"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Programmelys"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Laat toe dat tuisskerm gedraai word"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Wanneer foon gedraai word"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Huidige vertooninstelling laat nie rotasie toe nie"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Voeg ikoon by tuisskerm"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Vir nuwe programme"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Onbekend"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Verwyder"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Soek"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"Die program vir hierdie ikoon is nie geïnstalleer nie. Jy kan dit verwyder of die program soek en dit self installeer."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> laai tans af, <xliff:g id="PROGRESS">%2$s</xliff:g> voltooid"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> wag tans om te installeer"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g>-legstukke"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Voeg by tuisskerm"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Skuif item hierheen"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Item is by tuisskerm gevoeg"</string>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index c20dc12..3ae9666 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d ስፋት በ%2$d ከፍታ"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"ራስዎ ለማስቀመጥ ነክተው ይያዙት"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"በራስ-ሰር አክል"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"መተግበሪያዎችን ይፈልጉ"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"መተግበሪያዎችን በመጫን ላይ..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"ከ«<xliff:g id="QUERY">%1$s</xliff:g>» ጋር የሚዛመዱ ምንም መተግበሪያዎች አልተገኙም"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"ተጨማሪ መተግበሪያዎች ይፈልጉ"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"ማሳወቂያዎች"</string>
     <string name="out_of_space" msgid="4691004494942118364">"በዚህ መነሻ ማያ ገጽ ላይ ምንም ቦታ የለም።"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"በተወዳጆች መሣቢያ ውስጥ ተጨማሪ ቦታ የለም"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"የመተግበሪያዎች ዝርዝር"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"የመነሻ ማያ ገጽ ማሽከርከርን ይፍቀዱ"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"ስልኩ ሲዞር"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"የአሁኑ የማሳያ ቅንብር ማሽከርከርን አይፈቅድም"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"አዶ ወደ የመነሻ ማያ ገጽ አክል"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"ለአዲስ መተግበሪያዎች"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"የማይታወቅ"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"አስወግድ"</string>
     <string name="abandoned_search" msgid="891119232568284442">"ፈልግ"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"የዚህ አዶ መተግበሪያ አልተጫነም። ማስወገድ ወይም መተግበሪያውን መፈለግና ራስዎ መጫን ይችላሉ።"</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> በመውረድ ላይ፣ <xliff:g id="PROGRESS">%2$s</xliff:g> ተጠናቋል"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ለመጫን በመጠበቅ ላይ"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> ንዑስ ፕሮግራሞች"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"ወደ መነሻ ማያ ገጽ ያክሉ"</string>
     <string name="action_move_here" msgid="2170188780612570250">"ንጥልን ወደዚህ ውሰድ"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"ወደ መነሻ ማያ ገጽ ንጥል ታክሏል"</string>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index c80d162..9c2167a 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"‏العرض %1$d الطول %2$d"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"المس مع الاستمرار للإضافة يدويًا"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"إضافة تلقائيًا"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"البحث في التطبيقات"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"جارٍ تحميل التطبيقات…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"لم يتم العثور على أية تطبيقات تتطابق مع \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"البحث عن مزيد من التطبيقات"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"الإشعارات"</string>
     <string name="out_of_space" msgid="4691004494942118364">"ليس هناك مساحة أخرى في هذه الشاشة الرئيسية."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"لا يوجد المزيد من الحقول في علبة المفضلة"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"قائمة التطبيقات"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"السماح بتدوير الشاشة الرئيسية"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"عند تدوير الهاتف"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"لا يسمح إعداد العرض الحالي بالتدوير"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"إضافة رمز إلى الشاشة الرئيسية"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"للتطبيقات الجديدة"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"غير معروفة"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"إزالة"</string>
     <string name="abandoned_search" msgid="891119232568284442">"بحث"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"لم يتم تثبيت تطبيق لهذا الرمز. يمكنك إزالته أو البحث عن التطبيق وتثبيته يدويًا."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"جارٍ تنزيل <xliff:g id="NAME">%1$s</xliff:g>، اكتمل <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> في انتظار التثبيت"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"أدوات <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"إضافة إلى الشاشة الرئيسية"</string>
     <string name="action_move_here" msgid="2170188780612570250">"نقل العنصر إلى هنا"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"تمت إضافة العنصر إلى الشاشة الرئيسية"</string>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index 1a4f31b..5cbe408 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%2$d hündürlük %1$d enində"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Manual olaraq yerləşdirmək üçün toxunaraq basıb saxlayın"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Avtomatik əlavə edin"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Tətbiq Axtarın"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Tətbiqlər endirilir..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" sorğusuna uyğun Tətbiqlər tapılmadı"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Daha çox tətbiq üçün axtarış edin"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Bildirişlər"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Bu Əsas ekranda boş yer yoxdur."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Favoritlər-də yer yoxdur"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Tətbiq siyahısı"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Əsas ekranın firlanmağına icazə verin"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Telefon çevrilən zaman"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Cari Ekran ayarı fırlatmağa icazə vermir"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Əsas ekrana ikona əlavə edin"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Yeni tətbiqlər üçün"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Naməlum"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Yığışdır"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Axtarış"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"Bu ikona üçün tətbiq quraşdırılmayıb. Onu silə bilərsiniz, və ya tətbiqi taparaq manual yol ilə quraşdıra bilərsiniz."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> endirilir, <xliff:g id="PROGRESS">%2$s</xliff:g> tamamlandı"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> yüklənmək üçün gözləyir"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> vidcetləri"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Əsas ekrana əlavə edin"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Elementi bura köçürün"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Element əsas ekrana əlavə edildi"</string>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index a9344f9..1a9ad2a 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"širina od %1$d i visina od %2$d"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Dodirnite i zadržite da biste postavili ručno"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Automatski dodaj"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Pretražite aplikacije"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Aplikacije se učitavaju..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Nije pronađena nijedna aplikacija za „<xliff:g id="QUERY">%1$s</xliff:g>“"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Pretraži još aplikacija"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Obaveštenja"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Nema više prostora na ovom početnom ekranu."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Nema više prostora na traci Omiljeno"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Lista aplikacija"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Dozvoli rotaciju početnog ekrana"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Kada se telefon rotira"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Aktuelno podešavanje prikaza ne dozvoljava rotaciju"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Dodaj ikonu na početni ekran"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Za nove aplikacije"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Nepoznato"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Ukloni"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Pretraži"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"Aplikacija za ovu ikonu nije instalirana. Možete da je uklonite ili da potražite aplikaciju i instalirate je ručno."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> se preuzima, završeno je <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> čeka na instaliranje"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Vidžeti za <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Dodaj na početni ekran"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Premesti stavku ovde"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Stavka je dodata na početni ekran"</string>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 2c6d3d4..8dc1e10 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"Ширина %1$d и височина %2$d"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Докоснете и задръжте, за да поставите ръчно"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Автоматично добавяне"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Търсене в приложенията"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Приложенията се зареждат…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Няма намерени приложения, съответстващи на „<xliff:g id="QUERY">%1$s</xliff:g>“"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Търсене на още приложения"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Известия"</string>
     <string name="out_of_space" msgid="4691004494942118364">"На този начален екран няма повече място."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Няма повече място в областта с любимите"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Списък с приложения"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Разрешаване на завъртането на началния екран"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"При завъртане на телефона"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Текущата настройка на екрана не разрешава завъртане"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Добавяне на икона към началния екран"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"За нови приложения"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Няма информация"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Премахване"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Търсене"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"Приложението за тази икона не е инсталирано. Можете да я премахнете или да потърсите приложението и да го инсталирате ръчно."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> се изтегля. Завършено: <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> изчаква инсталиране"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Приспособления за <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Добавяне към началния екран"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Преместване на елемента тук"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Елементът е добавен към началния екран"</string>
diff --git a/res/values-bn-rBD/config.xml b/res/values-bn-rBD/config.xml
new file mode 100644
index 0000000..56f98c3
--- /dev/null
+++ b/res/values-bn-rBD/config.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="icon_shape_override_paths_values">
+    <item msgid="6640290598899495380"></item>
+    <item msgid="4009824731445917273">"M50,0L100,0 100,100 0,100 0,0z"</item>
+    <item msgid="3964229851574011244">"M50,0L80,0 A20,20,0,0 1 100,20 L100,80 A20,20,0,0 1 80,100 L20,100 A20,20,0,0 1 0,80 L 0,20 A20,20,0,0 1 20,0z"</item>
+    <item msgid="363553284746233331">"M50,0 C10,0 0,10 0,50 0,90 10,100 50,100 90,100 100,90 100,50 100,10 90,0 50,0 Z"</item>
+    <item msgid="4319038504053267455">"M50 0A50 50,0,1,1,50 100A50 50,0,1,1,50 0"</item>
+    <item msgid="483370082941112059">"M50,0A50,30 0,0,1 100,30V70A50,30 0,0,1 0,70V30A50,30 0,0,1 50,0z"</item>
+  </string-array>
+    <!-- no translation found for icon_shape_override_paths_names:0 (4837899951986816538) -->
+</resources>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index d5108e9..065019a 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%2$d উচ্চতা অনুযায়ী %1$d প্রস্থ"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"নিজে যোগ করতে টাচ করে ধরে রাখুন"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"স্বয়ংক্রিয়ভাবে যোগ করুন"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"অ্যাপ্লিকেশানগুলি অনুসন্ধান করুন"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"অ্যাপ্লিকেশানগুলি লোড হচ্ছে..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" এর সাথে মেলে এমন কোনো অ্যাপ্লিকেশান পাওয়া যায়নি"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"আরো অ্যাপ্লিকেশানের জন্য অনুসন্ধান করুন"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"বিজ্ঞপ্তি"</string>
     <string name="out_of_space" msgid="4691004494942118364">"এই হোম স্ক্রীনে আর কোনো জায়গা নেই৷"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"পছন্দসই ট্রে-তে আর কোনো জায়গা নেই"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"অ্যাপ্লিকেশানগুলির তালিকা"</string>
@@ -71,6 +76,11 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"হোমস্ক্রীন ঘোরানোর অনুমতি দিন"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"যখন ফোনটি ঘোরানো হয়"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"বর্তমান প্রদর্শনের সেটিংস ঘোরানোর মঞ্জুরি দেয় না"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"হোম স্ক্রিনে আইকন যোগ করুন"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"নতুন অ্যাপ্লিকেশানগুলির জন্যে"</string>
+    <string name="icon_shape_override_label" msgid="2977264953998281004">"আইকনের আকৃতি পরিবর্তন করুন"</string>
+    <string name="icon_shape_no_override" msgid="3678524428085518367">"পরিবর্তন করবেন না"</string>
+    <string name="icon_shape_override_progress" msgid="3461735694970239908">"আইকনের আকৃতি পরিবর্তন করা হচ্ছে"</string>
     <string name="package_state_unknown" msgid="7592128424511031410">"অজানা"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"সরান"</string>
     <string name="abandoned_search" msgid="891119232568284442">"অনুসন্ধান"</string>
@@ -78,6 +88,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"এই আইকনের অ্যাপ্লিকেশানটি ইন্সটল করা নাই। আপনি এটি সরাতে পারেন বা অ্যাপ্লিকেশানটি অনুসন্ধান করে এটি নিজে ইন্সটল করতে পারেন।"</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> ডাউনলোড হচ্ছে <xliff:g id="PROGRESS">%2$s</xliff:g> সম্পন্ন হয়েছে"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ইনস্টলের অপেক্ষায় রয়েছে"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> উইজেট"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"হোম স্ক্রীনে যোগ করুন"</string>
     <string name="action_move_here" msgid="2170188780612570250">"এখানে আইটেম সরান"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"হোম স্ক্রীনে আইটেম যোগ করা হয়েছে"</string>
@@ -93,10 +104,10 @@
     <string name="create_folder_with" msgid="4050141361160214248">"এর সাথে ফোল্ডার তৈরি করুন: <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_created" msgid="6409794597405184510">"ফোল্ডার তৈরি করা হয়েছে"</string>
     <string name="action_move_to_workspace" msgid="1603837886334246317">"হোম স্ক্রীনে সরান"</string>
-    <string name="action_move_screen_left" msgid="8854216831569401665">"স্ক্রীন বাম দিকে সরান"</string>
+    <string name="action_move_screen_left" msgid="8854216831569401665">"স্ক্রীন বাঁ দিকে সরান"</string>
     <string name="action_move_screen_right" msgid="329334910274311123">"স্ক্রীন ডান দিকে সরান"</string>
     <string name="screen_moved" msgid="266230079505650577">"স্ক্রীন সরানো হয়েছে"</string>
-    <string name="action_resize" msgid="1802976324781771067">"পুনরায় আকার দিন"</string>
+    <string name="action_resize" msgid="1802976324781771067">"আবার আকার দিন"</string>
     <string name="action_increase_width" msgid="8773715375078513326">"প্রস্থ বাড়ান"</string>
     <string name="action_increase_height" msgid="459390020612501122">"উচ্চতা বাড়ান"</string>
     <string name="action_decrease_width" msgid="1374549771083094654">"প্রস্থ কমান"</string>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index 579b95a..63aaaba 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -27,18 +27,23 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"Širina %1$d, visina %2$d"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Dodirnite i držite da postavite ručno"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Dodaj automatski"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Pretraži aplikacije"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Aplikacije se učitavaju…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Nije pronađena nijedna aplikacija koja odgovara upitu \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Pretraži više aplikacija"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Obavještenja"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Na ovom početnom ekranu nema više prostora."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Nema više prostora u ladici Favoriti"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Spisak aplikacija"</string>
-    <string name="all_apps_home_button_label" msgid="252062713717058851">"Tipka za početak"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"Početna"</string>
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Ukloni"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Deinstaliraj"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Informacije o aplikaciji"</string>
@@ -64,13 +69,21 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Ime fascikle je promijenjeno u <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format" msgid="6629239338071103179">"Fascikla: <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="widget_button_text" msgid="2880537293434387943">"Dodaci"</string>
-    <string name="wallpaper_button_text" msgid="8404103075899945851">"Pozadine"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"Pozadinske slike"</string>
     <string name="settings_button_text" msgid="8119458837558863227">"Postavke"</string>
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"Onemogućio vaš administrator"</string>
     <string name="accessibility_action_overview" msgid="6257665857640347026">"Pregled"</string>
     <string name="allow_rotation_title" msgid="7728578836261442095">"Dozvoli rotiranje početnog ekrana"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Kada se telefon zarotira"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Trenutne postavke ekrana ne dozvoljavaju rotiranje"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Dodajte ikonu na početni ekran"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Za nove aplikacije"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Nepoznato"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Ukloni"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Traži"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"Aplikacija za ovu ikonu nije instalirana. Možete je ukloniti ili potražiti aplikaciju i ručno je instalirati."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> se preuzima, završeno <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> čeka da se instalira"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Vidžeti za aplikaciju <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Dodaj na početni ekran"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Premjesti stavku ovdje"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Stavka je dodana na Početni ekran."</string>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 4ba0498..bfbd31e 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d d\'amplada per %2$d d\'alçada"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Toca i mantén premut l\'element per col·locar-lo manualment"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Afegeix automàticament"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Cerca a les aplicacions"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"S\'estan carregant les aplicacions..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"No s\'ha trobat cap aplicació que coincideixi amb <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Cerca més aplicacions"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Notificacions"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Ja no queda espai en aquesta pantalla d\'inici."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"No hi ha més espai a la safata Preferits."</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Llista d\'aplicacions"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Permet la rotació de la pantalla d\'inici"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"En girar el telèfon"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"La configuració actual de la pantalla no permet la rotació"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Afegeix la icona a la pantalla d\'inici"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Per a les aplicacions noves"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Desconegut"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Suprimeix"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Cerca"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"L\'aplicació d\'aquesta icona no està instal·lada. Pots suprimir-la o cercar l\'aplicació i instal·lar-la manualment."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"S\'està baixant <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> completat"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"S\'està esperant per instal·lar <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Widgets de: <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Afegeix a la pantalla d\'inici"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Mou l\'element aquí"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"S\'ha afegit l\'element a la pantalla d\'inici"</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 373920c..7c61023 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"šířka %1$d, výška %2$d"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Chcete-li položku umístit ručně, klepněte na ni a podržte ji"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Přidat automaticky"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Hledat aplikace"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Načítání aplikací…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Dotazu „<xliff:g id="QUERY">%1$s</xliff:g>“ neodpovídají žádné aplikace"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Vyhledat další aplikace"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Oznámení"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Na této ploše již není místo."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Na panelu Oblíbené položky již není místo."</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Seznam aplikací"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Povolit otáčení plochy"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Při otočení telefonu"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Aktuální nastavení displeje neumožňuje otáčení"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Přidat ikonu na plochu"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Pro nové aplikace"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Neznámé"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Odstranit"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Hledat"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"Aplikace pro tuto ikonu není nainstalována. Můžete ikonu odstranit nebo zkusit aplikaci vyhledat a nainstalovat ručně."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"Stahování aplikace <xliff:g id="NAME">%1$s</xliff:g> (dokončeno <xliff:g id="PROGRESS">%2$s</xliff:g>)"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"Instalace aplikace <xliff:g id="NAME">%1$s</xliff:g> čeká na zahájení"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Widgety <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Přidat na plochu"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Přesunout položku sem"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Položka byla přidána na plochu"</string>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 9304140..65e5134 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d i bredden og %2$d i højden"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Tryk, og hold fingeren nede for at placere manuelt"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Tilføj automatisk"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Søg i Apps"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Indlæser apps…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Der blev ikke fundet nogen apps, som matcher \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Søg efter flere apps"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Underretninger"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Der er ikke mere plads på denne startskærm."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Der er ikke mere plads i bakken Foretrukne"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Liste med apps"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Tillad rotation af startskærmen"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Når telefonen roteres"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Den aktuelle indstilling for visning tillader ikke rotation"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Føj ikon til startskærmen"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"For nye apps"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Ukendt"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Fjern"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Søg"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"Appen, der hører til dette ikon, er ikke installeret. Du kan fjerne den eller prøve at søge efter appen og installere den manuelt."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> downloades. <xliff:g id="PROGRESS">%2$s</xliff:g> er gennemført"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> venter på at installere"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g>-widgets"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Føj til startskærm"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Flyt elementet hertil"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Elementet er føjet til startskærmen"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index e461d46..59ce70c 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d breit und %2$d hoch"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Zum manuellen Hinzufügen gedrückt halten"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Automatisch hinzufügen"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"In Apps suchen"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Apps werden geladen..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Keine Apps für \"<xliff:g id="QUERY">%1$s</xliff:g>\" gefunden"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Weitere Apps suchen"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Benachrichtigungen"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Auf diesem Startbildschirm ist kein Platz mehr vorhanden."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Ablage \"Favoriten\" ist voll."</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Liste der Apps"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Drehung des Startbildschirms zulassen"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Bei Drehung des Smartphones"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Die aktuelle \"Display\"-Einstellung verhindert eine Drehung der Anzeige"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Symbol zu Startbildschirm hinzufügen"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Für neue Apps"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Unbekannt"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Entfernen"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Suchen"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"Die App für dieses Symbol ist nicht installiert. Du kannst das Symbol entfernen oder die App lokalisieren und dann manuell installieren."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> wird heruntergeladen, <xliff:g id="PROGRESS">%2$s</xliff:g> abgeschlossen"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"Warten auf Installation von <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g>-Widgets"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Zum Startbildschirm hinzufügen"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Element hierhin verschieben"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Element zum Startbildschirm hinzugefügt"</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 119232d..d8da34b 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"Πλάτος %1$d επί ύψος %2$d"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Αγγίξτε παρατεταμένα για μη αυτόματη τοποθέτηση"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Αυτόματη προσθήκη"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Αναζήτηση εφαρμογών"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Φόρτωση εφαρμογών…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Δεν βρέθηκαν εφαρμογές για το ερώτημα \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Αναζήτηση περισσότερων εφαρμογών"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Ειδοποιήσεις"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Δεν υπάρχει χώρος σε αυτήν την αρχική οθόνη."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Δεν υπάρχει επιπλέον χώρος στην περιοχή Αγαπημένα"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Λίστα εφαρμογών"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Να επιτρέπεται η περιστροφή της αρχικής οθόνης"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Όταν το τηλέφωνο περιστρέφεται"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Η τρέχουσα ρύθμιση οθόνης δεν επιτρέπει την περιστροφή"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Προσθήκη εικονιδίου στην Αρχική οθόνη"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Για νέες εφαρμογές"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Άγνωστο"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Κατάργηση"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Αναζήτηση"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"Η εφαρμογή γι\' αυτό το εικονίδιο δεν είναι εγκατεστημένη. Μπορείτε να το καταργήσετε ή να αναζητήσετε την εφαρμογή και να την εγκαταστήσετε με μη αυτόματο τρόπο."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"Λήψη <xliff:g id="NAME">%1$s</xliff:g>, ολοκληρώθηκε <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> σε αναμονή για εγκατάσταση"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Γραφικά στοιχεία <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Προσθήκη στην αρχική οθόνη"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Μετακίνηση στοιχείου εδώ"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Το στοιχείο προστέθηκε στην αρχική οθόνη"</string>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index 44daaa8..ecfb0f5 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d wide by %2$d high"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Touch &amp; hold to place manually"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Add automatically"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Search Apps"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Loading Apps…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"No Apps found matching \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Search for more apps"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Notifications"</string>
     <string name="out_of_space" msgid="4691004494942118364">"No more room on this Home screen."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"No more room in the Favourites tray"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Apps list"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Allow Homescreen rotation"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"When phone is rotated"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Current display setting doesn\'t permit rotation"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Add icon to Home screen"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"For new apps"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Unknown"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Remove"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Search"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"The app for this icon isn\'t installed. You can remove it, or search for the app and install it manually."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> downloading, <xliff:g id="PROGRESS">%2$s</xliff:g> complete"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> waiting to install"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> widgets"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Add to Home screen"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Move item here"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Item added to home screen"</string>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index 44daaa8..ecfb0f5 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d wide by %2$d high"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Touch &amp; hold to place manually"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Add automatically"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Search Apps"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Loading Apps…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"No Apps found matching \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Search for more apps"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Notifications"</string>
     <string name="out_of_space" msgid="4691004494942118364">"No more room on this Home screen."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"No more room in the Favourites tray"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Apps list"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Allow Homescreen rotation"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"When phone is rotated"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Current display setting doesn\'t permit rotation"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Add icon to Home screen"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"For new apps"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Unknown"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Remove"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Search"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"The app for this icon isn\'t installed. You can remove it, or search for the app and install it manually."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> downloading, <xliff:g id="PROGRESS">%2$s</xliff:g> complete"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> waiting to install"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> widgets"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Add to Home screen"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Move item here"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Item added to home screen"</string>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index 44daaa8..ecfb0f5 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d wide by %2$d high"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Touch &amp; hold to place manually"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Add automatically"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Search Apps"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Loading Apps…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"No Apps found matching \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Search for more apps"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Notifications"</string>
     <string name="out_of_space" msgid="4691004494942118364">"No more room on this Home screen."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"No more room in the Favourites tray"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Apps list"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Allow Homescreen rotation"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"When phone is rotated"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Current display setting doesn\'t permit rotation"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Add icon to Home screen"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"For new apps"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Unknown"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Remove"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Search"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"The app for this icon isn\'t installed. You can remove it, or search for the app and install it manually."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> downloading, <xliff:g id="PROGRESS">%2$s</xliff:g> complete"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> waiting to install"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> widgets"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Add to Home screen"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Move item here"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Item added to home screen"</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 4bd6c88..4a57396 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d de ancho por %2$d de alto"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Mantén presionado para ubicarlo manualmente"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Agregar automáticamente"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Buscar aplicaciones"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Cargando aplicaciones…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"No hay aplicaciones que coincidan con <xliff:g id="QUERY">%1$s</xliff:g>."</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Buscar más apps"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Notificaciones"</string>
     <string name="out_of_space" msgid="4691004494942118364">"No hay más espacio en esta pantalla principal."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"La bandeja de favoritos está llena."</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Lista de apps"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Permitir la rotación de la pantalla principal"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Al girar el teléfono"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"La configuración actual no permite la rotación de la pantalla"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Agregar ícono a la pantalla principal"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Para nuevas apps"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Desconocido"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Eliminar"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Buscar"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"La aplicación para este ícono no está instalada. Puedes eliminar el ícono o buscar la aplicación e instarla manualmente."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"Se completó el <xliff:g id="PROGRESS">%2$s</xliff:g> de la descarga de <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"Instalación de <xliff:g id="NAME">%1$s</xliff:g> en espera"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Widgets de <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Agregar a la pantalla principal"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Mover elemento aquí"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Se agregó el elemento a la pantalla principal."</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index a4fc620..3db1aa8 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d de ancho por %2$d de alto"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Mantenlo pulsado para añadirlo manualmente"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Añadir automáticamente"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Busca aplicaciones"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Cargando aplicaciones…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"No se han encontrado aplicaciones que contengan \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Buscar más aplicaciones"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Notificaciones"</string>
     <string name="out_of_space" msgid="4691004494942118364">"No queda espacio en la pantalla de inicio."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"La bandeja de favoritos está completa"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Lista de aplicaciones"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Permitir rotación de la pantalla de inicio"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Al girar el teléfono"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"La configuración de pantalla actual no permite girar la pantalla"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Añadir icono a la pantalla de inicio"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Para nuevas aplicaciones"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Desconocido"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Quitar"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Buscar"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"La aplicación de este icono no está instalada. Puedes quitar el icono o buscar la aplicación e instalarla manualmente."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"Descargando <xliff:g id="NAME">%1$s</xliff:g> (<xliff:g id="PROGRESS">%2$s</xliff:g> completado)"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"Esperando para instalar <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Widgets de <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Añadir a la pantalla de inicio"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Mover elemento aquí"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Elemento añadido a la pantalla de inicio"</string>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index 76ef5c6..1a4d809 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d zabal eta %2$d luze"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Eduki sakatuta eskuz gehitzeko"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Gehitu automatikoki"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Bilatu aplikazioetan"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Aplikazioak kargatzen…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Ez da aurkitu \"<xliff:g id="QUERY">%1$s</xliff:g>\" bilaketarekin bat datorren aplikaziorik"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Bilatu aplikazio gehiago"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Jakinarazpenak"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Hasierako pantaila honetan ez dago toki gehiago."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Ez dago toki gehiago Gogokoak erretiluan"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Aplikazioen zerrenda"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Baimendu hasierako pantaila biratzea"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Telefonoa biratzen denean"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Uneko pantaila-ezarpenak ez du onartzen ikuspegia biratzea"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Gehitu ikonoa hasierako pantailan"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Aplikazio berrietan"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Ezezaguna"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Kendu"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Bilatu"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"Ikono honen aplikazioa ez dago instalatuta. Ikonoa ken dezakezu, edo aplikazioa bilatu eta eskuz instalatu."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> deskargatzen, <xliff:g id="PROGRESS">%2$s</xliff:g> osatuta"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> instalatzeko zain"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> widgetak"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Gehitu hasierako pantailan"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Ekarri elementua hona"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Gehitu da elementua hasierako pantailan"</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 95675a7..3a9a85a 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"‏%1$d عرض در %2$d طول"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"برای قرار دادن به‌صورت دستی لمس کنید و بکشید"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"افزودن خودکار"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"جستجوی برنامه‌ها"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"در حال بارگیری برنامه‌ها..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"هیچ برنامه‌ای مطابق با «<xliff:g id="QUERY">%1$s</xliff:g>» پیدا نشد"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"جستجوی برنامه‌های بیشتر"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"اعلان‌ها"</string>
     <string name="out_of_space" msgid="4691004494942118364">"فضای بیشتری در این صفحه اصلی موجود نیست."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"فضای بیشتری در سینی موارد دلخواه وجود ندارد"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"فهرست برنامه‌ها"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"امکان دادن به چرخش صفحه اصلی"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"وقتی تلفن چرخانده می‌شود"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"تنظیم نمایشگر کنونی اجازه چرخش نمی‌دهد"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"افزودن نماد به صفحه اصلی"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"برای برنامه‌های جدید"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"نامشخص"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"حذف"</string>
     <string name="abandoned_search" msgid="891119232568284442">"جستجو"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"برنامه برای این نماد نصب نشده است. می‌توانید آن را حذف کنید یا سعی کنید برنامه را جستجو کنید و آن را به صورت دستی نصب کنید."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"درحال بارگیری <xliff:g id="NAME">%1$s</xliff:g>، <xliff:g id="PROGRESS">%2$s</xliff:g> کامل شد"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> درانتظار نصب"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"ابزارک‌های <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"افزودن به صفحه اصلی"</string>
     <string name="action_move_here" msgid="2170188780612570250">"انتقال مورد به اینجا"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"مورد به صفحه اصلی اضافه شد"</string>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 40e6d96..6e4bc93 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"Leveys: %1$d, korkeus: %2$d"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Sijoita manuaalisesti koskettamalla pitkään."</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Lisää automaattisesti"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Sovellushaku"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Ladataan sovelluksia…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"”<xliff:g id="QUERY">%1$s</xliff:g>” ei palauttanut sovelluksia."</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Hae lisää sovelluksia"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Ilmoitukset"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Tässä aloitusruudussa ei ole enää tilaa."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Suosikit-valikossa ei ole enää tilaa"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Sovellusluettelo"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Salli aloitusnäytön kiertäminen"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Kun puhelinta kierretään"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Nykyiset näyttöasetukset eivät salli näytön kiertämistä."</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Lisää kuvake aloitusruutuun"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Uusille sovelluksille"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Tuntematon"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Poista"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Haku"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"Kuvakkeen sovellusta ei ole asennettu. Voit poistaa kuvakkeen tai etsiä sovelluksen ja asentaa sen manuaalisesti."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> latautuu, valmiina <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> odottaa asennusta"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Widgetit: <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Lisää aloitusnäytölle"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Siirrä kohde tänne"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Kohde lisättiin aloitusnäytölle."</string>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index 01d6b27..a9f2947 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d de largeur sur %2$d de hauteur"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Maintenez le doigt sur l\'élément pour le placer manuellement"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Ajouter automatiquement"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Rechercher des applications"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Chargement des applications en cours..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Aucune application trouvée correspondant à « <xliff:g id="QUERY">%1$s</xliff:g> »"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Rechercher plus d\'applications"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Notifications"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Pas d\'espace libre sur l\'écran d\'accueil."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Il n\'y a plus d\'espace dans la zone des favoris"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Liste des applications"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Autoriser la rotation de l\'écran d\'accueil"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Lorsque vous faites pivoter le téléphone"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Le mode d\'affichage actuel ne permet pas le pivotement"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Ajouter l\'icône à l\'écran d\'accueil"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Pour les nouvelles applications"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Inconnu"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Supprimer"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Rechercher"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"L\'application liée à cette icône n\'est pas installée. Vous pouvez la supprimer ou rechercher l\'application et l\'installer manuellement."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"Téléchargement de <xliff:g id="NAME">%1$s</xliff:g> : <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> en attente d\'installation"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Widgets pour <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Ajouter à l\'écran d\'accueil"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Déplacer l\'élément ici"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Élément ajouté à l\'écran d\'accueil"</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index f42748c..2d7a38c 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d de largeur et %2$d de hauteur"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Appuyez de manière prolongée pour placer l\'élément manuellement"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Ajouter automatiquement"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Rechercher dans les applications"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Chargement des applications en cours…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Aucune application ne correspond à la requête \"<xliff:g id="QUERY">%1$s</xliff:g>\"."</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Rechercher plus d\'applications"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Notifications"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Pas d\'espace libre sur cet écran d\'accueil."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Plus d\'espace disponible dans la zone de favoris."</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Liste d\'applications"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Autoriser la rotation de l\'écran d\'accueil"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Lorsque vous faites pivoter le téléphone"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Le paramètre d\'affichage actuel n\'autorise pas la rotation."</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Ajouter l\'icône à l\'écran d\'accueil"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Pour les nouvelles applications"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Inconnu"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Supprimer"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Rechercher"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"L\'application correspondant à cette icône n\'est pas installée. Vous pouvez supprimer cette dernière, ou essayer de rechercher l\'application et de l\'installer manuellement."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> en cours de téléchargement, <xliff:g id="PROGRESS">%2$s</xliff:g> effectué(s)"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> en attente d\'installation"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Widgets <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Ajouter à l\'écran d\'accueil"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Déplacer l\'élément ici"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"L\'élément a bien été ajouté à l\'écran d\'accueil."</string>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index c03ecdc..e8213b5 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d de largo por %2$d de alto"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Mantén premido o elemento para colocalo manualmente"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Engadir automaticamente"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Aplicacións de busca"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Cargando aplicacións..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Non se atoparon aplicacións que coincidan con \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Buscar máis aplicacións"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Notificacións"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Non hai máis espazo nesta pantalla de inicio."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Non hai máis espazo na bandexa de favoritos"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Lista de aplicacións"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Permitir xirar a pantalla de inicio"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Ao xirar o teléfono"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"A configuración de visualización actual non permite xirar a pantalla"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Engadir icona á pantalla de inicio"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Para novas aplicacións"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Descoñecido"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Eliminar"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Buscar"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"A aplicación para esta icona non está instalada. Podes eliminala ou buscar a aplicación e instalala manualmente."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"Descargando <xliff:g id="NAME">%1$s</xliff:g> (<xliff:g id="PROGRESS">%2$s</xliff:g> completado)"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"Esperando para instalar <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Widgets de: <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Engadir á pantalla de inicio"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Mover elemento aquí"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Engadiuse o elemento á pantalla de inicio"</string>
diff --git a/res/values-gu-rIN/config.xml b/res/values-gu-rIN/config.xml
new file mode 100644
index 0000000..56f98c3
--- /dev/null
+++ b/res/values-gu-rIN/config.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="icon_shape_override_paths_values">
+    <item msgid="6640290598899495380"></item>
+    <item msgid="4009824731445917273">"M50,0L100,0 100,100 0,100 0,0z"</item>
+    <item msgid="3964229851574011244">"M50,0L80,0 A20,20,0,0 1 100,20 L100,80 A20,20,0,0 1 80,100 L20,100 A20,20,0,0 1 0,80 L 0,20 A20,20,0,0 1 20,0z"</item>
+    <item msgid="363553284746233331">"M50,0 C10,0 0,10 0,50 0,90 10,100 50,100 90,100 100,90 100,50 100,10 90,0 50,0 Z"</item>
+    <item msgid="4319038504053267455">"M50 0A50 50,0,1,1,50 100A50 50,0,1,1,50 0"</item>
+    <item msgid="483370082941112059">"M50,0A50,30 0,0,1 100,30V70A50,30 0,0,1 0,70V30A50,30 0,0,1 50,0z"</item>
+  </string-array>
+    <!-- no translation found for icon_shape_override_paths_names:0 (4837899951986816538) -->
+</resources>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index 39a176c..aa4992d 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d પહોળાઈ X %2$d ઊંચાઈ"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"મેન્યુઅલી મૂકવા માટે ટચ કરી દબાવી રાખો"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"આપમેળે ઉમેરો"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"શોધ ઍપ્લિકેશનો"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"ઍપ્લિકેશનો લોડ કરી રહ્યું છે…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" થી મેળ ખાતી કોઈ ઍપ્લિકેશનો મળી નથી"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"વધુ ઍપ્લિકેશનો શોધો"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"સૂચનાઓ"</string>
     <string name="out_of_space" msgid="4691004494942118364">"આ હોમ સ્ક્રીન પર વધુ જગ્યા નથી."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"મનપસંદ ટ્રે પર વધુ જગ્યા નથી"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"ઍપ્લિકેશનોની સૂચિ"</string>
@@ -71,6 +76,11 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"હોમ સ્ક્રીનને ફેરવવાની મંજૂરી આપો"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"જ્યારે ફોન ફેરવવામાં આવે ત્યારે"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"વર્તમાન પ્રદર્શન સેટિંગ ફેરવવાની પરવાનગી આપતી નથી"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"હોમ સ્ક્રીન પર આઇકન ઉમેરો"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"નવી ઍપ્લિકેશનો માટે"</string>
+    <string name="icon_shape_override_label" msgid="2977264953998281004">"આઇકનનો આકાર બદલો"</string>
+    <string name="icon_shape_no_override" msgid="3678524428085518367">"બદલશો નહીં"</string>
+    <string name="icon_shape_override_progress" msgid="3461735694970239908">"આઇકનના આકારમાં કરેલ ફેરફારો લાગુ કરી રહ્યા છીએ"</string>
     <string name="package_state_unknown" msgid="7592128424511031410">"અજાણ્યો"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"દૂર કરો"</string>
     <string name="abandoned_search" msgid="891119232568284442">"શોધો"</string>
@@ -78,6 +88,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"આ આયકન માટેની ઍપ્લિકેશન ઇન્સ્ટોલ થયેલ નથી. તમે તેને દૂર કરી શકો છો અથવા ઍપ્લિકેશન માટે શોધ કરી અને તેને મેન્યુઅલી ઇન્સ્ટોલ કરી શકો છો."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> ડાઉનલોડ કરી રહ્યાં છે, <xliff:g id="PROGRESS">%2$s</xliff:g> પૂર્ણ"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g>, ઇન્સ્ટૉલ થવાની રાહ જોઈ રહ્યું છે"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> વિજેટ"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"હોમ સ્ક્રીન પર ઉમેરો"</string>
     <string name="action_move_here" msgid="2170188780612570250">"આઇટમ અહીં ખસેડો"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"હોમ સ્ક્રીનમાં આઇટમ ઉમેરી"</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index f534e1e..ba03a92 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d चौड़ाई गुणा %2$d ऊंचाई"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"मैन्युअल रूप से जोड़ने के लिए स्पर्श करके रखें"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"अपने आप जोड़ें"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ऐप्‍स खोजें"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"ऐप्स लोड हो रहे हैं..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" से मिलान करने वाला कोई ऐप नहीं मिला"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"अधिक ऐप्लिकेशन खोजें"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"नोटिफ़िकेशन"</string>
     <string name="out_of_space" msgid="4691004494942118364">"इस होम स्‍क्रीन पर स्थान शेष नहीं है."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"पसंदीदा ट्रे में और स्थान नहीं है"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"ऐप्लिकेशन सूची"</string>
@@ -54,7 +59,7 @@
     <string name="uninstall_system_app_text" msgid="4172046090762920660">"यह एक सिस्टम ऐप्लिकेशन है और इसे अनइंस्टॉल नहीं किया जा सकता."</string>
     <string name="folder_hint_text" msgid="6617836969016293992">"अनामित फ़ोल्डर"</string>
     <string name="disabled_app_label" msgid="6673129024321402780">"<xliff:g id="APP_NAME">%1$s</xliff:g> अक्षम है"</string>
-    <string name="default_scroll_format" msgid="7475544710230993317">"पृष्ठ %2$d में से %1$d"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"पेज %2$d में से %1$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"होम स्क्रीन %2$d में से %1$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"नया होम स्‍क्रीन पृष्‍ठ"</string>
     <string name="folder_opened" msgid="94695026776264709">"फ़ोल्डर खोला गया, <xliff:g id="WIDTH">%1$d</xliff:g> गुणा <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"होमस्क्रीन घुमाने की अनुमति दें"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"फ़ोन घुुमाए जाने पर"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"वर्तमान प्रदर्शन सेटिंग घुमाने की अनुमति नहीं देती"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"होम स्क्रीन में आइकन जोड़ें"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"नए ऐप्लिकेशन के लिए"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"अज्ञात"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"निकालें"</string>
     <string name="abandoned_search" msgid="891119232568284442">"खोजें"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"इस आइकन का ऐप्स इंस्टॉल नहीं है. आप उसे निकाल सकते हैं या ऐप्स की खोज करके उसे मैन्युअल रूप से इंस्टॉल कर सकते हैं."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> डाउनलोड हो रहा है, <xliff:g id="PROGRESS">%2$s</xliff:g> पूर्ण"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> के इंस्टॉल होने की प्रतीक्षा की जा रही है"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> विजेट"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"होम स्‍क्रीन में जोड़ें"</string>
     <string name="action_move_here" msgid="2170188780612570250">"आइटम यहां ले जाएं"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"होम स्क्रीन में आइटम जोड़ा गया"</string>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index b4fb50e..b6448f1 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d širine i %2$d visine"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Dodirnite i zadržite stavku da biste je postavili ručno"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Dodaj automatski"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Pretraži aplikacije"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Učitavanje aplikacija…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Nema aplikacija podudarnih s upitom \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Traži više aplikacija"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Obavijesti"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Na ovom početnom zaslonu više nema mjesta."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Nema više prostora na traci Favoriti"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Popis aplikacija"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Dopusti zakretanje početnog zaslona"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Kada se telefon zakrene"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Trenutačna postavka zaslona ne dopušta zakretanje"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Dodaj ikonu na početni zaslon"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Za nove aplikacije"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Nepoznato"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Ukloni"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Traži"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"Aplikacija ove ikone nije instalirana. Možete je ukloniti ili potražiti aplikaciju i instalirati je ručno."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"Preuzimanje aplikacije <xliff:g id="NAME">%1$s</xliff:g>, dovršeno <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"Čekanje na instaliranje aplikacije <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> widgeti"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Dodavanje na početni zaslon"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Premjesti stavku ovdje"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Stavka je dodana na početni zaslon"</string>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 279749b..45a5054 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d széles és %2$d magas"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Tartsa lenyomva a manuális hozzáadáshoz"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Automatikus hozzáadás"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Alkalmazások keresése"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Alkalmazások betöltése…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Egy alkalmazás sem található a(z) „<xliff:g id="QUERY">%1$s</xliff:g>” lekérdezésre."</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"További alkalmazások keresése"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Értesítések"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Nincs több hely ezen a kezdőképernyőn."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Nincs több hely a Kedvencek tálcán"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Alkalmazások listája"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"A kezdőképernyő elforgatásának engedélyezése"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"A telefon elforgatásakor"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"A jelenlegi kijelzőbeállítások nem teszik lehetővé az elforgatást"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Ikon hozzáadása a kezdőképernyőhöz"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Új alkalmazásoknál"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Ismeretlen"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Eltávolítás"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Keresés"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"Az ikonhoz tartozó alkalmazás nincs telepítve. Törölheti az ikont, vagy az alkalmazás megkeresése után manuálisan telepítheti azt."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"A(z) <xliff:g id="NAME">%1$s</xliff:g> letöltése, <xliff:g id="PROGRESS">%2$s</xliff:g> kész"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"A(z) <xliff:g id="NAME">%1$s</xliff:g> telepítésre vár"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g>-modulok"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Hozzáadás a kezdőképernyőhöz"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Elem áthelyezése ide"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Elem hozzáadva a kezdőképernyőhöz"</string>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index c7401a9..7c39a3f 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"Լայնությունը՝ %1$d, բարձրությունը՝ %2$d"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Հպեք և պահեք՝ ձեռքով տեղադրելու համար"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Ավելացնել ավտոմատ կերպով"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Հավելվածների որոնում"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Հավելվածների բեռնում…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"«<xliff:g id="QUERY">%1$s</xliff:g>» հարցմանը համապատասխանող հավելվածներ չեն գտնվել"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Որոնել այլ հավելվածներ"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Ծանուցումներ"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Այլևս տեղ չկա այս հիմնական էկրանին:"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Ընտրյալների ցուցակում այլևս ազատ տեղ չկա"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Հավելվածների ցանկ"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Թույլ տալ հիմնական էկրանի պտտումը"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Հեռախոսը պտտելու դեպքում"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Ցուցադրման ընթացիկ կարգավորումներն արգելում են պտտումը"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Ավելացնել պատկերակը Հիմնական էկրանին"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Նոր հավելվածների համար"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Անհայտ է"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Հեռացնել"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Գտնել"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"Այս պատկերակի ծրագիրը տեղադրված չէ: Դուք կարող եք հեռացնել այն կամ գտնել ծրագիրը և տեղադրել այն ձեռքով:"</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g>–ի ներբեռնում (<xliff:g id="PROGRESS">%2$s</xliff:g>)"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g>-ի տեղադրման սպասում"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> վիջեթներ"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Ավելացնել Հիմնական էկրանին"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Տեղափոխել տարրն այստեղ"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Տարրն ավելացվեց հիմնական էկրանին"</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 00e8d63..4d26ab3 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -24,17 +24,22 @@
     <string name="work_folder_name" msgid="3753320833950115786">"Kantor"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"Aplikasi tidak dipasang."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"Aplikasi tidak tersedia"</string>
-    <string name="safemode_shortcut_error" msgid="9160126848219158407">"Aplikasi yang diunduh dinonaktifkan dalam mode Aman"</string>
+    <string name="safemode_shortcut_error" msgid="9160126848219158407">"Aplikasi yang didownload 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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"lebar %1$d x tinggi %2$d"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Sentuh &amp; tahan untuk menempatkan secara manual"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Tambahkan otomatis"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Telusuri Apps"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Memuat Aplikasi..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Tidak ditemukan Aplikasi yang cocok dengan \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Telusuri aplikasi lainnya"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Notifikasi"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Tidak ada ruang lagi pada layar Utama ini."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Tidak ada ruang tersisa di baki Favorit"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Daftar aplikasi"</string>
@@ -71,14 +76,23 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Izinkan layar Utama diputar"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Saat ponsel diputar"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Setelan Tampilan Saat Ini tidak memungkinkan putaran"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Tambahkan ikon ke layar Utama"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Untuk aplikasi baru"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Tidak dikenal"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Buang"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Telusuri"</string>
     <string name="abandoned_promises_title" msgid="7096178467971716750">"Aplikasi ini belum terpasang"</string>
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"Aplikasi untuk ikon ini belum dipasang. Anda dapat membuangnya, atau menelusuri aplikasi dan memasangnya secara manual."</string>
-    <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> sedang diunduh, <xliff:g id="PROGRESS">%2$s</xliff:g> selesai"</string>
+    <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> sedang didownload, <xliff:g id="PROGRESS">%2$s</xliff:g> selesai"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> menunggu dipasang"</string>
-    <string name="action_add_to_workspace" msgid="8902165848117513641">"Tambahkan ke layar Utama"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Widget <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="action_add_to_workspace" msgid="8902165848117513641">"Tambahkan ke Layar Utama"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Pindahkan item ke sini"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Item ditambahkan ke layar utama"</string>
     <string name="item_removed" msgid="851119963877842327">"Item dihapus"</string>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index 7586ae3..3694a10 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d á breidd og %2$d á hæð"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Haltu inni til að staðsetja handvirkt"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Bæta sjálfkrafa við"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Leita í forritum"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Hleður forrit…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Ekki fundust forrit sem samsvara „<xliff:g id="QUERY">%1$s</xliff:g>“"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Leita að fleiri forritum"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Tilkynningar"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Ekki meira pláss á þessum heimaskjá."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Ekki meira pláss í bakka fyrir uppáhald"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Forritalisti"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Leyfa snúning fyrir heimaskjá"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Þegar símanum er snúið"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Núverandi skjástilling leyfir ekki snúning"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Bæta tákni á heimaskjáinn"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Fyrir ný forrit"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Óþekkt"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Fjarlægja"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Leita"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"Forritið fyrir þetta tákn er ekki uppsett. Þú getur fjarlægt það eða leitað að forritinu og sett það upp handvirkt."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> í niðurhali, <xliff:g id="PROGRESS">%2$s</xliff:g> lokið"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> bíður uppsetningar"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g>-græjur"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Bæta á heimaskjá"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Færa atriði hingað"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Atriði bætt á heimaskjáinn"</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index b821222..1208874 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d di larghezza per %2$d di altezza"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Tieni premuto per posizionare l\'elemento manualmente"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Aggiungi automaticamente"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Cerca app"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Caricamento di app…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Nessuna app trovata corrispondente a \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Cerca altre app"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Notifiche"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Spazio nella schermata Home esaurito."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Spazio esaurito nella barra dei Preferiti"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Elenco di app"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Consenti rotazione della schermata Home"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Con il telefono ruotato"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"L\'impostazione corrente del display non consente la rotazione"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Aggiungi icone alla schermata Home"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Per le nuove app"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Sconosciuto"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Rimuovi"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Cerca"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"L\'app per questa icona non è installata. Puoi rimuoverla o cercare l\'app e installarla manualmente."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"Download di <xliff:g id="NAME">%1$s</xliff:g> in corso, <xliff:g id="PROGRESS">%2$s</xliff:g> completato"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> in attesa di installazione"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Widget di <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Aggiungi a schermata Home"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Sposta elemento qui"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Elemento aggiunto alla schermata Home"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index ffea51f..858abfd 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -27,14 +27,19 @@
     <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="long_press_widget_to_add" msgid="7699152356777458215">"גע נגיעה רציפה בווידג\'ט כדי לבחור בו."</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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"‏רוחב %1$d על גובה %2$d"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"גע והחזק כדי להוסיף ידנית"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"הוסף באופן אוטומטי"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"חפש אפליקציות"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"טוען אפליקציות…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"לא נמצאו אפליקציות התואמות ל-\"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"חפש אפליקציות נוספות"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"הודעות"</string>
     <string name="out_of_space" msgid="4691004494942118364">"אין עוד מקום במסך דף הבית הזה."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"אין עוד מקום במגש המועדפים"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"רשימת אפליקציות"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"אפשרות סיבוב של מסך דף הבית"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"כאשר הטלפון מסובב"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"הגדרת התצוגה הנוכחית אינה מאפשרת סיבוב"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"הוספת סמל במסך דף הבית"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"לאפליקציות חדשות"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"לא ידוע"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"הסר"</string>
     <string name="abandoned_search" msgid="891119232568284442">"חפש"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"האפליקציה של סמל זה אינה מותקנת. ניתן להסיר אותו, או לחפש את האפליקציה ולהתקין אותה ידנית."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"מוריד את <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> הושלמו"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"מחכה להתקנה של <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"ווידג\'טים של <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"הוסף למסך דף הבית"</string>
     <string name="action_move_here" msgid="2170188780612570250">"העבר את הפריט לכאן"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"הפריט הועבר אל מסך דף הבית"</string>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 8729aba..846b884 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"幅 %1$d、高さ %2$d"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"押し続けると、手動で追加できます"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"自動的に追加"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"アプリを検索"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"アプリを読み込んでいます…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"「<xliff:g id="QUERY">%1$s</xliff:g>」に一致するアプリは見つかりませんでした"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"他のアプリを検索"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"通知"</string>
     <string name="out_of_space" msgid="4691004494942118364">"このホーム画面に空きスペースがありません。"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"お気に入りトレイに空きスペースがありません"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"アプリのリスト"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"ホーム画面の回転を許可"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"スマートフォンが回転したとき"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"現在の [ディスプレイ] 設定では回転を使用できません"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"ホーム画面にアイコンを追加"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"新しいアプリをダウンロードしたときに"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"不明"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"削除"</string>
     <string name="abandoned_search" msgid="891119232568284442">"検索"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"このアイコンのアプリはインストールされていません。このアイコンは削除できます。または、手動でアプリを検索してインストールしください。"</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g>をダウンロード中、<xliff:g id="PROGRESS">%2$s</xliff:g>完了"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g>のインストール待ち"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g>のウィジェット"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"ホーム画面に追加"</string>
     <string name="action_move_here" msgid="2170188780612570250">"アイテムをここに移動"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"アイテムをホーム画面に追加しました"</string>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index 1a200d7..1979449 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"სიგრძე: %1$d, სიგანე: %2$d"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"ხანგრძლივად შეეხეთ ხელით განსათავსებლად"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"ავტომატურად დამატება"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"აპების ძიება"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"აპები იტვირთება..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"„<xliff:g id="QUERY">%1$s</xliff:g>“-ის თანხვედრი აპები არ მოიძებნა"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"მეტი აპის პოვნა"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"შეტყობინებები"</string>
     <string name="out_of_space" msgid="4691004494942118364">"ამ მთავარ ეკრანზე ადგილი აღარ არის."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"რჩეულების თაროზე ადგილი არ არის"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"აპების სია"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"მთავარი ეკრანის შეტრიალების დაშვება"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"ტელეფონის შეტრიალებისას"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"ბრუნვა დაუშვებელია ჩვენების მიმდინარე პარამეტრებით"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"ხატულას მთავარ ეკრანზე დამატება"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"ახალი აპებისთვის"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"უცნობი"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"ამოშლა"</string>
     <string name="abandoned_search" msgid="891119232568284442">"ძიება"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"ამ ხატულის აპი დაყენებული არ არის. შეგიძლიათ ამოშალოთ, ან მოიძიოთ აპი და ხელით მოახდინოთ მისი ინსტალაცია."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"მიმდინარეობს <xliff:g id="NAME">%1$s</xliff:g>-ის ჩამოტვირთვა, <xliff:g id="PROGRESS">%2$s</xliff:g> დასრულდა"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ელოდება ინსტალაციას"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g>-ის ვიჯეტები"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"მთავარ ეკრანზე დამატება"</string>
     <string name="action_move_here" msgid="2170188780612570250">"ერთეულის გადაადგილება აქ"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"ერთეული დაემატა მთავარ ეკრანს"</string>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index 009fa4a..5df503a 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"Ені: %1$d, биіктігі: %2$d"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Қолмен қою үшін басып тұрыңыз"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Автоматты енгізу"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Қолданбаларды іздеу"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Қолданбалар жүктелуде…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"«<xliff:g id="QUERY">%1$s</xliff:g>» сұрауына сәйкес келетін қолданбалар жоқ"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Қосымша қолданбалар іздеу"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Хабарландырулар"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Бұл Негізгі экранда орын қалмады."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Қалаулылар науасында орын қалмады"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Қолданбалар тізімі"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Негізгі экранның бұрылуына рұқсат ету"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Телефон бұрылғанда"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Экранның ағымдағы параметрі айналуға рұқсат бермейді"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Негізгі экранға белгіше енгізу"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Жаңа қолданбаларға арналған"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Белгісіз"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Алып тастау"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Іздеу"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"Осы белгіше үшін қолданба орнатылмаған. Оны жоюға болады немесе қолданбаны іздеп, қолмен орнатуға болады."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> жүктелуде, <xliff:g id="PROGRESS">%2$s</xliff:g> аяқталды"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> орнату күтілуде"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> виджеті"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Негізгі экранға қосу"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Элементті мұнда жылжыту"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Элемент негізгі экранға қосылды"</string>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index 864c979..a64b604 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"ទទឺង %1$d គុណនឹងកម្ពស់ %2$d"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"ចុច​ឲ្យជាប់​ដើម្បី​បញ្ចូលវា​ដោយផ្ទាល់"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"បញ្ចូល​ដោយ​ស្វ័យ​ប្រវត្តិ"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ស្វែងរកកម្មវិធី"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"កំពុងដំណើរការកម្មវិធី..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"គ្មានកម្មវិធីដែលត្រូវជាមួយ \"<xliff:g id="QUERY">%1$s</xliff:g>\" ទេ"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"ស្វែងរកកម្មវិធីច្រើនទៀត"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"ការ​ជូនដំណឹង"</string>
     <string name="out_of_space" msgid="4691004494942118364">"គ្មាន​បន្ទប់​នៅ​លើ​អេក្រង់​ដើម​នេះ​ទៀត​ទេ។"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"គ្មាន​បន្ទប់​​ក្នុង​ថាស​និយម​ប្រើ"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"បញ្ជីកម្មវិធី"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"អនុញ្ញាតការបងិ្វលអេក្រង់ដើម"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"នៅពេលដែលបង្វិលទូរស័ព្ទរបស់អ្នក"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"ការកំណត់អេក្រង់បច្ចុប្បន្នមិនអនុញ្ញាតការបង្វិលទេ"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"បញ្ចូល​រូបតំណាង​ទៅ​អេក្រង់​ដើម"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"សម្រាប់កម្មវិធីថ្មី"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"មិន​ស្គាល់"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"លុបចេញ"</string>
     <string name="abandoned_search" msgid="891119232568284442">"ស្វែងរក"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"មិន​បាន​ដំឡើង​កម្មវិធី​សម្រាប់​រូបតំណាង​នេះ។ អ្នក​អាច​លុប​វា ឬ​ស្វែងរក​កម្មវិធី និង​ដំឡើង​វា​ដោយ​ដៃ។"</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"កំពុងដោនឡូត <xliff:g id="NAME">%1$s</xliff:g> បានបញ្ចប់ <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> កំពុងរង់ចាំការដំឡើង"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"ធាតុ​ក្រាហ្វិក <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"បន្ថែមទៅអេក្រង់ដើម"</string>
     <string name="action_move_here" msgid="2170188780612570250">"ផ្លាស់ធាតុមកទីនេះ"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"ធាតុដែលត្រូវបានបន្ថែមទៅអេក្រង់ដើម"</string>
diff --git a/res/values-kn-rIN/config.xml b/res/values-kn-rIN/config.xml
new file mode 100644
index 0000000..56f98c3
--- /dev/null
+++ b/res/values-kn-rIN/config.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="icon_shape_override_paths_values">
+    <item msgid="6640290598899495380"></item>
+    <item msgid="4009824731445917273">"M50,0L100,0 100,100 0,100 0,0z"</item>
+    <item msgid="3964229851574011244">"M50,0L80,0 A20,20,0,0 1 100,20 L100,80 A20,20,0,0 1 80,100 L20,100 A20,20,0,0 1 0,80 L 0,20 A20,20,0,0 1 20,0z"</item>
+    <item msgid="363553284746233331">"M50,0 C10,0 0,10 0,50 0,90 10,100 50,100 90,100 100,90 100,50 100,10 90,0 50,0 Z"</item>
+    <item msgid="4319038504053267455">"M50 0A50 50,0,1,1,50 100A50 50,0,1,1,50 0"</item>
+    <item msgid="483370082941112059">"M50,0A50,30 0,0,1 100,30V70A50,30 0,0,1 0,70V30A50,30 0,0,1 50,0z"</item>
+  </string-array>
+    <!-- no translation found for icon_shape_override_paths_names:0 (4837899951986816538) -->
+</resources>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index 2c50567..b22af0d 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d ಅಗಲ ಮತ್ತು %2$d ಎತ್ತರ"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"ಹಸ್ತಚಾಲಿತವಾಗಿ ಸೇರಿಸಲು ಸ್ಪರ್ಶಿಸಿ ಮತ್ತು ಹೋಲ್ಡ್ ಮಾಡಿ"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಸೇರಿಸಿ"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ಅಪ್ಲಿಕೇಷನ್‌ಗಳನ್ನು ಹುಡುಕಿ"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"ಅಪ್ಲಿಕೇಶನ್‌ಗಳನ್ನು ಲೋಡ್ ಮಾಡಲಾಗುತ್ತಿದೆ..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" ಹೊಂದಿಕೆಯ ಯಾವುದೇ ಅಪ್ಲಿಕೇಶನ್‌ಗಳು ಕಂಡುಬಂದಿಲ್ಲ"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"ಮತ್ತಷ್ಟು ಅಪ್ಲಿಕೇಶನ್‌ಗಳನ್ನು ಹುಡುಕಿ"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"ಅಧಿಸೂಚನೆಗಳು"</string>
     <string name="out_of_space" msgid="4691004494942118364">"ಈ ಮುಖಪುಟದ ಪರದೆಯಲ್ಲಿ ಹೆಚ್ಚು ಸ್ಥಳಾವಕಾಶವಿಲ್ಲ."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"ಮೆಚ್ಚಿನವುಗಳ ಟ್ರೇನಲ್ಲಿ ಹೆಚ್ಚಿನ ಸ್ಥಳಾವಕಾಶವಿಲ್ಲ"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"ಅಪ್ಲಿಕೇಶನ್‌ಗಳ ಪಟ್ಟಿ"</string>
@@ -71,6 +76,11 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"ಮುಖಪುಟ ತಿರುಗುವಿಕೆಯನ್ನು ಅನುಮತಿಸಿ"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"ಫೋನ್‌ ತಿರುಗಿಸಿದಾಗ"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"ಪ್ರಸ್ತುತ ಪ್ರದರ್ಶನ ಸೆಟ್ಟಿಂಗ್ ತಿರುಗುವಿಕೆಯನ್ನು ಅನುಮತಿಸುವುದಿಲ್ಲ"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"ಮುಖಪುಟದ ಪರದೆಗೆ ಐಕಾನ್ ಸೇರಿಸಿ"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"ಹೊಸ ಅಪ್ಲಿಕೇಶನ್‌ಗಳಿಗೆ"</string>
+    <string name="icon_shape_override_label" msgid="2977264953998281004">"ಐಕಾನ್ ಆಕಾರವನ್ನು ಬದಲಿಸಿ"</string>
+    <string name="icon_shape_no_override" msgid="3678524428085518367">"ಬದಲಿಸಬೇಡಿ"</string>
+    <string name="icon_shape_override_progress" msgid="3461735694970239908">"ಐಕಾನ್ ಆಕಾರ ಬದಲಾವಣೆಯನ್ನು ಅನ್ವಯಿಸಲಾಗುತ್ತಿದೆ"</string>
     <string name="package_state_unknown" msgid="7592128424511031410">"ಅಪರಿಚಿತ"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"ತೆಗೆದುಹಾಕಿ"</string>
     <string name="abandoned_search" msgid="891119232568284442">"ಹುಡುಕಿ"</string>
@@ -78,6 +88,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"ಈ ಐಕಾನ್ ಅಪ್ಲಿಕೇಶನ್ ಸ್ಥಾಪನೆಗೊಂಡಿಲ್ಲ. ನೀವು ಅದನ್ನು ತೆಗೆದುಹಾಕಬಹುದು ಅಥವಾ ಅಪ್ಲಿಕೇಶನ್ ಹುಡುಕಬಹುದು ಮತ್ತು ಹಸ್ತಚಾಲಿತವಾಗಿ ಅದನ್ನು ಸ್ಥಾಪಿಸಬಹುದು."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> ಡೌನ್‌ಲೋಡ್‌ ಮಾಡಲಾಗುತ್ತಿದೆ, <xliff:g id="PROGRESS">%2$s</xliff:g> ಪೂರ್ಣಗೊಂಡಿದೆ"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ಸ್ಥಾಪಿಸಲು ಕಾಯಲಾಗುತ್ತಿದೆ"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> ವಿಜೆಟ್‌ಗಳು"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"ಮುಖಪುಟಕ್ಕೆ ಸೇರಿಸು"</string>
     <string name="action_move_here" msgid="2170188780612570250">"ಐಟಂ ಇಲ್ಲಿಗೆ ಸರಿಸಿ"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"ಮುಖಪುಟ ಪರದೆಗೆ ಐಟಂ ಸೇರಿಸಲಾಗಿದೆ"</string>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 1ef438f..9e1eba4 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"너비 %1$d, 높이 %2$d"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"길게 터치하여 직접 장소 추가"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"자동으로 추가"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"앱 검색"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"앱 로드 중..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\'<xliff:g id="QUERY">%1$s</xliff:g>\'와(과) 일치하는 앱이 없습니다."</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"더 많은 앱 검색"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"알림"</string>
     <string name="out_of_space" msgid="4691004494942118364">"홈 화면에 더 이상 공간이 없습니다."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"즐겨찾기 트레이에 더 이상 공간이 없습니다."</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"앱 목록"</string>
@@ -56,7 +61,7 @@
     <string name="disabled_app_label" msgid="6673129024321402780">"<xliff:g id="APP_NAME">%1$s</xliff:g> 사용 안함"</string>
     <string name="default_scroll_format" msgid="7475544710230993317">"페이지 %1$d/%2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"홈 화면 %1$d/%2$d"</string>
-    <string name="workspace_new_page" msgid="257366611030256142">"새로운 메인 스크린 페이지"</string>
+    <string name="workspace_new_page" msgid="257366611030256142">"새로운 홈 화면 페이지"</string>
     <string name="folder_opened" msgid="94695026776264709">"폴더 열림(<xliff:g id="WIDTH">%1$d</xliff:g>X<xliff:g id="HEIGHT">%2$d</xliff:g>)"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"탭하여 폴더 닫기"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"탭하여 변경된 이름 저장"</string>
@@ -68,9 +73,17 @@
     <string name="settings_button_text" msgid="8119458837558863227">"설정"</string>
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"관리자가 사용 중지함"</string>
     <string name="accessibility_action_overview" msgid="6257665857640347026">"개요"</string>
-    <string name="allow_rotation_title" msgid="7728578836261442095">"메인 스크린 회전 허용"</string>
+    <string name="allow_rotation_title" msgid="7728578836261442095">"홈 화면 회전 허용"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"휴대전화 회전 시"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"현재 표시 설정에는 회전 기능이 허용되지 않습니다."</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"홈 화면에 아이콘 추가"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"새로 설치한 앱에 적용"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"알 수 없음"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"삭제"</string>
     <string name="abandoned_search" msgid="891119232568284442">"검색"</string>
@@ -78,9 +91,10 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"이 아이콘의 앱이 설치되어 있지 않습니다. 아이콘을 삭제하거나 앱을 검색하여 수동으로 설치하세요."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> 다운로드 중, <xliff:g id="PROGRESS">%2$s</xliff:g> 완료"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> 설치 대기 중"</string>
-    <string name="action_add_to_workspace" msgid="8902165848117513641">"메인 스크린에 추가"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> 위젯"</string>
+    <string name="action_add_to_workspace" msgid="8902165848117513641">"홈 화면에 추가"</string>
     <string name="action_move_here" msgid="2170188780612570250">"여기에 항목을 이동"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"메인 스크린에 항목 추가됨"</string>
+    <string name="item_added_to_workspace" msgid="4211073925752213539">"홈 화면에 항목 추가됨"</string>
     <string name="item_removed" msgid="851119963877842327">"항목 삭제됨"</string>
     <string name="action_move" msgid="4339390619886385032">"항목 이동"</string>
     <string name="move_to_empty_cell" msgid="2833711483015685619">"<xliff:g id="NUMBER_0">%1$s</xliff:g>행 <xliff:g id="NUMBER_1">%2$s</xliff:g>열로 이동"</string>
@@ -92,7 +106,7 @@
     <string name="added_to_folder" msgid="4793259502305558003">"항목이 폴더에 추가되었습니다."</string>
     <string name="create_folder_with" msgid="4050141361160214248">"다음이 포함된 폴더 만들기: <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_created" msgid="6409794597405184510">"폴더를 만들었습니다."</string>
-    <string name="action_move_to_workspace" msgid="1603837886334246317">"메인 스크린으로 이동"</string>
+    <string name="action_move_to_workspace" msgid="1603837886334246317">"홈 화면으로 이동"</string>
     <string name="action_move_screen_left" msgid="8854216831569401665">"화면을 왼쪽으로 이동"</string>
     <string name="action_move_screen_right" msgid="329334910274311123">"화면을 오른쪽으로 이동"</string>
     <string name="screen_moved" msgid="266230079505650577">"화면 이동됨"</string>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index 71e02d0..cfdcc18 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"Туурасы: %1$d, бийиктиги: %2$d"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Кол менен жайгаштыруу үчүн басып туруп, таштаңыз"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Автоматтык түрдө кошуу"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Колдонмолорду издөө"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Колдонмолор жүктөлүүдө…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" дал келген колдонмолор табылган жок"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Көбүрөөк колдонмолорду издөө"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Эскертмелер"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Бул Үй экранында бош орун жок."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Тандамалдар тайпасында орун калган жок"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Колдонмолор тизмеси"</string>
@@ -65,12 +70,20 @@
     <string name="folder_name_format" msgid="6629239338071103179">"Фолдер: <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="widget_button_text" msgid="2880537293434387943">"Виджеттер"</string>
     <string name="wallpaper_button_text" msgid="8404103075899945851">"Тушкагаздар"</string>
-    <string name="settings_button_text" msgid="8119458837558863227">"Тууралоолор"</string>
+    <string name="settings_button_text" msgid="8119458837558863227">"Жөндөөлөр"</string>
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"Администраторуңуз өчүрүп койгон"</string>
     <string name="accessibility_action_overview" msgid="6257665857640347026">"Көз жүгүртүү"</string>
     <string name="allow_rotation_title" msgid="7728578836261442095">"Башкы экранды айлантууга уруксат берүү"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Телефон айланганда"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Экранды айлантуу параметри өчүрүлгөн"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Башкы экранга сүрөтчө кошуу"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Жаңы колдонмолор үчүн"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Белгисиз"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Алып салуу"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Издөө"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"Бул сүрөтчөнүн колдонмосу орнотулган эмес. Аны алып салсаңыз же колдонмону издеп, кол менен орнотсоңуз болот."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> жүктөлүп алынууда, <xliff:g id="PROGRESS">%2$s</xliff:g> аяктады"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> орнотулушу күтүлүүдө"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> виджеттери"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Башкы экранга кошуу"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Бул нерсени бул жерге жылдыруу"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Башкы экранга кошулду"</string>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index e231102..334305a 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"ກວ້າງ %1$d ຄູນສູງ %2$d"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"ແຕະຄ້າງໄວ້ເພື່ອວາງດ້ວຍຕົນເອງ"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"ເພີ່ມໂດຍອັດຕະໂນມັດ"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ຊອກຫາແອັບ"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"​ກຳ​ລັງ​ໂຫລດ​ແອັບ..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"ບໍ່​ພົບ​ແອັບ​ໃດ​ທີ່​ກົງ​ກັນ \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"ຊອກຫາແອັບເພີ່ມເຕີມ"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"ການແຈ້ງເຕືອນ"</string>
     <string name="out_of_space" msgid="4691004494942118364">"ບໍ່ມີຫ້ອງເຫຼືອໃນໜ້າຈໍຫຼັກນີ້."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"ບໍ່ມີບ່ອນຫວ່າງໃນຖາດສຳລັບເກັບສິ່ງທີ່ໃຊ້ເປັນປະຈຳ"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"ລາຍຊື່ແອັບ"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"ອະນຸຍາດໃຫ້ໝຸນໜ້າຈໍທຳອິດໄດ້"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"ເມື່ອໝຸນໂທລະສັບ"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"ການຕັ້ງຄ່າສະແດງຜົນປັດຈຸບັນບໍ່ອະນຸຍາດໃຫ້ໝຸນໄດ້"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"ເພີ່ມໄອຄອນໃສ່ໜ້າຈໍຫຼັກ"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"ສຳລັບແອັບໃໝ່"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"​ບໍ່​ຮູ້​ຈັກ"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"ລຶບ​"</string>
     <string name="abandoned_search" msgid="891119232568284442">"ຊອກຫາ"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"​ແອັບຯ​ສຳ​ລັບ​ໄອ​ຄອນ​ນີ້​ຍັງ​ບໍ່ໄດ້​ຕິດ​ຕັ້ງ​ເທື່ອ. ທ່ານ​ສາ​ມາດ​ລຶບ​ມັນ​ອອກ ຫຼື​ຊອກ​ຫາ​ແອັບຯ ແລ້ວ​ຕິດ​ຕັ້ງ​ມັນ​ໄດ້​ດ້ວຍ​ຕົນ​ເອງ."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> ກຳ​ລັງ​ດາວ​ໂຫຼດ, <xliff:g id="PROGRESS">%2$s</xliff:g> ສຳ​ເລັດ"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ກຳ​ລັງ​ລໍ​ຖ້າ​ຕິດ​ຕັ້ງ"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"ວິດເຈັດ <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"ເພີ່ມໃສ່ໜ້າຈໍຫຼັກ"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Move item here"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"ເພີ່ມ​ລາຍ​ການ​ໃສ່​ໜ້າ​ຈໍ​ຫຼັກ​ແລ້ວ"</string>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 13ebaa3..558e1a9 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d plotis ir %2$d aukštis"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Palieskite ir palaikykite, kad padėtumėte patys"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Pridėti automatiškai"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Ieškoti programų"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Įkeliamos programos..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Nerasta jokių užklausą „<xliff:g id="QUERY">%1$s</xliff:g>“ atitinkančių programų"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Ieškoti daugiau programų"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Pranešimai"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Šiame pagrindiniame ekrane vietos nebėra."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Mėgstamiausių dėkle nebėra vietos"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Programų sąrašas"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Leisti pasukti pagrindinį ekraną"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Kai telefonas pasukamas"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Naudojant dabartinį pateikties nustatymą neleidžiama pasukti"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Pridėti piktogr. prie pagrindinio ekrano"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Skirta naujoms programoms"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Nežinoma"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Pašalinti"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Ieškoti"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"Šios piktogramos programa neįdiegta. Galite ją pašalinti arba bandyti ieškoti programos ir ją įdiegti patys."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"Atsisiunčiama programa „<xliff:g id="NAME">%1$s</xliff:g>“, <xliff:g id="PROGRESS">%2$s</xliff:g> baigta"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"Laukiama, kol bus įdiegta programa „<xliff:g id="NAME">%1$s</xliff:g>“"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"„<xliff:g id="NAME">%1$s</xliff:g>“ valdikliai"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Pridėti prie pagrind. ekrano"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Perkelti elementą čia"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Elementas pridėtas prie pagrindinio ekrano"</string>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 924b399..733d647 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d plats un %2$d augsts"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Pieskarieties un turiet, lai manuāli pievienotu"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Pievienot automātiski"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Meklēt lietotnes"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Notiek lietotņu ielāde…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Vaicājumam “<xliff:g id="QUERY">%1$s</xliff:g>” neatbilda neviena lietotne."</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Meklēt citas lietotnes"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Paziņojumi"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Šajā sākuma ekrānā vairs nav vietas."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Izlases joslā vairs nav vietas."</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Lietotņu saraksts"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Atļaut sākuma ekrāna pagriešanu"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Pagriežot tālruni"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Pašreizējā displeja iestatījumā nav atļauta pagriešana."</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Pievienot ikonu sākuma ekrānā"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Jaunām lietotnēm"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Nezināma"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Noņemt"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Meklēt"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"Šai ikonai paredzētā lietotne nav instalēta. Varat noņemt ikonu vai meklēt lietotni un instalēt to manuāli."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"Lietotnes <xliff:g id="NAME">%1$s</xliff:g> lejupielāde (<xliff:g id="PROGRESS">%2$s</xliff:g> pabeigti)"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"Notiek <xliff:g id="NAME">%1$s</xliff:g> instalēšana"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> logrīki"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Pievienot sākuma ekrānam"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Pārvietot vienumu šeit"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Vienums pievienots sākuma ekrānam"</string>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index 7e44707..267e75b 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d широк на %2$d висок"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Допрете и задржете за рачно поставување"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Додај автоматски"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Пребарување апликации"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Се вчитуваат апликации…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Не се најдени апликации што одговараат на „<xliff:g id="QUERY">%1$s</xliff:g>“"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Пребарај други апликации"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Известувања"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Нема повеќе простор на овој екран на почетната страница."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Нема повеќе простор на лентата „Омилени“"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Список со апликации"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Дозволете ротација на Почетниот екран"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Кога телефонот се ротира"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Тековната поставка на Екранот не дозволува ротација"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Додајте икона на почетниот екран"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"За нови апликации"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Непознато"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Отстрани"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Барај"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"Апликацијата за оваа икона не е инсталирана. Може да ја отстраните или да се обидете да ја најдете апликацијата и да ја инсталирате рачно."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"Се презема <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> завршено"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> чека да се инсталира"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Виџети за <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Додај на Почетен екран"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Премести ја ставката овде"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Ставката е додадена на почетниот екран"</string>
diff --git a/res/values-ml-rIN/config.xml b/res/values-ml-rIN/config.xml
new file mode 100644
index 0000000..56f98c3
--- /dev/null
+++ b/res/values-ml-rIN/config.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="icon_shape_override_paths_values">
+    <item msgid="6640290598899495380"></item>
+    <item msgid="4009824731445917273">"M50,0L100,0 100,100 0,100 0,0z"</item>
+    <item msgid="3964229851574011244">"M50,0L80,0 A20,20,0,0 1 100,20 L100,80 A20,20,0,0 1 80,100 L20,100 A20,20,0,0 1 0,80 L 0,20 A20,20,0,0 1 20,0z"</item>
+    <item msgid="363553284746233331">"M50,0 C10,0 0,10 0,50 0,90 10,100 50,100 90,100 100,90 100,50 100,10 90,0 50,0 Z"</item>
+    <item msgid="4319038504053267455">"M50 0A50 50,0,1,1,50 100A50 50,0,1,1,50 0"</item>
+    <item msgid="483370082941112059">"M50,0A50,30 0,0,1 100,30V70A50,30 0,0,1 0,70V30A50,30 0,0,1 50,0z"</item>
+  </string-array>
+    <!-- no translation found for icon_shape_override_paths_names:0 (4837899951986816538) -->
+</resources>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index 941d1ac..a92fc03 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d വീതിയും %2$d ഉയരവും"</string>
-    <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ആപ്പ്‌സ് തിരയുക"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"സ്വമേധയാ സ്ഥാപിക്കുന്നതിന് സ്‌പർശിച്ചുപിടിക്കുക"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"സ്വയമേവ ചേർക്കുക"</string>
+    <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ആപ്പുകളെ തിരയുക"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"ആപ്പ്‌സ് ലോഡുചെയ്യുന്നു..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" എന്നതുമായി പൊരുത്തപ്പെടുന്ന ആപ്പ്‌സൊന്നും കണ്ടെത്തിയില്ല"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"കൂടുതൽ ആപ്പുകൾക്ക് തിരയുക"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"അറിയിപ്പുകൾ"</string>
     <string name="out_of_space" msgid="4691004494942118364">"ഈ ഹോം സ്‌ക്രീനിൽ ഒഴിവൊന്നുമില്ല."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"പ്രിയപ്പെട്ടവയുടെ ട്രേയിൽ ഒഴിവൊന്നുമില്ല"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"അപ്ലിക്കേഷനുകളുടെ ലിസ്‌റ്റ്"</string>
@@ -71,6 +76,11 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"ഹോം സ്ക്രീൻ തിരിക്കൽ അനുവദിക്കുക"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"ഫോൺ തിരിച്ച നിലയിലായിരിക്കുമ്പോൾ"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"നിലവിലെ ഡിസ്പ്ലേ ക്രമീകരണം തിരിക്കൽ അനുവദിക്കുന്നില്ല"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"ഹോം സ്ക്രീനിലേക്ക് ഐക്കൺ ചേർക്കുക"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"പുതിയ ആപ്പുകൾക്ക്"</string>
+    <string name="icon_shape_override_label" msgid="2977264953998281004">"ഐക്കണിന്റെ ആകാരം മാറ്റുക"</string>
+    <string name="icon_shape_no_override" msgid="3678524428085518367">"മാറ്റരുത്"</string>
+    <string name="icon_shape_override_progress" msgid="3461735694970239908">"ഐക്കൺ ആകാര മാറ്റങ്ങൾ പ്രയോഗിക്കുന്നു"</string>
     <string name="package_state_unknown" msgid="7592128424511031410">"അജ്ഞാതം"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"നീക്കംചെയ്യുക"</string>
     <string name="abandoned_search" msgid="891119232568284442">"തിരയുക"</string>
@@ -78,6 +88,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"ഈ ഐക്കണുവേണ്ടി അപ്ലിക്കേഷൻ ഇൻസ്റ്റാളുചെയ്‌തിട്ടില്ല. നിങ്ങൾക്കത് നീക്കംചെയ്യാനാകും അല്ലെങ്കിൽ അപ്ലിക്കേഷനുവേണ്ടി തിരഞ്ഞുകൊണ്ട് അത് സ്വമേധയാ ഇൻസ്റ്റാളുചെയ്യുക."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> ഡൗൺലോഡ് ചെയ്യുന്നു, <xliff:g id="PROGRESS">%2$s</xliff:g> പൂർത്തിയായി"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"ഇൻസ്റ്റാൾ ചെയ്യാൻ <xliff:g id="NAME">%1$s</xliff:g> കാക്കുന്നു"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> വിജറ്റുകൾ"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"ഹോം സ്ക്രീനിൽ ചേർക്കുക"</string>
     <string name="action_move_here" msgid="2170188780612570250">"ഇനം ഇവിടേക്ക് നീക്കുക"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"ഹോം സ്‌ക്രീനിൽ ഇനം ചേർത്തു"</string>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index 45e1856..5b74fd2 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d өргөн %2$d өндөр"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Гараар байршуулахын тулд дараад хүлээнэ үү"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Автоматаар нэмэх"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Апп хайх"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Аппликейшныг ачаалж байна..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\"-д нийцэх апп олдсонгүй"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Бусад апп-г хайх"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Мэдэгдэл"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Энэ Нүүр дэлгэц зайгүй."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"\"Дуртай\" трей дээр өөр зай байхгүй байна"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Апп-н жагсаалт"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Нүүр дэлгэцийг эргүүлэхийг зөвшөөрөх"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Утсыг эргүүлсэн үед"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Дэлгэцийн одоогийн тохиргоогоор эргүүлэх боломжгүй"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Нүүр хуудаст дүрс тэмдэг нэмэх"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Шинэ аппад зориулсан"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Тодорхойгүй"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Устгах"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Хайх"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"Энэ дүрсний апп-г суулгаагүй байна. Та үүнийг устгах буюу апп-г хайж суулгах боломжтой."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g>-г татаж байна, <xliff:g id="PROGRESS">%2$s</xliff:g> татсан"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> нь суулгахыг хүлээж байна"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> жижиг хэрэгсэл"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Нүүр дэлгэц нэмэх"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Энд байршуулах"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Нүүр дэлгэцэнд нэмсэн зүйл"</string>
diff --git a/res/values-mr-rIN/config.xml b/res/values-mr-rIN/config.xml
new file mode 100644
index 0000000..56f98c3
--- /dev/null
+++ b/res/values-mr-rIN/config.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="icon_shape_override_paths_values">
+    <item msgid="6640290598899495380"></item>
+    <item msgid="4009824731445917273">"M50,0L100,0 100,100 0,100 0,0z"</item>
+    <item msgid="3964229851574011244">"M50,0L80,0 A20,20,0,0 1 100,20 L100,80 A20,20,0,0 1 80,100 L20,100 A20,20,0,0 1 0,80 L 0,20 A20,20,0,0 1 20,0z"</item>
+    <item msgid="363553284746233331">"M50,0 C10,0 0,10 0,50 0,90 10,100 50,100 90,100 100,90 100,50 100,10 90,0 50,0 Z"</item>
+    <item msgid="4319038504053267455">"M50 0A50 50,0,1,1,50 100A50 50,0,1,1,50 0"</item>
+    <item msgid="483370082941112059">"M50,0A50,30 0,0,1 100,30V70A50,30 0,0,1 0,70V30A50,30 0,0,1 50,0z"</item>
+  </string-array>
+    <!-- no translation found for icon_shape_override_paths_names:0 (4837899951986816538) -->
+</resources>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index 1ec443c..1a10e4b 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d रूंद बाय %2$d उंच"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"स्वतः ठेवण्यासाठी स्पर्श करा आणि धरून ठेवा"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"स्वयंचलितपणे जोडा"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"अॅप्स शोधा"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"अॅप्स लोड करीत आहे..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" शी जुळणारे कोणतेही अॅप्स आढळले नाहीत"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"अधिक अॅप्स शोधा"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"सूचना"</string>
     <string name="out_of_space" msgid="4691004494942118364">"या मुख्य स्क्रीनवर आणखी जागा नाही."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"आवडीच्या ट्रे मध्ये आणखी जागा नाही"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"अॅप्स सूची"</string>
@@ -71,6 +76,11 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"मुख्यस्क्रीन फिरविण्‍यास अनुमती द्या"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"फोन फिरविला जातो तेव्हा"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"वर्तमान प्रदर्शन सेटिंग फिरविण्यास परवानगी देत नाही"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"मुख्य स्क्रीनवर चिन्ह जोडा"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"नवीन अॅप्ससाठी"</string>
+    <string name="icon_shape_override_label" msgid="2977264953998281004">"चिन्हाचा आकार बदला"</string>
+    <string name="icon_shape_no_override" msgid="3678524428085518367">"बदलू नका"</string>
+    <string name="icon_shape_override_progress" msgid="3461735694970239908">"चिन्हाचा आकार बदल लागू करत आहे"</string>
     <string name="package_state_unknown" msgid="7592128424511031410">"अज्ञात"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"काढा"</string>
     <string name="abandoned_search" msgid="891119232568284442">"शोधा"</string>
@@ -78,6 +88,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"या चिन्हासाठी अॅप स्थापित केलेला नाही. आपण ते काढू शकता किंवा अॅपचा शोध घेऊ शकता आणि त्यास व्यक्तिचलितपणे स्थापित करू शकता."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> डाउनलोड होत आहे , <xliff:g id="PROGRESS">%2$s</xliff:g> पूर्ण झाले"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> स्थापित करण्याची प्रतिक्षा करीत आहे"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> विजेट"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"मुख्य स्क्रीनवर जोडा"</string>
     <string name="action_move_here" msgid="2170188780612570250">"आयटम येथे हलवा"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"आयटम मुख्य स्क्रीनवर जोडला"</string>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index bc4e484..449aae8 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"အလျား %1$d နှင့် အမြင့် %2$d"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"ကိုယ်တိုင်ထည့်ရန် ထိထားပါ"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"အလိုအလျောက် ထည့်ရန်"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ရှာဖွေမှု အက်ပ်များ"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"အက်ပ်များ ရယူနေစဉ်..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" နှင့်ကိုက်ညီသည့် အပ်ဖ်များမတွေ့ပါ"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"နောက်ထပ် အက်ပ်များကို ရှာပါ"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"အကြောင်းကြားချက်များ"</string>
     <string name="out_of_space" msgid="4691004494942118364">"ဤပင်မမျက်နှာစာတွင် နေရာလွတ် မကျန်တော့ပါ"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"အနှစ်သက်ဆုံးများ ထားရာတွင် နေရာလွတ် မကျန်တော့ပါ"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"အက်ပ်စာရင်း"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"ပင်မစာမျက်နှာလှည့်ခြင်းကို ခွင့်ပြုပါ"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"ဖုန်းကိုလှည့်ထားစဉ်"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"လက်ရှိ မြင်ကွင်းဆက်တင်တွင် မြင်ကွင်းကို လှည့်ခွင့်မပေးပါ"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"ပင်မစာမျက်နှာသို့ သင်္ကေတပုံ ထည့်ရန်"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"အက်ပ်အသစ်များအတွက်"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"မသိရ"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"ဖယ်ရှားရန်"</string>
     <string name="abandoned_search" msgid="891119232568284442">"ရှာဖွေရန်"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"ဤအိုင်ကွန်အတွက် အက်ပ်အားမထည့်သွင်းထားပါ။ You can remove it, or search for the အက်ပ်and install it manually."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> ဒေါင်းလုဒ်လုပ်နေသည်၊ <xliff:g id="PROGRESS">%2$s</xliff:g> ပြီးပါပြီ"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ကိုထည့်သွင်းရန်စောင့်နေသည်"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> ဝိဂျက်များ"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"ပင်မမျက်နှာစာသို့ ထည့်ပါ"</string>
     <string name="action_move_here" msgid="2170188780612570250">"၎င်းအား ဤသို့ ရွှေ့ပါ"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"ပင်မ ဖန်မျက်နှာပြင်သို့ ထည့်ပြီး၏"</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 449094d..6c70038 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d bredde x %2$d høyde"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Trykk og hold for å plassere manuelt"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Legg til automatisk"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Søk i apper"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Laster inn apper …"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Fant ingen apper som samsvarer med «<xliff:g id="QUERY">%1$s</xliff:g>»"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Søk etter flere apper"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Varsler"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Denne startsiden er full."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Favoritter-skuffen er full"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"App-liste"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Tillat rotasjon av startskjermen"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Når telefonen roteres"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Med den nåværende skjerminnstillingen støttes ikke rotasjon"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Legg til ikon på startsiden"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"For nye apper"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Ukjent"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Fjern"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Søk"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"Appen for dette ikonet er ikke installert. Du kan fjerne det, eller prøve å søke etter appen og installere den manuelt."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"Laster ned <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> er fullført"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"Venter på å installere <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g>-moduler"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Legg til på startskjermen"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Flytt elementet hit"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Elementet er lagt til på startskjermen"</string>
diff --git a/res/values-ne-rNP/config.xml b/res/values-ne-rNP/config.xml
new file mode 100644
index 0000000..56f98c3
--- /dev/null
+++ b/res/values-ne-rNP/config.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="icon_shape_override_paths_values">
+    <item msgid="6640290598899495380"></item>
+    <item msgid="4009824731445917273">"M50,0L100,0 100,100 0,100 0,0z"</item>
+    <item msgid="3964229851574011244">"M50,0L80,0 A20,20,0,0 1 100,20 L100,80 A20,20,0,0 1 80,100 L20,100 A20,20,0,0 1 0,80 L 0,20 A20,20,0,0 1 20,0z"</item>
+    <item msgid="363553284746233331">"M50,0 C10,0 0,10 0,50 0,90 10,100 50,100 90,100 100,90 100,50 100,10 90,0 50,0 Z"</item>
+    <item msgid="4319038504053267455">"M50 0A50 50,0,1,1,50 100A50 50,0,1,1,50 0"</item>
+    <item msgid="483370082941112059">"M50,0A50,30 0,0,1 100,30V70A50,30 0,0,1 0,70V30A50,30 0,0,1 50,0z"</item>
+  </string-array>
+    <!-- no translation found for icon_shape_override_paths_names:0 (4837899951986816538) -->
+</resources>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 8accfcc..46c8f9c 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -27,14 +27,19 @@
     <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="long_accessible_way_to_add" msgid="4289502106628154155">"विजेटलाई छान्न वा आफू अनुकूल कार्यहरू प्रयोग गर्न डबल ट्याप गरी होल्ड गर्नुहोस्।"</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d चौडाइ गुणा %2$d उचाइ"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"म्यानुअल तरिकाले थप्न छुनुहोस् र थिची राख्नुहोस्‌"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"स्वतः थप्नुहोस्"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"अनुप्रयोगहरू खोज्नुहोस्"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"अनुप्रयोगहरू लोड गरिँदै..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" सँग मिल्दो कुनै अनुप्रयोगहरू फेला परेनन्"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"थप अनुप्रयोगहरू खोज्नुहोस्"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"सूचनाहरू"</string>
     <string name="out_of_space" msgid="4691004494942118364">"यो गृह स्क्रिनमा कुनै थप ठाउँ छैन।"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"मनपर्ने ट्रे अब कुनै ठाँउ छैन"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"अनुप्रयोगको सूची"</string>
@@ -71,13 +76,19 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"गृह स्क्रिनलाई घुम्ने अनुमति दिनुहोस्"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"फोनलाई घुमाइँदा"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"हालको प्रदर्शन सम्बन्धी सेटिङले घुमाउने सुविधालाई अनुमति दिँदैन"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"गृह स्क्रिनमा आइकन थप्नुहोस्"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"नयाँ अनुप्रयोगका लागि"</string>
+    <string name="icon_shape_override_label" msgid="2977264953998281004">"आइकनको आकार परिवर्तन गर्नुहोस्"</string>
+    <string name="icon_shape_no_override" msgid="3678524428085518367">"परिवर्तन नगर्नुहोस्"</string>
+    <string name="icon_shape_override_progress" msgid="3461735694970239908">"आइकनको आकारमा गरिएका परिवर्तनहरू लागू गरिँदैछन्"</string>
     <string name="package_state_unknown" msgid="7592128424511031410">"अज्ञात"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"हटाउनुहोस्"</string>
     <string name="abandoned_search" msgid="891119232568284442">"खोजी गर्नुहोस्"</string>
     <string name="abandoned_promises_title" msgid="7096178467971716750">"यो अनुप्रयोग स्थापित छैन"</string>
-    <string name="abandoned_promise_explanation" msgid="3990027586878167529">"यो प्रतिमाका लागि अनुप्रयोगलाई स्थापना गरिएको छैन। तपाईँ यसलाई हटाउन, वा अनुप्रयोग खोजी र स्वयं यो स्थापित गर्न सक्नुहुन्छ।"</string>
+    <string name="abandoned_promise_explanation" msgid="3990027586878167529">"यो प्रतिमाका लागि अनुप्रयोगलाई स्थापना गरिएको छैन। तपाईं यसलाई हटाउन, वा अनुप्रयोग खोजी र स्वयं यो स्थापित गर्न सक्नुहुन्छ।"</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> डाउनलोड गर्दै, <xliff:g id="PROGRESS">%2$s</xliff:g> सम्पन्‍न"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> स्थापना गर्न प्रतीक्षा गर्दै"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> विजेटहरू"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"गृह स्क्रिनमा थप्नुहोस्"</string>
     <string name="action_move_here" msgid="2170188780612570250">"वस्तु यहाँ सार्नुहोस्"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"वस्तु गृह स्क्रिनमा थपियो"</string>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 0134ae1..e8caeb4 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -27,27 +27,32 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d breed en %2$d hoog"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Tik op een item en houd dit vast om het handmatig te plaatsen"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Automatisch toevoegen"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Apps zoeken"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Apps laden…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Er zijn geen apps gevonden die overeenkomen met \'<xliff:g id="QUERY">%1$s</xliff:g>\'"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Zoeken naar meer apps"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Meldingen"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Er is geen ruimte meer op dit startscherm."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Geen ruimte meer in het vak \'Favorieten\'"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Lijst met apps"</string>
-    <string name="all_apps_home_button_label" msgid="252062713717058851">"Startpagina"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"Homepage"</string>
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Verwijderen"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Deïnstalleren"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"App-info"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"Snelle links instellen"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Een app toestaan snelkoppelingen toe te voegen zonder tussenkomst van de gebruiker."</string>
-    <string name="permlab_read_settings" msgid="1941457408239617576">"instellingen en snelkoppelingen op de startpagina lezen"</string>
-    <string name="permdesc_read_settings" msgid="5833423719057558387">"De app toestaan de instellingen en snelkoppelingen op de startpagina te lezen."</string>
-    <string name="permlab_write_settings" msgid="3574213698004620587">"instellingen en snelkoppelingen op de startpagina schrijven"</string>
-    <string name="permdesc_write_settings" msgid="5440712911516509985">"De app toestaan de instellingen en snelkoppelingen op de startpagina te wijzigen."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"instellingen en snelkoppelingen op de homepage lezen"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"De app toestaan de instellingen en snelkoppelingen op de homepage te lezen."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"instellingen en snelkoppelingen op de homepage schrijven"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"De app toestaan de instellingen en snelkoppelingen op de homepage te wijzigen."</string>
     <string name="msg_no_phone_permission" msgid="9208659281529857371">"<xliff:g id="APP_NAME">%1$s</xliff:g> mag niet bellen"</string>
     <string name="gadget_error_text" msgid="6081085226050792095">"Probleem bij het laden van widget"</string>
     <string name="gadget_setup_text" msgid="8274003207686040488">"Configuratie"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Draaien van startscherm toestaan"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Wanneer de telefoon gedraaid is"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Huidige scherminstelling staat draaien niet toe"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Pictogram toevoegen aan startscherm"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Voor nieuwe apps"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Onbekend"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Verwijderen"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Zoeken"</string>
@@ -78,7 +91,8 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"De app voor dit pictogram is niet geïnstalleerd. Je kunt het pictogram verwijderen of de app zoeken en handmatig installeren."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> wordt gedownload, <xliff:g id="PROGRESS">%2$s</xliff:g> voltooid"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> wacht op installatie"</string>
-    <string name="action_add_to_workspace" msgid="8902165848117513641">"Toevoegen aan startpagina"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g>-widgets"</string>
+    <string name="action_add_to_workspace" msgid="8902165848117513641">"Toevoegen aan homepage"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Item hier naartoe verplaatsen"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Item toegevoegd aan startscherm"</string>
     <string name="item_removed" msgid="851119963877842327">"Item verwijderd"</string>
diff --git a/res/values-pa-rIN/config.xml b/res/values-pa-rIN/config.xml
new file mode 100644
index 0000000..56f98c3
--- /dev/null
+++ b/res/values-pa-rIN/config.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="icon_shape_override_paths_values">
+    <item msgid="6640290598899495380"></item>
+    <item msgid="4009824731445917273">"M50,0L100,0 100,100 0,100 0,0z"</item>
+    <item msgid="3964229851574011244">"M50,0L80,0 A20,20,0,0 1 100,20 L100,80 A20,20,0,0 1 80,100 L20,100 A20,20,0,0 1 0,80 L 0,20 A20,20,0,0 1 20,0z"</item>
+    <item msgid="363553284746233331">"M50,0 C10,0 0,10 0,50 0,90 10,100 50,100 90,100 100,90 100,50 100,10 90,0 50,0 Z"</item>
+    <item msgid="4319038504053267455">"M50 0A50 50,0,1,1,50 100A50 50,0,1,1,50 0"</item>
+    <item msgid="483370082941112059">"M50,0A50,30 0,0,1 100,30V70A50,30 0,0,1 0,70V30A50,30 0,0,1 50,0z"</item>
+  </string-array>
+    <!-- no translation found for icon_shape_override_paths_names:0 (4837899951986816538) -->
+</resources>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index 3be9bec..c4c11ce 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d ਚੌੜਾਈ ਅਤੇ %2$d ਲੰਬਾਈ"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"ਹੱਥੀਂ ਰੱਖਣ ਲਈ ਸਪੱਰਸ਼ ਕਰੋ ਅਤੇ ਦਬਾਈ ਰੱਖੋ"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"ਸਵੈਚਲਿਤ ਤਰੀਕੇ ਨਾਲ ਸ਼ਾਮਲ ਕਰੋ"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ਐਪਸ ਖੋਜੋ"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"ਐਪਾਂ ਨੂੰ ਲੋਡ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" ਨਾਲ ਮਿਲਦੀਆਂ ਕੋਈ ਵੀ ਐਪਾਂ ਨਹੀਂ ਮਿਲੀਆਂ"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"ਹੋਰ ਐਪਾਂ ਖੋਜੋ"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"ਸੂਚਨਾਵਾਂ"</string>
     <string name="out_of_space" msgid="4691004494942118364">"ਇਸ ਹੋਮ ਸਕ੍ਰੀਨ ਲਈ ਹੋਰ ਖਾਲੀ ਸਥਾਨ ਨਹੀਂ ਹੈ।"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"ਮਨਪਸੰਦ ਟ੍ਰੇ ਵਿੱਚ ਹੋਰ ਖਾਲੀ ਸਥਾਨ ਨਹੀਂ।"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"ਐਪ ਸੂਚੀ"</string>
@@ -71,6 +76,11 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"ਮੁੱਖ ਸਕ੍ਰੀਨ ਨੂੰ ਘੁੰਮਾਉਣ ਦੀ ਆਗਿਆ ਦਿਓ"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"ਜਦੋਂ ਫ਼ੋਨ ਘੁੰਮਾਇਆ ਜਾਂਦਾ ਹੈ"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"ਵਰਤਮਾਨ ਡਿਸਪਲੇ ਸੈਟਿੰਗ ਘੁੰਮਾਉਣ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਦਿੰਦੀ ਹੈ"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"ਮੁੱਖ ਸਕ੍ਰੀਨ \'ਤੇ ਪ੍ਰਤੀਕ ਸ਼ਾਮਲ ਕਰੋ"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"ਨਵੀਆਂ ਐਪਾਂ ਲਈ"</string>
+    <string name="icon_shape_override_label" msgid="2977264953998281004">"ਆਈਕਨ ਦੀ ਆਕ੍ਰਿਤੀ ਬਦਲੋ"</string>
+    <string name="icon_shape_no_override" msgid="3678524428085518367">"ਨਾ ਬਦਲੋ"</string>
+    <string name="icon_shape_override_progress" msgid="3461735694970239908">"ਆਈਕਨ ਦੀ ਆਕ੍ਰਿਤੀ ਵਿੱਚ ਤਬਦੀਲੀਆਂ ਨੂੰ ਲਾਗੂ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ"</string>
     <string name="package_state_unknown" msgid="7592128424511031410">"ਅਗਿਆਤ"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"ਹਟਾਓ"</string>
     <string name="abandoned_search" msgid="891119232568284442">"ਖੋਜੋ"</string>
@@ -78,6 +88,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"ਇਸ ਆਈਕਨ ਲਈ ਐਪ ਇੰਸਟੌਲ ਨਹੀਂ ਕੀਤਾ ਹੋਇਆ ਹੈ। ਤੁਸੀਂ ਇਸਨੂੰ ਹਟਾ ਸਕਦੇ ਹੋ ਜਾਂ ਐਪ ਖੋਜ ਸਕਦੇ ਹੋ ਅਤੇ ਇਸਨੂੰ ਮੈਨੂਅਲੀ ਇੰਸਟੌਲ ਕਰ ਸਕਦੇ ਹੋ।"</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> ਡਾਉਨਲੋਡ ਹੋਰ ਰਿਹਾ ਹੈ, <xliff:g id="PROGRESS">%2$s</xliff:g> ਸੰਪੂਰਣ"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ਸਥਾਪਿਤ ਕਰਨ ਦੀ ਉਡੀਕ ਕਰ ਰਿਹਾ ਹੈ"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> ਵਿਜਿਟ"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"ਹੋਮ ਸਕ੍ਰੀਨ ਵਿੱਚ ਜੋੜੋ"</string>
     <string name="action_move_here" msgid="2170188780612570250">"ਆਈਟਮ ਨੂੰ ਇੱਥੇ ਮੂਵ ਕਰੋ"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"ਆਈਟਮ ਨੂੰ ਮੁੱਖ ਸਕ੍ਰੀਨ ਵਿੱਚ ਜੋੜਿਆ ਗਿਆ"</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index feb2a36..e611fcd 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"Szerokość %1$d, wysokość %2$d"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Kliknij i przytrzymaj, by umieścić ręcznie"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Dodaj automatycznie"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Szukaj w aplikacjach"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Wczytuję aplikacje…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Nie znaleziono aplikacji pasujących do zapytania „<xliff:g id="QUERY">%1$s</xliff:g>”"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Wyszukaj więcej aplikacji"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Powiadomienia"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Brak miejsca na tym ekranie głównym."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Brak miejsca w Ulubionych"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Lista aplikacji"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Zezwalaj na obrót ekranu głównego"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Po obróceniu telefonu"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Obecne ustawienia wyświetlania nie pozwalają na obrót ekranu"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Dodaj ikonę do ekranu głównego"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"W przypadku nowych aplikacji"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Brak informacji"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Usuń"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Szukaj"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"Aplikacja, której odpowiada ta ikona, nie jest zainstalowana. Możesz usunąć ikonę lub wyszukać aplikację i zainstalować ją ręcznie."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"Pobieranie elementu <xliff:g id="NAME">%1$s</xliff:g>, ukończono: <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> oczekuje na instalację"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> – widżety"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Dodaj do strony głównej"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Przenieś element tutaj"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Element został dodany do ekranu głównego"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 6d684fa..0aa987b 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d de largura por %2$d de altura"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Toque sem soltar para colocar manualmente"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Adicionar automaticamente"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Pesquisar aplicações"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"A carregar aplicações..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Não foram encontradas aplic. que correspondam a \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Pesquisar mais aplicações"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Notificações"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Sem espaço suficiente neste Ecrã principal."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Não existe mais espaço no tabuleiro de Favoritos"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Lista de aplicações"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Permitir rotação do ecrã principal"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Quando o telemóvel é rodado"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"A definição de visualização atual não permite a rotação"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Adicionar ícone ao ecrã principal"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Para novas aplicações"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Desconhecido"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Remover"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Pesquisar"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"A aplicação deste ícone não está instalada. Pode removê-lo ou pesquisar a aplicação e instalá-la manualmente."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"A transferir o <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> concluído"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"A aguardar a instalação do <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Widgets de <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Adicionar ao Ecrã principal"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Mover o item para aqui"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Item adicionado ao ecrã principal"</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 82a756f..39a1651 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d de largura por %2$d de altura"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Toque e mantenha pressionado para posicionar manualmente"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Adicionar automaticamente"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Pesquisar apps"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Carregando apps…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Nenhum app encontrado que corresponda a \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Pesquisar mais apps"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Notificações"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Não há mais espaço na tela inicial."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Sem espaço na bandeja de favoritos"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Lista de apps"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Permitir rotação da tela inicial"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Quando o smartphone for girado"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"A configuração atual de exibição não permite rotação"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Adicionar ícone à tela inicial"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Para novos apps"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Desconhecido"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Remover"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Pesquisar"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"O app deste ícone não está instalado. Você pode remover o ícone, ou procurar o app e instalá-lo manualmente."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"Fazendo download de <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> concluído"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"Aguardando instalação de <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Widgets do <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Adicionar à tela inicial"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Mover item para cá"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Item adicionado à tela inicial"</string>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 5905bcf..c7c0994 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d lățime și %2$d înălțime"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Atingeți lung pentru a plasa manual"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Adăugați automat"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Căutați aplicații"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Se încarcă aplicațiile..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Nu s-a găsit nicio aplicație pentru „<xliff:g id="QUERY">%1$s</xliff:g>”"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Căutați mai multe aplicații"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Notificări"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Nu mai este loc pe acest Ecran de pornire."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Spațiu epuizat în bara Preferate"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Lista de aplicații"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Permiteți rotirea ecranului de pornire"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Când telefonul este rotit"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Setarea actuală a afișajului nu permite rotirea"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Adaugă pictograme în ecranul de pornire"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Pentru aplicații noi"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Necunoscut"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Eliminați"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Căutați"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"Aplicația pentru această pictogramă nu este instalată. Puteți să ștergeți pictograma sau să căutați aplicația și s-o instalați manual."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> se descarcă (finalizat <xliff:g id="PROGRESS">%2$s</xliff:g>)"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> așteaptă instalarea"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Widgeturi <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Adăugați pe ecranul de pornire"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Mutați elementul aici"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Element adăugat pe ecranul de pornire"</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index ad30c97..acb49d5 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"Ширина %1$d, высота %2$d"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Нажмите и удерживайте, чтобы добавить вручную"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Добавить автоматически"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Поиск приложений"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Загрузка…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"По запросу \"<xliff:g id="QUERY">%1$s</xliff:g>\" ничего не найдено"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Искать другие приложения"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Уведомления"</string>
     <string name="out_of_space" msgid="4691004494942118364">"На этом экране все занято"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"В разделе \"Избранное\" больше нет места"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Список приложений"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Разрешить поворачивать главный экран"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Когда телефон повернут"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"В настройках отключен поворот экрана"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Добавлять значки"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Добавлять значки установленных приложений на главный экран."</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Неизвестно"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Удалить"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Найти"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"Приложение не установлено. Вы можете удалить значок или найти приложение и установить его вручную."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"Скачивается \"<xliff:g id="NAME">%1$s</xliff:g>\" (<xliff:g id="PROGRESS">%2$s</xliff:g>)"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"Ожидание установки \"<xliff:g id="NAME">%1$s</xliff:g>\""</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g>: виджеты"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Добавить на главный экран"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Переместить элемент сюда"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Элемент добавлен на главный экран"</string>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index 1e0ed28..569a77f 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"පළල %1$d උස %2$d"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"අතින් ස්ථානගත කිරීමට ස්පර්ශ කර අල්ලාගෙන සිටින්න"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"ස්වයංක්‍රියව එක් කරන්න"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"යෙදුම් සෙවීම"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"යෙදුම් පූරණය වෙමින්…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" සමග ගැළපෙන යෙදුම් හමු නොවිණි"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"තව යෙදුම් සඳහා සොයන්න"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"දැනුම්දීම්"</string>
     <string name="out_of_space" msgid="4691004494942118364">"මෙම මුල් පිටු තිරය මත තවත් අවසර නැත."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"ප්‍රියතම දෑ ඇති තැටියේ තවත් ඉඩ නොමැත"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"යෙදුම් ලැයිස්තුව"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"මුල් පිටු තිරය කරකැවීමට ඉඩ දෙන්න"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"දුරකථනය කරකවන විට"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"වත්මන් සංදර්ශක සැකසීම් කරකැවීමට සහාය නොදක්වයි"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"මුල් පිටු තිරය වෙත අයිකනය එක් කරන්න"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"නව යෙදුම් සඳහා"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"නොදනී"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"ඉවත් කරන්න"</string>
     <string name="abandoned_search" msgid="891119232568284442">"සොයන්න"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"මෙම නිරුපකයට යෙදුම ස්ථාපනය කර නොමැත. ඔබට එය ඉවත් කළ හැක, හෝ යෙදුම් සඳහා සොයන්න සහ අතින් ස්ථාපනය කරන්න."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> බාගත කරමින්, <xliff:g id="PROGRESS">%2$s</xliff:g> සම්පූර්ණයි"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ස්ථාපනය කිරීමට බලා සිටිමින්"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> විජට්"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"මුල් තිරය වෙත එක් කරන්න"</string>
     <string name="action_move_here" msgid="2170188780612570250">"මෙතනට අයිතමය ගෙන එන්න"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"අයිතමය මුල් තිරය වෙත එකතු කරන ලදි"</string>
@@ -88,7 +102,7 @@
     <string name="move_to_hotseat_position" msgid="6295412897075147808">"ප්‍රියතම ස්ථානය <xliff:g id="NUMBER">%1$s</xliff:g> වෙත ගෙන යන්න"</string>
     <string name="item_moved" msgid="4606538322571412879">"අයිතමය ගෙන යන ලදි"</string>
     <string name="add_to_folder" msgid="9040534766770853243">"ෆෝල්ඩරය එක් කරන්න: <xliff:g id="NAME">%1$s</xliff:g>"</string>
-    <string name="add_to_folder_with_app" msgid="4534929978967147231">"<xliff:g id="NAME">%1$s</xliff:g> සමග ෆෝල්ඩරය වෙත එකතු කරන්න"</string>
+    <string name="add_to_folder_with_app" msgid="4534929978967147231">"<xliff:g id="NAME">%1$s</xliff:g> සමඟ ෆෝල්ඩරය වෙත එක් කරන්න"</string>
     <string name="added_to_folder" msgid="4793259502305558003">"අයිතමය ෆෝඩරය වෙතට එක් කරන ලදි"</string>
     <string name="create_folder_with" msgid="4050141361160214248">"මේ සමග ෆෝල්ඩරය සාදන්න: <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_created" msgid="6409794597405184510">"ෆෝල්ඩරය සාදන ලදි"</string>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 7210d79..dd03fd6 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"šírka %1$d, výška %2$d"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Položku umiestnite ručne klepnutím a podržaním"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Pridať automaticky"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Hľadať aplikácie"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Načítavajú sa aplikácie..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Nenašli sa žiadne aplikácie zodpovedajúce dopytu <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Hľadať ďalšie aplikácie"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Upozornenia"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Na tejto ploche už nie je miesto"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Na paneli Obľúbené položky už nie je miesto"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Zoznam aplikácií"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Povoliť otáčanie plochy"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Pri otočení telefónu"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Aktuálne nastavenie obrazovky nepovoľuje otáčanie"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Pridať ikonu na plochu"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Pri inštalácii novej aplikácie"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Neznáme"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Odstrániť"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Vyhľadať"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"Aplikácia, ktorú zastupuje táto ikona, nie je nainštalovaná. Ikonu môžete odstrániť alebo vyhľadajte aplikáciu a ručne ju nainštalujte."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"Sťahuje sa aplikácia <xliff:g id="NAME">%1$s</xliff:g>. Stiahnuté: <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"Aplikácia <xliff:g id="NAME">%1$s</xliff:g> čaká na inštaláciu"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Miniaplikácie <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Pridať na plochu"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Presunúť položku sem"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Položka bola pridaná na plochu"</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index b55cb0d..f8761d2 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"Širina %1$d, višina %2$d"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Dotaknite se elementa in ga pridržite, da ga ročno dodate"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Samodejno dodaj"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Iskanje po aplikacijah"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Nalaganje aplikacij …"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Ni aplikacij, ki bi ustrezale poizvedbi »<xliff:g id="QUERY">%1$s</xliff:g>«"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Iskanje več aplikacij"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Obvestila"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Na tem začetnem zaslonu ni več prostora."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"V vrstici za priljubljene ni več prostora"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Seznam aplikacij"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Omogočanje sukanja začetnega zaslona"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Ko se telefon zasuka"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Trenutna nastavitev zaslona ne dovoljuje sukanja"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Dodaj ikono na začetni zaslon"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Za nove aplikacije"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Neznano"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Odstrani"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Iskanje"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"Aplikacija za to ikono ni nameščena. Lahko jo odstranite ali poiščete aplikacijo in to namestite ročno."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"Prenašanje aplikacije <xliff:g id="NAME">%1$s</xliff:g>; preneseno <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"Aplikacija <xliff:g id="NAME">%1$s</xliff:g> čaka na namestitev"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Pripomočki za <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Dodajanje na začetni zaslon"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Premik elementa sem"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Element je bil dodan na začetni zaslon"</string>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index c4fbaf3..2b65684 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d i gjerë me %2$d i lartë"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Prek dhe mbaj të shtypur për të vendosur në mënyrë manuale"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Shto automatikisht"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Kërko për aplikacione"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Po ngarkon aplikacionet..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Nuk u gjet asnjë aplikacion që përputhet me \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Kërko për më shumë aplikacione"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Njoftimet"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Nuk ka më hapësirë në këtë ekran bazë."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Nuk ka më hapësirë në tabakanë \"Të preferuarat\""</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Lista e aplikacioneve"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Lejo rrotullimin e ekranit kryesor"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Kur telefoni rrotullohet"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Cilësimi aktuali i afishimit nuk lejon rrotullimin"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Shto ikonë në ekranin bazë"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Për aplikacionet e reja"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"I panjohur"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Hiq"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Kërko"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"Aplikacioni për këtë ikonë nuk është i instaluar. Mund ta heqësh ose të kërkosh aplikacionin dhe ta instalosh atë në mënyrë manuale."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> po shkarkohet, <xliff:g id="PROGRESS">%2$s</xliff:g> të përfunduara"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> po pret të instalohet"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Miniaplikacionet e <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Shto në Ekranin bazë"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Zhvendose artikullin këtu"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Artikulli u shtua tek ekrani bazë"</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 67a63cf..ac21142 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"ширина од %1$d и висина од %2$d"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Додирните и задржите да бисте поставили ручно"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Аутоматски додај"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Претражите апликације"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Апликације се учитавају..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Није пронађена ниједна апликација за „<xliff:g id="QUERY">%1$s</xliff:g>“"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Претражи још апликација"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Обавештења"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Нема више простора на овом почетном екрану."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Нема више простора на траци Омиљено"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Листа апликација"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Дозволи ротацију почетног екрана"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Када се телефон ротира"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Актуелно подешавање приказа не дозвољава ротацију"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Додај икону на почетни екран"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"За нове апликације"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Непознато"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Уклони"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Претражи"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"Апликација за ову икону није инсталирана. Можете да је уклоните или да потражите апликацију и инсталирате је ручно."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> се преузима, завршено је <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> чека на инсталирање"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Виџети за <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Додај на почетни екран"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Премести ставку овде"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Ставка је додата на почетни екран"</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index dfcc1b0..7d6e053 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d bred gånger %2$d hög"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Placera manuellt genom att trycka länge"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Lägg till automatiskt"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Sök efter appar"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Läser in appar …"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Det gick inte att hitta några appar som matchar <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Sök efter fler appar"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Aviseringar"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Det finns inte plats för mer på den här startskärmen."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Favoritfältet är fullt"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Applista"</string>
@@ -58,8 +63,8 @@
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Startskärmen %1$d av %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Ny sida på startskärmen"</string>
     <string name="folder_opened" msgid="94695026776264709">"Mappen är öppen, <xliff:g id="WIDTH">%1$d</xliff:g> gånger <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
-    <string name="folder_tap_to_close" msgid="4625795376335528256">"Tryck om du vill stänga mappen"</string>
-    <string name="folder_tap_to_rename" msgid="4017685068016979677">"Tryck om du vill spara namnändringen"</string>
+    <string name="folder_tap_to_close" msgid="4625795376335528256">"Tryck för att stänga mappen"</string>
+    <string name="folder_tap_to_rename" msgid="4017685068016979677">"Tryck för att spara namnändringen"</string>
     <string name="folder_closed" msgid="4100806530910930934">"Mappen är stängd"</string>
     <string name="folder_renamed" msgid="1794088362165669656">"Mappen har bytt namn till <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format" msgid="6629239338071103179">"Mapp: <xliff:g id="NAME">%1$s</xliff:g>"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Tillåt rotering av startskärmen"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"När mobilen vrids"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Rotering tillåts inte i de nuvarande skärminställningarna"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Lägg till ikonen på startskärmen"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"För nya appar"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Okänt"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Ta bort"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Sök"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"Appen för den här ikonen har inte installerats. Du kan ta bort den eller söka efter appen och installera den manuellt."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> laddas ned, <xliff:g id="PROGRESS">%2$s</xliff:g> klart"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> väntar på installation"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> widgetar"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Lägg till på startskärmen"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Flytta objekt hit"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Objektet har lagts till på startskärmen"</string>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 03c8f06..62e3385 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"Upana wa %1$d na kimo cha %2$d"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Gusa na ushikilie ili uweke mwenyewe"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Ongeza kiotomatiki"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Tafuta Programu"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Inapakia Programu..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Haikupata programu zinazolingana na \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Tafuta programu zaidi"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Arifa"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Hakuna nafasi katika skrini hii ya Mwanzo."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Hakuna nafasi zaidi katika treya ya Vipendeleo"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Orodha ya programu"</string>
@@ -73,6 +78,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Ruhusu kuzungusha skrini ya Kwanza"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Simu inapozungushwa"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Mipangilio ya sasa ya sehemu ya Onyesho hairuhusu kuzungusha"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Ongeza aikoni kwenye Skrini ya kwanza"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Kwa ajili ya programu mpya"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Yasiyojulikana"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Ondoa"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Tafuta"</string>
@@ -80,6 +93,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"Programu ya ikoni hii haijasakinishwa. Unaweza kuiondoa, au utafute programu na uisakinishe wewe mwenyewe."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> inapakuliwa, <xliff:g id="PROGRESS">%2$s</xliff:g> imekamilika"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> inasubiri kusakinisha"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Wijeti za <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Ongeza kwenye skrini ya Kwanza"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Hamishia kipengee hapa"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Kipengee kimeongezwa kwenye skrini ya kwanza"</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-sw720dp-land/dimens.xml b/res/values-sw720dp-land/dimens.xml
deleted file mode 100644
index 514980f..0000000
--- a/res/values-sw720dp-land/dimens.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 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.
--->
-
-<resources>
-    <!-- the area at the edge of the screen that makes the workspace go left
-         or right while you're dragging. -->
-    <dimen name="scroll_zone">100dip</dimen>
-</resources>
diff --git a/res/values-sw720dp/styles.xml b/res/values-sw720dp/styles.xml
index 674edaa..de809b1 100644
--- a/res/values-sw720dp/styles.xml
+++ b/res/values-sw720dp/styles.xml
@@ -25,6 +25,7 @@
         <item name="android:windowShowWallpaper">true</item>
         <item name="android:windowNoTitle">true</item>
         <item name="android:windowActionModeOverlay">true</item>
+        <item name="android:colorEdgeEffect">?android:attr/textColorSecondary</item>
     </style>
 
     <!-- Workspace -->
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index 7db0ae4..a1e00a1 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d அகலத்திற்கு %2$d உயரம்"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"நீங்களே சேர்க்க, தொட்டுப் பிடித்திருக்கவும்"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"தானாகவே சேர்"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"பயன்பாடுகளில் தேடுக"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"பயன்பாடுகளை ஏற்றுகிறது..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" உடன் பொருந்தும் பயன்பாடுகள் இல்லை"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"கூடுதல் பயன்பாடுகளைத் தேடு"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"அறிவிப்புகள்"</string>
     <string name="out_of_space" msgid="4691004494942118364">"முகப்புத் திரையில் இடமில்லை."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"பிடித்தவை ட்ரேயில் இடமில்லை"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"பயன்பாடுகளின் பட்டியல்"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"முகப்புத் திரை சுழற்சியை அனுமதி"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"மொபைலைச் சுழற்றும் போது"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"தற்போதைய திரை அமைப்பு சுழற்றுவதை அனுமதிக்கவில்லை"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"முகப்புத் திரையில் ஐகானைச் சேர்"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"புதிய பயன்பாடுகளுக்கு"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"தெரியாதது"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"அகற்று"</string>
     <string name="abandoned_search" msgid="891119232568284442">"தேடு"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"ஐகானுக்கான பயன்பாடு நிறுவப்படவில்லை. இதை அகற்றலாம் அல்லது பயன்பாட்டைத் தேடி கைமுறையாக நிறுவலாம்."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g>ஐப் பதிவிறக்குகிறது, <xliff:g id="PROGRESS">%2$s</xliff:g> முடிந்தது"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g>ஐ நிறுவுவதற்காகக் காத்திருக்கிறது"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> விட்ஜெட்டுகள்"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"முகப்புத் திரையில் சேர்"</string>
     <string name="action_move_here" msgid="2170188780612570250">"இங்கு நகர்த்து"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"முகப்புத் திரையில் சேர்க்கப்பட்டது"</string>
diff --git a/res/values-te-rIN/config.xml b/res/values-te-rIN/config.xml
new file mode 100644
index 0000000..56f98c3
--- /dev/null
+++ b/res/values-te-rIN/config.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="icon_shape_override_paths_values">
+    <item msgid="6640290598899495380"></item>
+    <item msgid="4009824731445917273">"M50,0L100,0 100,100 0,100 0,0z"</item>
+    <item msgid="3964229851574011244">"M50,0L80,0 A20,20,0,0 1 100,20 L100,80 A20,20,0,0 1 80,100 L20,100 A20,20,0,0 1 0,80 L 0,20 A20,20,0,0 1 20,0z"</item>
+    <item msgid="363553284746233331">"M50,0 C10,0 0,10 0,50 0,90 10,100 50,100 90,100 100,90 100,50 100,10 90,0 50,0 Z"</item>
+    <item msgid="4319038504053267455">"M50 0A50 50,0,1,1,50 100A50 50,0,1,1,50 0"</item>
+    <item msgid="483370082941112059">"M50,0A50,30 0,0,1 100,30V70A50,30 0,0,1 0,70V30A50,30 0,0,1 50,0z"</item>
+  </string-array>
+    <!-- no translation found for icon_shape_override_paths_names:0 (4837899951986816538) -->
+</resources>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index 4e8b86f..65ca927 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d వెడల్పు X %2$d ఎత్తు"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"మాన్యువల్‌గా ఉంచడానికి నొక్కి &amp;amp పట్టుకోండి"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"స్వయంచాలకంగా జోడించు"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"అనువర్తనాలను శోధించండి"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"అనువర్తనాలను లోడ్ చేస్తోంది…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\"కి సరిపోలే అనువర్తనాలేవీ కనుగొనబడలేదు"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"మరిన్ని అనువర్తనాల కోసం శోధించు"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"నోటిఫికేషన్‌లు"</string>
     <string name="out_of_space" msgid="4691004494942118364">"ఈ హోమ్ స్క్రీన్‌లో ఖాళీ లేదు."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"ఇష్టమైనవి ట్రేలో ఖాళీ లేదు"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"అనువర్తనాల జాబితా"</string>
@@ -71,6 +76,11 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"హోమ్ స్క్రీన్ భ్రమణాన్ని అనుమతించండి"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"ఫోన్‌‌ను తిప్పినప్పుడు"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"ప్రస్తుత డిస్‌ప్లే సెట్టింగ్ భ్రమణాన్ని అనుమతించలేదు"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"హోమ్ స్క్రీన్‌కి చిహ్నాన్ని జోడించు"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"కొత్త అనువర్తనాల కోసం"</string>
+    <string name="icon_shape_override_label" msgid="2977264953998281004">"చిహ్న ఆకారాన్ని మార్చు"</string>
+    <string name="icon_shape_no_override" msgid="3678524428085518367">"మార్చవద్దు"</string>
+    <string name="icon_shape_override_progress" msgid="3461735694970239908">"చిహ్న ఆకార మార్పులను వర్తింపజేస్తోంది"</string>
     <string name="package_state_unknown" msgid="7592128424511031410">"తెలియదు"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"తీసివేయి"</string>
     <string name="abandoned_search" msgid="891119232568284442">"శోధించు"</string>
@@ -78,6 +88,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"ఈ చిహ్నం యొక్క అనువర్తనం ఇన్‌స్టాల్ చేయబడలేదు. మీరు దీన్ని తీసివేయవచ్చు లేదా ఆ అనువర్తనం కోసం శోధించి దాన్ని మాన్యువల్‌గా ఇన్‌స్టాల్ చేయవచ్చు."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> డౌన్‌లోడ్ అవుతోంది, <xliff:g id="PROGRESS">%2$s</xliff:g> పూర్తయింది"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ఇన్‌స్టాల్ కావడానికి వేచి ఉంది"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> విడ్జెట్‌లు"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"హోమ్ స్క్రీన్‌కు జోడించు"</string>
     <string name="action_move_here" msgid="2170188780612570250">"అంశాన్ని ఇక్కడికి తరలించు"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"అంశం హోమ్‌స్క్రీన్‌కి జోడించబడింది"</string>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 90dec72..fb7b587 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"กว้าง %1$d x สูง %2$d"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"แตะค้างไว้เพื่อวางด้วยตัวเอง"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"เพิ่มโดยอัตโนมัติ"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ค้นหาแอป"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"กำลังโหลดแอป…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"ไม่พบแอปที่ตรงกับ \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"ค้นหาแอปเพิ่มเติม"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"การแจ้งเตือน"</string>
     <string name="out_of_space" msgid="4691004494942118364">"ไม่มีที่ว่างในหน้าจอหลักนี้"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"ไม่มีพื้นที่เหลือในถาดรายการโปรด"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"รายชื่อแอป"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"อนุญาตให้หมุนหน้าจอหลัก"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"เมื่อหมุนโทรศัพท์"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"การตั้งค่าการแสดงผลปัจจุบันไม่อนุญาตให้มีการหมุน"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"เพิ่มไอคอนในหน้าจอหลัก"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"สำหรับแอปใหม่"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"ไม่รู้จัก"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"ลบ"</string>
     <string name="abandoned_search" msgid="891119232568284442">"ค้นหา"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"ยังไม่ได้ติดตั้งแอปสำหรับไอคอนนี้ คุณสามารถนำไอคอนออก หรือค้นหาแอปดังกล่าวแล้วติดตั้งด้วยตนเอง"</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"กำลังดาวน์โหลด <xliff:g id="NAME">%1$s</xliff:g> เสร็จแล้ว <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> กำลังรอติดตั้ง"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"วิดเจ็ตของ <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"เพิ่มลงในหน้าแรก"</string>
     <string name="action_move_here" msgid="2170188780612570250">"ย้ายรายการมาที่นี่"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"เพิ่มรายการไปยังหน้าจอหลักแล้ว"</string>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index b944ebc..d18bd1e 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d ang lapad at %2$d ang taas"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Pindutin nang matagal upang manual na ilagay"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Awtomatikong idagdag"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Mga App sa Paghahanap"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Nilo-load ang Mga App…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Walang nakitang Mga App na tumutugma sa \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Maghanap ng higit pang mga app"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Mga Notification"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Wala nang lugar sa Home screen na ito."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Wala nang lugar sa tray ng Mga Paborito"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Listahan ng mga app"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Payagan ang pag-rotate ng Home screen"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Kailan maro-rotate ang telepono"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Hindi pinahihintulutan ng kasalukuyang setting ng Display ang pag-rotate"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Idagdag ang icon sa Home screen"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Para sa mga bagong app"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Hindi kilala"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Alisin"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Maghanap"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"Hindi naka-install ang app para sa icon na ito. Maaari mo itong alisin, o maaari mong hanapin ang app at i-install ito nang manu-mano."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"Dina-download na ang <xliff:g id="NAME">%1$s</xliff:g>, tapos na ang <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"Hinihintay nang mag-install ang <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Mga widget ng <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Idagdag sa Home screen"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Ilipat ang item dito"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Naidagdag sa home screen ang item"</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index e95ee2d..0c655fd 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -27,14 +27,19 @@
     <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="long_accessible_way_to_add" msgid="4289502106628154155">"Bir widget\'ı seçmek veya özel işlemleri kullanmak için iki kez dokunun ve basılı tutun."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"genişlik: %1$d, yükseklik: %2$d"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Manuel olarak yerleştirmek için dokunun ve basılı tutun"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Otomatik olarak ekle"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Uygulamalarda Ara"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Uygulamalar Yükleniyor…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" ile eşleşen uygulama bulunamadı"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Başka uygulamalar ara"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Bildirimler"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Bu Ana ekranda yer kalmadı."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Favoriler tepsisinde başka yer kalmadı"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Uygulamalar listesi"</string>
@@ -58,8 +63,8 @@
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Ana ekran %1$d / %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Yeni ana ekran sayfası"</string>
     <string name="folder_opened" msgid="94695026776264709">"Klasör açıldı, <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
-    <string name="folder_tap_to_close" msgid="4625795376335528256">"Klasörü kapatmak için hafifçe dokunun"</string>
-    <string name="folder_tap_to_rename" msgid="4017685068016979677">"Yeni adın kaydedilmesi için hafifçe dokunun"</string>
+    <string name="folder_tap_to_close" msgid="4625795376335528256">"Klasörü kapatmak için dokunun"</string>
+    <string name="folder_tap_to_rename" msgid="4017685068016979677">"Yeni adın kaydedilmesi için dokunun"</string>
     <string name="folder_closed" msgid="4100806530910930934">"Klasör kapatıldı"</string>
     <string name="folder_renamed" msgid="1794088362165669656">"Klasörün adı <xliff:g id="NAME">%1$s</xliff:g> olarak değiştirildi"</string>
     <string name="folder_name_format" msgid="6629239338071103179">"Klasör: <xliff:g id="NAME">%1$s</xliff:g>"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Ana ekranı döndürmeye izin ver"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Telefon döndürüldüğünde"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Mevcut Ekran ayarı, döndürmeye izin vermiyor"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Ana ekrana simge ekle"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Yeni uygulamalar için"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Bilinmiyor"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Kaldır"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Ara"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"Bu simgenin uygulaması yüklü değil. Uygulamayı kaldırabilir veya arayıp manuel olarak yükleyebilirsiniz."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> indiriliyor, <xliff:g id="PROGRESS">%2$s</xliff:g> tamamlandı"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> uygulaması yüklenmek için bekliyor"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> widget\'ları"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Ana ekrana ekle"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Öğeyi buraya taşı"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Öğe ana ekrana eklendi"</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 1f73f04..8679628 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"Ширина – %1$d, висота – %2$d"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Натисніть і утримуйте, щоб додати вручну"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Додати автоматично"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Пошук додатків"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Завантаження додатків…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Немає додатків для запиту \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Шукати ще додатки"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Сповіщення"</string>
     <string name="out_of_space" msgid="4691004494942118364">"На цьому головному екрані більше немає місця."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"В області \"Вибране\" немає місця"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Список додатків"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Дозволити обертання головного екрана"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Коли телефон обертається"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Поточні налаштування дисплея не підтримують обертання"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Додати значок на головний екран"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Для нових додатків"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Невідомо"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Видалити"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Шукати"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"Додаток для цього значка не встановлено. Можна видалити значок або знайти додаток і встановити його вручну."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> завантажується, <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> очікує на завантаження"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Віджети додатка <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Додати на головний екран"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Перемістити елемент сюди"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Елемент додано на головний екран"</string>
diff --git a/res/values-ur-rPK/config.xml b/res/values-ur-rPK/config.xml
new file mode 100644
index 0000000..56f98c3
--- /dev/null
+++ b/res/values-ur-rPK/config.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <string-array name="icon_shape_override_paths_values">
+    <item msgid="6640290598899495380"></item>
+    <item msgid="4009824731445917273">"M50,0L100,0 100,100 0,100 0,0z"</item>
+    <item msgid="3964229851574011244">"M50,0L80,0 A20,20,0,0 1 100,20 L100,80 A20,20,0,0 1 80,100 L20,100 A20,20,0,0 1 0,80 L 0,20 A20,20,0,0 1 20,0z"</item>
+    <item msgid="363553284746233331">"M50,0 C10,0 0,10 0,50 0,90 10,100 50,100 90,100 100,90 100,50 100,10 90,0 50,0 Z"</item>
+    <item msgid="4319038504053267455">"M50 0A50 50,0,1,1,50 100A50 50,0,1,1,50 0"</item>
+    <item msgid="483370082941112059">"M50,0A50,30 0,0,1 100,30V70A50,30 0,0,1 0,70V30A50,30 0,0,1 50,0z"</item>
+  </string-array>
+    <!-- no translation found for icon_shape_override_paths_names:0 (4837899951986816538) -->
+</resources>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index e4c637d..29d1cd5 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"‏%1$d چوڑا اور ‎%2$d اونچا"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"‏دستی طور پر رکھنے کیلئے ‎&amp; ٹچ کرکے ہولڈ کریں"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"خود کار طور پر شامل کریں"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ایپس تلاش کریں"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"ایپس لوڈ ہو رہی ہیں…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" سے مماثل کوئی ایپس نہیں ملیں"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"مزید ایپس تلاش کریں"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"اطلاعات"</string>
     <string name="out_of_space" msgid="4691004494942118364">"اس ہوم اسکرین پر مزید کوئی گنجائش نہیں ہے۔"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"پسندیدہ ٹرے میں مزید کوئی گنجائش نہیں ہے"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"ایپس کی فہرست"</string>
@@ -71,6 +76,11 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"ہوم اسکرین گھمانے کی اجازت دیں"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"جب فون گھمایا جاتا ہے"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"موجودہ ڈسپلے ترتیب گھمانے کی اجازت نہیں دیتی"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"آئیکن کو ہوم اسکرین میں شامل کریں"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"نئی ایپس کیلئے"</string>
+    <string name="icon_shape_override_label" msgid="2977264953998281004">"آئیکن کی شکل تبدیل کریں"</string>
+    <string name="icon_shape_no_override" msgid="3678524428085518367">"تبدیل نہ کریں"</string>
+    <string name="icon_shape_override_progress" msgid="3461735694970239908">"آئيکن کی شکل کی تبدیلیاں لاگو ہو رہی ہیں"</string>
     <string name="package_state_unknown" msgid="7592128424511031410">"نامعلوم"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"ہٹائیں"</string>
     <string name="abandoned_search" msgid="891119232568284442">"تلاش کریں"</string>
@@ -78,6 +88,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"اس آئیکن کیلئے ایپ انسٹال کردہ نہیں ہے۔ آپ اسے ہٹا سکتے ہیں یا ایپ کو تلاش کر سکتے اور دستی طور پر اسے انسٹال کر سکتے ہیں۔"</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> ڈاؤن لوڈ ہو رہا ہے، <xliff:g id="PROGRESS">%2$s</xliff:g> مکمل ہو گیا"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> انسٹال ہونے کا انتظار کر رہی ہے"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> ویجیٹس"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"ہوم اسکرین میں شامل کریں"</string>
     <string name="action_move_here" msgid="2170188780612570250">"آئٹم یہاں منتقل کریں"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"آئٹم کو ہوم اسکرین میں شامل کر دیا گیا"</string>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index 0e541d3..81be979 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"Eni %1$d, bo‘yi %2$d"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Qo‘lda joylashtirish uchun bosib turing"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Avtomatik chiqarish"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Ilovalar ichidan qidirish"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Ilovalar yuklanmoqda…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"“<xliff:g id="QUERY">%1$s</xliff:g>” so‘rovi bo‘yicha hech narsa topilmadi"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Boshqa ilovalarni qidirish"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Bildirishnomalar"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Uy ekranida bitta ham xona yo‘q."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Ajratilganlarda birorta ham xona yo‘q"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Ilovalar ro‘yxati"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Asosiy ekranni aylantirishga ruxsat berish"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Telefon burilganda"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Ekran sozlamalariga ko‘ra uni aylantirib bo‘lmaydi"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Bosh ekranga ikonka qo‘shish"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Yangi o‘rnatilgan ilovalar ikonkasini bosh ekranga chiqarish"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Noma’lum"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"O‘chirish"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Qidirish"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"Ilova o‘rnatilmagan. Belgini o‘chirib tashlashingiz yoki ilovani topib, uni qo‘lda o‘rnatishingiz mumkin."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> yuklab olinmoqda, <xliff:g id="PROGRESS">%2$s</xliff:g> bajarildi"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ilovasi o‘rnatilishi kutilmoqda"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> vidjetlari"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Bosh ekranga qo‘shish"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Obyektni bu yerga ko‘chirish"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Obyekt bosh ekranga qo‘shildi"</string>
diff --git a/res/drawable/bg_screenpanel.xml b/res/values-v26/bools.xml
similarity index 64%
rename from res/drawable/bg_screenpanel.xml
rename to res/values-v26/bools.xml
index 346fca0..30537fe 100644
--- a/res/drawable/bg_screenpanel.xml
+++ b/res/values-v26/bools.xml
@@ -1,8 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-/*
-**
-** Copyright 2015, The Android Open Source Project
+/* Copyright 2017, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -18,10 +16,8 @@
 */
 -->
 
-<!-- TODO(twickham): Remove this file and the screenpanel drawables -->
-<transition xmlns:android="http://schemas.android.com/apk/res/android" >
+<resources>
+    <bool name="notification_badging_enabled">true</bool>
 
-    <item android:drawable="@drawable/screenpanel"/>
-    <item android:drawable="@drawable/screenpanel_hover"/>
-
-</transition>
\ No newline at end of file
+    <bool name="enable_install_shortcut_api">false</bool>
+</resources>
\ No newline at end of file
diff --git a/res/values-v25/styles.xml b/res/values-v26/styles.xml
similarity index 67%
rename from res/values-v25/styles.xml
rename to res/values-v26/styles.xml
index ed670a9..cb18409 100644
--- a/res/values-v25/styles.xml
+++ b/res/values-v26/styles.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
 /*
-* Copyright (C) 2016 The Android Open Source Project
+* Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
@@ -19,6 +19,13 @@
 <resources>
     <!-- Theme for the widget container. -->
     <style name="WidgetContainerTheme" parent="@android:style/Theme.DeviceDefault.Settings">
-        <item name="colorSecondary">?android:attr/colorSecondary</item>
+        <item name="android:colorEdgeEffect">?android:attr/textColorSecondary</item>
     </style>
+
+    <!-- From O and above, we show a dark nav bar in all-apps -->
+    <style name="AllAppsNavBarProtection">
+        <item name="android:alpha">0.6</item>
+        <item name="android:background">?android:attr/colorPrimary</item>
+    </style>
+
 </resources>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 2bf5ad6..5fc9542 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"Rộng %1$d x cao %2$d"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Chạm và giữ để đặt theo cách thủ công"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Tự động thêm"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Tìm kiếm ứng dụng"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Đang tải ứng dụng..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Không tìm thấy ứng dụng nào phù hợp với \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Tìm kiếm thêm ứng dụng"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Thông báo"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Không còn chỗ trên Màn hình chính này."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Không còn chỗ trong khay Mục yêu thích"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Danh sách ứng dụng"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Cho phép xoay Màn hình chính"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Khi xoay điện thoại"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Cài đặt Hiển thị hiện tại không cho phép xoay"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Thêm biểu tượng vào màn hình chính"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Cho ứng dụng mới"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Không xác định"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Xóa"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Tìm kiếm"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"Ứng dụng cho biểu tượng này chưa được cài đặt. Bạn có thể xóa ứng dụng hoặc tìm kiếm và cài đặt ứng dụng theo cách thủ công."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"Đang tải xuống <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> hoàn tất"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"Đang chờ cài đặt <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"Tiện ích của <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Thêm vào màn hình chính"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Di chuyển mục vào đây"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Đã thêm mục vào màn hình chính"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index e3ed354..a6f8bf4 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -25,16 +25,21 @@
     <string name="activity_not_found" msgid="8071924732094499514">"未安装该应用。"</string>
     <string name="activity_not_available" msgid="7456344436509528827">"应用不可用"</string>
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"安全模式下不允许使用下载的此应用"</string>
-    <string name="safemode_widget_error" msgid="4863470563535682004">"安全模式下不允许使用小部件"</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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"宽 %1$d,高 %2$d"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"触摸并按住即可手动放置"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"自动添加"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"搜索应用"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"正在加载应用…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"未找到与“<xliff:g id="QUERY">%1$s</xliff:g>”相符的应用"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"搜索更多应用"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"通知"</string>
     <string name="out_of_space" msgid="4691004494942118364">"此主屏幕上已没有空间。"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"收藏栏已满"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"应用列表"</string>
@@ -63,7 +68,7 @@
     <string name="folder_closed" msgid="4100806530910930934">"文件夹已关闭"</string>
     <string name="folder_renamed" msgid="1794088362165669656">"已将文件夹重命名为“<xliff:g id="NAME">%1$s</xliff:g>”"</string>
     <string name="folder_name_format" msgid="6629239338071103179">"文件夹:<xliff:g id="NAME">%1$s</xliff:g>"</string>
-    <string name="widget_button_text" msgid="2880537293434387943">"小部件"</string>
+    <string name="widget_button_text" msgid="2880537293434387943">"微件"</string>
     <string name="wallpaper_button_text" msgid="8404103075899945851">"壁纸"</string>
     <string name="settings_button_text" msgid="8119458837558863227">"设置"</string>
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"已被您的管理员停用"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"允许旋转主屏幕"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"手机旋转时"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"当前的显示设置不允许旋转设备"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"将图标添加到主屏幕"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"适用于新应用"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"未知"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"移除"</string>
     <string name="abandoned_search" msgid="891119232568284442">"搜索"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"未安装此图标对应的应用。您可以移除此图标,也可以尝试搜索相应的应用并手动安装。"</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"正在下载<xliff:g id="NAME">%1$s</xliff:g>,已完成 <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g>正在等待安装"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g>微件"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"添加到主屏幕"</string>
     <string name="action_move_here" msgid="2170188780612570250">"将项目移至此处"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"已将项目添加到主屏幕"</string>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index ad0f99a..867c350 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d 闊,%2$d 高"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"按住即可手動新增"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"自動新增"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"搜尋應用程式"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"正在載入應用程式…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"無法找到與「<xliff:g id="QUERY">%1$s</xliff:g>」相符的應用程式"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"搜尋更多應用程式"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"通知"</string>
     <string name="out_of_space" msgid="4691004494942118364">"主畫面已無空間。"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"我的收藏寄存區沒有足夠空間"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"應用程式清單"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"允許主畫面旋轉"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"當手機旋轉時"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"「目前顯示屏」設定不允許旋轉"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"將圖示加到主畫面"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"適用於新安裝的應用程式"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"不明"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"移除"</string>
     <string name="abandoned_search" msgid="891119232568284442">"搜尋"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"您尚未安裝這個圖示代表的應用程式。您可以移除這個圖示,也可以搜尋該應用程式並手動安裝。"</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"正在下載 <xliff:g id="NAME">%1$s</xliff:g>,已完成 <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"正在等待安裝 <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g>小工具"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"新增至主畫面"</string>
     <string name="action_move_here" msgid="2170188780612570250">"移動項目至這裡"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"已將項目加入至主畫面"</string>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 24f391d..ee92cf1 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -27,14 +27,19 @@
     <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="long_accessible_way_to_add" msgid="4289502106628154155">"輕觸兩下並按住小工具即可選取,你也可以使用自訂動作。"</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"寬度為 %1$d,高度為 %2$d"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"按住即可手動放置"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"自動新增"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"搜尋應用程式"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"正在載入應用程式…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"找不到符合「<xliff:g id="QUERY">%1$s</xliff:g>」的應用程式"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"搜尋更多應用程式"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"通知"</string>
     <string name="out_of_space" msgid="4691004494942118364">"這個主螢幕已無空間。"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"「我的最愛」匣已無可用空間"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"應用程式清單"</string>
@@ -66,18 +71,27 @@
     <string name="widget_button_text" msgid="2880537293434387943">"小工具"</string>
     <string name="wallpaper_button_text" msgid="8404103075899945851">"桌布"</string>
     <string name="settings_button_text" msgid="8119458837558863227">"設定"</string>
-    <string name="msg_disabled_by_admin" msgid="6898038085516271325">"已由您的管理員停用"</string>
+    <string name="msg_disabled_by_admin" msgid="6898038085516271325">"已由你的管理員停用"</string>
     <string name="accessibility_action_overview" msgid="6257665857640347026">"總覽"</string>
     <string name="allow_rotation_title" msgid="7728578836261442095">"允許旋轉主螢幕"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"當手機旋轉時"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"目前的顯示設定不允許旋轉畫面"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"將圖示加到主螢幕"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"適用於新安裝的應用程式"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"不明"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"移除"</string>
     <string name="abandoned_search" msgid="891119232568284442">"搜尋"</string>
     <string name="abandoned_promises_title" msgid="7096178467971716750">"尚未安裝這個應用程式"</string>
-    <string name="abandoned_promise_explanation" msgid="3990027586878167529">"您尚未安裝這個圖示代表的應用程式。您可以移除這個圖示,也可以搜尋該應用程式並手動安裝。"</string>
+    <string name="abandoned_promise_explanation" msgid="3990027586878167529">"你尚未安裝這個圖示代表的應用程式。你可以移除這個圖示,也可以搜尋該應用程式並手動安裝。"</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"正在下載「<xliff:g id="NAME">%1$s</xliff:g>」,已完成 <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"正在等待安裝「<xliff:g id="NAME">%1$s</xliff:g>」"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"「<xliff:g id="NAME">%1$s</xliff:g>」小工具"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"新增至主畫面"</string>
     <string name="action_move_here" msgid="2170188780612570250">"將項目移至這裡"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"已將項目新增到主畫面"</string>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 10fbef4..aa918b3 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -27,14 +27,19 @@
     <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>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d ububanzi ngokungu-%2$d ukuya phezulu"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Thinta futhi ubambe ukuze ubeke ngokwenza"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Engeza ngokuzenzakalelayo"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Sesha Izinhlelo Zokusebenza"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Ilayisha izinhlelo zokusebenza..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Azikho izinhlelo zokusebenza ezitholakele ezifana ne-\"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Sesha izinhlelo zokusebenza eziningi"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Izaziso"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Asisekho isikhala kulesi sikrini Sasekhaya."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Asisekho isikhala kwitreyi lezintandokazi"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Uhlu lwezinhlelo zokusebenza"</string>
@@ -71,6 +76,14 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Vumela ukuphendukiswa kwesikrini sasekhaya"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Uma ifoni iphendukiswa"</string>
     <string name="allow_rotation_blocked_desc" msgid="3212602545192996253">"Isilungiselelo sesiboniso samanje asivumeli ukuzungezisa"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Engeza isithonjana eskrinini sasekhaya"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Kwezinhlelo zokusebenza ezintsha"</string>
+    <!-- no translation found for icon_shape_override_label (2977264953998281004) -->
+    <skip />
+    <!-- no translation found for icon_shape_no_override (3678524428085518367) -->
+    <skip />
+    <!-- no translation found for icon_shape_override_progress (3461735694970239908) -->
+    <skip />
     <string name="package_state_unknown" msgid="7592128424511031410">"Akwaziwa"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Susa"</string>
     <string name="abandoned_search" msgid="891119232568284442">"Sesha"</string>
@@ -78,6 +91,7 @@
     <string name="abandoned_promise_explanation" msgid="3990027586878167529">"Uhlelo lokusebenza lalesi sithonjana alufakiwe. Ungalisusa, noma sesha uhlelo lokusebenza bese uzifakela lona ngokuzenzela."</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"I-<xliff:g id="NAME">%1$s</xliff:g> iyalandwa, <xliff:g id="PROGRESS">%2$s</xliff:g> kuqediwe"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ilinde ukufakwa"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> amawijethi"</string>
     <string name="action_add_to_workspace" msgid="8902165848117513641">"Faka kusikrini sasekhaya"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Hambisa into lapha"</string>
     <string name="item_added_to_workspace" msgid="4211073925752213539">"Into ingezwe kusikrini sasekhaya"</string>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 3423835..18f409f 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" />
@@ -71,12 +72,6 @@
         <attr name="folderItems" format="reference" />
     </declare-styleable>
 
-    <declare-styleable name="PreloadIconDrawable">
-        <attr name="background" format="reference" />
-        <attr name="ringOutset" format="dimension" />
-        <attr name="indicatorSize" format="dimension" />
-    </declare-styleable>
-
     <declare-styleable name="InsettableFrameLayout_Layout">
         <attr name="layout_ignoreInsets" format="boolean" />
     </declare-styleable>
@@ -85,9 +80,6 @@
         <attr name="hideParentOnDisable" format="boolean" />
     </declare-styleable>
 
-    <!-- Fallback attr for pre-API 25 support -->
-    <attr name="colorSecondary" format="reference|color" />
-
     <declare-styleable name="InvariantDeviceProfile">
         <attr name="name" format="string" />
         <attr name="minWidthDps" format="float" />
@@ -111,4 +103,12 @@
         <attr name="defaultLayoutId" format="reference" />
     </declare-styleable>
 
+    <declare-styleable name="CellLayout">
+        <attr name="containerType" format="integer">
+            <enum name="workspace" value="0" />
+            <enum name="hotseat" value="1" />
+            <enum name="folder" value="2" />
+        </attr>
+    </declare-styleable>
+
 </resources>
diff --git a/res/drawable/bg_screenpanel.xml b/res/values/bools.xml
similarity index 64%
copy from res/drawable/bg_screenpanel.xml
copy to res/values/bools.xml
index 346fca0..53c67e2 100644
--- a/res/drawable/bg_screenpanel.xml
+++ b/res/values/bools.xml
@@ -1,8 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-/*
-**
-** Copyright 2015, The Android Open Source Project
+/* Copyright 2017, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -18,10 +16,8 @@
 */
 -->
 
-<!-- TODO(twickham): Remove this file and the screenpanel drawables -->
-<transition xmlns:android="http://schemas.android.com/apk/res/android" >
+<resources>
+    <bool name="notification_badging_enabled">false</bool>
 
-    <item android:drawable="@drawable/screenpanel"/>
-    <item android:drawable="@drawable/screenpanel_hover"/>
-
-</transition>
\ No newline at end of file
+    <bool name="enable_install_shortcut_api">true</bool>
+</resources>
\ No newline at end of file
diff --git a/res/values/colors.xml b/res/values/colors.xml
index d5ce786..58717c2 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -26,28 +26,24 @@
     <color name="focused_background">#80c6c5c5</color>
 
     <color name="workspace_icon_text_color">#FFF</color>
-
     <color name="workspace_edge_effect_color">#FFFFFFFF</color>
-    <color name="folder_edge_effect_color">#FF757575</color>
-    <color name="page_indicator_dot_color">#FFDDDDDD</color>
 
-    <color name="quantum_panel_text_color">#FF666666</color>
-    <color name="quantum_panel_bg_color">#FFF5F5F5</color>
+    <color name="default_shadow_color_no_alpha">#FF000000</color>
 
     <color name="outline_color">#FFFFFFFF</color>
-    <color name="all_apps_divider_color">#1E000000</color>
-    <color name="all_apps_caret_color">#FFFFFFFF</color>
-    <color name="all_apps_caret_shadow_color">#22000000</color>
-    <color name="all_apps_container_color">#FFF2F2F2</color>
-    <color name="all_apps_navbar_color">#28000000</color>
 
     <color name="spring_loaded_panel_color">#40FFFFFF</color>
     <color name="spring_loaded_highlighted_panel_border_color">#FFF</color>
 
-    <!-- Widgets view -->
-    <color name="widgets_view_section_text_color">#FFFFFF</color>
-    <color name="widgets_view_item_text_color">#C4C4C4</color>
+    <!-- Popup container -->
+    <color name="popup_header_background_color">#EEEEEE</color> <!-- Gray 200 -->
+    <color name="popup_background_color">#FFF</color>
+    <color name="notification_icon_default_color">#757575</color> <!-- Gray 600 -->
+    <color name="notification_color_beneath">#E0E0E0</color> <!-- Gray 300 -->
 
-    <!-- Used as a fallback since colorSecondary doesn't exist pre-API 25 -->
-    <color name="fallback_secondary_color">#FF37474F</color>
+    <!-- System shortcuts -->
+    <color name="system_shortcuts_icon_color">@android:color/tertiary_text_light</color>
+
+    <color name="legacy_icon_background">#FFFFFF</color>
+    <color name="icon_background">#E0E0E0</color> <!-- Gray 300 -->
 </resources>
diff --git a/res/values/config.xml b/res/values/config.xml
index a942f02..19966f6 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -13,6 +13,26 @@
      easily override the app name without providing all translations -->
     <string name="derived_app_name" translatable="false">@string/app_name</string>
 
+    <!-- Values for icon shape overrides. These should correspond to entries defined
+     in icon_shape_override_paths_names -->
+    <string-array name="icon_shape_override_paths_values">
+        <item></item>
+        <item translatable="false">M50,0L100,0 100,100 0,100 0,0z</item>
+        <item translatable="false">M50,0L80,0 A20,20,0,0 1 100,20 L100,80 A20,20,0,0 1 80,100 L20,100 A20,20,0,0 1 0,80 L 0,20 A20,20,0,0 1 20,0z</item>
+        <item translatable="false">M50,0 C10,0 0,10 0,50 0,90 10,100 50,100 90,100 100,90 100,50 100,10 90,0 50,0 Z</item>
+        <item translatable="false">M50 0A50 50,0,1,1,50 100A50 50,0,1,1,50 0</item>
+        <item translatable="false">M50,0A50,30 0,0,1 100,30V70A50,30 0,0,1 0,70V30A50,30 0,0,1 50,0z</item>
+    </string-array>
+
+    <string-array name="icon_shape_override_paths_names">
+        <!-- Option to not change the icon shape on home screen. [CHAR LIMIT=50] -->
+        <item>@string/icon_shape_no_override</item>
+        <item translatable="false">Square</item>
+        <item translatable="false">Rounded corner rect</item>
+        <item translatable="false">Squircle</item>
+        <item translatable="false">Circle</item>
+        <item translatable="false">Cylinder</item>
+    </string-array>
 <!-- DragController -->
     <item type="id" name="drag_event_parity" />
 
@@ -72,13 +92,15 @@
          filter the activities shown in the launcher. Can be empty. -->
     <string name="app_filter_class" translatable="false"></string>
 
-    <!-- List of package names that com.android.launcher3.action.LAUNCH
-     should be targeting. Can be empty. -->
-    <array name="launch_broadcast_targets" translatable="false"></array>
-
     <!-- Name of an icon provider class. -->
     <string name="icon_provider_class" translatable="false"></string>
 
+    <!-- Name of a drawable factory class. -->
+    <string name="drawable_factory_class" translatable="false"></string>
+
+    <!-- Name of a user event dispatcher class. -->
+    <string name="user_event_dispatcher_class" translatable="false"></string>
+
     <!-- Package name of the default wallpaper picker. -->
     <string name="wallpaper_picker_package" translatable="false"></string>
 
@@ -91,12 +113,16 @@
     <!-- View ID used by cell layout to jail its content -->
     <item type="id" name="cell_layout_jail_id" />
 
-<!-- Deep shortcuts -->
+    <!-- View ID used by PreviewImageView to cache its instance -->
+    <item type="id" name="preview_image_id" />
+
+<!-- Popup items -->
     <integer name="config_deepShortcutOpenDuration">220</integer>
     <integer name="config_deepShortcutArrowOpenDuration">80</integer>
     <integer name="config_deepShortcutOpenStagger">40</integer>
     <integer name="config_deepShortcutCloseDuration">150</integer>
     <integer name="config_deepShortcutCloseStagger">20</integer>
+    <integer name="config_removeNotificationViewDuration">300</integer>
 
 <!-- Accessibility actions -->
     <item type="id" name="action_remove" />
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index eff9d21..bd6466b 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>
@@ -85,11 +81,15 @@
 
     <dimen name="all_apps_divider_margin_vertical">8dp</dimen>
 
-    <dimen name="all_apps_bezel_swipe_height">24dp</dimen>
-
 <!-- Widget tray -->
     <dimen name="widget_preview_label_vertical_padding">8dp</dimen>
-    <dimen name="widget_preview_label_horizontal_padding">8dp</dimen>
+    <dimen name="widget_preview_label_horizontal_padding">16dp</dimen>
+
+    <dimen name="widget_preview_shadow_blur">0.5dp</dimen>
+    <dimen name="widget_preview_key_shadow_distance">1dp</dimen>
+    <dimen name="widget_preview_corner_radius">2dp</dimen>
+    <dimen name="widget_preview_cell_divider_width">0.5dp</dimen>
+    <dimen name="widget_preview_shortcut_padding">8dp</dimen>
 
     <dimen name="widget_section_height">56dp</dimen>
     <dimen name="widget_section_icon_size">40dp</dimen>
@@ -113,14 +113,6 @@
     <!-- the distance an icon must be dragged before button drop targets accept it -->
     <dimen name="drag_distanceThreshold">30dp</dimen>
 
-    <!-- the area at the edge of the screen that makes the workspace go left
-         or right while you're dragging. -->
-    <dimen name="scroll_zone">20dp</dimen>
-
-    <!-- When dragging an item, how much bigger (fixed dps) the dragged view
-         should be. If 0, it will not be scaled at all. -->
-    <dimen name="dragViewScale">12dp</dimen>
-
     <!-- Elevation for the drag view. It should be larger than elevation of all other drag sources
          and drop targets like all-apps and folders -->
     <dimen name="drag_elevation">30dp</dimen>
@@ -129,9 +121,6 @@
 
     <dimen name="spring_loaded_panel_border">1dp</dimen>
 
-<!-- Theme -->
-    <dimen name="quantum_panel_outer_padding">4dp</dimen>
-
 <!-- Folders -->
     <!-- The size of the padding on the preview background drawable -->
     <dimen name="folder_preview_padding">6dp</dimen>
@@ -161,30 +150,69 @@
 
 <!-- Deep shortcuts -->
     <dimen name="deep_shortcuts_elevation">9dp</dimen>
-    <dimen name="bg_pill_width">208dp</dimen>
-    <dimen name="bg_pill_height">48dp</dimen>
-    <dimen name="bg_pill_radius">24dp</dimen>
-    <dimen name="deep_shortcuts_spacing">4dp</dimen>
-    <dimen name="deferred_drag_view_scale">6dp</dimen>
+    <dimen name="bg_popup_item_width">220dp</dimen>
+    <dimen name="bg_popup_item_height">56dp</dimen>
+    <dimen name="popup_items_spacing">4dp</dimen>
+    <dimen name="pre_drag_view_scale">6dp</dimen>
     <!-- an icon with shortcuts must be dragged this far before the container is removed. -->
     <dimen name="deep_shortcuts_start_drag_threshold">16dp</dimen>
     <dimen name="deep_shortcut_icon_size">36dp</dimen>
-    <dimen name="deep_shortcut_padding_start">6dp</dimen>
-    <dimen name="deep_shortcut_padding_end">16dp</dimen>
     <dimen name="deep_shortcut_drawable_padding">8dp</dimen>
-    <dimen name="deep_shortcut_anim_translation_y">5dp</dimen>
     <dimen name="deep_shortcut_drag_handle_size">16dp</dimen>
-    <dimen name="deep_shortcuts_arrow_width">10dp</dimen>
-    <dimen name="deep_shortcuts_arrow_height">8dp</dimen>
-    <dimen name="deep_shortcuts_arrow_vertical_offset">-2dp</dimen>
-    <!-- deep_shortcut_padding_start + deep_shortcut_icon_size / 2 - deep_shortcuts_arrow_width / 2-->
-    <!-- Note that this works for right-aligned shortcuts, too, because
-         deep_shortcut_padding_end + deep_shortcut_drag_handle_size / 2 - deep_shortcuts_arrow_width / 2
-         also happens to equal 19dp-->
-    <dimen name="deep_shortcuts_arrow_horizontal_offset">19dp</dimen>
+    <dimen name="popup_padding_start">10dp</dimen>
+    <dimen name="popup_padding_end">16dp</dimen>
+    <dimen name="popup_arrow_width">10dp</dimen>
+    <dimen name="popup_arrow_height">8dp</dimen>
+    <dimen name="popup_arrow_vertical_offset">-2dp</dimen>
+    <!-- popup_padding_start + deep_shortcut_icon_size / 2 -->
+    <dimen name="popup_arrow_horizontal_center_start">28dp</dimen>
+    <!-- popup_padding_end + deep_shortcut_drag_handle_size / 2 -->
+    <dimen name="popup_arrow_horizontal_center_end">24dp</dimen>
+    <!-- popup_arrow_center_start - popup_arrow_width / 2-->
+    <dimen name="popup_arrow_horizontal_offset_start">23dp</dimen>
+    <!-- popup_arrow_center_end - popup_arrow_width / 2-->
+    <dimen name="popup_arrow_horizontal_offset_end">19dp</dimen>
+    <dimen name="popup_arrow_corner_radius">2dp</dimen>
+    <!-- popup_padding_start + icon_size + 10dp -->
+    <dimen name="deep_shortcuts_text_padding_start">56dp</dimen>
+    <!-- popup_item_width - deep_shortcuts_text_padding_start -->
+    <dimen name="deep_shortcuts_divider_width">164dp</dimen>
+    <dimen name="system_shortcut_icon_size">24dp</dimen>
+    <!-- popup_arrow_center_start - system_shortcut_icon_size / 2 -->
+    <dimen name="system_shortcut_margin_start">16dp</dimen>
+    <dimen name="system_shortcut_header_height">40dp</dimen>
+    <dimen name="system_shortcut_header_icon_touch_size">48dp</dimen>
+    <!-- (touch_size - icon_size) / 2 -->
+    <dimen name="system_shortcut_header_icon_padding">12dp</dimen>
+
+<!-- Icon badges (with notification counts) -->
+    <dimen name="badge_small_padding">0dp</dimen>
+    <dimen name="badge_large_padding">3dp</dimen>
+
+<!-- Notifications -->
+    <dimen name="bg_round_rect_radius">12dp</dimen>
+    <dimen name="notification_padding_start">16dp</dimen>
+    <dimen name="notification_padding_end">12dp</dimen>
+    <!-- notification_padding_end + (icon_size - footer_icon_size) / 2 -->
+    <dimen name="notification_footer_icon_row_padding">15dp</dimen>
+    <dimen name="notification_header_height">32dp</dimen>
+    <dimen name="notification_main_height">80dp</dimen>
+    <dimen name="notification_footer_height">32dp</dimen>
+    <dimen name="notification_header_text_size">13sp</dimen>
+    <dimen name="notification_header_count_text_size">12sp</dimen>
+    <dimen name="notification_main_text_size">14sp</dimen>
+    <dimen name="notification_icon_size">24dp</dimen>
+    <dimen name="notification_footer_icon_size">18dp</dimen>
+    <!-- notification_icon_size + notification_padding_end + 16dp padding between icon and text -->
+    <dimen name="notification_main_text_padding_end">52dp</dimen>
+    <dimen name="notification_elevation">2dp</dimen>
+    <dimen name="horizontal_ellipsis_size">18dp</dimen>
+    <!-- arrow_horizontal_offset_start - (ellipsis_size - arrow_width) / 2 -->
+    <dimen name="horizontal_ellipsis_offset">19dp</dimen>
+    <dimen name="popup_item_divider_height">0.5dp</dimen>
+    <dimen name="swipe_helper_falsing_threshold">70dp</dimen>
 
 <!-- Other -->
     <!-- Approximates the system status bar height. Not guaranteed to be always be correct. -->
     <dimen name="status_bar_height">24dp</dimen>
-
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 60a37e5..4bee87d 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] -->
@@ -48,6 +52,10 @@
     <string name="widget_dims_format">%1$d \u00d7 %2$d</string>
     <!-- Accessibility spoken message format for the dimensions of a widget in the drawer -->
     <string name="widget_accessible_dims_format">%1$d wide by %2$d high</string>
+    <!-- Message to tell the user to press and hold a widget/icon to add it  -->
+    <string name="add_item_request_drag_hint">Touch &amp; hold to place manually</string>
+    <!-- Button label to automatically add icon on home screen [CHAR_LIMIT=50] -->
+    <string name="place_automatically">Add automatically</string>
 
     <!-- All Apps -->
     <!-- Search bar text in the apps view. [CHAR_LIMIT=50] -->
@@ -59,6 +67,10 @@
     <!-- Label for the button which allows the user to get app search results. [CHAR_LIMIT=50] -->
     <string name="all_apps_search_market_message">Search for more apps</string>
 
+    <!-- Popup items -->
+    <!-- Text to display as the header above notifications. [CHAR_LIMIT=30] -->
+    <string name="notifications_header">Notifications</string>
+
     <!-- Drag and drop -->
     <skip />
     <!-- Error message when user has filled a home screen -->
@@ -161,6 +173,18 @@
     <!-- Text explaining that rotation is disabled in Display settings. 'Display' refers to the Display section in system settings [CHAR LIMIT=100] -->
     <string name="allow_rotation_blocked_desc">Current Display setting doesn\'t permit rotation</string>
 
+    <!-- Label for the setting that allows the automatic placement of launcher shortcuts for applications and games installed on the device [CHAR LIMIT=40] -->
+    <string name="auto_add_shortcuts_label">Add icon to Home screen</string>
+    <!-- Text description of the setting that allows the automatic placement of launcher shortcuts for applications and games installed on the device [CHAR LIMIT=NONE] -->
+    <string name="auto_add_shortcuts_description">For new apps</string>
+
+    <!-- Developer setting to change the shape of icons on home screen. [CHAR LIMIT=50] -->
+    <string name="icon_shape_override_label">Change icon shape</string>
+    <!-- Option to not change the icon shape on home screen. [CHAR LIMIT=50] -->
+    <string name="icon_shape_no_override">Do not change</string>
+    <!-- Message shown in the progress dialog when the icon shape override is being applied [CHAR LIMIT=100]-->
+    <string name="icon_shape_override_progress">Applying icon shape changes</string>
+
     <!-- Label on an icon that references an uninstalled package, for which we have no information about when it might be installed. [CHAR_LIMIT=15] -->
     <string name="package_state_unknown">Unknown</string>
 
@@ -180,6 +204,10 @@
     <!-- Title for an app whose download has been started. -->
     <string name="app_waiting_download_title"><xliff:g id="name" example="Messenger">%1$s</xliff:g> waiting to install</string>
 
+    <!-- Strings for widgets & more in the popup container/bottom sheet -->
+    <!-- Title for a bottom sheet that shows widgets for a particular app -->
+    <string name="widgets_bottom_sheet_title"><xliff:g id="name" example="Messenger">%1$s</xliff:g> widgets</string>
+
 <!-- Strings for accessibility actions -->
     <!-- Accessibility action to add an app to workspace. [CHAR_LIMIT=30] -->
     <string name="action_add_to_workspace">Add to Home screen</string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index cd06b75..8a46e83 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -24,78 +24,61 @@
         <item name="android:colorBackgroundCacheHint">@null</item>
         <item name="android:windowShowWallpaper">true</item>
         <item name="android:windowNoTitle">true</item>
+        <item name="android:colorEdgeEffect">#FF757575</item>
     </style>
 
     <style name="LauncherTheme" parent="@style/BaseLauncherTheme"></style>
 
-    <style name="Theme" parent="@style/LauncherTheme"></style>
-
-    <!-- Theme for the widget container. Overridden on API 25. -->
+    <!-- Theme for the widget container. Overridden on API 26. -->
     <style name="WidgetContainerTheme" parent="@android:style/Theme.DeviceDefault.Settings">
-        <item name="colorSecondary">@color/fallback_secondary_color</item>
+        <item name="android:colorEdgeEffect">?android:attr/textColorSecondaryInverse</item>
+        <item name="android:textColorPrimary">?android:attr/textColorPrimaryInverse</item>
+        <item name="android:textColorSecondary">?android:attr/textColorSecondaryInverse</item>
     </style>
 
-    <!-- Overscroll effect -->
-    <style name="CustomOverscroll" />
-
-    <style name="CustomOverscroll.Light" parent="@android:style/Theme.DeviceDefault.Light">
-        <item name="android:colorEdgeEffect">@color/folder_edge_effect_color</item>
+    <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>
 
-    <style name="CustomOverscroll.Dark">
-        <item name="android:colorEdgeEffect">@color/workspace_edge_effect_color</item>
+    <!-- Style for nav bar background in all-apps screen -->
+    <style name="AllAppsNavBarProtection">
+        <item name="android:alpha">?android:attr/spotShadowAlpha</item>
+        <item name="android:background">@color/default_shadow_color_no_alpha</item>
     </style>
 
-    <!-- Different icons -->
-    <style name="Icon">
+    <!-- Base theme for BubbleTextView and sub classes -->
+    <style name="BaseIcon">
         <item name="android:layout_width">match_parent</item>
         <item name="android:layout_height">match_parent</item>
         <item name="android:layout_gravity">center</item>
+        <item name="android:focusable">true</item>
         <item name="android:gravity">center_horizontal</item>
         <item name="android:singleLine">true</item>
         <item name="android:ellipsize">marquee</item>
+        <item name="android:textColor">?android:attr/textColorSecondary</item>
+        <item name="android:fontFamily">sans-serif-condensed</item>
+
+        <!-- No shadows in the base theme -->
+        <item name="android:shadowRadius">0</item>
+        <item name="customShadows">false</item>
+    </style>
+
+    <!-- Icon displayed on the worksapce -->
+    <style name="BaseIcon.Workspace">
+        <item name="customShadows">true</item>
         <item name="android:textColor">@color/workspace_icon_text_color</item>
         <item name="android:shadowRadius">2.0</item>
         <item name="android:shadowColor">#B0000000</item>
-        <item name="android:fontFamily">sans-serif-condensed</item>
-    </style>
-
-    <style name="Icon.AllApps">
-        <item name="android:background">@null</item>
-        <item name="android:textColor">@color/quantum_panel_text_color</item>
-        <item name="android:drawablePadding">@dimen/dynamic_grid_icon_drawable_padding</item>
-        <item name="android:shadowRadius">0</item>
-        <item name="android:paddingLeft">4dp</item>
-        <item name="android:paddingRight">4dp</item>
-        <item name="customShadows">false</item>
-    </style>
-
-    <style name="Icon.Folder">
-        <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>
-        <item name="iconDisplay">folder</item>
-    </style>
-
-    <style name="Icon.DeepShortcut">
-        <item name="android:gravity">start|center_vertical</item>
-        <item name="android:textAlignment">viewStart</item>
-        <item name="android:elevation">@dimen/deep_shortcuts_elevation</item>
-        <item name="android:paddingStart">@dimen/bg_pill_height</item>
-        <item name="android:paddingEnd">@dimen/deep_shortcut_padding_end</item>
-        <item name="android:drawableEnd">@drawable/deep_shortcuts_drag_handle</item>
-        <item name="android:drawablePadding">@dimen/deep_shortcut_drawable_padding</item>
-        <item name="android:textColor">#FF212121</item>
-        <item name="android:textSize">14sp</item>
-        <item name="android:fontFamily">sans-serif</item>
-        <item name="android:shadowRadius">0</item>
-        <item name="customShadows">false</item>
-        <item name="layoutHorizontal">true</item>
-        <item name="iconSizeOverride">@dimen/deep_shortcut_icon_size</item>
     </style>
 
     <!-- Drop targets -->
@@ -103,28 +86,15 @@
         <item name="android:drawablePadding">7.5dp</item>
         <item name="android:paddingLeft">16dp</item>
         <item name="android:paddingRight">16dp</item>
-        <item name="android:textColor">#FFFFFFFF</item>
+        <item name="android:textColor">@color/workspace_icon_text_color</item>
         <item name="android:textSize">@dimen/drop_target_text_size</item>
         <item name="android:singleLine">true</item>
         <item name="android:ellipsize">end</item>
-        <item name="android:shadowColor">#FF000000</item>
+        <item name="android:shadowColor">@color/default_shadow_color_no_alpha</item>
         <item name="android:shadowDx">0.0</item>
         <item name="android:shadowDy">1.0</item>
         <item name="android:shadowRadius">4.0</item>
     </style>
 
     <style name="DropTargetButton" parent="DropTargetButtonBase" />
-
-    <!-- Virtual preloaders -->
-    <style name="PreloadIcon">
-        <item name="background">@drawable/virtual_preload</item>
-        <item name="indicatorSize">4dp</item>
-        <item name="ringOutset">4dp</item>
-    </style>
-
-    <style name="PreloadIcon.Folder">
-        <item name="background">@drawable/virtual_preload_folder</item>
-        <item name="indicatorSize">4dp</item>
-        <item name="ringOutset">4dp</item>
-    </style>
 </resources>
diff --git a/res/xml/app_target_browser.xml b/res/xml/app_target_browser.xml
deleted file mode 100644
index d7c3ed5..0000000
--- a/res/xml/app_target_browser.xml
+++ /dev/null
@@ -1,23 +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.
--->
-
-<resolve xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3" >
-
-    <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_BROWSER;end" />
-    <favorite launcher:uri="http://www.example.com/" />
-
-</resolve>
\ No newline at end of file
diff --git a/res/xml/app_target_camera.xml b/res/xml/app_target_camera.xml
deleted file mode 100644
index f65a2b1..0000000
--- a/res/xml/app_target_camera.xml
+++ /dev/null
@@ -1,23 +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.
--->
-
-<resolve xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3" >
-
-    <favorite launcher:uri="#Intent;action=android.media.action.STILL_IMAGE_CAMERA;end" />
-    <favorite launcher:uri="#Intent;action=android.intent.action.CAMERA_BUTTON;end" />
-
-</resolve>
\ No newline at end of file
diff --git a/res/xml/app_target_email.xml b/res/xml/app_target_email.xml
deleted file mode 100644
index 44f0a40..0000000
--- a/res/xml/app_target_email.xml
+++ /dev/null
@@ -1,23 +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.
--->
-
-<resolve xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3" >
-
-    <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_EMAIL;end" />
-    <favorite launcher:uri="mailto:" />
-
-</resolve>
\ No newline at end of file
diff --git a/res/xml/app_target_gallery.xml b/res/xml/app_target_gallery.xml
deleted file mode 100644
index c9d3492..0000000
--- a/res/xml/app_target_gallery.xml
+++ /dev/null
@@ -1,23 +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.
--->
-
-<resolve xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3" >
-
-    <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_GALLERY;end" />
-    <favorite launcher:uri="#Intent;type=images/*;end" />
-
-</resolve>
\ No newline at end of file
diff --git a/res/xml/app_target_messenger.xml b/res/xml/app_target_messenger.xml
deleted file mode 100644
index 278eb5c..0000000
--- a/res/xml/app_target_messenger.xml
+++ /dev/null
@@ -1,26 +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.
--->
-
-<resolve xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3" >
-
-    <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MESSAGING;end" />
-    <favorite launcher:uri="sms:" />
-    <favorite launcher:uri="smsto:" />
-    <favorite launcher:uri="mms:" />
-    <favorite launcher:uri="mmsto:" />
-
-</resolve>
\ No newline at end of file
diff --git a/res/xml/app_target_phone.xml b/res/xml/app_target_phone.xml
deleted file mode 100644
index 5d6ca31..0000000
--- a/res/xml/app_target_phone.xml
+++ /dev/null
@@ -1,24 +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.
--->
-
-<resolve xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3" >
-
-    <favorite launcher:uri="#Intent;action=android.intent.action.DIAL;end" />
-    <favorite launcher:uri="tel:123" />
-    <favorite launcher:uri="#Intent;action=android.intent.action.CALL_BUTTON;end" />
-
-</resolve>
\ No newline at end of file
diff --git a/res/xml/launcher_preferences.xml b/res/xml/launcher_preferences.xml
index 624d9eb..301bef1 100644
--- a/res/xml/launcher_preferences.xml
+++ b/res/xml/launcher_preferences.xml
@@ -23,4 +23,21 @@
             android:persistent="true"
     />
 
+    <SwitchPreference
+        android:key="pref_add_icon_to_home"
+        android:title="@string/auto_add_shortcuts_label"
+        android:summary="@string/auto_add_shortcuts_description"
+        android:defaultValue="true"
+        android:persistent="true"
+        />
+
+    <ListPreference
+        android:key="pref_override_icon_shape"
+        android:title="@string/icon_shape_override_label"
+        android:summary="%s"
+        android:entries="@array/icon_shape_override_paths_names"
+        android:entryValues="@array/icon_shape_override_paths_values"
+        android:defaultValue=""
+        android:persistent="false" />
+
 </PreferenceScreen>
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
new file mode 100644
index 0000000..597e937
--- /dev/null
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -0,0 +1,146 @@
+/*
+ * 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.annotation.SuppressLint;
+import android.content.Context;
+import android.support.annotation.IntDef;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+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_POPUP_CONTAINER_WITH_ARROW,
+            TYPE_WIDGETS_BOTTOM_SHEET
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface FloatingViewType {}
+    public static final int TYPE_FOLDER = 1 << 0;
+    public static final int TYPE_POPUP_CONTAINER_WITH_ARROW = 1 << 1;
+    public static final int TYPE_WIDGETS_BOTTOM_SHEET = 1 << 2;
+
+    protected boolean mIsOpen;
+
+    public AbstractFloatingView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public AbstractFloatingView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    /**
+     * 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;
+    }
+
+    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 void onWidgetsBound() {
+    }
+
+    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;
+    }
+
+    public 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_POPUP_CONTAINER_WITH_ARROW
+                | TYPE_WIDGETS_BOTTOM_SHEET);
+    }
+
+    public abstract int getLogContainerType();
+}
diff --git a/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/AllAppsList.java
index c431593..5b42cad 100644
--- a/src/com/android/launcher3/AllAppsList.java
+++ b/src/com/android/launcher3/AllAppsList.java
@@ -18,12 +18,12 @@
 
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.pm.LauncherActivityInfo;
+import android.os.UserHandle;
 
-import com.android.launcher3.compat.LauncherActivityInfoCompat;
 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. */
@@ -65,13 +65,15 @@
      *
      * If the app is already in the list, doesn't add it.
      */
-    public void add(AppInfo info) {
-        if (mAppFilter != null && !mAppFilter.shouldShowApp(info.componentName)) {
+    public void add(AppInfo info, LauncherActivityInfo activityInfo) {
+        if (!mAppFilter.shouldShowApp(info.componentName)) {
             return;
         }
         if (findActivity(data, info.componentName, info.user)) {
             return;
         }
+        mIconCache.getTitleAndIcon(info, activityInfo, true /* useLowResIcon */);
+
         data.add(info);
         added.add(info);
     }
@@ -95,25 +97,24 @@
     /**
      * Add the icons for the supplied apk called packageName.
      */
-    public void addPackage(Context context, String packageName, UserHandleCompat user) {
+    public void addPackage(Context context, String packageName, UserHandle user) {
         final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
-        final List<LauncherActivityInfoCompat> matches = launcherApps.getActivityList(packageName,
+        final List<LauncherActivityInfo> matches = launcherApps.getActivityList(packageName,
                 user);
 
-        for (LauncherActivityInfoCompat info : matches) {
-            add(new AppInfo(context, info, user, mIconCache));
+        for (LauncherActivityInfo info : matches) {
+            add(new AppInfo(context, info, user), info);
         }
     }
 
     /**
      * Remove the apps for the given apk identified by packageName.
      */
-    public void removePackage(String packageName, UserHandleCompat user) {
+    public void removePackage(String packageName, UserHandle user) {
         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,21 +122,20 @@
     }
 
     /**
-     * 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);
             }
         }
     }
 
-    public void updateIconsAndLabels(HashSet<String> packages, UserHandleCompat user,
+    public void updateIconsAndLabels(HashSet<String> packages, UserHandle user,
             ArrayList<AppInfo> outUpdates) {
         for (AppInfo info : data) {
             if (info.user.equals(user) && packages.contains(info.componentName.getPackageName())) {
@@ -148,19 +148,18 @@
     /**
      * Add and remove icons for this package which has been updated.
      */
-    public void updatePackage(Context context, String packageName, UserHandleCompat user) {
+    public void updatePackage(Context context, String packageName, UserHandle user) {
         final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
-        final List<LauncherActivityInfoCompat> matches = launcherApps.getActivityList(packageName,
+        final List<LauncherActivityInfo> matches = launcherApps.getActivityList(packageName,
                 user);
         if (matches.size() > 0) {
             // Find disabled/removed activities and remove them from data and add them
             // 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);
                     }
@@ -169,12 +168,12 @@
 
             // Find enabled activities and add them to the adapter
             // Also updates existing activities with new labels/icons
-            for (final LauncherActivityInfoCompat info : matches) {
+            for (final LauncherActivityInfo info : matches) {
                 AppInfo applicationInfo = findApplicationInfoLocked(
                         info.getComponentName().getPackageName(), user,
                         info.getComponentName().getClassName());
                 if (applicationInfo == null) {
-                    add(new AppInfo(context, info, user, mIconCache));
+                    add(new AppInfo(context, info, user), info);
                 } else {
                     mIconCache.getTitleAndIcon(applicationInfo, info, true /* useLowResIcon */);
                     modified.add(applicationInfo);
@@ -184,11 +183,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);
                 }
             }
@@ -199,9 +197,9 @@
     /**
      * Returns whether <em>apps</em> contains <em>component</em>.
      */
-    private static boolean findActivity(List<LauncherActivityInfoCompat> apps,
+    private static boolean findActivity(List<LauncherActivityInfo> apps,
             ComponentName component) {
-        for (LauncherActivityInfoCompat info : apps) {
+        for (LauncherActivityInfo info : apps) {
             if (info.getComponentName().equals(component)) {
                 return true;
             }
@@ -210,20 +208,10 @@
     }
 
     /**
-     * Query the launcher apps service for whether the supplied package has
-     * MAIN/LAUNCHER activities in the supplied package.
-     */
-    static boolean packageHasActivities(Context context, String packageName,
-            UserHandleCompat user) {
-        final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
-        return launcherApps.getActivityList(packageName, user).size() > 0;
-    }
-
-    /**
      * Returns whether <em>apps</em> contains <em>component</em>.
      */
     private static boolean findActivity(ArrayList<AppInfo> apps, ComponentName component,
-            UserHandleCompat user) {
+            UserHandle user) {
         final int N = apps.size();
         for (int i = 0; i < N; i++) {
             final AppInfo info = apps.get(i);
@@ -237,12 +225,11 @@
     /**
      * Find an ApplicationInfo object for the given packageName and className.
      */
-    private AppInfo findApplicationInfoLocked(String packageName, UserHandleCompat user,
+    private AppInfo findApplicationInfoLocked(String packageName, UserHandle 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
deleted file mode 100644
index 0e18874..0000000
--- a/src/com/android/launcher3/AnotherWindowDropTarget.java
+++ /dev/null
@@ -1,64 +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.content.Context;
-import android.graphics.PointF;
-import android.graphics.Rect;
-
-/**
- * Drop target used when another window (i.e. another process) has accepted a global system drag.
- * If the accepted item was a shortcut, we delete it from Launcher.
- */
-public class AnotherWindowDropTarget implements DropTarget {
-    final Launcher mLauncher;
-
-    public AnotherWindowDropTarget (Context context) { mLauncher = (Launcher) context; }
-
-    @Override
-    public boolean isDropEnabled() { return true; }
-
-    @Override
-    public void onDrop(DragObject dragObject) {
-        dragObject.deferDragViewCleanupPostAnimation = false;
-        LauncherModel.deleteItemFromDatabase(mLauncher, (ShortcutInfo) dragObject.dragInfo);
-    }
-
-    @Override
-    public void onDragEnter(DragObject dragObject) {}
-
-    @Override
-    public void onDragOver(DragObject dragObject) {}
-
-    @Override
-    public void onDragExit(DragObject dragObject) {}
-
-    @Override
-    public void onFlingToDelete(DragObject dragObject, PointF vec) {}
-
-    @Override
-    public boolean acceptDrop(DragObject dragObject) {
-        return dragObject.dragInfo instanceof ShortcutInfo;
-    }
-
-    @Override
-    public void prepareAccessibilityDrop() {}
-
-    // These methods are implemented in Views
-    @Override
-    public void getHitRectRelativeToDragLayer(Rect outRect) {}
-}
diff --git a/src/com/android/launcher3/AppFilter.java b/src/com/android/launcher3/AppFilter.java
index e01436d..db8f5dd 100644
--- a/src/com/android/launcher3/AppFilter.java
+++ b/src/com/android/launcher3/AppFilter.java
@@ -1,35 +1,10 @@
 package com.android.launcher3;
 
 import android.content.ComponentName;
-import android.text.TextUtils;
-import android.util.Log;
 
-public abstract class AppFilter {
+public class AppFilter {
 
-    private static final boolean DBG = false;
-    private static final String TAG = "AppFilter";
-
-    public abstract boolean shouldShowApp(ComponentName app);
-
-    public static AppFilter loadByName(String className) {
-        if (TextUtils.isEmpty(className)) return null;
-        if (DBG) Log.d(TAG, "Loading AppFilter: " + className);
-        try {
-            Class<?> cls = Class.forName(className);
-            return (AppFilter) cls.newInstance();
-        } catch (ClassNotFoundException e) {
-            Log.e(TAG, "Bad AppFilter class", e);
-            return null;
-        } catch (InstantiationException e) {
-            Log.e(TAG, "Bad AppFilter class", e);
-            return null;
-        } catch (IllegalAccessException e) {
-            Log.e(TAG, "Bad AppFilter class", e);
-            return null;
-        } catch (ClassCastException e) {
-            Log.e(TAG, "Bad AppFilter class", e);
-            return null;
-        }
+    public boolean shouldShowApp(ComponentName app) {
+        return true;
     }
-
 }
diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/AppInfo.java
index 4c4d67c..9ec26e2 100644
--- a/src/com/android/launcher3/AppInfo.java
+++ b/src/com/android/launcher3/AppInfo.java
@@ -19,51 +19,32 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.graphics.Bitmap;
-import android.util.Log;
+import android.content.pm.LauncherActivityInfo;
+import android.os.UserHandle;
 
-import com.android.launcher3.compat.LauncherActivityInfoCompat;
-import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.PackageManagerHelper;
 
-import java.util.ArrayList;
-
 /**
  * Represents an app in AllAppsView.
  */
-public class AppInfo extends ItemInfo {
+public class AppInfo extends ItemInfoWithIcon {
 
     /**
      * The intent used to start the application.
      */
     public Intent intent;
 
-    /**
-     * A bitmap version of the application icon.
-     */
-    public Bitmap iconBitmap;
-
-    /**
-     * Indicates whether we're using a low res icon
-     */
-    boolean usingLowResIcon;
-
     public ComponentName componentName;
 
-    static final int DOWNLOADED_FLAG = 1;
-    static final int UPDATED_SYSTEM_APP_FLAG = 2;
-
-    int flags = 0;
-
     /**
      * {@see ShortcutInfo#isDisabled}
      */
-    int isDisabled = ShortcutInfo.DEFAULT;
+    public int isDisabled = ShortcutInfo.DEFAULT;
 
     public AppInfo() {
-        itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT;
+        itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
     }
 
     @Override
@@ -71,24 +52,17 @@
         return intent;
     }
 
-    protected Intent getRestoredIntent() {
-        return null;
-    }
-
     /**
      * Must not hold the Context.
      */
-    public AppInfo(Context context, LauncherActivityInfoCompat info, UserHandleCompat user,
-            IconCache iconCache) {
-        this(context, info, user, iconCache,
-                UserManagerCompat.getInstance(context).isQuietModeEnabled(user));
+    public AppInfo(Context context, LauncherActivityInfo info, UserHandle user) {
+        this(info, user, UserManagerCompat.getInstance(context).isQuietModeEnabled(user));
     }
 
-    public AppInfo(Context context, LauncherActivityInfoCompat info, UserHandleCompat user,
-            IconCache iconCache, boolean quietModeEnabled) {
+    public AppInfo(LauncherActivityInfo info, UserHandle user, boolean quietModeEnabled) {
         this.componentName = info.getComponentName();
         this.container = ItemInfo.NO_ID;
-        flags = initFlags(info);
+        this.user = user;
         if (PackageManagerHelper.isAppSuspended(info.getApplicationInfo())) {
             isDisabled |= ShortcutInfo.FLAG_DISABLED_SUSPENDED;
         }
@@ -96,22 +70,7 @@
             isDisabled |= ShortcutInfo.FLAG_DISABLED_QUIET_USER;
         }
 
-        iconCache.getTitleAndIcon(this, info, true /* useLowResIcon */);
-        intent = makeLaunchIntent(context, info, user);
-        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;
+        intent = makeLaunchIntent(info);
     }
 
     public AppInfo(AppInfo info) {
@@ -119,9 +78,7 @@
         componentName = info.componentName;
         title = Utilities.trim(info.title);
         intent = new Intent(info.intent);
-        flags = info.flags;
         isDisabled = info.isDisabled;
-        iconBitmap = info.iconBitmap;
     }
 
     @Override
@@ -129,17 +86,6 @@
         return super.dumpProperties() + " componentName=" + componentName;
     }
 
-    /**
-     * Helper method used for debugging.
-     */
-    public static void dumpApplicationInfoList(String tag, String label, ArrayList<AppInfo> list) {
-        Log.d(tag, label + " size=" + list.size());
-        for (AppInfo info: list) {
-            Log.d(tag, "   title=\"" + info.title + "\" iconBitmap=" + info.iconBitmap
-                    + " componentName=" + info.componentName.getPackageName());
-        }
-    }
-
     public ShortcutInfo makeShortcut() {
         return new ShortcutInfo(this);
     }
@@ -148,14 +94,11 @@
         return new ComponentKey(componentName, user);
     }
 
-    public static Intent makeLaunchIntent(Context context, LauncherActivityInfoCompat info,
-            UserHandleCompat user) {
-        long serialNumber = UserManagerCompat.getInstance(context).getSerialNumberForUser(user);
+    public static Intent makeLaunchIntent(LauncherActivityInfo info) {
         return new Intent(Intent.ACTION_MAIN)
             .addCategory(Intent.CATEGORY_LAUNCHER)
             .setComponent(info.getComponentName())
-            .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
-            .putExtra(EXTRA_PROFILE, serialNumber);
+            .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
     }
 
     @Override
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index cd27b4c..1a405f9 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,91 @@
         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);
+
+        // When the widget resizes in multi-window mode, the translation value changes to maintain
+        // a center fit. These overrides ensure the resize frame always aligns with the widget view.
+        getSnappedRectRelativeToDragLayer(sTmpRect);
+        if (mLeftBorderActive) {
+            lp.width = sTmpRect.width() + sTmpRect.left - lp.x;
+        }
+        if (mTopBorderActive) {
+            lp.height = sTmpRect.height() + sTmpRect.top - lp.y;
+        }
+        if (mRightBorderActive) {
+            lp.x = sTmpRect.left;
+        }
+        if (mBottomBorderActive) {
+            lp.y = sTmpRect.top;
+        }
+
+        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 +258,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;
@@ -365,7 +320,7 @@
 
     public static Rect getWidgetSizeRanges(Context context, int spanX, int spanY, Rect rect) {
         if (sCellSize == null) {
-            InvariantDeviceProfile inv = LauncherAppState.getInstance().getInvariantDeviceProfile();
+            InvariantDeviceProfile inv = LauncherAppState.getIDP(context);
 
             // Initiate cell sizes.
             sCellSize = new Point[2];
@@ -398,12 +353,12 @@
         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; 
+        mDeltaXAddOn = mRunningHInc * xThreshold;
+        mDeltaYAddOn = mRunningVInc * yThreshold;
         mDeltaX = 0;
         mDeltaY = 0;
 
@@ -415,19 +370,35 @@
         });
     }
 
+    /**
+     * Returns the rect of this view when the frame is snapped around the widget, with the bounds
+     * relative to the {@link DragLayer}.
+     */
+    private void getSnappedRectRelativeToDragLayer(Rect out) {
+        float scale = mWidgetView.getScaleToFit();
+
+        mDragLayer.getViewRectRelativeToSelf(mWidgetView, out);
+
+        int width = 2 * mBackgroundPadding
+                + (int) (scale * (out.width() - mWidgetPadding.left - mWidgetPadding.right));
+        int height = 2 * mBackgroundPadding
+                + (int) (scale * (out.height() - mWidgetPadding.top - mWidgetPadding.bottom));
+
+        int x = (int) (out.left - mBackgroundPadding + scale * mWidgetPadding.left);
+        int y = (int) (out.top - mBackgroundPadding + scale * mWidgetPadding.top);
+
+        out.left = x;
+        out.top = y;
+        out.right = out.left + width;
+        out.bottom = out.top + height;
+    }
+
     public void snapToWidget(boolean animate) {
-        final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
-        int newWidth = mWidgetView.getWidth() + 2 * mBackgroundPadding
-                - mWidgetPadding.left - mWidgetPadding.right;
-        int newHeight = mWidgetView.getHeight() + 2 * mBackgroundPadding
-                - mWidgetPadding.top - mWidgetPadding.bottom;
-
-        mTmpPt[0] = mWidgetView.getLeft();
-        mTmpPt[1] = mWidgetView.getTop();
-        mDragLayer.getDescendantCoordRelativeToSelf(mCellLayout.getShortcutsAndWidgets(), mTmpPt);
-
-        int newX = mTmpPt[0] - mBackgroundPadding + mWidgetPadding.left;
-        int newY = mTmpPt[1] - mBackgroundPadding + mWidgetPadding.top;
+        getSnappedRectRelativeToDragLayer(sTmpRect);
+        int newWidth = sTmpRect.width();
+        int newHeight = sTmpRect.height();
+        int newX = sTmpRect.left;
+        int newY = sTmpRect.top;
 
         // We need to make sure the frame's touchable regions lie fully within the bounds of the
         // DragLayer. We allow the actual handles to be clipped, but we shift the touch regions
@@ -445,15 +416,15 @@
             mBottomTouchRegionAdjustment = 0;
         }
 
+        final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
         if (!animate) {
             lp.width = newWidth;
             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 +434,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 +457,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 (out.start < 0) {
+                out.start = 0;
+            }
+            if (out.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/AppWidgetsRestoredReceiver.java b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
index c5b3104..84a8bce 100644
--- a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
+++ b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
@@ -5,25 +5,33 @@
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
-import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
 import android.database.Cursor;
+import android.os.Handler;
 import android.util.Log;
 
 import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.util.ContentWriter;
 
 public class AppWidgetsRestoredReceiver extends BroadcastReceiver {
 
     private static final String TAG = "AWRestoredReceiver";
 
     @Override
-    public void onReceive(Context context, Intent intent) {
+    public void onReceive(final Context context, Intent intent) {
         if (AppWidgetManager.ACTION_APPWIDGET_HOST_RESTORED.equals(intent.getAction())) {
-            int[] oldIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_OLD_IDS);
-            int[] newIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
+            final int[] oldIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_OLD_IDS);
+            final int[] newIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
             if (oldIds.length == newIds.length) {
-                restoreAppWidgetIds(context, oldIds, newIds);
+                final PendingResult asyncResult = goAsync();
+                new Handler(LauncherModel.getWorkerLooper())
+                        .postAtFrontOfQueue(new Runnable() {
+                            @Override
+                            public void run() {
+                                restoreAppWidgetIds(context, asyncResult, oldIds, newIds);
+                            }
+                        });
             } else {
                 Log.e(TAG, "Invalid host restored received");
             }
@@ -33,7 +41,8 @@
     /**
      * Updates the app widgets whose id has changed during the restore process.
      */
-    static void restoreAppWidgetIds(Context context, int[] oldWidgetIds, int[] newWidgetIds) {
+    static void restoreAppWidgetIds(Context context, PendingResult asyncResult,
+            int[] oldWidgetIds, int[] newWidgetIds) {
         final ContentResolver cr = context.getContentResolver();
         final AppWidgetManager widgets = AppWidgetManager.getInstance(context);
         AppWidgetHost appWidgetHost = new AppWidgetHost(context, Launcher.APPWIDGET_HOST_ID);
@@ -50,14 +59,13 @@
                 state = LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
             }
 
-            ContentValues values = new ContentValues();
-            values.put(LauncherSettings.Favorites.APPWIDGET_ID, newWidgetIds[i]);
-            values.put(LauncherSettings.Favorites.RESTORED, state);
-
             String[] widgetIdParams = new String[] { Integer.toString(oldWidgetIds[i]) };
+            int result = new ContentWriter(context, new ContentWriter.CommitParams(
+                    "appWidgetId=? and (restored & 1) = 1", widgetIdParams))
+                    .put(LauncherSettings.Favorites.APPWIDGET_ID, newWidgetIds[i])
+                    .put(LauncherSettings.Favorites.RESTORED, state)
+                    .commit();
 
-            int result = cr.update(Favorites.CONTENT_URI, values,
-                    "appWidgetId=? and (restored & 1) = 1", widgetIdParams);
             if (result == 0) {
                 Cursor cursor = cr.query(Favorites.CONTENT_URI,
                         new String[] {Favorites.APPWIDGET_ID},
@@ -75,7 +83,8 @@
 
         LauncherAppState app = LauncherAppState.getInstanceNoCreate();
         if (app != null) {
-            app.reloadWorkspace();
+            app.getModel().forceReload();
         }
+        asyncResult.finish();
     }
 }
diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java
index d5309b4..c4086a8 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;
@@ -79,7 +79,7 @@
 
     static AutoInstallsLayout get(Context context, String pkg, Resources targetRes,
             AppWidgetHost appWidgetHost, LayoutParserCallback callback) {
-        InvariantDeviceProfile grid = LauncherAppState.getInstance().getInvariantDeviceProfile();
+        InvariantDeviceProfile grid = LauncherAppState.getIDP(context);
 
         // Try with grid size and hotseat count
         String layoutName = String.format(Locale.ENGLISH, FORMATTED_LAYOUT_RES_WITH_HOSTEAT,
@@ -182,7 +182,7 @@
         mSourceRes = res;
         mLayoutId = layoutId;
 
-        mIdp = LauncherAppState.getInstance().getInvariantDeviceProfile();
+        mIdp = LauncherAppState.getIDP(context);
         mRowCount = mIdp.numRows;
         mColumnCount = mIdp.numColumns;
     }
@@ -195,7 +195,7 @@
         try {
             return parseLayout(mLayoutId, screenIds);
         } catch (Exception e) {
-            Log.w(TAG, "Got exception parsing layout.", e);
+            Log.e(TAG, "Error parsing layout: " + e);
             return -1;
         }
     }
@@ -362,7 +362,7 @@
                     return addShortcut(info.loadLabel(mPackageManager).toString(),
                             intent, Favorites.ITEM_TYPE_APPLICATION);
                 } catch (PackageManager.NameNotFoundException e) {
-                    Log.e(TAG, "Unable to add favorite: " + packageName + "/" + className, e);
+                    Log.e(TAG, "Favorite not found: " + packageName + "/" + className);
                 }
                 return -1;
             } else {
@@ -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/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
new file mode 100644
index 0000000..410d590
--- /dev/null
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.view.View.AccessibilityDelegate;
+
+import com.android.launcher3.logging.UserEventDispatcher;
+
+public abstract class BaseActivity extends Activity {
+
+    protected DeviceProfile mDeviceProfile;
+    protected UserEventDispatcher mUserEventDispatcher;
+
+    public DeviceProfile getDeviceProfile() {
+        return mDeviceProfile;
+    }
+
+    public AccessibilityDelegate getAccessibilityDelegate() {
+        return null;
+    }
+
+    public final UserEventDispatcher getUserEventDispatcher() {
+        if (mUserEventDispatcher == null) {
+            mUserEventDispatcher = UserEventDispatcher.newInstance(this,
+                    isInMultiWindowModeCompat());
+        }
+        return mUserEventDispatcher;
+    }
+
+    public boolean isInMultiWindowModeCompat() {
+        return Utilities.ATLEAST_NOUGAT && isInMultiWindowMode();
+    }
+
+    public static BaseActivity fromContext(Context context) {
+        if (context instanceof BaseActivity) {
+            return (BaseActivity) context;
+        }
+        return ((BaseActivity) ((ContextWrapper) context).getBaseContext());
+    }
+}
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..5feb42e 100644
--- a/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
+++ b/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
@@ -15,54 +15,67 @@
  */
 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 android.widget.TextView;
 
-import com.android.launcher3.util.Thunk;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.util.Themes;
 
 /**
  * The track and scrollbar that shows when you scroll the list.
  */
 public class BaseRecyclerViewFastScrollBar {
 
-    public interface FastScrollFocusableView {
-        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 +83,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(Themes.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 +122,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 +196,12 @@
         switch (action) {
             case MotionEvent.ACTION_DOWN:
                 if (isNearThumb(downX, downY)) {
-                    mTouchOffset = downY - mThumbOffset.y;
+                    mTouchOffsetY = downY - mThumbOffsetY;
+                } else if (FeatureFlags.LAUNCHER3_DIRECT_SCROLL
+                        && mRv.supportsFastScrolling()
+                        && isNearScrollBar(downX)) {
+                    calcTouchOffsetAndPrepToFastScroll(downY, lastY);
+                    updateFastScrollSectionNameAndThumbOffset(lastY, y);
                 }
                 break;
             case MotionEvent.ACTION_MOVE:
@@ -186,112 +211,113 @@
                 if (!mIsDragging && !mIgnoreDragGesture && mRv.supportsFastScrolling() &&
                         isNearThumb(downX, lastY) &&
                         Math.abs(y - downY) > config.getScaledTouchSlop()) {
-                    mRv.getParent().requestDisallowInterceptTouchEvent(true);
-                    mIsDragging = true;
-                    if (mCanThumbDetach) {
-                        mIsThumbDetached = true;
-                    }
-                    mTouchOffset += (lastY - downY);
-                    mPopup.animateVisibility(true);
-                    showActiveScrollbar(true);
+                    calcTouchOffsetAndPrepToFastScroll(downY, lastY);
                 }
                 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));
-                    mLastTouchY = boundedY;
-                    setThumbOffset(mRv.getScrollBarX(), (int) mLastTouchY);
+                    updateFastScrollSectionNameAndThumbOffset(lastY, y);
                 }
                 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;
         }
     }
 
+    private void calcTouchOffsetAndPrepToFastScroll(int downY, int lastY) {
+        mRv.getParent().requestDisallowInterceptTouchEvent(true);
+        mIsDragging = true;
+        if (mCanThumbDetach) {
+            mIsThumbDetached = true;
+        }
+        mTouchOffsetY += (lastY - downY);
+        animatePopupVisibility(true);
+        showActiveScrollbar(true);
+    }
+
+    private void updateFastScrollSectionNameAndThumbOffset(int lastY, int y) {
+        // Update the fastscroller section name at this touch position
+        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;
+        setThumbOffsetY((int) mLastTouchY);
+    }
+
     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();
+        mWidthAnimator = ObjectAnimator.ofInt(this, TRACK_WIDTH,
+                isScrolling ? mMaxWidth : mMinWidth);
+        mWidthAnimator.setDuration(SCROLL_BAR_VIS_DURATION);
+        mWidthAnimator.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();
-    }
-
-    /**
-     * Returns whether the specified points are near the scroll bar bounds.
+     * Returns whether the specified point is inside the thumb 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);
     }
+
+    /**
+     * Returns whether the specified x position is near the scroll bar.
+     */
+    public boolean isNearScrollBar(int x) {
+        int left = getDrawLeft();
+        return x >= left && x <= left + mMaxWidth;
+    }
+
+    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..cb40d3d 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -16,21 +16,20 @@
 
 package com.android.launcher3;
 
-import android.annotation.TargetApi;
+import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
-import android.content.res.Resources.Theme;
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.Rect;
 import android.graphics.Region;
-import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
-import android.os.Build;
 import android.util.AttributeSet;
-import android.util.SparseArray;
+import android.util.Property;
 import android.util.TypedValue;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -41,7 +40,14 @@
 import android.widget.TextView;
 
 import com.android.launcher3.IconCache.IconLoadRequest;
+import com.android.launcher3.IconCache.ItemInfoUpdateReceiver;
+import com.android.launcher3.badge.BadgeInfo;
+import com.android.launcher3.badge.BadgeRenderer;
 import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.graphics.DrawableFactory;
+import com.android.launcher3.graphics.HolographicOutlineHelper;
+import com.android.launcher3.graphics.IconPalette;
+import com.android.launcher3.graphics.PreloadIconDrawable;
 import com.android.launcher3.model.PackageItemInfo;
 
 import java.text.NumberFormat;
@@ -51,10 +57,7 @@
  * because we want to make the bubble taller than the text and TextView's clip is
  * too aggressive.
  */
-public class BubbleTextView extends TextView
-        implements BaseRecyclerViewFastScrollBar.FastScrollFocusableView {
-
-    private static SparseArray<Theme> sPreloaderThemes = new SparseArray<Theme>(2);
+public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver {
 
     // Dimensions in DP
     private static final float AMBIENT_SHADOW_RADIUS = 2.5f;
@@ -67,6 +70,8 @@
     private static final int DISPLAY_ALL_APPS = 1;
     private static final int DISPLAY_FOLDER = 2;
 
+    private static final int[] STATE_PRESSED = new int[] {android.R.attr.state_pressed};
+
     private final Launcher mLauncher;
     private Drawable mIcon;
     private final boolean mCenterVertically;
@@ -89,6 +94,28 @@
     @ViewDebug.ExportedProperty(category = "launcher")
     private int mTextColor;
 
+    private BadgeInfo mBadgeInfo;
+    private BadgeRenderer mBadgeRenderer;
+    private IconPalette mIconPalette;
+    private float mBadgeScale;
+    private boolean mForceHideBadge;
+    private Point mTempSpaceForBadgeOffset = new Point();
+    private Rect mTempIconBounds = new Rect();
+
+    private static final Property<BubbleTextView, Float> BADGE_SCALE_PROPERTY
+            = new Property<BubbleTextView, Float>(Float.TYPE, "badgeScale") {
+        @Override
+        public Float get(BubbleTextView bubbleTextView) {
+            return bubbleTextView.mBadgeScale;
+        }
+
+        @Override
+        public void set(BubbleTextView bubbleTextView, Float value) {
+            bubbleTextView.mBadgeScale = value;
+            bubbleTextView.invalidate();
+        }
+    };
+
     @ViewDebug.ExportedProperty(category = "launcher")
     private boolean mStayPressed;
     @ViewDebug.ExportedProperty(category = "launcher")
@@ -113,7 +140,7 @@
 
         TypedArray a = context.obtainStyledAttributes(attrs,
                 R.styleable.BubbleTextView, defStyle, 0);
-        mCustomShadowsEnabled = a.getBoolean(R.styleable.BubbleTextView_customShadows, true);
+        mCustomShadowsEnabled = a.getBoolean(R.styleable.BubbleTextView_customShadows, false);
         mLayoutHorizontal = a.getBoolean(R.styleable.BubbleTextView_layoutHorizontal, false);
         mDeferShadowGenerationOnTouch =
                 a.getBoolean(R.styleable.BubbleTextView_deferShadowGeneration, false);
@@ -127,7 +154,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,21 +179,22 @@
         mLongPressHelper = new CheckLongPressHelper(this);
         mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
 
-        mOutlineHelper = HolographicOutlineHelper.obtain(getContext());
+        mOutlineHelper = HolographicOutlineHelper.getInstance(getContext());
         setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
     }
 
-    public void applyFromShortcutInfo(ShortcutInfo info, IconCache iconCache) {
-        applyFromShortcutInfo(info, iconCache, false);
+    public void applyFromShortcutInfo(ShortcutInfo info) {
+        applyFromShortcutInfo(info, false);
     }
 
-    public void applyFromShortcutInfo(ShortcutInfo info, IconCache iconCache,
-            boolean promiseStateChanged) {
-        applyIconAndLabel(info.getIcon(iconCache), info);
+    public void applyFromShortcutInfo(ShortcutInfo info, boolean promiseStateChanged) {
+        applyIconAndLabel(info.iconBitmap, info);
         setTag(info);
         if (promiseStateChanged || info.isPromise()) {
-            applyState(promiseStateChanged);
+            applyPromiseState(promiseStateChanged);
         }
+
+        applyBadgeState(info, false /* animate */);
     }
 
     public void applyFromApplicationInfo(AppInfo info) {
@@ -175,6 +205,8 @@
 
         // Verify high res immediately
         verifyHighRes();
+
+        applyBadgeState(info, false /* animate */);
     }
 
     public void applyFromPackageItemInfo(PackageItemInfo info) {
@@ -187,10 +219,8 @@
     }
 
     private void applyIconAndLabel(Bitmap icon, ItemInfo info) {
-        FastBitmapDrawable iconDrawable = mLauncher.createIconDrawable(icon);
-        if (info.isDisabled()) {
-            iconDrawable.setState(FastBitmapDrawable.State.DISABLED);
-        }
+        FastBitmapDrawable iconDrawable = DrawableFactory.get(getContext()).newIcon(icon, info);
+        iconDrawable.setIsDisabled(info.isDisabled());
         setIcon(iconDrawable);
         setText(info.title);
         if (info.contentDescription != null) {
@@ -201,15 +231,6 @@
     }
 
     /**
-     * Used for measurement only, sets some dummy values on this view.
-     */
-    public void applyDummyInfo() {
-        ColorDrawable d = new ColorDrawable();
-        setIcon(mLauncher.resizeIconDrawable(d));
-        setText("");
-    }
-
-    /**
      * Overrides the default long press timeout.
      */
     public void setLongPressTimeout(int longPressTimeout) {
@@ -238,14 +259,21 @@
     }
 
     @Override
-    public void setPressed(boolean pressed) {
-        super.setPressed(pressed);
-
+    public void refreshDrawableState() {
         if (!mIgnorePressedStateChange) {
-            updateIconState();
+            super.refreshDrawableState();
         }
     }
 
+    @Override
+    protected int[] onCreateDrawableState(int extraSpace) {
+        final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
+        if (mStayPressed) {
+            mergeDrawableStates(drawableState, STATE_PRESSED);
+        }
+        return drawableState;
+    }
+
     /** Returns the icon for this view. */
     public Drawable getIcon() {
         return mIcon;
@@ -256,20 +284,6 @@
         return mLayoutHorizontal;
     }
 
-    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) {
-                d.animateState(FastBitmapDrawable.State.PRESSED);
-            } else {
-                d.animateState(FastBitmapDrawable.State.NORMAL);
-            }
-        }
-    }
-
     @Override
     public void setOnLongClickListener(OnLongClickListener l) {
         super.setOnLongClickListener(l);
@@ -328,7 +342,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) {
@@ -343,7 +357,7 @@
                     this, mPressedBackground);
         }
 
-        updateIconState();
+        refreshDrawableState();
     }
 
     void clearPressedBackground() {
@@ -373,7 +387,7 @@
 
         mPressedBackground = null;
         mIgnorePressedStateChange = false;
-        updateIconState();
+        refreshDrawableState();
         return result;
     }
 
@@ -381,6 +395,7 @@
     public void draw(Canvas canvas) {
         if (!mCustomShadowsEnabled) {
             super.draw(canvas);
+            drawBadgeIfNecessary(canvas);
             return;
         }
 
@@ -404,9 +419,10 @@
         }
 
         // If text is transparent, don't draw any shadow
-        if (getCurrentTextColor() == getResources().getColor(android.R.color.transparent)) {
+        if ((getCurrentTextColor() >> 24) == 0) {
             getPaint().clearShadowLayer();
             super.draw(canvas);
+            drawBadgeIfNecessary(canvas);
             return;
         }
 
@@ -422,6 +438,50 @@
                 density * KEY_SHADOW_RADIUS, 0.0f, density * KEY_SHADOW_OFFSET, KEY_SHADOW_COLOR);
         super.draw(canvas);
         canvas.restore();
+
+        drawBadgeIfNecessary(canvas);
+    }
+
+    /**
+     * Draws the icon badge in the top right corner of the icon bounds.
+     * @param canvas The canvas to draw to.
+     */
+    private void drawBadgeIfNecessary(Canvas canvas) {
+        if (!mForceHideBadge && (hasBadge() || mBadgeScale > 0)) {
+            getIconBounds(mTempIconBounds);
+            mTempSpaceForBadgeOffset.set((getWidth() - mIconSize) / 2, getPaddingTop());
+            final int scrollX = getScrollX();
+            final int scrollY = getScrollY();
+            canvas.translate(scrollX, scrollY);
+            mBadgeRenderer.draw(canvas, mIconPalette, mBadgeInfo, mTempIconBounds, mBadgeScale,
+                    mTempSpaceForBadgeOffset);
+            canvas.translate(-scrollX, -scrollY);
+        }
+    }
+
+    public void forceHideBadge(boolean forceHideBadge) {
+        if (mForceHideBadge == forceHideBadge) {
+            return;
+        }
+        mForceHideBadge = forceHideBadge;
+
+        if (forceHideBadge) {
+            invalidate();
+        } else if (hasBadge()) {
+            ObjectAnimator.ofFloat(this, BADGE_SCALE_PROPERTY, 0, 1).start();
+        }
+    }
+
+    private boolean hasBadge() {
+        return mBadgeInfo != null;
+    }
+
+    public void getIconBounds(Rect outBounds) {
+        int top = getPaddingTop();
+        int left = (getWidth() - mIconSize) / 2;
+        int right = left + mIconSize;
+        int bottom = top + mIconSize;
+        outBounds.set(left, top, right, bottom);
     }
 
     @Override
@@ -429,10 +489,6 @@
         super.onAttachedToWindow();
 
         if (mBackground != null) mBackground.setCallback(this);
-
-        if (mIcon instanceof PreloadIconDrawable) {
-            ((PreloadIconDrawable) mIcon).applyPreloaderTheme(getPreloaderTheme());
-        }
         mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
     }
 
@@ -483,7 +539,7 @@
         mLongPressHelper.cancelLongPress();
     }
 
-    public void applyState(boolean promiseStateChanged) {
+    public void applyPromiseState(boolean promiseStateChanged) {
         if (getTag() instanceof ShortcutInfo) {
             ShortcutInfo info = (ShortcutInfo) getTag();
             final boolean isPromise = info.isPromise();
@@ -492,8 +548,8 @@
                             info.getInstallProgress() : 0)) : 100;
 
             setContentDescription(progressLevel > 0 ?
-                getContext().getString(R.string.app_downloading_title, info.title,
-                        NumberFormat.getPercentInstance().format(progressLevel * 0.01)) :
+                    getContext().getString(R.string.app_downloading_title, info.title,
+                            NumberFormat.getPercentInstance().format(progressLevel * 0.01)) :
                     getContext().getString(R.string.app_waiting_download_title, info.title));
 
             if (mIcon != null) {
@@ -501,7 +557,8 @@
                 if (mIcon instanceof PreloadIconDrawable) {
                     preloadDrawable = (PreloadIconDrawable) mIcon;
                 } else {
-                    preloadDrawable = new PreloadIconDrawable(mIcon, getPreloaderTheme());
+                    preloadDrawable = DrawableFactory.get(getContext())
+                            .newPendingIcon(info.iconBitmap, getContext());
                     setIcon(preloadDrawable);
                 }
 
@@ -513,39 +570,38 @@
         }
     }
 
-    private Theme getPreloaderTheme() {
-        Object tag = getTag();
-        int style = ((tag != null) && (tag instanceof ShortcutInfo) &&
-                (((ShortcutInfo) tag).container >= 0)) ? R.style.PreloadIcon_Folder
-                        : R.style.PreloadIcon;
-        Theme theme = sPreloaderThemes.get(style);
-        if (theme == null) {
-            theme = getResources().newTheme();
-            theme.applyStyle(style, true);
-            sPreloaderThemes.put(style, theme);
+    public void applyBadgeState(ItemInfo itemInfo, boolean animate) {
+        if (mIcon instanceof FastBitmapDrawable) {
+            boolean wasBadged = mBadgeInfo != null;
+            mBadgeInfo = mLauncher.getPopupDataProvider().getBadgeInfoForItem(itemInfo);
+            boolean isBadged = mBadgeInfo != null;
+            float newBadgeScale = isBadged ? 1f : 0;
+            mBadgeRenderer = mLauncher.getDeviceProfile().mBadgeRenderer;
+            if (wasBadged || isBadged) {
+                mIconPalette = ((FastBitmapDrawable) mIcon).getIconPalette();
+                // Animate when a badge is first added or when it is removed.
+                if (animate && (wasBadged ^ isBadged) && isShown()) {
+                    ObjectAnimator.ofFloat(this, BADGE_SCALE_PROPERTY, newBadgeScale).start();
+                } else {
+                    mBadgeScale = newBadgeScale;
+                    invalidate();
+                }
+            }
         }
-        return theme;
     }
 
     /**
      * Sets the icon for this view based on the layout direction.
      */
-    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
     private void setIcon(Drawable icon) {
         mIcon = icon;
-        if (mIconSize != -1) {
-            mIcon.setBounds(0, 0, mIconSize, mIconSize);
-        }
+        mIcon.setBounds(0, 0, mIconSize, mIconSize);
         applyCompoundDrawables(mIcon);
     }
 
     protected void applyCompoundDrawables(Drawable icon) {
         if (mLayoutHorizontal) {
-            if (Utilities.ATLEAST_JB_MR1) {
-                setCompoundDrawablesRelative(icon, null, null, null);
-            } else {
-                setCompoundDrawables(icon, null, null, null);
-            }
+            setCompoundDrawablesRelative(icon, null, null, null);
         } else {
             setCompoundDrawables(null, icon, null, null);
         }
@@ -561,20 +617,16 @@
     /**
      * Applies the item info if it is same as what the view is pointing to currently.
      */
-    public void reapplyItemInfo(final ItemInfo info) {
+    @Override
+    public void reapplyItemInfo(ItemInfoWithIcon info) {
         if (getTag() == info) {
-            FastBitmapDrawable.State prevState = FastBitmapDrawable.State.NORMAL;
-            if (mIcon instanceof FastBitmapDrawable) {
-                prevState = ((FastBitmapDrawable) mIcon).getCurrentState();
-            }
             mIconLoadRequest = null;
             mDisableRelayout = true;
 
             if (info instanceof AppInfo) {
                 applyFromApplicationInfo((AppInfo) info);
             } else if (info instanceof ShortcutInfo) {
-                applyFromShortcutInfo((ShortcutInfo) info,
-                        LauncherAppState.getInstance().getIconCache());
+                applyFromShortcutInfo((ShortcutInfo) info);
                 if ((info.rank < FolderIcon.NUM_ITEMS_IN_PREVIEW) && (info.container >= 0)) {
                     View folderIcon =
                             mLauncher.getWorkspace().getHomescreenIconByItemId(info.container);
@@ -586,12 +638,6 @@
                 applyFromPackageItemInfo((PackageItemInfo) info);
             }
 
-            // If we are reapplying over an old icon, then we should update the new icon to the same
-            // state as the old icon
-            if (mIcon instanceof FastBitmapDrawable) {
-                ((FastBitmapDrawable) mIcon).setState(prevState);
-            }
-
             mDisableRelayout = false;
         }
     }
@@ -604,52 +650,12 @@
             mIconLoadRequest.cancel();
             mIconLoadRequest = null;
         }
-        if (getTag() instanceof AppInfo) {
-            AppInfo info = (AppInfo) getTag();
+        if (getTag() instanceof ItemInfoWithIcon) {
+            ItemInfoWithIcon info = (ItemInfoWithIcon) getTag();
             if (info.usingLowResIcon) {
-                mIconLoadRequest = LauncherAppState.getInstance().getIconCache()
+                mIconLoadRequest = LauncherAppState.getInstance(getContext()).getIconCache()
                         .updateIconInBackground(BubbleTextView.this, info);
             }
-        } else if (getTag() instanceof ShortcutInfo) {
-            ShortcutInfo info = (ShortcutInfo) getTag();
-            if (info.usingLowResIcon) {
-                mIconLoadRequest = LauncherAppState.getInstance().getIconCache()
-                        .updateIconInBackground(BubbleTextView.this, info);
-            }
-        } else if (getTag() instanceof PackageItemInfo) {
-            PackageItemInfo info = (PackageItemInfo) getTag();
-            if (info.usingLowResIcon) {
-                mIconLoadRequest = LauncherAppState.getInstance().getIconCache()
-                        .updateIconInBackground(BubbleTextView.this, info);
-            }
-        }
-    }
-
-    @Override
-    public void setFastScrollFocusState(final FastBitmapDrawable.State focusState, boolean animated) {
-        // We can only set the fast scroll focus state on a FastBitmapDrawable
-        if (!(mIcon instanceof FastBitmapDrawable)) {
-            return;
-        }
-
-        FastBitmapDrawable d = (FastBitmapDrawable) mIcon;
-        if (animated) {
-            FastBitmapDrawable.State prevState = d.getCurrentState();
-            if (d.animateState(focusState)) {
-                // If the state was updated, then update the view accordingly
-                animate().scaleX(focusState.viewScale)
-                        .scaleY(focusState.viewScale)
-                        .setStartDelay(getStartDelayForStateChange(prevState, focusState))
-                        .setDuration(d.getDurationForStateChange(prevState, focusState))
-                        .start();
-            }
-        } else {
-            if (d.setState(focusState)) {
-                // If the state was updated, then update the view accordingly
-                animate().cancel();
-                setScaleX(focusState.viewScale);
-                setScaleY(focusState.viewScale);
-            }
         }
     }
 
@@ -657,22 +663,8 @@
      * Returns true if the view can show custom shortcuts.
      */
     public boolean hasDeepShortcuts() {
-        return !mLauncher.getShortcutIdsForItem((ItemInfo) getTag()).isEmpty();
-    }
-
-    /**
-     * Returns the start delay when animating between certain {@link FastBitmapDrawable} states.
-     */
-    private static int getStartDelayForStateChange(final FastBitmapDrawable.State fromState,
-            final FastBitmapDrawable.State toState) {
-        switch (toState) {
-            case NORMAL:
-                switch (fromState) {
-                    case FAST_SCROLL_HIGHLIGHTED:
-                        return FastBitmapDrawable.FAST_SCROLL_INACTIVE_DURATION / 4;
-                }
-        }
-        return 0;
+        return !mLauncher.getPopupDataProvider().getShortcutIdsForItem((ItemInfo) getTag())
+                .isEmpty();
     }
 
     /**
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
index 60a2cc3..8a477d8 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -21,17 +21,14 @@
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.ColorMatrix;
 import android.graphics.ColorMatrixColorFilter;
-import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
-import android.os.Build;
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.View.OnClickListener;
@@ -45,6 +42,7 @@
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.Thunk;
 
 /**
@@ -101,17 +99,11 @@
         mOriginalTextColor = getTextColors();
     }
 
-    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
     protected void setDrawable(int resId) {
         // We do not set the drawable in the xml as that inflates two drawables corresponding to
         // drawableLeft and drawableStart.
         mDrawable = getResources().getDrawable(resId);
-
-        if (Utilities.ATLEAST_JB_MR1) {
-            setCompoundDrawablesRelativeWithIntrinsicBounds(mDrawable, null, null, null);
-        } else {
-            setCompoundDrawablesWithIntrinsicBounds(mDrawable, null, null, null);
-        }
+        setCompoundDrawablesRelativeWithIntrinsicBounds(mDrawable, null, null, null);
     }
 
     public void setDropTargetBar(DropTargetBar dropTargetBar) {
@@ -119,21 +111,9 @@
     }
 
     @Override
-    public void onFlingToDelete(DragObject d, PointF vec) { }
-
-    @Override
     public final void onDragEnter(DragObject d) {
         d.dragView.setColor(mHoverColor);
-        if (Utilities.ATLEAST_LOLLIPOP) {
-            animateTextColor(mHoverColor);
-        } else {
-            if (mCurrentFilter == null) {
-                mCurrentFilter = new ColorMatrix();
-            }
-            DragView.setColorScale(mHoverColor, mCurrentFilter);
-            mDrawable.setColorFilter(new ColorMatrixColorFilter(mCurrentFilter));
-            setTextColor(mHoverColor);
-        }
+        animateTextColor(mHoverColor);
         if (d.stateAnnouncer != null) {
             d.stateAnnouncer.cancel();
         }
@@ -146,15 +126,9 @@
     }
 
     protected void resetHoverColor() {
-        if (Utilities.ATLEAST_LOLLIPOP) {
-            animateTextColor(mOriginalTextColor.getDefaultColor());
-        } else {
-            mDrawable.setColorFilter(null);
-            setTextColor(mOriginalTextColor);
-        }
+        animateTextColor(mOriginalTextColor.getDefaultColor());
     }
 
-    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
     private void animateTextColor(int targetColor) {
         if (mCurrentColorAnim != null) {
             mCurrentColorAnim.cancel();
@@ -169,8 +143,8 @@
             mCurrentFilter = new ColorMatrix();
         }
 
-        DragView.setColorScale(getTextColor(), mSrcFilter);
-        DragView.setColorScale(targetColor, mDstFilter);
+        Themes.setColorScaleOnMatrix(getTextColor(), mSrcFilter);
+        Themes.setColorScaleOnMatrix(targetColor, mDstFilter);
         ValueAnimator anim1 = ValueAnimator.ofObject(
                 new FloatArrayEvaluator(mCurrentFilter.getArray()),
                 mSrcFilter.getArray(), mDstFilter.getArray());
@@ -243,10 +217,7 @@
         final Rect from = new Rect();
         dragLayer.getViewRectRelativeToSelf(d.dragView, from);
 
-        int width = mDrawable.getIntrinsicWidth();
-        int height = mDrawable.getIntrinsicHeight();
-        final Rect to = getIconRect(d.dragView.getMeasuredWidth(), d.dragView.getMeasuredHeight(),
-                width, height);
+        final Rect to = getIconRect(d);
         final float scale = (float) to.width() / from.width();
         mDropTargetBar.deferOnDragEnd();
 
@@ -259,7 +230,7 @@
             }
         };
         dragLayer.animateView(d.dragView, from, to, scale, 1f, 1f, 0.1f, 0.1f,
-                mLauncher.getDragController().isExternalDrag() ? 1 : DRAG_VIEW_DROP_DURATION,
+                DRAG_VIEW_DROP_DURATION,
                 new DecelerateInterpolator(2),
                 new LinearInterpolator(), onAnimationEndRunnable,
                 DragLayer.ANIMATION_END_DISAPPEAR, null);
@@ -268,7 +239,7 @@
     @Override
     public void prepareAccessibilityDrop() { }
 
-    @Thunk abstract void completeDrop(DragObject d);
+    public abstract void completeDrop(DragObject d);
 
     @Override
     public void getHitRectRelativeToDragLayer(android.graphics.Rect outRect) {
@@ -280,7 +251,11 @@
         outRect.offsetTo(coords[0], coords[1]);
     }
 
-    protected Rect getIconRect(int viewWidth, int viewHeight, int drawableWidth, int drawableHeight) {
+    public Rect getIconRect(DragObject dragObject) {
+        int viewWidth = dragObject.dragView.getMeasuredWidth();
+        int viewHeight = dragObject.dragView.getMeasuredHeight();
+        int drawableWidth = mDrawable.getIntrinsicWidth();
+        int drawableHeight = mDrawable.getIntrinsicHeight();
         DragLayer dragLayer = mLauncher.getDragLayer();
 
         // Find the rect to animate to (the view is center aligned)
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 57fd0e7..8179dad 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -21,9 +21,9 @@
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.Resources;
+import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
@@ -32,9 +32,8 @@
 import android.graphics.Rect;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
-import android.graphics.drawable.TransitionDrawable;
-import android.os.Build;
 import android.os.Parcelable;
+import android.support.annotation.IntDef;
 import android.support.v4.view.ViewCompat;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -51,7 +50,7 @@
 import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
 import com.android.launcher3.accessibility.FolderAccessibilityHelper;
 import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
-import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.anim.PropertyListBuilder;
 import com.android.launcher3.config.ProviderConfig;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.graphics.DragPreviewProvider;
@@ -60,6 +59,8 @@
 import com.android.launcher3.util.ParcelableSparseArray;
 import com.android.launcher3.util.Thunk;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -87,13 +88,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;
@@ -111,13 +105,12 @@
 
     private ArrayList<FolderIcon.PreviewBackground> mFolderBackgrounds = new ArrayList<FolderIcon.PreviewBackground>();
     FolderIcon.PreviewBackground mFolderLeaveBehind = new FolderIcon.PreviewBackground();
-    Paint mFolderBgPaint = new Paint();
 
     private float mBackgroundAlpha;
 
-    private static final int BACKGROUND_ACTIVATE_DURATION =
-            FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND ? 120 : 0;
-    private final TransitionDrawable mBackground;
+    private static final int[] BACKGROUND_STATE_ACTIVE = new int[] { android.R.attr.state_active };
+    private static final int[] BACKGROUND_STATE_DEFAULT = new int[0];
+    private final Drawable mBackground;
 
     // These values allow a fixed measurement to be set on the CellLayout.
     private int mFixedWidth = -1;
@@ -152,8 +145,16 @@
     private TimeInterpolator mEaseOutInterpolator;
     private ShortcutAndWidgetContainer mShortcutsAndWidgets;
 
-    private boolean mIsHotseat = false;
-    private float mHotseatScale = 1f;
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({WORKSPACE, HOTSEAT, FOLDER})
+    public @interface ContainerType{}
+    public static final int WORKSPACE = 0;
+    public static final int HOTSEAT = 1;
+    public static final int FOLDER = 2;
+
+    @ContainerType private final int mContainerType;
+
+    private final float mChildScale;
 
     public static final int MODE_SHOW_REORDER_HINT = 0;
     public static final int MODE_DRAG_OVER = 1;
@@ -165,7 +166,7 @@
 
     private static final float REORDER_PREVIEW_MAGNITUDE = 0.12f;
     private static final int REORDER_ANIMATION_DURATION = 150;
-    @Thunk float mReorderPreviewAnimationMagnitude;
+    @Thunk final float mReorderPreviewAnimationMagnitude;
 
     private ArrayList<View> mIntersectingViews = new ArrayList<View>();
     private Rect mOccupiedRect = new Rect();
@@ -191,6 +192,9 @@
 
     public CellLayout(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
+        mContainerType = a.getInteger(R.styleable.CellLayout_containerType, WORKSPACE);
+        a.recycle();
 
         // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
         // the user where a dragged item will land when dropped.
@@ -202,9 +206,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;
@@ -217,18 +218,16 @@
         mFolderLeaveBehind.delegateCellX = -1;
         mFolderLeaveBehind.delegateCellY = -1;
 
+        mChildScale = mContainerType == HOTSEAT ? grid.inv.hotseatScale : 1f;
+
         setAlwaysDrawnWithCacheEnabled(false);
         final Resources res = getResources();
-        mHotseatScale = (float) grid.hotseatIconSizePx / grid.iconSizePx;
 
-        mBackground = (TransitionDrawable) res.getDrawable(
-                FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND ? R.drawable.bg_screenpanel
-                        : R.drawable.bg_celllayout);
+        mBackground = res.getDrawable(R.drawable.bg_celllayout);
         mBackground.setCallback(this);
         mBackground.setAlpha((int) (mBackgroundAlpha * 255));
 
-        mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE *
-                grid.iconSizePx);
+        mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE * grid.iconSizePx);
 
         // Initialize the data structures used for the drag visualization.
         mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out
@@ -286,9 +285,8 @@
             mDragOutlineAnims[i] = anim;
         }
 
-        mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context);
-        mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
-                mCountX, mCountY);
+        mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context, mContainerType);
+        mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY);
 
         mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
 
@@ -297,7 +295,6 @@
         addView(mShortcutsAndWidgets);
     }
 
-    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
     public void enableAccessibleDrag(boolean enable, int dragType) {
         mUseTouchHelper = enable;
         if (!enable) {
@@ -366,15 +363,10 @@
         mShortcutsAndWidgets.buildLayer();
     }
 
-    public float getChildrenScale() {
-        return mIsHotseat ? mHotseatScale : 1.0f;
-    }
-
     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 +375,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();
     }
 
@@ -426,15 +417,8 @@
     void setIsDragOverlapping(boolean isDragOverlapping) {
         if (mIsDragOverlapping != isDragOverlapping) {
             mIsDragOverlapping = isDragOverlapping;
-            if (mIsDragOverlapping) {
-                mBackground.startTransition(BACKGROUND_ACTIVATE_DURATION);
-            } else {
-                if (mBackgroundAlpha > 0f) {
-                    mBackground.reverseTransition(BACKGROUND_ACTIVATE_DURATION);
-                } else {
-                    mBackground.resetTransition();
-                }
-            }
+            mBackground.setState(mIsDragOverlapping
+                    ? BACKGROUND_STATE_ACTIVE : BACKGROUND_STATE_DEFAULT);
             invalidate();
         }
     }
@@ -516,9 +500,9 @@
             cellToPoint(bg.delegateCellX, bg.delegateCellY, mTempLocation);
             canvas.save();
             canvas.translate(mTempLocation[0], mTempLocation[1]);
-            bg.drawBackground(canvas, mFolderBgPaint);
+            bg.drawBackground(canvas);
             if (!bg.isClipping) {
-                bg.drawBackgroundStroke(canvas, mFolderBgPaint);
+                bg.drawBackgroundStroke(canvas);
             }
             canvas.restore();
         }
@@ -528,7 +512,7 @@
                     mFolderLeaveBehind.delegateCellY, mTempLocation);
             canvas.save();
             canvas.translate(mTempLocation[0], mTempLocation[1]);
-            mFolderLeaveBehind.drawLeaveBehind(canvas, mFolderBgPaint);
+            mFolderLeaveBehind.drawLeaveBehind(canvas);
             canvas.restore();
         }
     }
@@ -543,7 +527,7 @@
                 cellToPoint(bg.delegateCellX, bg.delegateCellY, mTempLocation);
                 canvas.save();
                 canvas.translate(mTempLocation[0], mTempLocation[1]);
-                bg.drawBackgroundStroke(canvas, mFolderBgPaint);
+                bg.drawBackgroundStroke(canvas);
                 canvas.restore();
             }
         }
@@ -616,13 +600,8 @@
         return mCountY;
     }
 
-    public void setIsHotseat(boolean isHotseat) {
-        mIsHotseat = isHotseat;
-        mShortcutsAndWidgets.setIsHotseat(isHotseat);
-    }
-
-    public boolean isHotseat() {
-        return mIsHotseat;
+    public boolean acceptsWidget() {
+        return mContainerType == WORKSPACE;
     }
 
     public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
@@ -632,11 +611,11 @@
         // Hotseat icons - remove text
         if (child instanceof BubbleTextView) {
             BubbleTextView bubbleChild = (BubbleTextView) child;
-            bubbleChild.setTextVisibility(!mIsHotseat);
+            bubbleChild.setTextVisibility(mContainerType != HOTSEAT);
         }
 
-        child.setScaleX(getChildrenScale());
-        child.setScaleY(getChildrenScale());
+        child.setScaleX(mChildScale);
+        child.setScaleY(mChildScale);
 
         // Generate an id for each view, this assumes we have at most 256x256 cells
         // per workspace screen
@@ -717,8 +696,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 +730,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 +757,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 +771,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 +789,6 @@
         return mCellHeight;
     }
 
-    int getWidthGap() {
-        return mWidthGap;
-    }
-
-    int getHeightGap() {
-        return mHeightGap;
-    }
-
     public void setFixedSize(int width, int height) {
         mFixedWidth = width;
         mFixedHeight = height;
@@ -840,8 +808,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 +821,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 +880,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;
     }
@@ -997,7 +937,7 @@
                 lp.tmpCellX = cellX;
                 lp.tmpCellY = cellY;
             }
-            clc.setupLp(lp);
+            clc.setupLp(child);
             lp.isLockedToGrid = false;
             final int newX = lp.x;
             final int newY = lp.y;
@@ -1011,14 +951,14 @@
                 return true;
             }
 
-            ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
+            ValueAnimator va = LauncherAnimUtils.ofFloat(0f, 1f);
             va.setDuration(duration);
             mReorderAnimators.put(lp, va);
 
             va.addUpdateListener(new AnimatorUpdateListener() {
                 @Override
                 public void onAnimationUpdate(ValueAnimator animation) {
-                    float r = ((Float) animation.getAnimatedValue()).floatValue();
+                    float r = (Float) animation.getAnimatedValue();
                     lp.x = (int) ((1 - r) * oldX + r * newX);
                     lp.y = (int) ((1 - r) * oldY + r * newY);
                     child.requestLayout();
@@ -1054,11 +994,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();
@@ -1073,6 +1013,10 @@
 
             if (resize) {
                 cellToRect(cellX, cellY, spanX, spanY, r);
+                if (v instanceof LauncherAppWidgetHostView) {
+                    DeviceProfile profile = mLauncher.getDeviceProfile();
+                    Utilities.shrinkRect(r, profile.appWidgetScale.x, profile.appWidgetScale.y);
+                }
             } else {
                 // Find the top left corner of the rect the object will occupy
                 final int[] topLeft = mTmpPoint;
@@ -1091,48 +1035,46 @@
                     // Offsets due to the size difference between the View and the dragOutline.
                     // There is a size difference to account for the outer blur, which may lie
                     // outside the bounds of the view.
-                    top += (v.getHeight() - dragOutline.getHeight()) / 2;
+                    top += ((mCellHeight * spanY) - 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());
             }
 
-            Utilities.scaleRectAboutCenter(r, getChildrenScale());
+            Utilities.scaleRectAboutCenter(r, mChildScale);
             mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
             mDragOutlineAnims[mDragOutlineCurrent].animateIn();
 
             if (dragObject.stateAnnouncer != null) {
-                String msg;
-                if (isHotseat()) {
-                    msg = getContext().getString(R.string.move_to_hotseat_position,
-                            Math.max(cellX, cellY) + 1);
-                } else {
-                    msg = getContext().getString(R.string.move_to_empty_cell,
-                            cellY + 1, cellX + 1);
-                }
-                dragObject.stateAnnouncer.announce(msg);
+                dragObject.stateAnnouncer.announce(getItemMoveDescription(cellX, cellY));
             }
         }
     }
 
+    public String getItemMoveDescription(int cellX, int cellY) {
+        if (mContainerType == HOTSEAT) {
+            return getContext().getString(R.string.move_to_hotseat_position,
+                    Math.max(cellX, cellY) + 1);
+        } else {
+            return getContext().getString(R.string.move_to_empty_cell,
+                    cellY + 1, cellX + 1);
+        }
+    }
+
     public void clearDragOutlines() {
         final int oldIndex = mDragOutlineCurrent;
         mDragOutlineAnims[oldIndex].animateOut();
@@ -1198,8 +1140,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];
@@ -1884,7 +1826,7 @@
      * the provided point and the provided cell
      */
     private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
-        double angle = Math.atan(((float) deltaY) / deltaX);
+        double angle = Math.atan(deltaY / deltaX);
 
         result[0] = 0;
         result[1] = 0;
@@ -2030,6 +1972,8 @@
         private static final int PREVIEW_DURATION = 300;
         private static final int HINT_DURATION = Workspace.REORDER_TIMEOUT;
 
+        private static final float CHILD_DIVIDEND = 4.0f;
+
         public static final int MODE_HINT = 0;
         public static final int MODE_PREVIEW = 1;
 
@@ -2045,45 +1989,65 @@
             final int y1 = mTmpPoint[1];
             final int dX = x1 - x0;
             final int dY = y1 - y0;
-            finalDeltaX = 0;
-            finalDeltaY = 0;
+
+            this.child = child;
+            this.mode = mode;
+            setInitialAnimationValues(false);
+            finalScale = (mChildScale - (CHILD_DIVIDEND / child.getWidth())) * initScale;
+            finalDeltaX = initDeltaX;
+            finalDeltaY = initDeltaY;
             int dir = mode == MODE_HINT ? -1 : 1;
             if (dX == dY && dX == 0) {
             } else {
                 if (dY == 0) {
-                    finalDeltaX = - dir * Math.signum(dX) * mReorderPreviewAnimationMagnitude;
+                    finalDeltaX += - dir * Math.signum(dX) * mReorderPreviewAnimationMagnitude;
                 } else if (dX == 0) {
-                    finalDeltaY = - dir * Math.signum(dY) * mReorderPreviewAnimationMagnitude;
+                    finalDeltaY += - dir * Math.signum(dY) * mReorderPreviewAnimationMagnitude;
                 } else {
                     double angle = Math.atan( (float) (dY) / dX);
-                    finalDeltaX = (int) (- dir * Math.signum(dX) *
+                    finalDeltaX += (int) (- dir * Math.signum(dX) *
                             Math.abs(Math.cos(angle) * mReorderPreviewAnimationMagnitude));
-                    finalDeltaY = (int) (- dir * Math.signum(dY) *
+                    finalDeltaY += (int) (- dir * Math.signum(dY) *
                             Math.abs(Math.sin(angle) * mReorderPreviewAnimationMagnitude));
                 }
             }
-            this.mode = mode;
-            initDeltaX = child.getTranslationX();
-            initDeltaY = child.getTranslationY();
-            finalScale = getChildrenScale() - 4.0f / child.getWidth();
-            initScale = child.getScaleX();
-            this.child = child;
+        }
+
+        void setInitialAnimationValues(boolean restoreOriginalValues) {
+            if (restoreOriginalValues) {
+                if (child instanceof LauncherAppWidgetHostView) {
+                    LauncherAppWidgetHostView lahv = (LauncherAppWidgetHostView) child;
+                    initScale = lahv.getScaleToFit();
+                    initDeltaX = lahv.getTranslationForCentering().x;
+                    initDeltaY = lahv.getTranslationForCentering().y;
+                } else {
+                    initScale = mChildScale;
+                    initDeltaX = 0;
+                    initDeltaY = 0;
+                }
+            } else {
+                initScale = child.getScaleX();
+                initDeltaX = child.getTranslationX();
+                initDeltaY = child.getTranslationY();
+            }
         }
 
         void animate() {
+            boolean noMovement = (finalDeltaX == initDeltaX) && (finalDeltaY == initDeltaY);
+
             if (mShakeAnimators.containsKey(child)) {
                 ReorderPreviewAnimation oldAnimation = mShakeAnimators.get(child);
                 oldAnimation.cancel();
                 mShakeAnimators.remove(child);
-                if (finalDeltaX == 0 && finalDeltaY == 0) {
+                if (noMovement) {
                     completeAnimationImmediately();
                     return;
                 }
             }
-            if (finalDeltaX == 0 && finalDeltaY == 0) {
+            if (noMovement) {
                 return;
             }
-            ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
+            ValueAnimator va = LauncherAnimUtils.ofFloat(0f, 1f);
             a = va;
 
             // Animations are disabled in power save mode, causing the repeated animation to jump
@@ -2099,7 +2063,7 @@
             va.addUpdateListener(new AnimatorUpdateListener() {
                 @Override
                 public void onAnimationUpdate(ValueAnimator animation) {
-                    float r = ((Float) animation.getAnimatedValue()).floatValue();
+                    float r = (Float) animation.getAnimatedValue();
                     float r1 = (mode == MODE_HINT && repeating) ? 1.0f : r;
                     float x = r1 * finalDeltaX + (1 - r1) * initDeltaX;
                     float y = r1 * finalDeltaY + (1 - r1) * initDeltaY;
@@ -2113,9 +2077,7 @@
             va.addListener(new AnimatorListenerAdapter() {
                 public void onAnimationRepeat(Animator animation) {
                     // We make sure to end only after a full period
-                    initDeltaX = 0;
-                    initDeltaY = 0;
-                    initScale = getChildrenScale();
+                    setInitialAnimationValues(true);
                     repeating = true;
                 }
             });
@@ -2134,12 +2096,14 @@
                 a.cancel();
             }
 
-            a = new LauncherViewPropertyAnimator(child)
-                .scaleX(getChildrenScale())
-                .scaleY(getChildrenScale())
-                .translationX(0)
-                .translationY(0)
-                .setDuration(REORDER_ANIMATION_DURATION);
+            setInitialAnimationValues(true);
+            a = LauncherAnimUtils.ofPropertyValuesHolder(child,
+                    new PropertyListBuilder()
+                            .scale(initScale)
+                            .translationX(initDeltaX)
+                            .translationY(initDeltaY)
+                            .build())
+                    .setDuration(REORDER_ANIMATION_DURATION);
             a.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f));
             a.start();
         }
@@ -2158,7 +2122,7 @@
         long screenId = mLauncher.getWorkspace().getIdForScreen(this);
         int container = Favorites.CONTAINER_DESKTOP;
 
-        if (mLauncher.isHotseatLayout(this)) {
+        if (mContainerType == HOTSEAT) {
             screenId = -1;
             container = Favorites.CONTAINER_HOTSEAT;
         }
@@ -2181,7 +2145,7 @@
                 info.spanY = lp.cellVSpan;
 
                 if (requiresDbUpdate) {
-                    LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId,
+                    mLauncher.getModelWriter().modifyItemInDatabase(info, container, screenId,
                             info.cellX, info.cellY, info.spanX, info.spanY);
                 }
             }
@@ -2215,10 +2179,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 +2558,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 +2583,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 +2707,19 @@
             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) {
+            setup(cellWidth, cellHeight, invertHorizontally, colCount, 1.0f, 1.0f);
+        }
+
+        /**
+         * Use this method, as opposed to {@link #setup(int, int, boolean, int)}, if the view needs
+         * to be scaled.
+         *
+         * ie. In multi-window mode, we setup widgets so that they are measured and laid out
+         * using their full/invariant device profile sizes.
+         */
+        public void setup(int cellWidth, int cellHeight, boolean invertHorizontally, int colCount,
+                float cellScaleX, float cellScaleY) {
             if (isLockedToGrid) {
                 final int myCellHSpan = cellHSpan;
                 final int myCellVSpan = cellVSpan;
@@ -2764,12 +2730,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 = (int) (myCellHSpan * cellWidth / cellScaleX - leftMargin - rightMargin);
+                height = (int) (myCellVSpan * cellHeight / cellScaleY - topMargin - bottomMargin);
+                x = (myCellX * cellWidth + leftMargin);
+                y = (myCellY * cellHeight + topMargin);
             }
         }
 
diff --git a/src/com/android/launcher3/CommonAppTypeParser.java b/src/com/android/launcher3/CommonAppTypeParser.java
deleted file mode 100644
index c2bd883..0000000
--- a/src/com/android/launcher3/CommonAppTypeParser.java
+++ /dev/null
@@ -1,157 +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.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.XmlResourceParser;
-import android.database.sqlite.SQLiteDatabase;
-import android.util.Log;
-
-import com.android.launcher3.AutoInstallsLayout.LayoutParserCallback;
-import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.util.Thunk;
-
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-
-/**
- * A class that parses content values corresponding to some common app types.
- */
-public class CommonAppTypeParser implements LayoutParserCallback {
-    private static final String TAG = "CommonAppTypeParser";
-
-    // Including TARGET_NONE
-    public static final int SUPPORTED_TYPE_COUNT = 7;
-
-    private static final int RESTORE_FLAG_BIT_SHIFT = 4;
-
-    public static final int TARGET_PHONE = 1;
-    public static final int TARGET_MESSENGER = 2;
-    public static final int TARGET_EMAIL = 3;
-    public static final int TARGET_BROWSER = 4;
-    public static final int TARGET_GALLERY = 5;
-    public static final int TARGET_CAMERA = 6;
-
-    private final long mItemId;
-    @Thunk final int mResId;
-    @Thunk final Context mContext;
-
-    ContentValues parsedValues;
-    Intent parsedIntent;
-    String parsedTitle;
-
-    public CommonAppTypeParser(long itemId, int itemType, Context context) {
-        mItemId = itemId;
-        mContext = context;
-        mResId = getResourceForItemType(itemType);
-    }
-
-    @Override
-    public long generateNewItemId() {
-        return mItemId;
-    }
-
-    @Override
-    public long insertAndCheck(SQLiteDatabase db, ContentValues values) {
-        parsedValues = values;
-
-        // Remove unwanted values
-        values.put(Favorites.ICON_PACKAGE, (String) null);
-        values.put(Favorites.ICON_RESOURCE, (String) null);
-        values.put(Favorites.ICON, (byte[]) null);
-        return 1;
-    }
-
-    /**
-     * Tries to find a suitable app to the provided app type.
-     */
-    public boolean findDefaultApp() {
-        if (mResId == 0) {
-            return false;
-        }
-
-        parsedIntent = null;
-        parsedValues = null;
-        new MyLayoutParser().parseValues();
-        return (parsedValues != null) && (parsedIntent != null);
-    }
-
-    private class MyLayoutParser extends DefaultLayoutParser {
-
-        public MyLayoutParser() {
-            super(CommonAppTypeParser.this.mContext, null, CommonAppTypeParser.this,
-                    CommonAppTypeParser.this.mContext.getResources(), mResId, TAG_RESOLVE);
-        }
-
-        @Override
-        protected long addShortcut(String title, Intent intent, int type) {
-            if (type == Favorites.ITEM_TYPE_APPLICATION) {
-                parsedIntent = intent;
-                parsedTitle = title;
-            }
-            return super.addShortcut(title, intent, type);
-        }
-
-        public void parseValues() {
-            XmlResourceParser parser = mSourceRes.getXml(mLayoutId);
-            try {
-                beginDocument(parser, mRootTag);
-                new ResolveParser().parseAndAdd(parser);
-            } catch (IOException | XmlPullParserException e) {
-                Log.e(TAG, "Unable to parse default app info", e);
-            }
-            parser.close();
-        }
-    }
-
-    public static int getResourceForItemType(int type) {
-        switch (type) {
-            case TARGET_PHONE:
-                return R.xml.app_target_phone;
-
-            case TARGET_MESSENGER:
-                return R.xml.app_target_messenger;
-
-            case TARGET_EMAIL:
-                return R.xml.app_target_email;
-
-            case TARGET_BROWSER:
-                return R.xml.app_target_browser;
-
-            case TARGET_GALLERY:
-                return R.xml.app_target_gallery;
-
-            case TARGET_CAMERA:
-                return R.xml.app_target_camera;
-
-            default:
-                return 0;
-        }
-    }
-
-    public static int encodeItemTypeToFlag(int itemType) {
-        return itemType << RESTORE_FLAG_BIT_SHIFT;
-    }
-
-    public static int decodeItemTypeFromFlag(int flag) {
-        return (flag & ShortcutInfo.FLAG_RESTORED_APP_TYPE) >> RESTORE_FLAG_BIT_SHIFT;
-    }
-
-}
diff --git a/src/com/android/launcher3/DefaultLayoutParser.java b/src/com/android/launcher3/DefaultLayoutParser.java
index ef28d1e..05911ab 100644
--- a/src/com/android/launcher3/DefaultLayoutParser.java
+++ b/src/com/android/launcher3/DefaultLayoutParser.java
@@ -54,11 +54,6 @@
         super(context, appWidgetHost, callback, sourceRes, layoutId, TAG_FAVORITES);
     }
 
-    public DefaultLayoutParser(Context context, AppWidgetHost appWidgetHost,
-            LayoutParserCallback callback, Resources sourceRes, int layoutId, String rootTag) {
-        super(context, appWidgetHost, callback, sourceRes, layoutId, rootTag);
-    }
-
     @Override
     protected HashMap<String, TagParser> getFolderElementsMap() {
         return getFolderElementsMap(mSourceRes);
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index 705f841..9097ed2 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -16,19 +16,13 @@
 
 package com.android.launcher3;
 
-import android.animation.TimeInterpolator;
 import android.content.Context;
-import android.graphics.PointF;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.view.View;
-import android.view.animation.AnimationUtils;
 
-import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.folder.Folder;
-import com.android.launcher3.util.FlingAnimation;
-import com.android.launcher3.util.Thunk;
 
 public class DeleteDropTarget extends ButtonDropTarget {
 
@@ -78,7 +72,7 @@
     }
 
     @Override
-    @Thunk void completeDrop(DragObject d) {
+    public void completeDrop(DragObject d) {
         ItemInfo item = d.dragInfo;
         if ((d.dragSource instanceof Workspace) || (d.dragSource instanceof Folder)) {
             removeWorkspaceOrFolderItem(mLauncher, item, null);
@@ -96,53 +90,4 @@
         launcher.getWorkspace().stripEmptyScreens();
         launcher.getDragLayer().announceForAccessibility(launcher.getString(R.string.item_removed));
     }
-
-    @Override
-    public void onFlingToDelete(final DragObject d, PointF vel) {
-        // Don't highlight the icon as it's animating
-        d.dragView.setColor(0);
-
-        final DragLayer dragLayer = mLauncher.getDragLayer();
-        FlingAnimation fling = new FlingAnimation(d, vel,
-                getIconRect(d.dragView.getMeasuredWidth(), d.dragView.getMeasuredHeight(),
-                        mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight()),
-                        dragLayer);
-
-        final int duration = fling.getDuration();
-        final long startTime = AnimationUtils.currentAnimationTimeMillis();
-
-        // NOTE: Because it takes time for the first frame of animation to actually be
-        // called and we expect the animation to be a continuation of the fling, we have
-        // to account for the time that has elapsed since the fling finished.  And since
-        // we don't have a startDelay, we will always get call to update when we call
-        // start() (which we want to ignore).
-        final TimeInterpolator tInterpolator = new TimeInterpolator() {
-            private int mCount = -1;
-            private float mOffset = 0f;
-
-            @Override
-            public float getInterpolation(float t) {
-                if (mCount < 0) {
-                    mCount++;
-                } else if (mCount == 0) {
-                    mOffset = Math.min(0.5f, (float) (AnimationUtils.currentAnimationTimeMillis() -
-                            startTime) / duration);
-                    mCount++;
-                }
-                return Math.min(1f, mOffset + t);
-            }
-        };
-
-        Runnable onAnimationEndRunnable = new Runnable() {
-            @Override
-            public void run() {
-                mLauncher.exitSpringLoadedDragMode();
-                completeDrop(d);
-                mLauncher.getDragController().onDeferredEndFling(d);
-            }
-        };
-
-        dragLayer.animateView(d.dragView, fling, duration, tInterpolator, onAnimationEndRunnable,
-                DragLayer.ANIMATION_END_DISAPPEAR, null);
-    }
 }
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index f9f8e80..e47031a 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Point;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.util.DisplayMetrics;
 import android.view.Gravity;
@@ -29,6 +30,8 @@
 import android.view.ViewGroup.LayoutParams;
 import android.widget.FrameLayout;
 
+import com.android.launcher3.CellLayout.ContainerType;
+import com.android.launcher3.badge.BadgeRenderer;
 import com.android.launcher3.config.FeatureFlags;
 
 import java.util.ArrayList;
@@ -73,7 +76,6 @@
     public final Rect defaultWidgetPadding;
     private final int defaultPageSpacingPx;
     private final int topWorkspacePadding;
-    private float dragViewScale;
     public float workspaceSpringLoadShrinkFactor;
     public final int workspaceSpringLoadedBottomSpace;
 
@@ -96,16 +98,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,9 +125,8 @@
     public int allAppsIconDrawablePaddingPx;
     public float allAppsIconTextSizePx;
 
-    // Containers
-    private final int containerLeftPaddingPx;
-    private final int containerRightPaddingPx;
+    // Widgets
+    public final PointF appWidgetScale = new PointF(1.0f, 1.0f);
 
     // Drop Target
     public int dropTargetBarSizePx;
@@ -129,6 +137,9 @@
     // Listeners
     private ArrayList<LauncherLayoutChangeListener> mListeners = new ArrayList<>();
 
+    // Icon badges
+    public BadgeRenderer mBadgeRenderer;
+
     public DeviceProfile(Context context, InvariantDeviceProfile inv,
             Point minSize, Point maxSize,
             int width, int height, boolean isLandscape) {
@@ -183,11 +194,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;
@@ -203,6 +211,33 @@
         // Calculate the remaining vars
         updateAvailableDimensions(dm, res);
         computeAllAppsButtonSize(context);
+
+        // This is done last, after iconSizePx is calculated above.
+        mBadgeRenderer = new BadgeRenderer(context, iconSizePx);
+    }
+
+    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;
+
+        // We use these scales to measure and layout the widgets using their full invariant profile
+        // sizes and then draw them scaled and centered to fit in their multi-window mode cellspans.
+        float appWidgetScaleX = (float) profile.getCellSize().x / getCellSize().x;
+        float appWidgetScaleY = (float) profile.getCellSize().y / getCellSize().y;
+        profile.appWidgetScale.set(appWidgetScaleX, appWidgetScaleY);
+
+        return profile;
     }
 
     public void addLauncherLayoutChangedListener(LauncherLayoutChangeListener listener) {
@@ -229,19 +264,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,
@@ -257,9 +290,6 @@
         cellWidthPx = iconSizePx;
         cellHeightPx = iconSizePx + iconDrawablePaddingPx
                 + Utilities.calculateTextHeight(iconTextSizePx);
-        final float scaleDps = !FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND ? 0f
-                : res.getDimensionPixelSize(R.dimen.dragViewScale);
-        dragViewScale = (iconSizePx + scaleDps) / iconSizePx;
 
         // Hotseat
         hotseatCellWidthPx = iconSizePx;
@@ -277,31 +307,53 @@
                     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);
+
+        // Don't let the folder get too close to the edges of the screen.
+        int folderMargin = 4 * edgeMarginPx;
+
+        // Check if the icons fit within the available height.
+        float usedHeight = folderCellHeightPx * inv.numFolderRows + folderBottomPanelSize;
+        int maxHeight = availableHeightPx - getTotalWorkspacePadding().y - folderMargin;
+        float scaleY = maxHeight / usedHeight;
+
+        // Check if the icons fit within the available width.
+        float usedWidth = folderCellWidthPx * inv.numFolderColumns;
+        int maxWidth = availableWidthPx - getTotalWorkspacePadding().x - folderMargin;
+        float scaleX = maxWidth / usedWidth;
+
+        float scale = Math.min(scaleX, scaleY);
+        if (scale < 1f) {
+            updateFolderCellSize(scale, dm, res);
+        }
+    }
+
+    private void updateFolderCellSize(float scale, DisplayMetrics dm, Resources res) {
+        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);
+
+        folderCellWidthPx = folderChildIconSizePx + 2 * cellPaddingX;
+        folderCellHeightPx = folderChildIconSizePx + 2 * cellPaddingY + textHeight;
+        folderChildDrawablePaddingPx = Math.max(0,
+                (folderCellHeightPx - folderChildIconSizePx - textHeight) / 3);
+    }
+
     public void updateInsets(Rect insets) {
         mInsets.set(insets);
     }
@@ -368,16 +420,16 @@
             if (isTablet) {
                 // Pad the left and right of the workspace to ensure consistent spacing
                 // between all icons
-                float gapScale = 1f + (dragViewScale - 1f) / 2f;
                 int width = getCurrentWidth();
                 int height = getCurrentHeight();
                 // The amount of screen space available for left/right padding.
-                int availablePaddingX = Math.max(0, width - (int) ((inv.numColumns * cellWidthPx) +
-                        ((inv.numColumns - 1) * gapScale * cellWidthPx)));
+                int availablePaddingX = Math.max(0, width - ((inv.numColumns * cellWidthPx) +
+                        ((inv.numColumns - 1) * cellWidthPx)));
                 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 +515,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();
@@ -481,10 +532,13 @@
                 workspacePadding.bottom);
         workspace.setPageSpacing(getWorkspacePageSpacing());
 
-        View qsbContainer = launcher.getQsbContainer();
-        lp = (FrameLayout.LayoutParams) qsbContainer.getLayoutParams();
-        lp.topMargin = mInsets.top + workspacePadding.top;
-        qsbContainer.setLayoutParams(lp);
+        // Only display when enabled
+        if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
+            View qsbContainer = launcher.getQsbContainer();
+            lp = (FrameLayout.LayoutParams) qsbContainer.getLayoutParams();
+            lp.topMargin = mInsets.top + workspacePadding.top;
+            qsbContainer.setLayoutParams(lp);
+        }
 
         // Layout the hotseat
         Hotseat hotseat = (Hotseat) launcher.findViewById(R.id.hotseat);
@@ -511,7 +565,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 +574,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 +603,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);
         }
 
@@ -583,13 +632,24 @@
                 : Math.max(widthPx, heightPx);
     }
 
+    public int getCellHeight(@ContainerType int containerType) {
+        switch (containerType) {
+            case CellLayout.WORKSPACE:
+                return cellHeightPx;
+            case CellLayout.FOLDER:
+                return folderCellHeightPx;
+            case CellLayout.HOTSEAT:
+                return hotseatCellHeightPx;
+            default:
+                // ??
+                return 0;
+        }
+    }
 
     /**
      * @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 +660,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..dcd8f58 100644
--- a/src/com/android/launcher3/DragSource.java
+++ b/src/com/android/launcher3/DragSource.java
@@ -19,17 +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 {
-
-    /**
-     * @return whether items dragged from this source supports
-     */
-    boolean supportsFlingToDelete();
+public interface DragSource extends LogContainerProvider {
 
     /**
      * @return whether items dragged from this source supports 'App Info'
@@ -48,13 +43,6 @@
     float getIntrinsicIconScaleFactor();
 
     /**
-     * A callback specifically made back to the source after an item from this source has been flung
-     * to be deleted on a DropTarget.  In such a situation, this method will be called after
-     * onDropCompleted, and more importantly, after the fling animation has completed.
-     */
-    void onFlingToDeleteCompleted();
-
-    /**
      * A callback made back to the source after an item from this source has been dropped on a
      * DropTarget.
      */
diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java
index efdeb1f..7d047d7 100644
--- a/src/com/android/launcher3/DropTarget.java
+++ b/src/com/android/launcher3/DropTarget.java
@@ -16,12 +16,10 @@
 
 package com.android.launcher3;
 
-import com.android.launcher3.dragndrop.DragView;
-
-import android.graphics.PointF;
 import android.graphics.Rect;
 
 import com.android.launcher3.accessibility.DragViewStateAnnouncer;
+import com.android.launcher3.dragndrop.DragView;
 
 /**
  * Interface defining an object that can receive a drag.
@@ -107,17 +105,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);
 
@@ -128,25 +115,8 @@
     void onDragExit(DragObject dragObject);
 
     /**
-     * Handle an object being dropped as a result of flinging to delete and will be called in place
-     * of onDrop().  (This is only called on objects that are set as the DragController's
-     * fling-to-delete target.
-     */
-    void onFlingToDelete(DragObject dragObject, PointF vec);
-
-    /**
      * 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/DropTargetBar.java b/src/com/android/launcher3/DropTargetBar.java
index 42bab47..0840b70 100644
--- a/src/com/android/launcher3/DropTargetBar.java
+++ b/src/com/android/launcher3/DropTargetBar.java
@@ -21,6 +21,7 @@
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.ViewDebug;
+import android.view.ViewGroup;
 import android.view.ViewPropertyAnimator;
 import android.view.accessibility.AccessibilityManager;
 import android.view.animation.AccelerateInterpolator;
@@ -56,11 +57,6 @@
 
     private ViewPropertyAnimator mCurrentAnimation;
 
-    // Drop targets
-    private ButtonDropTarget mDeleteDropTarget;
-    private ButtonDropTarget mAppInfoDropTarget;
-    private ButtonDropTarget mUninstallDropTarget;
-
     public DropTargetBar(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
@@ -73,30 +69,27 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
 
-        // Get the individual components
-        mDeleteDropTarget = (ButtonDropTarget) findViewById(R.id.delete_target_text);
-        mAppInfoDropTarget = (ButtonDropTarget) findViewById(R.id.info_target_text);
-        mUninstallDropTarget = (ButtonDropTarget) findViewById(R.id.uninstall_target_text);
-
-        mDeleteDropTarget.setDropTargetBar(this);
-        mAppInfoDropTarget.setDropTargetBar(this);
-        mUninstallDropTarget.setDropTargetBar(this);
-
         // Initialize with hidden state
         setAlpha(0f);
     }
 
     public void setup(DragController dragController) {
         dragController.addDragListener(this);
-        dragController.setFlingToDeleteDropTarget(mDeleteDropTarget);
+        setupButtonDropTarget(this, dragController);
+    }
 
-        dragController.addDragListener(mDeleteDropTarget);
-        dragController.addDragListener(mAppInfoDropTarget);
-        dragController.addDragListener(mUninstallDropTarget);
-
-        dragController.addDropTarget(mDeleteDropTarget);
-        dragController.addDropTarget(mAppInfoDropTarget);
-        dragController.addDropTarget(mUninstallDropTarget);
+    private void setupButtonDropTarget(View view, DragController dragController) {
+        if (view instanceof ButtonDropTarget) {
+            ButtonDropTarget bdt = (ButtonDropTarget) view;
+            bdt.setDropTargetBar(this);
+            dragController.addDragListener(bdt);
+            dragController.addDropTarget(bdt);
+        } else if (view instanceof ViewGroup) {
+            ViewGroup vg = (ViewGroup) view;
+            for (int i = vg.getChildCount() - 1; i >= 0; i--) {
+                setupButtonDropTarget(vg.getChildAt(i), dragController);
+            }
+        }
     }
 
     private void animateToVisibility(boolean isVisible) {
diff --git a/src/com/android/launcher3/ExtendedEditText.java b/src/com/android/launcher3/ExtendedEditText.java
index c06f727..596aa8f 100644
--- a/src/com/android/launcher3/ExtendedEditText.java
+++ b/src/com/android/launcher3/ExtendedEditText.java
@@ -29,6 +29,7 @@
 public class ExtendedEditText extends EditText {
 
     private boolean mShowImeAfterFirstLayout;
+    private boolean mForceDisableSuggestions = false;
 
     /**
      * Implemented by listeners of the back key.
@@ -99,4 +100,25 @@
                 ((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();
+        }
+    }
+
+    /**
+     * Set to true when you want isSuggestionsEnabled to return false.
+     * Use this to disable the red underlines that appear under typos when suggestions is enabled.
+     */
+    public void forceDisableSuggestions(boolean forceDisableSuggestions) {
+        mForceDisableSuggestions = forceDisableSuggestions;
+    }
+
+    @Override
+    public boolean isSuggestionsEnabled() {
+        return !mForceDisableSuggestions && super.isSuggestionsEnabled();
+    }
 }
diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java
index 3870080..a096a1d 100644
--- a/src/com/android/launcher3/FastBitmapDrawable.java
+++ b/src/com/android/launcher3/FastBitmapDrawable.java
@@ -16,7 +16,6 @@
 
 package com.android.launcher3;
 
-import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
 import android.graphics.Bitmap;
@@ -30,37 +29,18 @@
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffColorFilter;
 import android.graphics.drawable.Drawable;
+import android.util.Property;
 import android.util.SparseArray;
-import android.view.animation.DecelerateInterpolator;
+
+import com.android.launcher3.graphics.IconPalette;
 
 public class FastBitmapDrawable extends Drawable {
 
-    /**
-     * The possible states that a FastBitmapDrawable can be in.
-     */
-    public enum State {
+    private static final int[] STATE_PRESSED = new int[] {android.R.attr.state_pressed};
 
-        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());
-
-        public final float desaturation;
-        public final float brightness;
-        /**
-         * Used specifically by the view drawing this FastBitmapDrawable.
-         */
-        public final float viewScale;
-        public final TimeInterpolator interpolator;
-
-        State(float desaturation, float brightness, float viewScale, TimeInterpolator interpolator) {
-            this.desaturation = desaturation;
-            this.brightness = brightness;
-            this.viewScale = viewScale;
-            this.interpolator = interpolator;
-        }
-    }
+    private static final float PRESSED_BRIGHTNESS = 100f / 255f;
+    private static final float DISABLED_DESATURATION = 1f;
+    private static final float DISABLED_BRIGHTNESS = 0.5f;
 
     public static final TimeInterpolator CLICK_FEEDBACK_INTERPOLATOR = new TimeInterpolator() {
 
@@ -76,10 +56,6 @@
         }
     };
     public static final int CLICK_FEEDBACK_DURATION = 2000;
-    public static final int FAST_SCROLL_HIGHLIGHT_DURATION = 225;
-    public static final int FAST_SCROLL_UNHIGHLIGHT_DURATION = 150;
-    public static final int FAST_SCROLL_UNHIGHLIGHT_FROM_NORMAL_DURATION = 225;
-    public static final int FAST_SCROLL_INACTIVE_DURATION = 275;
 
     // Since we don't need 256^2 values for combinations of both the brightness and saturation, we
     // reduce the value space to a smaller value V, which reduces the number of cached
@@ -93,9 +69,26 @@
     private static final ColorMatrix sTempBrightnessMatrix = new ColorMatrix();
     private static final ColorMatrix sTempFilterMatrix = new ColorMatrix();
 
-    private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
+    protected final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
     private final Bitmap mBitmap;
-    private State mState = State.NORMAL;
+
+    private boolean mIsPressed;
+    private boolean mIsDisabled;
+
+    private IconPalette mIconPalette;
+
+    private static final Property<FastBitmapDrawable, Float> BRIGHTNESS
+            = new Property<FastBitmapDrawable, Float>(Float.TYPE, "brightness") {
+        @Override
+        public Float get(FastBitmapDrawable fastBitmapDrawable) {
+            return fastBitmapDrawable.getBrightness();
+        }
+
+        @Override
+        public void set(FastBitmapDrawable fastBitmapDrawable, Float value) {
+            fastBitmapDrawable.setBrightness(value);
+        }
+    };
 
     // 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
@@ -104,19 +97,38 @@
     private int mAlpha = 255;
     private int mPrevUpdateKey = Integer.MAX_VALUE;
 
-    // Animators for the fast bitmap drawable's properties
-    private AnimatorSet mPropertyAnimator;
+    // Animators for the fast bitmap drawable's brightness
+    private ObjectAnimator mBrightnessAnimator;
 
     public FastBitmapDrawable(Bitmap b) {
         mBitmap = b;
-        setBounds(0, 0, b.getWidth(), b.getHeight());
+        setFilterBitmap(true);
     }
 
     @Override
     public void draw(Canvas canvas) {
+        drawInternal(canvas);
+    }
+
+    public void drawWithBrightness(Canvas canvas, float brightness) {
+        float oldBrightness = getBrightness();
+        setBrightness(brightness);
+        drawInternal(canvas);
+        setBrightness(oldBrightness);
+    }
+
+    protected void drawInternal(Canvas canvas) {
         canvas.drawBitmap(mBitmap, null, getBounds(), mPaint);
     }
 
+    public IconPalette getIconPalette() {
+        if (mIconPalette == null) {
+            mIconPalette = IconPalette.fromDominantColor(Utilities
+                    .findDominantColorByHue(mBitmap, 20));
+        }
+        return mIconPalette;
+    }
+
     @Override
     public void setColorFilter(ColorFilter cf) {
         // No op
@@ -167,104 +179,63 @@
         return mBitmap;
     }
 
-    /**
-     * Animates this drawable to a new state.
-     *
-     * @return whether the state has changed.
-     */
-    public boolean animateState(State newState) {
-        State prevState = mState;
-        if (mState != newState) {
-            mState = newState;
+    @Override
+    public boolean isStateful() {
+        return true;
+    }
 
-            mPropertyAnimator = cancelAnimator(mPropertyAnimator);
-            mPropertyAnimator = new AnimatorSet();
-            mPropertyAnimator.playTogether(
-                    ObjectAnimator
-                            .ofFloat(this, "desaturation", newState.desaturation),
-                    ObjectAnimator
-                            .ofFloat(this, "brightness", newState.brightness));
-            mPropertyAnimator.setInterpolator(newState.interpolator);
-            mPropertyAnimator.setDuration(getDurationForStateChange(prevState, newState));
-            mPropertyAnimator.setStartDelay(getStartDelayForStateChange(prevState, newState));
-            mPropertyAnimator.start();
+    @Override
+    protected boolean onStateChange(int[] state) {
+        boolean isPressed = false;
+        for (int s : state) {
+            if (s == android.R.attr.state_pressed) {
+                isPressed = true;
+                break;
+            }
+        }
+        if (mIsPressed != isPressed) {
+            mIsPressed = isPressed;
+
+            if (mBrightnessAnimator != null) {
+                mBrightnessAnimator.cancel();
+            }
+
+            if (mIsPressed) {
+                // Animate when going to pressed state
+                mBrightnessAnimator = ObjectAnimator.ofFloat(
+                        this, BRIGHTNESS, getExpectedBrightness());
+                mBrightnessAnimator.setDuration(CLICK_FEEDBACK_DURATION);
+                mBrightnessAnimator.setInterpolator(CLICK_FEEDBACK_INTERPOLATOR);
+                mBrightnessAnimator.start();
+            } else {
+                setBrightness(getExpectedBrightness());
+            }
             return true;
         }
         return false;
     }
 
-    /**
-     * Immediately sets this drawable to a new state.
-     *
-     * @return whether the state has changed.
-     */
-    public boolean setState(State newState) {
-        if (mState != newState) {
-            mState = newState;
-
-            mPropertyAnimator = cancelAnimator(mPropertyAnimator);
-
-            setDesaturation(newState.desaturation);
-            setBrightness(newState.brightness);
-            return true;
-        }
-        return false;
+    private void invalidateDesaturationAndBrightness() {
+        setDesaturation(mIsDisabled ? DISABLED_DESATURATION : 0);
+        setBrightness(getExpectedBrightness());
     }
 
-    /**
-     * Returns the current state.
-     */
-    public State getCurrentState() {
-        return mState;
+    private float getExpectedBrightness() {
+        return mIsDisabled ? DISABLED_BRIGHTNESS :
+                (mIsPressed ? PRESSED_BRIGHTNESS : 0);
     }
 
-    /**
-     * Returns the duration for the state change animation.
-     */
-    public static int getDurationForStateChange(State fromState, State toState) {
-        switch (toState) {
-            case NORMAL:
-                switch (fromState) {
-                    case PRESSED:
-                        return 0;
-                    case FAST_SCROLL_HIGHLIGHTED:
-                    case FAST_SCROLL_UNHIGHLIGHTED:
-                        return FAST_SCROLL_INACTIVE_DURATION;
-                }
-            case PRESSED:
-                return CLICK_FEEDBACK_DURATION;
-            case FAST_SCROLL_HIGHLIGHTED:
-                return FAST_SCROLL_HIGHLIGHT_DURATION;
-            case FAST_SCROLL_UNHIGHLIGHTED:
-                switch (fromState) {
-                    case NORMAL:
-                        // When animating from normal state, take a little longer
-                        return FAST_SCROLL_UNHIGHLIGHT_FROM_NORMAL_DURATION;
-                    default:
-                        return FAST_SCROLL_UNHIGHLIGHT_DURATION;
-                }
+    public void setIsDisabled(boolean isDisabled) {
+        if (mIsDisabled != isDisabled) {
+            mIsDisabled = isDisabled;
+            invalidateDesaturationAndBrightness();
         }
-        return 0;
-    }
-
-    /**
-     * Returns the start delay when animating between certain fast scroll states.
-     */
-    public static int getStartDelayForStateChange(State fromState, State toState) {
-        switch (toState) {
-            case FAST_SCROLL_UNHIGHLIGHTED:
-                switch (fromState) {
-                    case NORMAL:
-                        return FAST_SCROLL_UNHIGHLIGHT_DURATION / 4;
-                }
-        }
-        return 0;
     }
 
     /**
      * Sets the saturation of this icon, 0 [full color] -> 1 [desaturated]
      */
-    public void setDesaturation(float desaturation) {
+    private void setDesaturation(float desaturation) {
         int newDesaturation = (int) Math.floor(desaturation * REDUCED_FILTER_VALUE_SPACE);
         if (mDesaturation != newDesaturation) {
             mDesaturation = newDesaturation;
@@ -279,7 +250,7 @@
     /**
      * Sets the brightness of this icon, 0 [no add. brightness] -> 1 [2bright2furious]
      */
-    public void setBrightness(float brightness) {
+    private void setBrightness(float brightness) {
         int newBrightness = (int) Math.floor(brightness * REDUCED_FILTER_VALUE_SPACE);
         if (mBrightness != newBrightness) {
             mBrightness = newBrightness;
@@ -287,7 +258,7 @@
         }
     }
 
-    public float getBrightness() {
+    private float getBrightness() {
         return (float) mBrightness / REDUCED_FILTER_VALUE_SPACE;
     }
 
@@ -347,12 +318,4 @@
         }
         invalidateSelf();
     }
-
-    private AnimatorSet cancelAnimator(AnimatorSet animator) {
-        if (animator != null) {
-            animator.removeAllListeners();
-            animator.cancel();
-        }
-        return null;
-    }
 }
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..0041bb4 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 android.os.Process;
 
-import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.model.ModelWriter;
+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;
 
     /**
@@ -61,7 +56,7 @@
 
     public FolderInfo() {
         itemType = LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
-        user = UserHandleCompat.myUserHandle();
+        user = Process.myUserHandle();
     }
 
     /**
@@ -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);
+    public void onAddToDatabase(ContentWriter writer) {
+        super.onAddToDatabase(writer);
+        writer.put(LauncherSettings.Favorites.TITLE, title)
+                .put(LauncherSettings.Favorites.OPTIONS, options);
 
     }
 
@@ -119,11 +114,18 @@
         }
     }
 
+    public void prepareAutoUpdate() {
+        for (int i = 0; i < listeners.size(); i++) {
+            listeners.get(i).prepareAutoUpdate();
+        }
+    }
+
     public interface FolderListener {
         public void onAdd(ShortcutInfo item);
         public void onRemove(ShortcutInfo item);
         public void onTitleChanged(CharSequence title);
         public void onItemsChanged(boolean animate);
+        public void prepareAutoUpdate();
     }
 
     public boolean hasOption(int optionFlag) {
@@ -133,17 +135,17 @@
     /**
      * @param option flag to set or clear
      * @param isEnabled whether to set or clear the flag
-     * @param context if not null, save changes to the db.
+     * @param writer if not null, save changes to the db.
      */
-    public void setOption(int option, boolean isEnabled, Context context) {
+    public void setOption(int option, boolean isEnabled, ModelWriter writer) {
         int oldOptions = options;
         if (isEnabled) {
             options |= option;
         } else {
             options &= ~option;
         }
-        if (context != null && oldOptions != options) {
-            LauncherModel.updateItemInDatabase(context, this);
+        if (writer != null && oldOptions != options) {
+            writer.updateItemInDatabase(this);
         }
     }
 }
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 0fbbc19..47052a7 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -25,7 +25,6 @@
 import android.graphics.Rect;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
-import android.support.v4.content.ContextCompat;
 import android.support.v4.graphics.ColorUtils;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
@@ -38,11 +37,12 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dynamicui.ExtractedColors;
 import com.android.launcher3.logging.UserEventDispatcher;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+import com.android.launcher3.util.Themes;
 
 public class Hotseat extends FrameLayout
-        implements UserEventDispatcher.LaunchSourceProvider {
+        implements UserEventDispatcher.LogContainerProvider {
 
     private CellLayout mContent;
 
@@ -70,7 +70,7 @@
         mLauncher = Launcher.getLauncher(context);
         mHasVerticalHotseat = mLauncher.getDeviceProfile().isVerticalBarLayout();
         mBackgroundColor = ColorUtils.setAlphaComponent(
-                ContextCompat.getColor(context, R.color.all_apps_container_color), 0);
+                Themes.getAttrColor(context, android.R.attr.colorPrimary), 0);
         mBackground = new ColorDrawable(mBackgroundColor);
         setBackground(mBackground);
     }
@@ -113,12 +113,11 @@
         super.onFinishInflate();
         DeviceProfile grid = mLauncher.getDeviceProfile();
         mContent = (CellLayout) findViewById(R.id.layout);
-        if (grid.isLandscape && !grid.isLargeTablet) {
-            mContent.setGridSize(1, (int) grid.inv.numHotseatIcons);
+        if (grid.isVerticalBarLayout()) {
+            mContent.setGridSize(1, grid.inv.numHotseatIcons);
         } else {
-            mContent.setGridSize((int) grid.inv.numHotseatIcons, 1);
+            mContent.setGridSize(grid.inv.numHotseatIcons, 1);
         }
-        mContent.setIsHotseat(true);
 
         resetLayout();
     }
@@ -129,14 +128,15 @@
         if (!FeatureFlags.NO_ALL_APPS_ICON) {
             // Add the Apps button
             Context context = getContext();
-            int allAppsButtonRank = mLauncher.getDeviceProfile().inv.getAllAppsButtonRank();
+            DeviceProfile grid = mLauncher.getDeviceProfile();
+            int allAppsButtonRank = grid.inv.getAllAppsButtonRank();
 
             LayoutInflater inflater = LayoutInflater.from(context);
             TextView allAppsButton = (TextView)
                     inflater.inflate(R.layout.all_apps_button, mContent, false);
             Drawable d = context.getResources().getDrawable(R.drawable.all_apps_button_icon);
+            d.setBounds(0, 0, grid.iconSizePx, grid.iconSizePx);
 
-            mLauncher.resizeIconDrawable(d);
             int scaleDownPx = getResources().getDimensionPixelSize(R.dimen.all_apps_button_scale_down);
             Rect bounds = d.getBounds();
             d.setBounds(bounds.left, bounds.top + scaleDownPx / 2, bounds.right - scaleDownPx,
@@ -167,15 +167,15 @@
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         // We don't want any clicks to go through to the hotseat unless the workspace is in
         // the normal state or an accessible drag is in progress.
-        return mLauncher.getWorkspace().workspaceInModalState() &&
+        return !mLauncher.getWorkspace().workspaceIconsCanBeDragged() &&
                 !mLauncher.getAccessibilityDelegate().isInAccessibleDrag();
     }
 
     @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;
+        targetParent.containerType = ContainerType.HOTSEAT;
     }
 
     public void updateColor(ExtractedColors extractedColors, boolean animate) {
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index 5c86b6b..1217030 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -22,11 +22,11 @@
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherActivityInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.Resources;
-import android.content.res.TypedArray;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteException;
@@ -37,19 +37,25 @@
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.os.Build;
 import android.os.Handler;
+import android.os.Process;
 import android.os.SystemClock;
+import android.os.UserHandle;
+import android.support.annotation.NonNull;
 import android.text.TextUtils;
 import android.util.Log;
 
-import com.android.launcher3.compat.LauncherActivityInfoCompat;
 import com.android.launcher3.compat.LauncherAppsCompat;
-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.Preconditions;
+import com.android.launcher3.util.Provider;
 import com.android.launcher3.util.SQLiteCacheHelper;
+import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.Thunk;
 
 import java.util.Collections;
@@ -78,14 +84,14 @@
 
     @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 = "";
         public boolean isLowResIcon;
     }
 
-    private final HashMap<UserHandleCompat, Bitmap> mDefaultIcons = new HashMap<>();
+    private final HashMap<UserHandle, Bitmap> mDefaultIcons = new HashMap<>();
     @Thunk final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
 
     private final Context mContext;
@@ -122,15 +128,13 @@
         mLowResCanvas = new Canvas();
         mLowResPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
 
-        mIconProvider = IconProvider.loadByName(context.getString(R.string.icon_provider_class),
-                context);
-
+        mIconProvider = Utilities.getOverrideObject(
+                IconProvider.class, context, R.string.icon_provider_class);
         mWorkerHandler = new Handler(LauncherModel.getWorkerLooper());
 
-        mActivityBgColor = context.getResources().getColor(R.color.quantum_panel_bg_color);
-        TypedArray ta = context.obtainStyledAttributes(new int[]{R.attr.colorSecondary});
-        mPackageBgColor = ta.getColor(0, 0);
-        ta.recycle();
+        mActivityBgColor = Themes.getColorPrimary(context, R.style.LauncherTheme);
+        mPackageBgColor = Themes.getColorPrimary(context, R.style.WidgetContainerTheme);
+
         mLowResOptions = new BitmapFactory.Options();
         // Always prefer RGB_565 config for low res. If the bitmap has transparency, it will
         // automatically be loaded as ALPHA_8888.
@@ -185,22 +189,26 @@
         return getFullResDefaultActivityIcon();
     }
 
-    private Bitmap makeDefaultIcon(UserHandleCompat user) {
+    public Drawable getFullResIcon(LauncherActivityInfo info) {
+        return mIconProvider.getIcon(info, mIconDpi);
+    }
+
+    protected Bitmap makeDefaultIcon(UserHandle user) {
         Drawable unbadged = getFullResDefaultActivityIcon();
-        return Utilities.createBadgedIconBitmap(unbadged, user, mContext);
+        return LauncherIcons.createBadgedIconBitmap(unbadged, user, mContext, Build.VERSION_CODES.O);
     }
 
     /**
      * Remove any records for the supplied ComponentName.
      */
-    public synchronized void remove(ComponentName componentName, UserHandleCompat user) {
+    public synchronized void remove(ComponentName componentName, UserHandle user) {
         mCache.remove(new ComponentKey(componentName, user));
     }
 
     /**
      * Remove any records for the supplied package name from memory.
      */
-    private void removeFromMemCacheLocked(String packageName, UserHandleCompat user) {
+    private void removeFromMemCacheLocked(String packageName, UserHandle user) {
         HashSet<ComponentKey> forDeletion = new HashSet<ComponentKey>();
         for (ComponentKey key: mCache.keySet()) {
             if (key.componentName.getPackageName().equals(packageName)
@@ -216,14 +224,14 @@
     /**
      * Updates the entries related to the given package in memory and persistent DB.
      */
-    public synchronized void updateIconsForPkg(String packageName, UserHandleCompat user) {
+    public synchronized void updateIconsForPkg(String packageName, UserHandle user) {
         removeIconsForPkg(packageName, user);
         try {
             PackageInfo info = mPackageManager.getPackageInfo(packageName,
                     PackageManager.GET_UNINSTALLED_PACKAGES);
             long userSerial = mUserManager.getSerialNumberForUser(user);
-            for (LauncherActivityInfoCompat app : mLauncherApps.getActivityList(packageName, user)) {
-                addIconToDBAndMemCache(app, info, userSerial);
+            for (LauncherActivityInfo app : mLauncherApps.getActivityList(packageName, user)) {
+                addIconToDBAndMemCache(app, info, userSerial, false /*replace existing*/);
             }
         } catch (NameNotFoundException e) {
             Log.d(TAG, "Package not found", e);
@@ -234,7 +242,7 @@
     /**
      * Removes the entries related to the given package in memory and persistent DB.
      */
-    public synchronized void removeIconsForPkg(String packageName, UserHandleCompat user) {
+    public synchronized void removeIconsForPkg(String packageName, UserHandle user) {
         removeFromMemCacheLocked(packageName, user);
         long userSerial = mUserManager.getSerialNumberForUser(user);
         mIconDb.delete(
@@ -247,9 +255,9 @@
         mWorkerHandler.removeCallbacksAndMessages(ICON_UPDATE_TOKEN);
 
         mIconProvider.updateSystemStateString();
-        for (UserHandleCompat user : mUserManager.getUserProfiles()) {
+        for (UserHandle user : mUserManager.getUserProfiles()) {
             // Query for the set of apps
-            final List<LauncherActivityInfoCompat> apps = mLauncherApps.getActivityList(null, user);
+            final List<LauncherActivityInfo> apps = mLauncherApps.getActivityList(null, user);
             // Fail if we don't have any apps
             // TODO: Fix this. Only fail for the current user.
             if (apps == null || apps.isEmpty()) {
@@ -258,7 +266,7 @@
 
             // Update icon cache. This happens in segments and {@link #onPackageIconsUpdated}
             // is called by the icon cache when the job is complete.
-            updateDBIcons(user, apps, UserHandleCompat.myUserHandle().equals(user)
+            updateDBIcons(user, apps, Process.myUserHandle().equals(user)
                     ? ignorePackagesForMainUser : Collections.<String>emptySet());
         }
     }
@@ -268,7 +276,7 @@
      * the DB and are updated.
      * @return The set of packages for which icons have updated.
      */
-    private void updateDBIcons(UserHandleCompat user, List<LauncherActivityInfoCompat> apps,
+    private void updateDBIcons(UserHandle user, List<LauncherActivityInfo> apps,
             Set<String> ignorePackages) {
         long userSerial = mUserManager.getSerialNumberForUser(user);
         PackageManager pm = mContext.getPackageManager();
@@ -277,13 +285,13 @@
             pkgInfoMap.put(info.packageName, info);
         }
 
-        HashMap<ComponentName, LauncherActivityInfoCompat> componentMap = new HashMap<>();
-        for (LauncherActivityInfoCompat app : apps) {
+        HashMap<ComponentName, LauncherActivityInfo> componentMap = new HashMap<>();
+        for (LauncherActivityInfo app : apps) {
             componentMap.put(app.getComponentName(), app);
         }
 
         HashSet<Integer> itemsToRemove = new HashSet<Integer>();
-        Stack<LauncherActivityInfoCompat> appsToUpdate = new Stack<>();
+        Stack<LauncherActivityInfo> appsToUpdate = new Stack<>();
 
         Cursor c = null;
         try {
@@ -318,7 +326,7 @@
 
                 long updateTime = c.getLong(indexLastUpdate);
                 int version = c.getInt(indexVersion);
-                LauncherActivityInfoCompat app = componentMap.remove(component);
+                LauncherActivityInfo app = componentMap.remove(component);
                 if (version == info.versionCode && updateTime == info.lastUpdateTime &&
                         TextUtils.equals(c.getString(systemStateIndex),
                                 mIconProvider.getIconSystemState(info.packageName))) {
@@ -346,36 +354,21 @@
 
         // Insert remaining apps.
         if (!componentMap.isEmpty() || !appsToUpdate.isEmpty()) {
-            Stack<LauncherActivityInfoCompat> appsToAdd = new Stack<>();
+            Stack<LauncherActivityInfo> appsToAdd = new Stack<>();
             appsToAdd.addAll(componentMap.values());
             new SerializedIconUpdateTask(userSerial, pkgInfoMap,
                     appsToAdd, appsToUpdate).scheduleNext();
         }
     }
 
-    @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(LauncherActivityInfo app,
+            PackageInfo info, long userSerial, boolean replaceExisting) {
         final ComponentKey key = new ComponentKey(app.getComponentName(), app.getUser());
         CacheEntry entry = null;
         if (!replaceExisting) {
@@ -387,38 +380,46 @@
         }
         if (entry == null) {
             entry = new CacheEntry();
-            entry.icon = Utilities.createBadgedIconBitmap(
-                    mIconProvider.getIcon(app, mIconDpi), app.getUser(),
-                    mContext);
+            entry.icon = LauncherIcons.createBadgedIconBitmap(getFullResIcon(app), app.getUser(),
+                    mContext,  app.getApplicationInfo().targetSdkVersion);
         }
         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);
     }
 
     /**
      * Fetches high-res icon for the provided ItemInfo and updates the caller when done.
      * @return a request ID that can be used to cancel the request.
      */
-    public IconLoadRequest updateIconInBackground(final BubbleTextView caller, final ItemInfo info) {
+    public IconLoadRequest updateIconInBackground(final ItemInfoUpdateReceiver caller,
+            final ItemInfoWithIcon info) {
         Runnable request = new Runnable() {
 
             @Override
             public void run() {
-                if (info instanceof AppInfo) {
-                    getTitleAndIcon((AppInfo) info, null, false);
-                } else if (info instanceof ShortcutInfo) {
-                    ShortcutInfo st = (ShortcutInfo) info;
-                    getTitleAndIcon(st,
-                            st.promisedIntent != null ? st.promisedIntent : st.intent,
-                            st.user, false);
+                if (info instanceof AppInfo || info instanceof ShortcutInfo) {
+                    getTitleAndIcon(info, false);
                 } else if (info instanceof PackageItemInfo) {
-                    PackageItemInfo pti = (PackageItemInfo) info;
-                    getTitleAndIconForApp(pti, false);
+                    getTitleAndIconForApp((PackageItemInfo) info, false);
                 }
                 mMainThreadExecutor.execute(new Runnable() {
 
@@ -433,87 +434,55 @@
         return new IconLoadRequest(request, mWorkerHandler);
     }
 
-    private Bitmap getNonNullIcon(CacheEntry entry, UserHandleCompat user) {
-        return entry.icon == null ? getDefaultIcon(user) : entry.icon;
-    }
-
-    /**
-     * Fill in "application" with the icon and label for "info."
-     */
-    public synchronized void getTitleAndIcon(AppInfo application,
-            LauncherActivityInfoCompat info, boolean useLowResIcon) {
-        UserHandleCompat user = info == null ? application.user : info.getUser();
-        CacheEntry entry = cacheLocked(application.componentName, info, user,
-                false, useLowResIcon);
-        application.title = Utilities.trim(entry.title);
-        application.contentDescription = entry.contentDescription;
-        application.iconBitmap = getNonNullIcon(entry, user);
-        application.usingLowResIcon = entry.isLowResIcon;
-    }
-
     /**
      * Updates {@param application} only if a valid entry is found.
      */
     public synchronized void updateTitleAndIcon(AppInfo application) {
-        CacheEntry entry = cacheLocked(application.componentName, null, application.user,
-                false, application.usingLowResIcon);
+        CacheEntry entry = cacheLocked(application.componentName,
+                Provider.<LauncherActivityInfo>of(null),
+                application.user, false, application.usingLowResIcon);
         if (entry.icon != null && !isDefaultIcon(entry.icon, application.user)) {
-            application.title = Utilities.trim(entry.title);
-            application.contentDescription = entry.contentDescription;
-            application.iconBitmap = entry.icon;
-            application.usingLowResIcon = entry.isLowResIcon;
+            applyCacheEntry(entry, application);
         }
     }
 
     /**
-     * Returns a high res icon for the given intent and user
+     * Fill in {@param info} with the icon and label for {@param activityInfo}
      */
-    public synchronized Bitmap getIcon(Intent intent, UserHandleCompat user) {
-        ComponentName component = intent.getComponent();
-        // null info means not installed, but if we have a component from the intent then
-        // we should still look in the cache for restored app icons.
-        if (component == null) {
-            return getDefaultIcon(user);
-        }
-
-        LauncherActivityInfoCompat launcherActInfo = mLauncherApps.resolveActivity(intent, user);
-        CacheEntry entry = cacheLocked(component, launcherActInfo, user, true, false /* useLowRes */);
-        return entry.icon;
+    public synchronized void getTitleAndIcon(ItemInfoWithIcon info,
+            LauncherActivityInfo activityInfo, boolean useLowResIcon) {
+        // If we already have activity info, no need to use package icon
+        getTitleAndIcon(info, Provider.of(activityInfo), false, useLowResIcon);
     }
 
     /**
-     * Fill in {@param shortcutInfo} with the icon and label for {@param intent}. If the
+     * Fill in {@param info} with the icon and label. If the
      * corresponding activity is not found, it reverts to the package icon.
      */
-    public synchronized void getTitleAndIcon(ShortcutInfo shortcutInfo, Intent intent,
-            UserHandleCompat user, boolean useLowResIcon) {
-        ComponentName component = intent.getComponent();
+    public synchronized void getTitleAndIcon(ItemInfoWithIcon info, boolean useLowResIcon) {
         // null info means not installed, but if we have a component from the intent then
         // we should still look in the cache for restored app icons.
-        if (component == null) {
-            shortcutInfo.setIcon(getDefaultIcon(user));
-            shortcutInfo.title = "";
-            shortcutInfo.contentDescription = "";
-            shortcutInfo.usingFallbackIcon = true;
-            shortcutInfo.usingLowResIcon = false;
+        if (info.getTargetComponent() == null) {
+            info.iconBitmap = getDefaultIcon(info.user);
+            info.title = "";
+            info.contentDescription = "";
+            info.usingLowResIcon = false;
         } else {
-            LauncherActivityInfoCompat info = mLauncherApps.resolveActivity(intent, user);
-            getTitleAndIcon(shortcutInfo, component, info, user, true, useLowResIcon);
+            getTitleAndIcon(info, new ActivityInfoProvider(info.getIntent(), info.user),
+                    true, useLowResIcon);
         }
     }
 
     /**
      * Fill in {@param shortcutInfo} with the icon and label for {@param info}
      */
-    public synchronized void getTitleAndIcon(
-            ShortcutInfo shortcutInfo, ComponentName component, LauncherActivityInfoCompat info,
-            UserHandleCompat user, boolean usePkgIcon, boolean useLowResIcon) {
-        CacheEntry entry = cacheLocked(component, info, user, usePkgIcon, useLowResIcon);
-        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;
+    private synchronized void getTitleAndIcon(
+            @NonNull ItemInfoWithIcon infoInOut,
+            @NonNull Provider<LauncherActivityInfo> activityInfoProvider,
+            boolean usePkgIcon, boolean useLowResIcon) {
+        CacheEntry entry = cacheLocked(infoInOut.getTargetComponent(), activityInfoProvider,
+                infoInOut.user, usePkgIcon, useLowResIcon);
+        applyCacheEntry(entry, infoInOut);
     }
 
     /**
@@ -523,20 +492,24 @@
             PackageItemInfo infoInOut, boolean useLowResIcon) {
         CacheEntry entry = getEntryForPackageLocked(
                 infoInOut.packageName, infoInOut.user, useLowResIcon);
-        infoInOut.title = Utilities.trim(entry.title);
-        infoInOut.contentDescription = entry.contentDescription;
-        infoInOut.iconBitmap = getNonNullIcon(entry, infoInOut.user);
-        infoInOut.usingLowResIcon = entry.isLowResIcon;
+        applyCacheEntry(entry, infoInOut);
     }
 
-    public synchronized Bitmap getDefaultIcon(UserHandleCompat user) {
+    private void applyCacheEntry(CacheEntry entry, ItemInfoWithIcon info) {
+        info.title = Utilities.trim(entry.title);
+        info.contentDescription = entry.contentDescription;
+        info.iconBitmap = entry.icon == null ? getDefaultIcon(info.user) : entry.icon;
+        info.usingLowResIcon = entry.isLowResIcon;
+    }
+
+    public synchronized Bitmap getDefaultIcon(UserHandle user) {
         if (!mDefaultIcons.containsKey(user)) {
             mDefaultIcons.put(user, makeDefaultIcon(user));
         }
         return mDefaultIcons.get(user);
     }
 
-    public boolean isDefaultIcon(Bitmap icon, UserHandleCompat user) {
+    public boolean isDefaultIcon(Bitmap icon, UserHandle user) {
         return mDefaultIcons.get(user) == icon;
     }
 
@@ -544,8 +517,11 @@
      * 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,
-            UserHandleCompat user, boolean usePackageIcon, boolean useLowResIcon) {
+    protected CacheEntry cacheLocked(
+            @NonNull ComponentName componentName,
+            @NonNull Provider<LauncherActivityInfo> infoProvider,
+            UserHandle user, boolean usePackageIcon, boolean useLowResIcon) {
+        Preconditions.assertWorkerThread();
         ComponentKey cacheKey = new ComponentKey(componentName, user);
         CacheEntry entry = mCache.get(cacheKey);
         if (entry == null || (entry.isLowResIcon && !useLowResIcon)) {
@@ -553,11 +529,17 @@
             mCache.put(cacheKey, entry);
 
             // Check the DB first.
+            LauncherActivityInfo info = null;
+            boolean providerFetchedOnce = false;
+
             if (!getEntryFromDB(cacheKey, entry, useLowResIcon) || DEBUG_IGNORE_CACHE) {
+                info = infoProvider.get();
+                providerFetchedOnce = true;
+
                 if (info != null) {
-                    entry.icon = Utilities.createBadgedIconBitmap(
-                            mIconProvider.getIcon(info, mIconDpi), info.getUser(),
-                            mContext);
+                    entry.icon = LauncherIcons.createBadgedIconBitmap(
+                            getFullResIcon(info), info.getUser(), mContext,
+                            infoProvider.get().getApplicationInfo().targetSdkVersion);
                 } else {
                     if (usePackageIcon) {
                         CacheEntry packageEntry = getEntryForPackageLocked(
@@ -578,19 +560,30 @@
                 }
             }
 
-            if (TextUtils.isEmpty(entry.title) && info != null) {
-                entry.title = info.getLabel();
-                entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
+            if (TextUtils.isEmpty(entry.title)) {
+                if (info == null && !providerFetchedOnce) {
+                    info = infoProvider.get();
+                    providerFetchedOnce = true;
+                }
+                if (info != null) {
+                    entry.title = info.getLabel();
+                    entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
+                }
             }
         }
         return entry;
     }
 
+    public synchronized void clear() {
+        Preconditions.assertWorkerThread();
+        mIconDb.clear();
+    }
+
     /**
      * Adds a default package entry in the cache. This entry is not persisted and will be removed
      * when the cache is flushed.
      */
-    public synchronized void cachePackageInstallInfo(String packageName, UserHandleCompat user,
+    public synchronized void cachePackageInstallInfo(String packageName, UserHandle user,
             Bitmap icon, CharSequence title) {
         removeFromMemCacheLocked(packageName, user);
 
@@ -606,11 +599,11 @@
             entry.title = title;
         }
         if (icon != null) {
-            entry.icon = Utilities.createIconBitmap(icon, mContext);
+            entry.icon = LauncherIcons.createIconBitmap(icon, mContext);
         }
     }
 
-    private static ComponentKey getPackageKey(String packageName, UserHandleCompat user) {
+    private static ComponentKey getPackageKey(String packageName, UserHandle user) {
         ComponentName cn = new ComponentName(packageName, packageName + EMPTY_CLASS_NAME);
         return new ComponentKey(cn, user);
     }
@@ -619,8 +612,9 @@
      * Gets an entry for the package, which can be used as a fallback entry for various components.
      * This method is not thread safe, it must be called from a synchronized method.
      */
-    private CacheEntry getEntryForPackageLocked(String packageName, UserHandleCompat user,
+    private CacheEntry getEntryForPackageLocked(String packageName, UserHandle user,
             boolean useLowResIcon) {
+        Preconditions.assertWorkerThread();
         ComponentKey cacheKey = getPackageKey(packageName, user);
         CacheEntry entry = mCache.get(cacheKey);
 
@@ -631,7 +625,7 @@
             // Check the DB first.
             if (!getEntryFromDB(cacheKey, entry, useLowResIcon)) {
                 try {
-                    int flags = UserHandleCompat.myUserHandle().equals(user) ? 0 :
+                    int flags = Process.myUserHandle().equals(user) ? 0 :
                         PackageManager.GET_UNINSTALLED_PACKAGES;
                     PackageInfo info = mPackageManager.getPackageInfo(packageName, flags);
                     ApplicationInfo appInfo = info.applicationInfo;
@@ -641,8 +635,8 @@
 
                     // 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(
-                            appInfo.loadIcon(mPackageManager), user, mContext);
+                    Bitmap icon = LauncherIcons.createBadgedIconBitmap(
+                            appInfo.loadIcon(mPackageManager), user, mContext, appInfo.targetSdkVersion);
                     Bitmap lowResIcon =  generateLowResIcon(icon, mPackageBgColor);
                     entry.title = appInfo.loadLabel(mPackageManager);
                     entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
@@ -670,37 +664,6 @@
         return entry;
     }
 
-    /**
-     * Pre-load an icon into the persistent cache.
-     *
-     * <P>Queries for a component that does not exist in the package manager
-     * will be answered by the persistent cache.
-     *
-     * @param componentName the icon should be returned for this component
-     * @param icon the icon to be persisted
-     * @param dpi the native density of the icon
-     */
-    public void preloadIcon(ComponentName componentName, Bitmap icon, int dpi, String label,
-            long userSerial, InvariantDeviceProfile idp) {
-        // TODO rescale to the correct native DPI
-        try {
-            PackageManager packageManager = mContext.getPackageManager();
-            packageManager.getActivityIcon(componentName);
-            // component is present on the system already, do nothing
-            return;
-        } catch (PackageManager.NameNotFoundException e) {
-            // pass
-        }
-
-        icon = Bitmap.createScaledBitmap(icon, idp.iconBitmapSize, idp.iconBitmapSize, true);
-        Bitmap lowResIcon = generateLowResIcon(icon, Color.TRANSPARENT);
-        ContentValues values = newContentValues(icon, lowResIcon, label,
-                componentName.getPackageName());
-        values.put(IconDB.COLUMN_COMPONENT, componentName.flattenToString());
-        values.put(IconDB.COLUMN_USER, userSerial);
-        mIconDb.insertOrReplace(values);
-    }
-
     private boolean getEntryFromDB(ComponentKey cacheKey, CacheEntry entry, boolean lowRes) {
         Cursor c = null;
         try {
@@ -749,19 +712,19 @@
 
     /**
      * A runnable that updates invalid icons and adds missing icons in the DB for the provided
-     * LauncherActivityInfoCompat list. Items are updated/added one at a time, so that the
+     * LauncherActivityInfo list. Items are updated/added one at a time, so that the
      * worker thread doesn't get blocked.
      */
     @Thunk class SerializedIconUpdateTask implements Runnable {
         private final long mUserSerial;
         private final HashMap<String, PackageInfo> mPkgInfoMap;
-        private final Stack<LauncherActivityInfoCompat> mAppsToAdd;
-        private final Stack<LauncherActivityInfoCompat> mAppsToUpdate;
+        private final Stack<LauncherActivityInfo> mAppsToAdd;
+        private final Stack<LauncherActivityInfo> mAppsToUpdate;
         private final HashSet<String> mUpdatedPackages = new HashSet<String>();
 
         @Thunk SerializedIconUpdateTask(long userSerial, HashMap<String, PackageInfo> pkgInfoMap,
-                Stack<LauncherActivityInfoCompat> appsToAdd,
-                Stack<LauncherActivityInfoCompat> appsToUpdate) {
+                Stack<LauncherActivityInfo> appsToAdd,
+                Stack<LauncherActivityInfo> appsToUpdate) {
             mUserSerial = userSerial;
             mPkgInfoMap = pkgInfoMap;
             mAppsToAdd = appsToAdd;
@@ -771,31 +734,27 @@
         @Override
         public void run() {
             if (!mAppsToUpdate.isEmpty()) {
-                LauncherActivityInfoCompat app = mAppsToUpdate.pop();
+                LauncherActivityInfo 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(
+                    LauncherAppState.getInstance(mContext).getModel().onPackageIconsUpdated(
                             mUpdatedPackages, mUserManager.getUserForSerialNumber(mUserSerial));
                 }
 
                 // Let it run one more time.
                 scheduleNext();
             } else if (!mAppsToAdd.isEmpty()) {
-                LauncherActivityInfoCompat app = mAppsToAdd.pop();
+                LauncherActivityInfo 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()) {
@@ -810,7 +769,7 @@
     }
 
     private static final class IconDB extends SQLiteCacheHelper {
-        private final static int DB_VERSION = 10;
+        private final static int DB_VERSION = 13;
 
         private final static int RELEASE_VERSION = DB_VERSION +
                 (FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION ? 0 : 1);
@@ -891,4 +850,28 @@
             return null;
         }
     }
+
+    private class ActivityInfoProvider extends Provider<LauncherActivityInfo> {
+
+        private final Intent mIntent;
+        private final UserHandle mUser;
+
+        public ActivityInfoProvider(Intent intent, UserHandle user) {
+            mIntent = intent;
+            mUser = user;
+        }
+
+        @Override
+        public LauncherActivityInfo get() {
+            return mLauncherApps.resolveActivity(mIntent, mUser);
+        }
+    }
+
+    /**
+     * Interface for receiving itemInfo with high-res icon.
+     */
+    public interface ItemInfoUpdateReceiver {
+
+        void reapplyItemInfo(ItemInfoWithIcon info);
+    }
 }
diff --git a/src/com/android/launcher3/IconProvider.java b/src/com/android/launcher3/IconProvider.java
index 0a273bb..a5d3990 100644
--- a/src/com/android/launcher3/IconProvider.java
+++ b/src/com/android/launcher3/IconProvider.java
@@ -1,13 +1,8 @@
 package com.android.launcher3;
 
-import android.content.Context;
+import android.content.pm.LauncherActivityInfo;
 import android.graphics.drawable.Drawable;
-import android.text.TextUtils;
-import android.util.Log;
 
-import com.android.launcher3.compat.LauncherActivityInfoCompat;
-
-import java.lang.reflect.InvocationTargetException;
 import java.util.Locale;
 
 public class IconProvider {
@@ -21,19 +16,6 @@
         updateSystemStateString();
     }
 
-    public static IconProvider loadByName(String className, Context context) {
-        if (TextUtils.isEmpty(className)) return new IconProvider();
-        if (DBG) Log.d(TAG, "Loading IconProvider: " + className);
-        try {
-            Class<?> cls = Class.forName(className);
-            return (IconProvider) cls.getDeclaredConstructor(Context.class).newInstance(context);
-        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException
-                | ClassCastException | NoSuchMethodException | InvocationTargetException e) {
-            Log.e(TAG, "Bad IconProvider class", e);
-            return new IconProvider();
-        }
-    }
-
     public void updateSystemStateString() {
         mSystemState = Locale.getDefault().toString();
     }
@@ -43,7 +25,7 @@
     }
 
 
-    public Drawable getIcon(LauncherActivityInfoCompat info, int iconDpi) {
+    public Drawable getIcon(LauncherActivityInfo info, int iconDpi) {
         return info.getIcon(iconDpi);
     }
 }
diff --git a/src/com/android/launcher3/InfoDropTarget.java b/src/com/android/launcher3/InfoDropTarget.java
index 398c9d2..0608fdd 100644
--- a/src/com/android/launcher3/InfoDropTarget.java
+++ b/src/com/android/launcher3/InfoDropTarget.java
@@ -18,14 +18,16 @@
 
 import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
-import android.content.ContentResolver;
 import android.content.Context;
+import android.graphics.Rect;
+import android.os.Bundle;
 import android.provider.Settings;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.widget.Toast;
 
 import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.util.Themes;
 
 public class InfoDropTarget extends UninstallDropTarget {
 
@@ -43,13 +45,13 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         // Get the hover color
-        mHoverColor = Utilities.getColorAccent(getContext());
+        mHoverColor = Themes.getColorAccent(getContext());
 
         setDrawable(R.drawable.ic_info_launcher);
     }
 
     @Override
-    void completeDrop(DragObject d) {
+    public void completeDrop(DragObject d) {
         DropTargetResultCallback callback = d.dragSource instanceof DropTargetResultCallback
                 ? (DropTargetResultCallback) d.dragSource : null;
         startDetailsActivityForInfo(d.dragInfo, mLauncher, callback);
@@ -60,6 +62,11 @@
      */
     public static boolean startDetailsActivityForInfo(
             ItemInfo info, Launcher launcher, DropTargetResultCallback callback) {
+        return startDetailsActivityForInfo(info, launcher, callback, null, null);
+    }
+
+    public static boolean startDetailsActivityForInfo(ItemInfo info, Launcher launcher,
+            DropTargetResultCallback callback, Rect sourceBounds, Bundle opts) {
         boolean result = false;
         ComponentName componentName = null;
         if (info instanceof AppInfo) {
@@ -74,7 +81,7 @@
         if (componentName != null) {
             try {
                 LauncherAppsCompat.getInstance(launcher)
-                        .showAppDetailsForProfile(componentName, info.user);
+                        .showAppDetailsForProfile(componentName, info.user, sourceBounds, opts);
                 result = true;
             } catch (SecurityException | ActivityNotFoundException e) {
                 Toast.makeText(launcher, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
@@ -90,17 +97,21 @@
 
     @Override
     protected boolean supportsDrop(DragSource source, ItemInfo info) {
-        return source.supportsAppInfoDropTarget() && supportsDrop(info);
+        return source.supportsAppInfoDropTarget() && supportsDrop(getContext(), info);
     }
 
-    public static boolean supportsDrop(ItemInfo info) {
+    public static boolean supportsDrop(Context context, ItemInfo info) {
         // Only show the App Info drop target if developer settings are enabled.
-        ContentResolver resolver = LauncherAppState.getInstance().getContext().getContentResolver();
-        boolean developmentSettingsEnabled = Settings.Global.getInt(resolver,
+        boolean developmentSettingsEnabled = Settings.Global.getInt(context.getContentResolver(),
                 Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) == 1;
-        return developmentSettingsEnabled
-                && (info instanceof AppInfo || info instanceof ShortcutInfo
-                || info instanceof PendingAddItemInfo || info instanceof LauncherAppWidgetInfo)
-                && info.itemType != LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
+        if (!developmentSettingsEnabled) {
+            return false;
+        }
+        return info.itemType != LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT &&
+                (info instanceof AppInfo ||
+                (info instanceof ShortcutInfo && !((ShortcutInfo) info).isPromise()) ||
+                (info instanceof LauncherAppWidgetInfo &&
+                        ((LauncherAppWidgetInfo) info).restoreStatus == 0) ||
+                info instanceof PendingAddItemInfo);
     }
 }
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index d8e58d8..ce85570 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -16,35 +16,46 @@
 
 package com.android.launcher3;
 
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.pm.ActivityInfo;
+import android.content.pm.LauncherActivityInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
+import android.os.Looper;
+import android.os.Parcelable;
+import android.os.Process;
+import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Base64;
 import android.util.Log;
 
-import com.android.launcher3.compat.LauncherActivityInfoCompat;
 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.shortcuts.DeepShortcutManager;
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.util.Preconditions;
+import com.android.launcher3.util.Provider;
 import com.android.launcher3.util.Thunk;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.json.JSONStringer;
-import org.json.JSONTokener;
 
 import java.net.URISyntaxException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Set;
 
 public class InstallShortcutReceiver extends BroadcastReceiver {
@@ -61,6 +72,8 @@
     private static final String ICON_RESOURCE_PACKAGE_NAME_KEY = "iconResourcePackage";
 
     private static final String APP_SHORTCUT_TYPE_KEY = "isAppShortcut";
+    private static final String DEEPSHORTCUT_TYPE_KEY = "isDeepShortcut";
+    private static final String APP_WIDGET_TYPE_KEY = "isAppWidget";
     private static final String USER_HANDLE_KEY = "userHandle";
 
     // The set of shortcuts that are pending install
@@ -77,11 +90,7 @@
             String encoded = info.encodeToString();
             if (encoded != null) {
                 Set<String> strings = sharedPrefs.getStringSet(APPS_PENDING_INSTALL, null);
-                if (strings == null) {
-                    strings = new HashSet<String>(1);
-                } else {
-                    strings = new HashSet<String>(strings);
-                }
+                strings = (strings != null) ? new HashSet<>(strings) : new HashSet<String>(1);
                 strings.add(encoded);
                 sharedPrefs.edit().putStringSet(APPS_PENDING_INSTALL, strings).apply();
             }
@@ -89,7 +98,7 @@
     }
 
     public static void removeFromInstallQueue(Context context, HashSet<String> packageNames,
-            UserHandleCompat user) {
+            UserHandle user) {
         if (packageNames.isEmpty()) {
             return;
         }
@@ -100,32 +109,37 @@
                 Log.d(TAG, "APPS_PENDING_INSTALL: " + strings
                         + ", removing packages: " + packageNames);
             }
-            if (strings != null) {
-                Set<String> newStrings = new HashSet<String>(strings);
-                Iterator<String> newStringsIter = newStrings.iterator();
-                while (newStringsIter.hasNext()) {
-                    String encoded = newStringsIter.next();
-                    PendingInstallShortcutInfo info = decode(encoded, context);
-                    if (info == null || (packageNames.contains(info.getTargetPackage())
-                            && user.equals(info.user))) {
+            if (Utilities.isEmpty(strings)) {
+                return;
+            }
+            Set<String> newStrings = new HashSet<>(strings);
+            Iterator<String> newStringsIter = newStrings.iterator();
+            while (newStringsIter.hasNext()) {
+                String encoded = newStringsIter.next();
+                try {
+                    Decoder decoder = new Decoder(encoded, context);
+                    if (packageNames.contains(getIntentPackage(decoder.launcherIntent)) &&
+                            user.equals(decoder.user)) {
                         newStringsIter.remove();
                     }
+                } catch (JSONException | URISyntaxException e) {
+                    Log.d(TAG, "Exception reading shortcut to add: " + e);
+                    newStringsIter.remove();
                 }
-                sp.edit().putStringSet(APPS_PENDING_INSTALL, newStrings).apply();
             }
+            sp.edit().putStringSet(APPS_PENDING_INSTALL, newStrings).apply();
         }
     }
 
-    private static ArrayList<PendingInstallShortcutInfo> getAndClearInstallQueue(
-            SharedPreferences sharedPrefs, Context context) {
+    private static ArrayList<PendingInstallShortcutInfo> getAndClearInstallQueue(Context context) {
+        SharedPreferences sharedPrefs = Utilities.getPrefs(context);
         synchronized(sLock) {
+            ArrayList<PendingInstallShortcutInfo> infos = new ArrayList<>();
             Set<String> strings = sharedPrefs.getStringSet(APPS_PENDING_INSTALL, null);
             if (DBG) Log.d(TAG, "Getting and clearing APPS_PENDING_INSTALL: " + strings);
             if (strings == null) {
-                return new ArrayList<PendingInstallShortcutInfo>();
+                return infos;
             }
-            ArrayList<PendingInstallShortcutInfo> infos =
-                new ArrayList<PendingInstallShortcutInfo>();
             for (String encoded : strings) {
                 PendingInstallShortcutInfo info = decode(encoded, context);
                 if (info != null) {
@@ -149,8 +163,8 @@
         if (info != null) {
             if (!info.isLauncherActivity()) {
                 // Since its a custom shortcut, verify that it is safe to launch.
-                if (!PackageManagerHelper.hasPermissionForActivity(
-                        context, info.launchIntent, null)) {
+                if (!new PackageManagerHelper(context).hasPermissionForActivity(
+                        info.launchIntent, null)) {
                     // Target cannot be launched, or requires some special permission to launch
                     Log.e(TAG, "Ignoring malicious intent " + info.launchIntent.toUri(0));
                     return;
@@ -181,7 +195,8 @@
             return null;
         }
 
-        PendingInstallShortcutInfo info = new PendingInstallShortcutInfo(data, context);
+        PendingInstallShortcutInfo info = new PendingInstallShortcutInfo(
+                data, Process.myUserHandle(), context);
         if (info.launchIntent == null || info.label == null) {
             if (DBG) Log.e(TAG, "Invalid install shortcut intent");
             return null;
@@ -192,12 +207,45 @@
 
     public static ShortcutInfo fromShortcutIntent(Context context, Intent data) {
         PendingInstallShortcutInfo info = createPendingInfo(context, data);
-        return info == null ? null : info.getShortcutInfo();
+        return info == null ? null : (ShortcutInfo) info.getItemInfo();
+    }
+
+    public static void queueShortcut(ShortcutInfoCompat info, Context context) {
+        queuePendingShortcutInfo(new PendingInstallShortcutInfo(info, context), context);
+    }
+
+    public static void queueWidget(AppWidgetProviderInfo info, int widgetId, Context context) {
+        queuePendingShortcutInfo(new PendingInstallShortcutInfo(info, widgetId, context), context);
+    }
+
+    public static void queueActivityInfo(LauncherActivityInfo activity, Context context) {
+        queuePendingShortcutInfo(new PendingInstallShortcutInfo(activity, context), context);
+    }
+
+    public static HashSet<ShortcutKey> getPendingShortcuts(Context context) {
+        HashSet<ShortcutKey> result = new HashSet<>();
+
+        Set<String> strings = Utilities.getPrefs(context).getStringSet(APPS_PENDING_INSTALL, null);
+        if (Utilities.isEmpty(strings)) {
+            return result;
+        }
+
+        for (String encoded : strings) {
+            try {
+                Decoder decoder = new Decoder(encoded, context);
+                if (decoder.optBoolean(DEEPSHORTCUT_TYPE_KEY)) {
+                    result.add(ShortcutKey.fromIntent(decoder.launcherIntent, decoder.user));
+                }
+            } catch (JSONException | URISyntaxException e) {
+                Log.d(TAG, "Exception reading shortcut to add: " + e);
+            }
+        }
+        return result;
     }
 
     private static void queuePendingShortcutInfo(PendingInstallShortcutInfo info, Context context) {
         // Queue the item up for adding if launcher has not loaded properly yet
-        LauncherAppState app = LauncherAppState.getInstance();
+        LauncherAppState app = LauncherAppState.getInstance(context);
         boolean launcherNotLoaded = app.getModel().getCallback() == null;
 
         addToInstallQueue(Utilities.getPrefs(context), info);
@@ -213,35 +261,12 @@
         mUseInstallQueue = false;
         flushInstallQueue(context);
     }
+
     static void flushInstallQueue(Context context) {
-        SharedPreferences sp = Utilities.getPrefs(context);
-        ArrayList<PendingInstallShortcutInfo> installQueue = getAndClearInstallQueue(sp, context);
-        if (!installQueue.isEmpty()) {
-            Iterator<PendingInstallShortcutInfo> iter = installQueue.iterator();
-            ArrayList<ItemInfo> addShortcuts = new ArrayList<ItemInfo>();
-            while (iter.hasNext()) {
-                final PendingInstallShortcutInfo pendingInfo = iter.next();
-
-                // If the intent specifies a package, make sure the package exists
-                String packageName = pendingInfo.getTargetPackage();
-                if (!TextUtils.isEmpty(packageName)) {
-                    UserHandleCompat myUserHandle = UserHandleCompat.myUserHandle();
-                    if (!LauncherModel.isValidPackage(context, packageName, myUserHandle)) {
-                        if (DBG) Log.d(TAG, "Ignoring shortcut for absent package: "
-                                + pendingInfo.launchIntent);
-                        continue;
-                    }
-                }
-
-                // Generate a shortcut info to add into the model
-                addShortcuts.add(pendingInfo.getShortcutInfo());
-            }
-
-            // Add the new apps to the model and bind them
-            if (!addShortcuts.isEmpty()) {
-                LauncherAppState app = LauncherAppState.getInstance();
-                app.getModel().addAndBindAddedWorkspaceItems(context, addShortcuts);
-            }
+        ArrayList<PendingInstallShortcutInfo> items = getAndClearInstallQueue(context);
+        if (!items.isEmpty()) {
+            LauncherAppState.getInstance(context).getModel().addAndBindAddedWorkspaceItems(
+                    new LazyShortcutsProvider(context.getApplicationContext(), items));
         }
     }
 
@@ -264,43 +289,86 @@
 
     private static class PendingInstallShortcutInfo {
 
-        final LauncherActivityInfoCompat activityInfo;
+        final LauncherActivityInfo activityInfo;
+        final ShortcutInfoCompat shortcutInfo;
+        final AppWidgetProviderInfo providerInfo;
 
         final Intent data;
         final Context mContext;
         final Intent launchIntent;
         final String label;
-        final UserHandleCompat user;
+        final UserHandle user;
 
         /**
          * Initializes a PendingInstallShortcutInfo received from a different app.
          */
-        public PendingInstallShortcutInfo(Intent data, Context context) {
+        public PendingInstallShortcutInfo(Intent data, UserHandle user, Context context) {
+            activityInfo = null;
+            shortcutInfo = null;
+            providerInfo = null;
+
             this.data = data;
+            this.user = user;
             mContext = context;
 
             launchIntent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
             label = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
-            user = UserHandleCompat.myUserHandle();
-            activityInfo = null;
+
         }
 
         /**
          * Initializes a PendingInstallShortcutInfo to represent a launcher target.
          */
-        public PendingInstallShortcutInfo(LauncherActivityInfoCompat info, Context context) {
-            this.data = null;
-            mContext = context;
+        public PendingInstallShortcutInfo(LauncherActivityInfo info, Context context) {
             activityInfo = info;
-            user = info.getUser();
+            shortcutInfo = null;
+            providerInfo = null;
 
-            launchIntent = AppInfo.makeLaunchIntent(context, info, user);
+            data = null;
+            user = info.getUser();
+            mContext = context;
+
+            launchIntent = AppInfo.makeLaunchIntent(info);
             label = info.getLabel().toString();
         }
 
+        /**
+         * Initializes a PendingInstallShortcutInfo to represent a launcher target.
+         */
+        public PendingInstallShortcutInfo(ShortcutInfoCompat info, Context context) {
+            activityInfo = null;
+            shortcutInfo = info;
+            providerInfo = null;
+
+            data = null;
+            mContext = context;
+            user = info.getUserHandle();
+
+            launchIntent = info.makeIntent();
+            label = info.getShortLabel().toString();
+        }
+
+        /**
+         * Initializes a PendingInstallShortcutInfo to represent a launcher target.
+         */
+        public PendingInstallShortcutInfo(
+                AppWidgetProviderInfo info, int widgetId, Context context) {
+            activityInfo = null;
+            shortcutInfo = null;
+            providerInfo = info;
+
+            data = null;
+            mContext = context;
+            user = info.getProfile();
+
+            launchIntent = new Intent().setComponent(info.provider)
+                    .putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
+            label = info.label;
+        }
+
         public String encodeToString() {
-            if (activityInfo != null) {
-                try {
+            try {
+                if (activityInfo != null) {
                     // If it a launcher target, we only need component name, and user to
                     // recreate this.
                     return new JSONStringer()
@@ -310,30 +378,45 @@
                         .key(USER_HANDLE_KEY).value(UserManagerCompat.getInstance(mContext)
                                 .getSerialNumberForUser(user))
                         .endObject().toString();
-                } catch (JSONException e) {
-                    Log.d(TAG, "Exception when adding shortcut: " + e);
-                    return null;
+                } else if (shortcutInfo != null) {
+                    // If it a launcher target, we only need component name, and user to
+                    // recreate this.
+                    return new JSONStringer()
+                            .object()
+                            .key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0))
+                            .key(DEEPSHORTCUT_TYPE_KEY).value(true)
+                            .key(USER_HANDLE_KEY).value(UserManagerCompat.getInstance(mContext)
+                                    .getSerialNumberForUser(user))
+                            .endObject().toString();
+                } else if (providerInfo != null) {
+                    // If it a launcher target, we only need component name, and user to
+                    // recreate this.
+                    return new JSONStringer()
+                            .object()
+                            .key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0))
+                            .key(APP_WIDGET_TYPE_KEY).value(true)
+                            .key(USER_HANDLE_KEY).value(UserManagerCompat.getInstance(mContext)
+                                    .getSerialNumberForUser(user))
+                            .endObject().toString();
                 }
-            }
 
-            if (launchIntent.getAction() == null) {
-                launchIntent.setAction(Intent.ACTION_VIEW);
-            } else if (launchIntent.getAction().equals(Intent.ACTION_MAIN) &&
-                    launchIntent.getCategories() != null &&
-                    launchIntent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
-                launchIntent.addFlags(
-                        Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
-            }
+                if (launchIntent.getAction() == null) {
+                    launchIntent.setAction(Intent.ACTION_VIEW);
+                } else if (launchIntent.getAction().equals(Intent.ACTION_MAIN) &&
+                        launchIntent.getCategories() != null &&
+                        launchIntent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
+                    launchIntent.addFlags(
+                            Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+                }
 
-            // This name is only used for comparisons and notifications, so fall back to activity
-            // name if not supplied
-            String name = ensureValidName(mContext, launchIntent, label).toString();
-            Bitmap icon = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
-            Intent.ShortcutIconResource iconResource =
-                data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
+                // This name is only used for comparisons and notifications, so fall back to activity
+                // name if not supplied
+                String name = ensureValidName(mContext, launchIntent, label).toString();
+                Bitmap icon = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
+                Intent.ShortcutIconResource iconResource =
+                    data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
 
-            // Only encode the parameters which are supported by the API.
-            try {
+                // Only encode the parameters which are supported by the API.
                 JSONStringer json = new JSONStringer()
                     .object()
                     .key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0))
@@ -352,57 +435,100 @@
                 return json.endObject().toString();
             } catch (JSONException e) {
                 Log.d(TAG, "Exception when adding shortcut: " + e);
+                return null;
             }
-            return null;
         }
 
-        public ShortcutInfo getShortcutInfo() {
+        public ItemInfo getItemInfo() {
             if (activityInfo != null) {
-                return new ShortcutInfo(activityInfo, mContext);
+                AppInfo appInfo = new AppInfo(mContext, activityInfo, user);
+                final LauncherAppState app = LauncherAppState.getInstance(mContext);
+                // Set default values until proper values is loaded.
+                appInfo.title = "";
+                appInfo.iconBitmap = app.getIconCache().getDefaultIcon(user);
+                final ShortcutInfo si = appInfo.makeShortcut();
+                if (Looper.myLooper() == LauncherModel.getWorkerLooper()) {
+                    app.getIconCache().getTitleAndIcon(si, activityInfo, false /* useLowResIcon */);
+                } else {
+                    app.getModel().updateAndBindShortcutInfo(new Provider<ShortcutInfo>() {
+                        @Override
+                        public ShortcutInfo get() {
+                            app.getIconCache().getTitleAndIcon(
+                                    si, activityInfo, false /* useLowResIcon */);
+                            return si;
+                        }
+                    });
+                }
+                return si;
+            } else if (shortcutInfo != null) {
+                ShortcutInfo si = new ShortcutInfo(shortcutInfo, mContext);
+                si.iconBitmap = LauncherIcons.createShortcutIcon(shortcutInfo, mContext);
+                return si;
+            } else if (providerInfo != null) {
+                LauncherAppWidgetProviderInfo info = LauncherAppWidgetProviderInfo
+                        .fromProviderInfo(mContext, providerInfo);
+                LauncherAppWidgetInfo widgetInfo = new LauncherAppWidgetInfo(
+                        launchIntent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 0),
+                        info.provider);
+                InvariantDeviceProfile idp = LauncherAppState.getIDP(mContext);
+                widgetInfo.minSpanX = info.minSpanX;
+                widgetInfo.minSpanY = info.minSpanY;
+                widgetInfo.spanX = Math.min(info.spanX, idp.numColumns);
+                widgetInfo.spanY = Math.min(info.spanY, idp.numRows);
+                return widgetInfo;
             } else {
-                return LauncherAppState.getInstance().getModel().infoFromShortcutIntent(mContext, data);
+                return createShortcutInfo(data, LauncherAppState.getInstance(mContext));
             }
         }
 
-        public String getTargetPackage() {
-            String packageName = launchIntent.getPackage();
-            if (packageName == null) {
-                packageName = launchIntent.getComponent() == null ? null :
-                    launchIntent.getComponent().getPackageName();
-            }
-            return packageName;
-        }
-
         public boolean isLauncherActivity() {
             return activityInfo != null;
         }
     }
 
+    private static String getIntentPackage(Intent intent) {
+        return intent.getComponent() == null
+                ? intent.getPackage() : intent.getComponent().getPackageName();
+    }
+
     private static PendingInstallShortcutInfo decode(String encoded, Context context) {
         try {
-            JSONObject object = (JSONObject) new JSONTokener(encoded).nextValue();
-            Intent launcherIntent = Intent.parseUri(object.getString(LAUNCH_INTENT_KEY), 0);
-
-            if (object.optBoolean(APP_SHORTCUT_TYPE_KEY)) {
-                // The is an internal launcher target shortcut.
-                UserHandleCompat user = UserManagerCompat.getInstance(context)
-                        .getUserForSerialNumber(object.getLong(USER_HANDLE_KEY));
-                if (user == null) {
+            Decoder decoder = new Decoder(encoded, context);
+            if (decoder.optBoolean(APP_SHORTCUT_TYPE_KEY)) {
+                LauncherActivityInfo info = LauncherAppsCompat.getInstance(context)
+                        .resolveActivity(decoder.launcherIntent, decoder.user);
+                return info == null ? null : new PendingInstallShortcutInfo(info, context);
+            } else if (decoder.optBoolean(DEEPSHORTCUT_TYPE_KEY)) {
+                DeepShortcutManager sm = DeepShortcutManager.getInstance(context);
+                List<ShortcutInfoCompat> si = sm.queryForFullDetails(
+                        decoder.launcherIntent.getPackage(),
+                        Arrays.asList(decoder.launcherIntent.getStringExtra(
+                                ShortcutInfoCompat.EXTRA_SHORTCUT_ID)),
+                        decoder.user);
+                if (si.isEmpty()) {
+                    return null;
+                } else {
+                    return new PendingInstallShortcutInfo(si.get(0), context);
+                }
+            } else if (decoder.optBoolean(APP_WIDGET_TYPE_KEY)) {
+                int widgetId = decoder.launcherIntent
+                        .getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 0);
+                AppWidgetProviderInfo info = AppWidgetManager.getInstance(context)
+                        .getAppWidgetInfo(widgetId);
+                if (info == null || !info.provider.equals(decoder.launcherIntent.getComponent()) ||
+                        !info.getProfile().equals(decoder.user)) {
                     return null;
                 }
-
-                LauncherActivityInfoCompat info = LauncherAppsCompat.getInstance(context)
-                        .resolveActivity(launcherIntent, user);
-                return info == null ? null : new PendingInstallShortcutInfo(info, context);
+                return new PendingInstallShortcutInfo(info, widgetId, context);
             }
 
             Intent data = new Intent();
-            data.putExtra(Intent.EXTRA_SHORTCUT_INTENT, launcherIntent);
-            data.putExtra(Intent.EXTRA_SHORTCUT_NAME, object.getString(NAME_KEY));
+            data.putExtra(Intent.EXTRA_SHORTCUT_INTENT, decoder.launcherIntent);
+            data.putExtra(Intent.EXTRA_SHORTCUT_NAME, decoder.getString(NAME_KEY));
 
-            String iconBase64 = object.optString(ICON_KEY);
-            String iconResourceName = object.optString(ICON_RESOURCE_NAME_KEY);
-            String iconResourcePackageName = object.optString(ICON_RESOURCE_PACKAGE_NAME_KEY);
+            String iconBase64 = decoder.optString(ICON_KEY);
+            String iconResourceName = decoder.optString(ICON_RESOURCE_NAME_KEY);
+            String iconResourcePackageName = decoder.optString(ICON_RESOURCE_PACKAGE_NAME_KEY);
             if (iconBase64 != null && !iconBase64.isEmpty()) {
                 byte[] iconArray = Base64.decode(iconBase64, Base64.DEFAULT);
                 Bitmap b = BitmapFactory.decodeByteArray(iconArray, 0, iconArray.length);
@@ -415,13 +541,29 @@
                 data.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource);
             }
 
-            return new PendingInstallShortcutInfo(data, context);
+            return new PendingInstallShortcutInfo(data, decoder.user, context);
         } catch (JSONException | URISyntaxException e) {
             Log.d(TAG, "Exception reading shortcut to add: " + e);
         }
         return null;
     }
 
+    private static class Decoder extends JSONObject {
+        public final Intent launcherIntent;
+        public final UserHandle user;
+
+        private Decoder(String encoded, Context context) throws JSONException, URISyntaxException {
+            super(encoded);
+            launcherIntent = Intent.parseUri(getString(LAUNCH_INTENT_KEY), 0);
+            user = has(USER_HANDLE_KEY) ? UserManagerCompat.getInstance(context)
+                    .getUserForSerialNumber(getLong(USER_HANDLE_KEY))
+                    : Process.myUserHandle();
+            if (user == null) {
+                throw new JSONException("Invalid user");
+            }
+        }
+    }
+
     /**
      * Tries to create a new PendingInstallShortcutInfo which represents the same target,
      * but is an app target and not a shortcut.
@@ -433,22 +575,90 @@
             // 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);
-
+        LauncherActivityInfo 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);
     }
+
+    private static class LazyShortcutsProvider extends Provider<List<ItemInfo>> {
+
+        private final Context mContext;
+        private final ArrayList<PendingInstallShortcutInfo> mPendingItems;
+
+        public LazyShortcutsProvider(Context context, ArrayList<PendingInstallShortcutInfo> items) {
+            mContext = context;
+            mPendingItems = items;
+        }
+
+        /**
+         * This must be called on the background thread as this requires multiple calls to
+         * packageManager and icon cache.
+         */
+        @Override
+        public ArrayList<ItemInfo> get() {
+            Preconditions.assertNonUiThread();
+            ArrayList<ItemInfo> installQueue = new ArrayList<>();
+            LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(mContext);
+            for (PendingInstallShortcutInfo pendingInfo : mPendingItems) {
+                // If the intent specifies a package, make sure the package exists
+                String packageName = getIntentPackage(pendingInfo.launchIntent);
+                if (!TextUtils.isEmpty(packageName) && !launcherApps.isPackageEnabledForProfile(
+                        packageName, pendingInfo.user)) {
+                    if (DBG) Log.d(TAG, "Ignoring shortcut for absent package: "
+                            + pendingInfo.launchIntent);
+                    continue;
+                }
+
+                // Generate a shortcut info to add into the model
+                installQueue.add(pendingInfo.getItemInfo());
+            }
+            return installQueue;
+        }
+    }
+
+    private static ShortcutInfo createShortcutInfo(Intent data, LauncherAppState app) {
+        Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
+        String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
+        Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
+
+        if (intent == null) {
+            // If the intent is null, we can't construct a valid ShortcutInfo, so we return null
+            Log.e(TAG, "Can't construct ShorcutInfo with null intent");
+            return null;
+        }
+
+        final ShortcutInfo info = new ShortcutInfo();
+
+        // Only support intents for current user for now. Intents sent from other
+        // users wouldn't get here without intent forwarding anyway.
+        info.user = Process.myUserHandle();
+
+        if (bitmap instanceof Bitmap) {
+            info.iconBitmap = LauncherIcons.createIconBitmap((Bitmap) bitmap, app.getContext());
+        } else {
+            Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
+            if (extra instanceof Intent.ShortcutIconResource) {
+                info.iconResource = (Intent.ShortcutIconResource) extra;
+                info.iconBitmap = LauncherIcons.createIconBitmap(info.iconResource, app.getContext());
+            }
+        }
+        if (info.iconBitmap == null) {
+            info.iconBitmap = app.getIconCache().getDefaultIcon(info.user);
+        }
+
+        info.title = Utilities.trim(name);
+        info.contentDescription = UserManagerCompat.getInstance(app.getContext())
+                .getBadgedLabelForUser(info.title, info.user);
+        info.intent = intent;
+        return info;
+    }
+
 }
diff --git a/src/com/android/launcher3/InterruptibleInOutAnimator.java b/src/com/android/launcher3/InterruptibleInOutAnimator.java
index 29df38b..8501e24 100644
--- a/src/com/android/launcher3/InterruptibleInOutAnimator.java
+++ b/src/com/android/launcher3/InterruptibleInOutAnimator.java
@@ -48,7 +48,7 @@
     @Thunk int mDirection = STOPPED;
 
     public InterruptibleInOutAnimator(View view, long duration, float fromValue, float toValue) {
-        mAnimator = LauncherAnimUtils.ofFloat(view, fromValue, toValue).setDuration(duration);
+        mAnimator = LauncherAnimUtils.ofFloat(fromValue, toValue).setDuration(duration);
         mOriginalDuration = duration;
         mOriginalFromValue = fromValue;
         mOriginalToValue = toValue;
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 38545e2..9e214d1 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -18,6 +18,7 @@
 
 import android.annotation.TargetApi;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
 import android.graphics.Point;
@@ -85,10 +86,11 @@
      */
     public int numHotseatIcons;
     float hotseatIconSize;
+    public float hotseatScale;
     int defaultLayoutId;
 
-    DeviceProfile landscapeProfile;
-    DeviceProfile portraitProfile;
+    public DeviceProfile landscapeProfile;
+    public DeviceProfile portraitProfile;
 
     public Point defaultWallpaperSize;
 
@@ -117,6 +119,8 @@
         numHotseatIcons = hs;
         hotseatIconSize = his;
         defaultLayoutId = dlId;
+
+        hotseatScale = hotseatIconSize / iconSize;
     }
 
     @TargetApi(23)
@@ -158,6 +162,8 @@
         // Supported overrides: numRows, numColumns, iconSize
         applyPartnerDeviceProfileOverrides(context, dm);
 
+        hotseatScale = hotseatIconSize / iconSize;
+
         Point realSize = new Point();
         display.getRealSize(realSize);
         // The real size never changes. smallSide and largeSide will remain the
@@ -321,6 +327,11 @@
         return rank == getAllAppsButtonRank();
     }
 
+    public DeviceProfile getDeviceProfile(Context context) {
+        return context.getResources().getConfiguration().orientation
+                == Configuration.ORIENTATION_LANDSCAPE ? landscapeProfile : portraitProfile;
+    }
+
     private float weight(float x0, float y0, float x1, float y1, float pow) {
         float d = dist(x0, y0, x1, y1);
         if (Float.compare(d, 0f) == 0) {
diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java
index c0c22a3..0779a3d 100644
--- a/src/com/android/launcher3/ItemInfo.java
+++ b/src/com/android/launcher3/ItemInfo.java
@@ -18,23 +18,17 @@
 
 import android.content.ComponentName;
 import android.content.ContentValues;
-import android.content.Context;
 import android.content.Intent;
-import android.graphics.Bitmap;
+import android.os.Process;
+import android.os.UserHandle;
 
-import com.android.launcher3.compat.UserHandleCompat;
-import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.util.ContentWriter;
 
 /**
  * Represents an item in the launcher.
  */
 public class ItemInfo {
 
-    /**
-     * Intent extra to store the profile. Format: UserHandle
-     */
-    public static final String EXTRA_PROFILE = "profile";
-
     public static final int NO_ID = -1;
 
     /**
@@ -59,7 +53,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;
 
@@ -108,10 +102,10 @@
      */
     public CharSequence contentDescription;
 
-    public UserHandleCompat user;
+    public UserHandle user;
 
     public ItemInfo() {
-        user = UserHandleCompat.myUserHandle();
+        user = Process.myUserHandle();
     }
 
     ItemInfo(ItemInfo info) {
@@ -142,15 +136,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 +160,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);
-
+    public 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/ItemInfoWithIcon.java b/src/com/android/launcher3/ItemInfoWithIcon.java
new file mode 100644
index 0000000..1e020e2
--- /dev/null
+++ b/src/com/android/launcher3/ItemInfoWithIcon.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.graphics.Bitmap;
+
+/**
+ * Represents an ItemInfo which also holds an icon.
+ */
+public abstract class ItemInfoWithIcon extends ItemInfo {
+
+    /**
+     * A bitmap version of the application icon.
+     */
+    public Bitmap iconBitmap;
+
+    /**
+     * Indicates whether we're using a low res icon
+     */
+    public boolean usingLowResIcon;
+
+    protected ItemInfoWithIcon() { }
+
+    protected ItemInfoWithIcon(ItemInfoWithIcon info) {
+        super(info);
+        iconBitmap = info.iconBitmap;
+        usingLowResIcon = info.usingLowResIcon;
+    }
+}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 4672e08..eef578d 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -18,19 +18,15 @@
 
 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;
-import android.app.Activity;
 import android.app.ActivityOptions;
 import android.app.AlertDialog;
 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;
@@ -45,22 +41,20 @@
 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
-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.Process;
 import android.os.StrictMode;
 import android.os.SystemClock;
 import android.os.Trace;
 import android.os.UserHandle;
+import android.support.annotation.Nullable;
 import android.text.Selection;
 import android.text.SpannableStringBuilder;
 import android.text.TextUtils;
@@ -69,20 +63,19 @@
 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;
+import android.view.WindowManager;
 import android.view.accessibility.AccessibilityEvent;
 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,39 +85,50 @@
 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;
-import com.android.launcher3.compat.UserHandleCompat;
-import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.compat.PinItemRequestCompat;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.config.ProviderConfig;
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.dragndrop.PinItemDragListener;
 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.ModelWriter;
+import com.android.launcher3.model.PackageItemInfo;
+import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.notification.NotificationListener;
 import com.android.launcher3.pageindicators.PageIndicator;
+import com.android.launcher3.popup.PopupContainerWithArrow;
+import com.android.launcher3.popup.PopupDataProvider;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
-import com.android.launcher3.shortcuts.DeepShortcutsContainer;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
 import com.android.launcher3.util.ActivityResultInfo;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.PendingRequestArgs;
 import com.android.launcher3.util.TestingUtils;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.ViewOnDrawExecutor;
+import com.android.launcher3.widget.PendingAddShortcutInfo;
 import com.android.launcher3.widget.PendingAddWidgetInfo;
+import com.android.launcher3.widget.WidgetAddFlowHandler;
 import com.android.launcher3.widget.WidgetHostViewLoader;
 import com.android.launcher3.widget.WidgetsContainerView;
 
@@ -132,15 +136,15 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Default launcher application.
  */
-public class Launcher extends Activity
+public class Launcher extends BaseActivity
         implements LauncherExterns, View.OnClickListener, OnLongClickListener,
                    LauncherModel.Callbacks, View.OnTouchListener, LauncherProviderChangeListener,
                    AccessibilityManager.AccessibilityStateChangeListener {
@@ -170,17 +174,15 @@
      */
     protected static final int REQUEST_LAST = 100;
 
-    // To turn on these properties, type
-    // adb shell setprop logTap.tag.PROPERTY_NAME [VERBOSE | SUPPRESS]
-    static final String DUMP_STATE_PROPERTY = "launcher_dump_state";
+    private static final int SOFT_INPUT_MODE_DEFAULT =
+            WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
+    private static final int SOFT_INPUT_MODE_ALL_APPS =
+            WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 
     // The Intent extra that defines whether to ignore the launch animation
     static final String INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION =
             "com.android.launcher3.intent.extra.shortcut.INGORE_LAUNCH_ANIMATION";
 
-    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
@@ -201,7 +203,7 @@
 
     private boolean mIsSafeModeEnabled;
 
-    static final int APPWIDGET_HOST_ID = 1024;
+    public static final int APPWIDGET_HOST_ID = 1024;
     public static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT = 500;
     private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500;
     private static final int ACTIVITY_START_DELAY = 1000;
@@ -211,18 +213,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 +240,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.
@@ -270,30 +259,19 @@
     private ViewOnDrawExecutor mPendingExecutor;
 
     private LauncherModel mModel;
+    private ModelWriter mModelWriter;
     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 PopupDataProvider mPopupDataProvider;
 
     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,15 +282,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;
 
     // This is set to the view that launched the activity that navigated the user away from
@@ -350,7 +319,7 @@
      */
     private PendingRequestArgs mPendingRequestArgs;
 
-    private UserEventDispatcher mUserEventDispatcher;
+    private float mLastDispatchTouchEventX = 0.0f;
 
     public ViewGroupFocusHelper mFocusHandler;
     private boolean mRotationEnabled = false;
@@ -392,17 +361,21 @@
 
         super.onCreate(savedInstanceState);
 
-        LauncherAppState app = LauncherAppState.getInstance();
+        LauncherAppState app = LauncherAppState.getInstance(this);
 
         // Load configuration-specific DeviceProfile
-        mDeviceProfile = getResources().getConfiguration().orientation
-                == Configuration.ORIENTATION_LANDSCAPE ?
-                app.getInvariantDeviceProfile().landscapeProfile
-                : app.getInvariantDeviceProfile().portraitProfile;
+        mDeviceProfile = app.getInvariantDeviceProfile().getDeviceProfile(this);
+        if (isInMultiWindowModeCompat()) {
+            Display display = getWindowManager().getDefaultDisplay();
+            Point mwSize = new Point();
+            display.getSize(mwSize);
+            mDeviceProfile = mDeviceProfile.getMultiWindowProfile(this, mwSize);
+        }
 
         mSharedPrefs = Utilities.getPrefs(this);
         mIsSafeModeEnabled = getPackageManager().isSafeMode();
         mModel = app.setLauncher(this);
+        mModelWriter = mModel.getWriter(mDeviceProfile.isVerticalBarLayout());
         mIconCache = app.getIconCache();
         mAccessibilityDelegate = new LauncherAccessibilityDelegate(this);
 
@@ -420,20 +393,21 @@
         // LauncherModel load.
         mPaused = false;
 
-        setContentView(R.layout.launcher);
+        mLauncherView = getLayoutInflater().inflate(R.layout.launcher, null);
 
         setupViews();
         mDeviceProfile.layout(this, false /* notifyListeners */);
         mExtractedColors = new ExtractedColors();
         loadExtractedColorsAndColorItems();
 
+        mPopupDataProvider = new PopupDataProvider(this);
+
         ((AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE))
                 .addAccessibilityStateChangeListener(this);
 
         lockAllApps();
 
-        mSavedState = savedInstanceState;
-        restoreState(mSavedState);
+        restoreState(savedInstanceState);
 
         if (LauncherAppState.PROFILE_STARTUP) {
             Trace.endSection();
@@ -441,11 +415,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 +434,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.
@@ -465,47 +443,82 @@
             mSharedPrefs.registerOnSharedPreferenceChangeListener(mRotationPrefChangeHandler);
         }
 
+        if (PinItemDragListener.handleDragRequest(this, getIntent())) {
+            // Temporarily enable the rotation
+            mRotationEnabled = true;
+        }
+
         // On large interfaces, or on devices that a user has specifically enabled screen rotation,
         // 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();
     }
 
-    private void loadExtractedColorsAndColorItems() {
-        // TODO: do this in pre-N as well, once the extraction part is complete.
-        if (Utilities.isNycOrAbove()) {
-            mExtractedColors.load(this);
-            mHotseat.updateColor(mExtractedColors, !mPaused);
-            mWorkspace.getPageIndicator().updateColor(mExtractedColors);
-            // It's possible that All Apps is visible when this is run,
-            // so always use light status bar in that case.
-            activateLightStatusBar(isAllAppsVisible());
+    @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.ATLEAST_NOUGAT) {
+            mExtractedColors.load(this);
+            mHotseat.updateColor(mExtractedColors, !mPaused);
+            mWorkspace.getPageIndicator().updateColor(mExtractedColors);
+            boolean lightStatusBar = (FeatureFlags.LIGHT_STATUS_BAR
+                    && mExtractedColors.getColor(ExtractedColors.STATUS_BAR_INDEX,
+                    ExtractedColors.DEFAULT_DARK) == ExtractedColors.DEFAULT_LIGHT);
+            // It's possible that All Apps is visible when this is run,
+            // so always use light status bar in that case. Only change nav bar color to status bar
+            // color when All Apps is visible.
+            activateLightSystemBars(lightStatusBar || isAllAppsVisible(), true, isAllAppsVisible());
+        }
+    }
+
+    // TODO: use platform flag on API >= 26
+    private static final int SYSTEM_UI_FLAG_LIGHT_NAV_BAR = 0x10;
+
     /**
-     * Sets the status bar to be light or not. Light status bar means dark icons.
-     * @param activate if true, make sure the status bar is light, otherwise base on wallpaper.
+     * Sets the status and/or nav bar to be light or not. Light status bar means dark icons.
+     * @param isLight make sure the system bar is light.
+     * @param statusBar if true, make the status bar theme match the isLight param.
+     * @param navBar if true, make the nav bar theme match the isLight param.
      */
-    public void activateLightStatusBar(boolean activate) {
-        boolean lightStatusBar = activate || (FeatureFlags.LIGHT_STATUS_BAR
-                && mExtractedColors.getColor(ExtractedColors.STATUS_BAR_INDEX,
-                ExtractedColors.DEFAULT_DARK) == ExtractedColors.DEFAULT_LIGHT);
+    public void activateLightSystemBars(boolean isLight, boolean statusBar, boolean navBar) {
         int oldSystemUiFlags = getWindow().getDecorView().getSystemUiVisibility();
         int newSystemUiFlags = oldSystemUiFlags;
-        if (lightStatusBar) {
-            newSystemUiFlags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
+        if (isLight) {
+            if (statusBar) {
+                newSystemUiFlags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
+            }
+            if (navBar && Utilities.isAtLeastO()) {
+                newSystemUiFlags |= SYSTEM_UI_FLAG_LIGHT_NAV_BAR;
+            }
         } else {
-            newSystemUiFlags &= ~(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
+            if (statusBar) {
+                newSystemUiFlags &= ~(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
+            }
+            if (navBar && Utilities.isAtLeastO()) {
+                newSystemUiFlags &= ~(SYSTEM_UI_FLAG_LIGHT_NAV_BAR);
+            }
         }
+
         if (newSystemUiFlags != oldSystemUiFlags) {
             getWindow().getDecorView().setSystemUiVisibility(newSystemUiFlags);
         }
@@ -582,7 +595,7 @@
     }
 
     @Override
-    public void onLauncherProviderChange() {
+    public void onLauncherProviderChanged() {
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onLauncherProviderChange();
         }
@@ -608,7 +621,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() {
@@ -626,23 +639,6 @@
         }
     }
 
-    public UserEventDispatcher getUserEventDispatcher() {
-        if (mLauncherCallbacks != null) {
-            UserEventDispatcher dispatcher = mLauncherCallbacks.getUserEventDispatcher();
-            if (dispatcher != null) {
-                return dispatcher;
-            }
-        }
-
-        // Logger object is a singleton and does not have to be coupled with the foreground
-        // activity. Since most user event logging is done on the UI, the object is retrieved
-        // from the callback for convenience.
-        if (mUserEventDispatcher == null) {
-            mUserEventDispatcher = new UserEventDispatcher();
-        }
-        return mUserEventDispatcher;
-    }
-
     public boolean isDraggingEnabled() {
         // We prevent dragging when we are loading the workspace as it is possible to pick up a view
         // that is subsequently removed from the workspace in startBinding().
@@ -657,6 +653,10 @@
         return (int) info.id;
     }
 
+    public PopupDataProvider getPopupDataProvider() {
+        return mPopupDataProvider;
+    }
+
     /**
      * Returns whether we should delay spring loaded mode -- for shortcuts and widgets that have
      * a configuration step, this allows the proper animations to run after other transitions.
@@ -688,8 +688,9 @@
                     // Since the view was just bound, also launch the configure activity if needed
                     LauncherAppWidgetProviderInfo provider = mAppWidgetManager
                             .getLauncherAppWidgetInfo(widgetId);
-                    if (provider != null && provider.configure != null) {
-                        startRestoredWidgetReconfigActivity(provider, widgetInfo);
+                    if (provider != null) {
+                        new WidgetAddFlowHandler(provider)
+                                .startConfigActivity(this, widgetInfo, REQUEST_RECONFIGURE_APPWIDGET);
                     }
                 }
                 break;
@@ -736,7 +737,7 @@
             } else if (resultCode == RESULT_OK) {
                 addAppWidgetImpl(
                         appWidgetId, requestArgs, null,
-                        requestArgs.getWidgetProvider(),
+                        requestArgs.getWidgetHandler(),
                         ON_ACTIVITY_RESULT_ANIMATION_DELAY);
             }
             return;
@@ -895,7 +896,7 @@
         if (resultCode == RESULT_OK) {
             animationType = Workspace.COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION;
             final AppWidgetHostView layout = mAppWidgetHost.createView(this, appWidgetId,
-                    requestArgs.getWidgetProvider());
+                    requestArgs.getWidgetHandler().getProviderInfo(this));
             boundWidget = layout;
             onCompleteRunnable = new Runnable() {
                 @Override
@@ -928,9 +929,11 @@
             mLauncherCallbacks.onStop();
         }
 
-        if (Utilities.isNycMR1OrAbove()) {
+        if (Utilities.ATLEAST_NOUGAT_MR1) {
             mAppWidgetHost.stopListening();
         }
+
+        NotificationListener.removeNotificationsChangedListener();
     }
 
     @Override
@@ -942,9 +945,13 @@
             mLauncherCallbacks.onStart();
         }
 
-        if (Utilities.isNycMR1OrAbove()) {
+        if (Utilities.ATLEAST_NOUGAT_MR1) {
             mAppWidgetHost.startListening();
         }
+
+        if (!isWorkspaceLoading()) {
+            NotificationListener.setNotificationsChangedListener(mPopupDataProvider);
+        }
     }
 
     @Override
@@ -1059,6 +1066,7 @@
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onResume();
         }
+
     }
 
     @Override
@@ -1151,9 +1159,10 @@
         if (mLauncherCallbacks != null) {
             return mLauncherCallbacks.hasSettings();
         } else {
-            // On devices with a locked orientation, we will at least have the allow rotation
-            // setting.
-            return !getResources().getBoolean(R.bool.allow_rotation);
+            // On O and above we there is always some setting present settings (add icon to
+            // home screen or icon badging). On earlier APIs we will have the allow rotation
+            // setting, on devices with a locked orientation,
+            return Utilities.isAtLeastO() || !getResources().getBoolean(R.bool.allow_rotation);
         }
     }
 
@@ -1227,11 +1236,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 +1266,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 +1275,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 +1295,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,8 +1341,6 @@
         }
 
         // Setup the drag controller (drop targets have to be added in reverse order in priority)
-        mDragController.setDragScoller(mWorkspace);
-        mDragController.setScrollView(mDragLayer);
         mDragController.setMoveTarget(mWorkspace);
         mDragController.addDropTarget(mWorkspace);
         mDropTargetBar.setup(mDragController);
@@ -1373,53 +1357,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(ControlType.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(ControlType.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(ControlType.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);
@@ -1464,7 +1431,7 @@
     public View createShortcut(ViewGroup parent, ShortcutInfo info) {
         BubbleTextView favorite = (BubbleTextView) getLayoutInflater().inflate(R.layout.app_icon,
                 parent, false);
-        favorite.applyFromShortcutInfo(info, mIconCache);
+        favorite.applyFromShortcutInfo(info);
         favorite.setCompoundDrawablePadding(mDeviceProfile.iconDrawablePaddingPx);
         favorite.setOnClickListener(this);
         favorite.setOnFocusChangeListener(mFocusHandler);
@@ -1481,19 +1448,34 @@
         int[] cellXY = mTmpAddItemCellCoordinates;
         CellLayout layout = getCellLayout(container, screenId);
 
-        ShortcutInfo info = InstallShortcutReceiver.fromShortcutIntent(this, data);
-        if (info == null || args.getRequestCode() != REQUEST_CREATE_SHORTCUT ||
+        if (args.getRequestCode() != REQUEST_CREATE_SHORTCUT ||
                 args.getPendingIntent().getComponent() == null) {
             return;
         }
-        if (!PackageManagerHelper.hasPermissionForActivity(
-                this, info.intent, args.getPendingIntent().getComponent().getPackageName())) {
-            // The app is trying to add a shortcut without sufficient permissions
-            Log.e(TAG, "Ignoring malicious intent " + info.intent.toUri(0));
-            return;
-        }
-        final View view = createShortcut(info);
 
+        ShortcutInfo info = null;
+        if (Utilities.isAtLeastO()) {
+            info = LauncherAppsCompat.createShortcutInfoFromPinItemRequest(
+                    this, PinItemRequestCompat.getPinItemRequest(data), 0);
+        }
+
+        if (info == null) {
+            // Legacy shortcuts are only supported for primary profile.
+            info = Process.myUserHandle().equals(args.user)
+                    ? InstallShortcutReceiver.fromShortcutIntent(this, data) : null;
+
+            if (info == null) {
+                Log.e(TAG, "Unable to parse a valid custom shortcut result");
+                return;
+            } else if (!new PackageManagerHelper(this).hasPermissionForActivity(
+                    info.intent, args.getPendingIntent().getComponent().getPackageName())) {
+                // The app is trying to add a shortcut without sufficient permissions
+                Log.e(TAG, "Ignoring malicious intent " + info.intent.toUri(0));
+                return;
+            }
+        }
+
+        final View view = createShortcut(info);
         boolean foundCellSpan = false;
         // First we check if we already know the exact location where we want to add this item.
         if (cellX >= 0 && cellY >= 0) {
@@ -1517,14 +1499,12 @@
         }
 
         if (!foundCellSpan) {
-            showOutOfSpaceMessage(isHotseatLayout(layout));
+            mWorkspace.onNoCellFound(layout);
             return;
         }
 
-        LauncherModel.addItemToDatabase(this, info, container, screenId, cellXY[0], cellXY[1]);
-
-        mWorkspace.addInScreen(view, container, screenId, cellXY[0], cellXY[1], 1, 1,
-                isWorkspaceLocked());
+        getModelWriter().addItemToDatabase(info, container, screenId, cellXY[0], cellXY[1]);
+        mWorkspace.addInScreen(view, info);
     }
 
     /**
@@ -1549,9 +1529,9 @@
         launcherInfo.spanY = itemInfo.spanY;
         launcherInfo.minSpanX = itemInfo.minSpanX;
         launcherInfo.minSpanY = itemInfo.minSpanY;
-        launcherInfo.user = mAppWidgetManager.getUser(appWidgetInfo);
+        launcherInfo.user = appWidgetInfo.getUser();
 
-        LauncherModel.addItemToDatabase(this, launcherInfo,
+        getModelWriter().addItemToDatabase(launcherInfo,
                 itemInfo.container, itemInfo.screenId, itemInfo.cellX, itemInfo.cellY);
 
         if (hostView == null) {
@@ -1559,24 +1539,15 @@
             hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
         }
         hostView.setVisibility(View.VISIBLE);
-        addAppWidgetToWorkspace(hostView, launcherInfo, appWidgetInfo, isWorkspaceLocked());
+        prepareAppWidget(hostView, launcherInfo);
+        mWorkspace.addInScreen(hostView, launcherInfo);
     }
 
-    private void addAppWidgetToWorkspace(
-            AppWidgetHostView hostView, LauncherAppWidgetInfo item,
-            LauncherAppWidgetProviderInfo appWidgetInfo, boolean insert) {
+    private void prepareAppWidget(AppWidgetHostView hostView, LauncherAppWidgetInfo item) {
         hostView.setTag(item);
         item.onBindAppWidget(this, hostView);
-
         hostView.setFocusable(true);
         hostView.setOnFocusChangeListener(mFocusHandler);
-
-        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 +1555,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,13 +1566,28 @@
                     }
                 }
                 mIsResumeFromActionScreenOff = true;
-            } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
-                mUserPresent = true;
-                updateAutoAdvanceState();
             }
         }
     };
 
+    public void updateIconBadges(final Set<PackageUserKey> updatedBadges) {
+        Runnable r = new Runnable() {
+            @Override
+            public void run() {
+                mWorkspace.updateIconBadges(updatedBadges);
+                mAppsView.updateIconBadges(updatedBadges);
+
+                PopupContainerWithArrow popup = PopupContainerWithArrow.getOpen(Launcher.this);
+                if (popup != null) {
+                    popup.updateNotificationHeader(updatedBadges);
+                }
+            }
+        };
+        if (!waitUntilResume(r)) {
+            r.run();
+        }
+    }
+
     @Override
     public void onAttachedToWindow() {
         super.onAttachedToWindow();
@@ -1611,11 +1595,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 +1607,10 @@
     @Override
     public void onDetachedFromWindow() {
         super.onDetachedFromWindow();
-        mVisible = false;
-
         if (mAttached) {
             unregisterReceiver(mReceiver);
             mAttached = false;
         }
-        updateAutoAdvanceState();
 
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onDetachedFromWindow();
@@ -1639,12 +1618,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 +1656,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;
     }
@@ -1790,21 +1696,14 @@
         return mModel;
     }
 
+    public ModelWriter getModelWriter() {
+        return mModelWriter;
+    }
+
     public SharedPreferences getSharedPrefs() {
         return mSharedPrefs;
     }
 
-    public DeviceProfile getDeviceProfile() {
-        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,22 +1718,36 @@
 
         // 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;
             }
-            // In all these cases, only animate if we're already on home
+
+            // Note: There should be at most one log per method call. This is enforced implicitly
+            // by using if-else statements.
+            UserEventDispatcher ued = getUserEventDispatcher();
+
+            // TODO: Log this case.
             mWorkspace.exitWidgetResizeMode();
 
-            closeFolder(alreadyOnHome);
-            closeShortcutsContainer(alreadyOnHome);
+            AbstractFloatingView topOpenView = AbstractFloatingView.getTopOpenView(this);
+            if (topOpenView instanceof PopupContainerWithArrow) {
+                ued.logActionCommand(Action.Command.HOME_INTENT,
+                        topOpenView.getExtendedTouchView(), ContainerType.DEEPSHORTCUTS);
+            } else if (topOpenView instanceof Folder) {
+                ued.logActionCommand(Action.Command.HOME_INTENT,
+                            ((Folder) topOpenView).getFolderIcon(), ContainerType.FOLDER);
+            } else if (alreadyOnHome) {
+                ued.logActionCommand(Action.Command.HOME_INTENT,
+                        mWorkspace.getState().containerType, mWorkspace.getCurrentPage());
+            }
+
+            // In all these cases, only animate if we're already on home
+            AbstractFloatingView.closeAllOpenViews(this, alreadyOnHome);
             exitSpringLoadedDragMode();
 
             // If we are already on home, then just animate back to the workspace,
@@ -1866,6 +1779,7 @@
                 mLauncherCallbacks.onHomeIntent();
             }
         }
+        PinItemDragListener.handleDragRequest(this, intent);
 
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onNewIntent(intent);
@@ -1917,11 +1831,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 +1851,6 @@
     public void onDestroy() {
         super.onDestroy();
 
-        // Remove all pending runnables
-        mHandler.removeMessages(ADVANCE_MSG);
-        mHandler.removeMessages(0);
         mWorkspace.removeCallbacks(mBuildLayersRunnable);
         mWorkspace.removeFolderListeners();
 
@@ -1950,7 +1859,7 @@
         // been created. In this case, don't interfere with the new Launcher.
         if (mModel.isCurrentCallbacks(this)) {
             mModel.stopLoader();
-            LauncherAppState.getInstance().setLauncher(null);
+            LauncherAppState.getInstance(this).setLauncher(null);
         }
 
         if (mRotationPrefChangeHandler != null) {
@@ -1964,15 +1873,11 @@
         }
         mAppWidgetHost = null;
 
-        mWidgetsToAdvance.clear();
-
         TextKeyListener.getInstance().release();
 
         ((AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE))
                 .removeAccessibilityStateChangeListener(this);
 
-        unregisterReceiver(mUiBroadcastReceiver);
-
         LauncherAnimUtils.onDestroyActivity();
 
         if (mLauncherCallbacks != null) {
@@ -2107,7 +2012,7 @@
         }
     }
 
-    private void setWaitingForResult(PendingRequestArgs args) {
+    public void setWaitingForResult(PendingRequestArgs args) {
         boolean isLocked = isWorkspaceLocked();
         mPendingRequestArgs = args;
         if (isLocked != isWorkspaceLocked()) {
@@ -2122,24 +2027,18 @@
     }
 
     void addAppWidgetFromDropImpl(int appWidgetId, ItemInfo info, AppWidgetHostView boundWidget,
-            LauncherAppWidgetProviderInfo appWidgetInfo) {
+            WidgetAddFlowHandler addFlowHandler) {
         if (LOGD) {
             Log.d(TAG, "Adding widget from drop");
         }
-        addAppWidgetImpl(appWidgetId, info, boundWidget, appWidgetInfo, 0);
+        addAppWidgetImpl(appWidgetId, info, boundWidget, addFlowHandler, 0);
     }
 
     void addAppWidgetImpl(int appWidgetId, ItemInfo info,
-            AppWidgetHostView boundWidget, LauncherAppWidgetProviderInfo appWidgetInfo,
-            int delay) {
-        if (appWidgetInfo.configure != null) {
-            setWaitingForResult(PendingRequestArgs.forWidgetInfo(appWidgetId, appWidgetInfo, info));
+            AppWidgetHostView boundWidget, WidgetAddFlowHandler addFlowHandler, int delay) {
+        if (!addFlowHandler.startConfigActivity(this, appWidgetId, info, REQUEST_CREATE_APPWIDGET)) {
+            // If the configuration flow was not started, add the widget
 
-            // Launch over to configure widget, if needed
-            mAppWidgetManager.startConfigActivity(appWidgetInfo, appWidgetId, this,
-                    mAppWidgetHost, REQUEST_CREATE_APPWIDGET);
-        } else {
-            // Otherwise just add it
             Runnable onComplete = new Runnable() {
                 @Override
                 public void run() {
@@ -2148,14 +2047,14 @@
                             null);
                 }
             };
-            completeAddAppWidget(appWidgetId, info, boundWidget, appWidgetInfo);
+            completeAddAppWidget(appWidgetId, info, boundWidget, addFlowHandler.getProviderInfo(this));
             mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, delay, false);
         }
     }
 
     protected void moveToCustomContentScreen(boolean animate) {
         // Close any folders that may be open.
-        closeFolder();
+        AbstractFloatingView.closeAllOpenViews(this, animate);
         mWorkspace.moveToCustomContentScreen(animate);
     }
 
@@ -2176,7 +2075,7 @@
                 addAppWidgetFromDrop((PendingAddWidgetInfo) info);
                 break;
             case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
-                processShortcutFromDrop(info);
+                processShortcutFromDrop((PendingAddShortcutInfo) info);
                 break;
             default:
                 throw new IllegalStateException("Unknown item type: " + info.itemType);
@@ -2186,10 +2085,12 @@
     /**
      * Process a shortcut drop.
      */
-    private void processShortcutFromDrop(PendingAddItemInfo info) {
+    private void processShortcutFromDrop(PendingAddShortcutInfo info) {
         Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT).setComponent(info.componentName);
         setWaitingForResult(PendingRequestArgs.forIntent(REQUEST_CREATE_SHORTCUT, intent, info));
-        Utilities.startActivityForResultSafely(this, intent, REQUEST_CREATE_SHORTCUT);
+        if (!info.activityInfo.startConfigActivity(this, REQUEST_CREATE_SHORTCUT)) {
+            handleActivityResult(REQUEST_CREATE_SHORTCUT, RESULT_CANCELED, null);
+        }
     }
 
     /**
@@ -2198,6 +2099,7 @@
     private void addAppWidgetFromDrop(PendingAddWidgetInfo info) {
         AppWidgetHostView hostView = info.boundWidget;
         int appWidgetId;
+        WidgetAddFlowHandler addFlowHandler = info.getHandler();
         if (hostView != null) {
             // In the case where we've prebound the widget, we remove it from the DragLayer
             if (LOGD) {
@@ -2206,7 +2108,7 @@
             getDragLayer().removeView(hostView);
 
             appWidgetId = hostView.getAppWidgetId();
-            addAppWidgetFromDropImpl(appWidgetId, info, hostView, info.info);
+            addAppWidgetFromDropImpl(appWidgetId, info, hostView, addFlowHandler);
 
             // Clear the boundWidget so that it doesn't get destroyed.
             info.boundWidget = null;
@@ -2219,17 +2121,9 @@
             boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
                     appWidgetId, info.info, options);
             if (success) {
-                addAppWidgetFromDropImpl(appWidgetId, info, null, info.info);
+                addAppWidgetFromDropImpl(appWidgetId, info, null, addFlowHandler);
             } else {
-                setWaitingForResult(PendingRequestArgs.forWidgetInfo(appWidgetId, info.info, info));
-                Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
-                intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
-                intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, info.componentName);
-                mAppWidgetManager.getUser(info.info)
-                    .addToIntent(intent, AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE);
-                // TODO: we need to make sure that this accounts for the options bundle.
-                // intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options);
-                startActivityForResult(intent, REQUEST_BIND_APPWIDGET);
+                addFlowHandler.startBindFlow(this, appWidgetId, info, REQUEST_BIND_APPWIDGET);
             }
         }
     }
@@ -2240,14 +2134,11 @@
         folderInfo.title = getText(R.string.folder_name);
 
         // Update the model
-        LauncherModel.addItemToDatabase(Launcher.this, folderInfo, container, screenId,
-                cellX, cellY);
+        getModelWriter().addItemToDatabase(folderInfo, container, screenId, cellX, cellY);
 
         // Create the view
-        FolderIcon newFolder =
-            FolderIcon.fromXml(R.layout.folder_icon, this, layout, folderInfo, mIconCache);
-        mWorkspace.addInScreen(newFolder, container, screenId, cellX, cellY, 1, 1,
-                isWorkspaceLocked());
+        FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this, layout, folderInfo);
+        mWorkspace.addInScreen(newFolder, folderInfo);
         // Force measure the new folder icon
         CellLayout parent = mWorkspace.getParentCellLayoutForView(newFolder);
         parent.getShortcutsAndWidgets().measureChild(newFolder);
@@ -2271,7 +2162,7 @@
                 mWorkspace.removeWorkspaceItem(v);
             }
             if (deleteFromDb) {
-                LauncherModel.deleteItemFromDatabase(this, itemInfo);
+                getModelWriter().deleteItemFromDatabase(itemInfo);
             }
         } else if (itemInfo instanceof FolderInfo) {
             final FolderInfo folderInfo = (FolderInfo) itemInfo;
@@ -2280,12 +2171,11 @@
             }
             mWorkspace.removeWorkspaceItem(v);
             if (deleteFromDb) {
-                LauncherModel.deleteFolderAndContentsFromDatabase(this, folderInfo);
+                getModelWriter().deleteFolderAndContentsFromDatabase(folderInfo);
             }
         } else if (itemInfo instanceof LauncherAppWidgetInfo) {
             final LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) itemInfo;
             mWorkspace.removeWorkspaceItem(v);
-            removeWidgetToAutoAdvance(v);
             if (deleteFromDb) {
                 deleteWidgetInfo(widgetInfo);
             }
@@ -2310,30 +2200,12 @@
                 }
             }.executeOnExecutor(Utilities.THREAD_POOL_EXECUTOR);
         }
-        LauncherModel.deleteItemFromDatabase(this, widgetInfo);
+        getModelWriter().deleteItemFromDatabase(widgetInfo);
     }
 
     @Override
     public boolean dispatchKeyEvent(KeyEvent event) {
-        if (event.getAction() == KeyEvent.ACTION_DOWN) {
-            switch (event.getKeyCode()) {
-                case KeyEvent.KEYCODE_HOME:
-                    return true;
-                case KeyEvent.KEYCODE_VOLUME_DOWN:
-                    if (Utilities.isPropertyEnabled(DUMP_STATE_PROPERTY)) {
-                        dumpState();
-                        return true;
-                    }
-                    break;
-            }
-        } else if (event.getAction() == KeyEvent.ACTION_UP) {
-            switch (event.getKeyCode()) {
-                case KeyEvent.KEYCODE_HOME:
-                    return true;
-            }
-        }
-
-        return super.dispatchKeyEvent(event);
+        return (event.getKeyCode() == KeyEvent.KEYCODE_HOME) || super.dispatchKeyEvent(event);
     }
 
     @Override
@@ -2347,22 +2219,34 @@
             return;
         }
 
-        if (getOpenShortcutsContainer() != null) {
-            closeShortcutsContainer();
+        // Note: There should be at most one log per method call. This is enforced implicitly
+        // by using if-else statements.
+        UserEventDispatcher ued = getUserEventDispatcher();
+        AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this);
+        if (topView != null) {
+            if (topView.getActiveTextView() != null) {
+                topView.getActiveTextView().dispatchBackKey();
+            } else {
+                if (topView instanceof PopupContainerWithArrow) {
+                    ued.logActionCommand(Action.Command.BACK,
+                            topView.getExtendedTouchView(), ContainerType.DEEPSHORTCUTS);
+                } else if (topView instanceof Folder) {
+                    ued.logActionCommand(Action.Command.BACK,
+                            ((Folder) topView).getFolderIcon(), ContainerType.FOLDER);
+                }
+                topView.close(true);
+            }
         } else if (isAppsViewVisible()) {
+            ued.logActionCommand(Action.Command.BACK, ContainerType.ALLAPPS);
             showWorkspace(true);
         } else if (isWidgetsViewVisible())  {
+            ued.logActionCommand(Action.Command.BACK, ContainerType.WIDGETS);
             showOverviewMode(true);
         } else if (mWorkspace.isInOverviewMode()) {
+            ued.logActionCommand(Action.Command.BACK, ContainerType.OVERVIEW);
             showWorkspace(true);
-        } else if (mWorkspace.getOpenFolder() != null) {
-            Folder openFolder = mWorkspace.getOpenFolder();
-            if (openFolder.isEditingName()) {
-                openFolder.dismissEditingName();
-            } else {
-                closeFolder();
-            }
         } else {
+            // TODO: Log this case.
             mWorkspace.exitWidgetResizeMode();
 
             // Back button is a no-op here, but give at least some feedback for the button press
@@ -2388,6 +2272,9 @@
 
         if (v instanceof Workspace) {
             if (mWorkspace.isInOverviewMode()) {
+                getUserEventDispatcher().logActionOnContainer(LauncherLogProto.Action.Type.TOUCH,
+                        LauncherLogProto.Action.Direction.NONE,
+                        LauncherLogProto.ContainerType.OVERVIEW, mWorkspace.getCurrentPage());
                 showWorkspace(true);
             }
             return;
@@ -2395,7 +2282,11 @@
 
         if (v instanceof CellLayout) {
             if (mWorkspace.isInOverviewMode()) {
-                mWorkspace.snapToPageFromOverView(mWorkspace.indexOfChild(v));
+                int page = mWorkspace.indexOfChild(v);
+                getUserEventDispatcher().logActionOnContainer(LauncherLogProto.Action.Type.TOUCH,
+                        LauncherLogProto.Action.Direction.NONE,
+                        LauncherLogProto.ContainerType.OVERVIEW, page);
+                mWorkspace.snapToPageFromOverView(page);
                 showWorkspace(true);
             }
             return;
@@ -2436,54 +2327,29 @@
 
         final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag();
         if (v.isReadyForClickSetup()) {
+            LauncherAppWidgetProviderInfo appWidgetInfo =
+                    mAppWidgetManager.findProvider(info.providerName, info.user);
+            if (appWidgetInfo == null) {
+                return;
+            }
+            WidgetAddFlowHandler addFlowHandler = new WidgetAddFlowHandler(appWidgetInfo);
+
             if (info.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
                 if (!info.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_ALLOCATED)) {
                     // This should not happen, as we make sure that an Id is allocated during bind.
                     return;
                 }
-                LauncherAppWidgetProviderInfo appWidgetInfo =
-                        mAppWidgetManager.findProvider(info.providerName, info.user);
-                if (appWidgetInfo != null) {
-                    setWaitingForResult(PendingRequestArgs
-                            .forWidgetInfo(info.appWidgetId, appWidgetInfo, info));
-
-                    Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
-                    intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, info.appWidgetId);
-                    intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, appWidgetInfo.provider);
-                    mAppWidgetManager.getUser(appWidgetInfo)
-                            .addToIntent(intent, AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE);
-                    startActivityForResult(intent, REQUEST_BIND_PENDING_APPWIDGET);
-                }
+                addFlowHandler.startBindFlow(this, info.appWidgetId, info,
+                        REQUEST_BIND_PENDING_APPWIDGET);
             } else {
-                LauncherAppWidgetProviderInfo appWidgetInfo =
-                        mAppWidgetManager.getLauncherAppWidgetInfo(info.appWidgetId);
-                if (appWidgetInfo != null) {
-                    startRestoredWidgetReconfigActivity(appWidgetInfo, info);
-                }
+                addFlowHandler.startConfigActivity(this, info, REQUEST_RECONFIGURE_APPWIDGET);
             }
-        } else if (info.installProgress < 0) {
-            // The install has not been queued
-            final String packageName = info.providerName.getPackageName();
-            showBrokenAppInstallDialog(packageName,
-                new DialogInterface.OnClickListener() {
-                    public void onClick(DialogInterface dialog, int id) {
-                        startActivitySafely(v, LauncherModel.getMarketIntent(packageName), info);
-                    }
-                });
         } else {
-            // Download has started.
             final String packageName = info.providerName.getPackageName();
-            startActivitySafely(v, LauncherModel.getMarketIntent(packageName), info);
+            onClickPendingAppItem(v, packageName, info.installProgress >= 0);
         }
     }
 
-    private void startRestoredWidgetReconfigActivity(
-            LauncherAppWidgetProviderInfo provider, LauncherAppWidgetInfo info) {
-        setWaitingForResult(PendingRequestArgs.forWidgetInfo(info.appWidgetId, provider, info));
-        mAppWidgetManager.startConfigActivity(provider,
-                info.appWidgetId, this, mAppWidgetHost, REQUEST_RECONFIGURE_APPWIDGET);
-    }
-
     /**
      * Event handler for the "grid" button that appears on the home screen, which
      * enters all apps mode.
@@ -2493,8 +2359,8 @@
     protected void onClickAllAppsButton(View v) {
         if (LOGD) Log.d(TAG, "onClickAllAppsButton");
         if (!isAppsViewVisible()) {
-            getUserEventDispatcher().logActionOnControl(LauncherLogProto.Action.TAP,
-                    LauncherLogProto.ALL_APPS_BUTTON);
+            getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
+                    ControlType.ALL_APPS_BUTTON);
             showAppsView(true /* animated */, true /* updatePredictedApps */,
                     false /* focusSearchBar */);
         }
@@ -2503,28 +2369,47 @@
     protected void onLongClickAllAppsButton(View v) {
         if (LOGD) Log.d(TAG, "onLongClickAllAppsButton");
         if (!isAppsViewVisible()) {
-            getUserEventDispatcher().logActionOnControl(LauncherLogProto.Action.LONGPRESS,
-                    LauncherLogProto.ALL_APPS_BUTTON);
+            getUserEventDispatcher().logActionOnControl(Action.Touch.LONGPRESS,
+                    ControlType.ALL_APPS_BUTTON);
             showAppsView(true /* animated */,
                     true /* updatePredictedApps */, true /* focusSearchBar */);
         }
     }
 
-    private void showBrokenAppInstallDialog(final String packageName,
-            DialogInterface.OnClickListener onSearchClickListener) {
+    private void onClickPendingAppItem(final View v, final String packageName,
+            boolean downloadStarted) {
+        if (downloadStarted) {
+            // If the download has started, simply direct to the market app.
+            startMarketIntentForPackage(v, packageName);
+            return;
+        }
         new AlertDialog.Builder(this)
             .setTitle(R.string.abandoned_promises_title)
             .setMessage(R.string.abandoned_promise_explanation)
-            .setPositiveButton(R.string.abandoned_search, onSearchClickListener)
+            .setPositiveButton(R.string.abandoned_search, new DialogInterface.OnClickListener() {
+                @Override
+                public void onClick(DialogInterface dialogInterface, int i) {
+                    startMarketIntentForPackage(v, packageName);
+                }
+            })
             .setNeutralButton(R.string.abandoned_clean_this,
                 new DialogInterface.OnClickListener() {
                     public void onClick(DialogInterface dialog, int id) {
-                        final UserHandleCompat user = UserHandleCompat.myUserHandle();
+                        final UserHandle user = Process.myUserHandle();
                         mWorkspace.removeAbandonedPromise(packageName, user);
                     }
                 })
             .create().show();
-        return;
+    }
+
+    private void startMarketIntentForPackage(View v, String packageName) {
+        ItemInfo item = (ItemInfo) v.getTag();
+        Intent intent = PackageManagerHelper.getMarketIntent(packageName);
+        boolean success = startActivitySafely(v, intent, item);
+        if (success && v instanceof BubbleTextView) {
+            mWaitingForResume = (BubbleTextView) v;
+            mWaitingForResume.setStayPressed(true);
+        }
     }
 
     /**
@@ -2568,17 +2453,14 @@
         }
 
         // Check for abandoned promise
-        if ((v instanceof BubbleTextView)
-                && shortcut.isPromise()
-                && !shortcut.hasStatusFlag(ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE)) {
-            showBrokenAppInstallDialog(
-                    shortcut.getTargetComponent().getPackageName(),
-                    new DialogInterface.OnClickListener() {
-                        public void onClick(DialogInterface dialog, int id) {
-                            startAppShortcutOrInfoActivity(v);
-                        }
-                    });
-            return;
+        if ((v instanceof BubbleTextView) && shortcut.isPromise()) {
+            String packageName = shortcut.intent.getComponent() != null ?
+                    shortcut.intent.getComponent().getPackageName() : shortcut.intent.getPackage();
+            if (!TextUtils.isEmpty(packageName)) {
+                onClickPendingAppItem(v, packageName,
+                        shortcut.hasStatusFlag(ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE));
+                return;
+            }
         }
 
         // Start activities
@@ -2592,7 +2474,7 @@
             throw new IllegalArgumentException("Input must have a valid intent");
         }
         boolean success = startActivitySafely(v, intent, item);
-        getUserEventDispatcher().logAppLaunch(v, intent);
+        getUserEventDispatcher().logAppLaunch(v, intent); // TODO for discovered apps b/35802115
 
         if (success && v instanceof BubbleTextView) {
             mWaitingForResume = (BubbleTextView) v;
@@ -2611,10 +2493,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 +2518,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();
+        }
     }
 
     /**
@@ -2666,6 +2555,7 @@
         Intent intent = new Intent(Intent.ACTION_APPLICATION_PREFERENCES)
                 .setPackage(getPackageName());
         intent.setSourceBounds(getViewBounds(v));
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         startActivity(intent, getActivityLaunchOptions(v));
     }
 
@@ -2749,7 +2639,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,7 +2668,8 @@
         }
     }
 
-    private Bundle getActivityLaunchOptions(View v) {
+    @TargetApi(Build.VERSION_CODES.M)
+    public Bundle getActivityLaunchOptions(View v) {
         if (Utilities.ATLEAST_MARSHMALLOW) {
             int left = 0, top = 0;
             int width = v.getMeasuredWidth(), height = v.getMeasuredHeight();
@@ -2804,7 +2695,7 @@
         return null;
     }
 
-    private Rect getViewBounds(View v) {
+    public Rect getViewBounds(View v) {
         int[] pos = new int[2];
         v.getLocationOnScreen(pos);
         return new Rect(pos[0], pos[1], pos[0] + v.getWidth(), pos[1] + v.getHeight());
@@ -2821,11 +2712,7 @@
                 !intent.hasExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION);
         Bundle optsBundle = useLaunchAnimation ? getActivityLaunchOptions(v) : null;
 
-        UserHandleCompat user = null;
-        if (intent.hasExtra(AppInfo.EXTRA_PROFILE)) {
-            long serialNumber = intent.getLongExtra(AppInfo.EXTRA_PROFILE, -1);
-            user = UserManagerCompat.getInstance(this).getUserForSerialNumber(serialNumber);
-        }
+        UserHandle user = item == null ? null : item.user;
 
         // Prepare intent
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -2833,13 +2720,14 @@
             intent.setSourceBounds(getViewBounds(v));
         }
         try {
-            if (Utilities.ATLEAST_MARSHMALLOW && item != null
+            if (Utilities.ATLEAST_MARSHMALLOW
+                    && (item instanceof ShortcutInfo)
                     && (item.itemType == Favorites.ITEM_TYPE_SHORTCUT
-                    || item.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT)
-                    && ((ShortcutInfo) item).promisedIntent == null) {
+                     || item.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT)
+                    && !((ShortcutInfo) item).isPromise()) {
                 // Shortcuts need some special checks due to legacy reasons.
                 startShortcutIntentSafely(intent, optsBundle, item);
-            } else if (user == null || user.equals(UserHandleCompat.myUserHandle())) {
+            } else if (user == null || user.equals(Process.myUserHandle())) {
                 // Could be launching some bookkeeping activity
                 startActivity(intent, optsBundle);
             } else {
@@ -2854,227 +2742,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 +2760,16 @@
             return true;
         }
 
+
+        boolean ignoreLongPressToOverview =
+                mDeviceProfile.shouldIgnoreLongPressToOverview(mLastDispatchTouchEventX);
+
         if (v instanceof Workspace) {
             if (!mWorkspace.isInOverviewMode()) {
-                if (!mWorkspace.isTouchActive()) {
+                if (!mWorkspace.isTouchActive() && !ignoreLongPressToOverview) {
+                    getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
+                            Action.Direction.NONE, ContainerType.WORKSPACE,
+                            mWorkspace.getCurrentPage());
                     showOverviewMode(true);
                     mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
                             HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
@@ -3118,13 +2796,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(Action.Touch.LONGPRESS,
+                            Action.Direction.NONE, ContainerType.OVERVIEW);
                 } else {
+                    if (ignoreLongPressToOverview) {
+                        return false;
+                    }
+                    getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
+                            Action.Direction.NONE, ContainerType.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 +2818,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());
                 }
             }
         }
@@ -3150,6 +2826,7 @@
     }
 
     boolean isHotseatLayout(View layout) {
+        // TODO: Remove this method
         return mHotseat != null && layout != null &&
                 (layout instanceof CellLayout) && (layout == mHotseat.getLayout());
     }
@@ -3219,11 +2896,7 @@
         }
 
         // Change the state *after* we've called all the transition code
-        mState = State.WORKSPACE;
-
-        // Resume the auto-advance of widgets
-        mUserPresent = true;
-        updateAutoAdvanceState();
+        setState(State.WORKSPACE);
 
         if (changed) {
             // Send an accessibility event to announce the context change
@@ -3260,12 +2933,30 @@
         mWorkspace.setVisibility(View.VISIBLE);
         mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(),
                 Workspace.State.OVERVIEW, animated, postAnimRunnable);
-        mState = State.WORKSPACE;
+        setState(State.WORKSPACE);
+
         // If animated from long press, then don't allow any of the controller in the drag
         // layer to intercept any remaining touch.
         mWorkspace.requestDisallowInterceptTouchEvent(animated);
     }
 
+    private void setState(State state) {
+        this.mState = state;
+        updateSoftInputMode();
+    }
+
+    private void updateSoftInputMode() {
+        if (FeatureFlags.LAUNCHER3_UPDATE_SOFT_INPUT_MODE) {
+            final int mode;
+            if (isAppsViewVisible()) {
+                mode = SOFT_INPUT_MODE_ALL_APPS;
+            } else {
+                mode = SOFT_INPUT_MODE_DEFAULT;
+            }
+            getWindow().setSoftInputMode(mode);
+        }
+    }
+
     /**
      * Shows the apps view.
      */
@@ -3321,20 +3012,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();
+        setState(toState);
+        AbstractFloatingView.closeAllOpenViews(this);
 
         // Send an accessibility event to announce the context change
         getWindow().getDecorView()
@@ -3347,7 +3032,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);
@@ -3363,16 +3048,7 @@
         mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(),
                 Workspace.State.SPRING_LOADED, true /* animated */,
                 null /* onCompleteRunnable */);
-
-        if (isAppsViewVisible()) {
-            mState = State.APPS_SPRING_LOADED;
-        } else if (isWidgetsViewVisible()) {
-            mState = State.WIDGETS_SPRING_LOADED;
-        } else if (!FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND) {
-            mState = State.WORKSPACE_SPRING_LOADED;
-        } else {
-            mState = State.WORKSPACE;
-        }
+        setState(State.WORKSPACE_SPRING_LOADED);
     }
 
     public void exitSpringLoadedDragModeDelayed(final boolean successfulDrop, int delay,
@@ -3407,7 +3083,7 @@
                 || mState == State.WIDGETS_SPRING_LOADED;
     }
 
-    void exitSpringLoadedDragMode() {
+    public void exitSpringLoadedDragMode() {
         if (mState == State.APPS_SPRING_LOADED) {
             showAppsView(true /* animated */,
                     false /* updatePredictedApps */, false /* focusSearchBar */);
@@ -3552,13 +3228,15 @@
         if (LauncherAppState.PROFILE_STARTUP) {
             Trace.beginSection("Starting page bind");
         }
+
+        AbstractFloatingView.closeAllOpenViews(this);
+
         setWorkspaceLoading(true);
 
         // Clear the workspace because it's going to be rebound
         mWorkspace.clearDropTargets();
         mWorkspace.removeAllWorkspaceScreens();
 
-        mWidgetsToAdvance.clear();
         if (mHotseat != null) {
             mHotseat.resetLayout();
         }
@@ -3648,25 +3326,25 @@
      * Implementation of the method from LauncherModel.Callbacks.
      */
     @Override
-    public void bindItems(final ArrayList<ItemInfo> shortcuts, final int start, final int end,
+    public void bindItems(final ArrayList<ItemInfo> items, final int start, final int end,
                           final boolean forceAnimateIcons) {
         Runnable r = new Runnable() {
             public void run() {
-                bindItems(shortcuts, start, end, forceAnimateIcons);
+                bindItems(items, start, end, forceAnimateIcons);
             }
         };
         if (waitUntilResume(r)) {
             return;
         }
 
-        // Get the list of added shortcuts and intersect them with the set of shortcuts here
+        // Get the list of added items and intersect them with the set of items here
         final AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
         final Collection<Animator> bounceAnims = new ArrayList<Animator>();
         final boolean animateIcons = forceAnimateIcons && canRunNewAppsAnimation();
         Workspace workspace = mWorkspace;
-        long newShortcutsScreenId = -1;
+        long newItemsScreenId = -1;
         for (int i = start; i < end; i++) {
-            final ItemInfo item = shortcuts.get(i);
+            final ItemInfo item = items.get(i);
 
             // Short circuit if we are loading dock items for a configuration which has no dock
             if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
@@ -3678,15 +3356,33 @@
             switch (item.itemType) {
                 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
                 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
-                case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
+                case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
                     ShortcutInfo info = (ShortcutInfo) item;
                     view = createShortcut(info);
                     break;
-                case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+                }
+                case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: {
                     view = FolderIcon.fromXml(R.layout.folder_icon, this,
                             (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
-                            (FolderInfo) item, mIconCache);
+                            (FolderInfo) item);
                     break;
+                }
+                case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: {
+                    LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) item;
+                    if (mIsSafeModeEnabled) {
+                        view = new PendingAppWidgetHostView(this, info, mIconCache, true);
+                    } else {
+                        LauncherAppWidgetProviderInfo providerInfo =
+                                mAppWidgetManager.getLauncherAppWidgetInfo(info.appWidgetId);
+                        if (providerInfo == null) {
+                            deleteWidgetInfo(info);
+                            continue;
+                        }
+                        view = mAppWidgetHost.createView(this, info.appWidgetId, providerInfo);
+                    }
+                    prepareAppWidget((AppWidgetHostView) view, info);
+                    break;
+                }
                 default:
                     throw new RuntimeException("Invalid Item Type");
             }
@@ -3705,35 +3401,34 @@
                         throw (new RuntimeException(desc));
                     } else {
                         Log.d(TAG, desc);
-                        LauncherModel.deleteItemFromDatabase(this, item);
+                        getModelWriter().deleteItemFromDatabase(item);
                         continue;
                     }
                 }
             }
-            workspace.addInScreenFromBind(view, item.container, item.screenId, item.cellX,
-                    item.cellY, 1, 1);
+            workspace.addInScreenFromBind(view, item);
             if (animateIcons) {
                 // Animate all the applications up now
                 view.setAlpha(0f);
                 view.setScaleX(0f);
                 view.setScaleY(0f);
                 bounceAnims.add(createNewAppBounceAnimation(view, i));
-                newShortcutsScreenId = item.screenId;
+                newItemsScreenId = item.screenId;
             }
         }
 
         if (animateIcons) {
             // Animate to the correct page
-            if (newShortcutsScreenId > -1) {
+            if (newItemsScreenId > -1) {
                 long currentScreenId = mWorkspace.getScreenIdForPageIndex(mWorkspace.getNextPage());
-                final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newShortcutsScreenId);
+                final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newItemsScreenId);
                 final Runnable startBounceAnimRunnable = new Runnable() {
                     public void run() {
                         anim.playTogether(bounceAnims);
                         anim.start();
                     }
                 };
-                if (newShortcutsScreenId != currentScreenId) {
+                if (newItemsScreenId != currentScreenId) {
                     // We post the animation slightly delayed to prevent slowdowns
                     // when we are loading right after we return to launcher.
                     mWorkspace.postDelayed(new Runnable() {
@@ -3753,15 +3448,6 @@
         workspace.requestLayout();
     }
 
-    private void bindSafeModeWidget(LauncherAppWidgetInfo item) {
-        PendingAppWidgetHostView view = new PendingAppWidgetHostView(this, item, true);
-        view.updateIcon(mIconCache);
-        view.updateAppWidget(null);
-        view.setOnClickListener(this);
-        addAppWidgetToWorkspace(view, item, null, false);
-        mWorkspace.requestLayout();
-    }
-
     /**
      * Add the views for a widget to the workspace.
      *
@@ -3778,7 +3464,11 @@
         }
 
         if (mIsSafeModeEnabled) {
-            bindSafeModeWidget(item);
+            PendingAppWidgetHostView view =
+                    new PendingAppWidgetHostView(this, item, mIconCache, true);
+            prepareAppWidget(view, item);
+            mWorkspace.addInScreen(view, item);
+            mWorkspace.requestLayout();
             return;
         }
 
@@ -3806,9 +3496,9 @@
                 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);
+                getModelWriter().deleteItemFromDatabase(item);
                 return;
             }
 
@@ -3821,7 +3511,7 @@
 
                     // Also try to bind the widget. If the bind fails, the user will be shown
                     // a click to setup UI, which will ask for the bind permission.
-                    PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(this, appWidgetInfo);
+                    PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(appWidgetInfo);
                     pendingInfo.spanX = item.spanX;
                     pendingInfo.spanY = item.spanY;
                     pendingInfo.minSpanX = item.minSpanX;
@@ -3855,17 +3545,18 @@
                                 : LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
                     }
 
-                    LauncherModel.updateItemInDatabase(this, item);
+                    getModelWriter().updateItemInDatabase(item);
                 }
             } else if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_UI_NOT_READY)
                     && (appWidgetInfo.configure == null)) {
                 // The widget was marked as UI not ready, but there is no configure activity to
                 // update the UI.
                 item.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED;
-                LauncherModel.updateItemInDatabase(this, item);
+                getModelWriter().updateItemInDatabase(item);
             }
         }
 
+        final AppWidgetHostView view;
         if (item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) {
             if (DEBUG_WIDGETS) {
                 Log.d(TAG, "bindAppWidget: id=" + item.appWidgetId + " belongs to component "
@@ -3881,16 +3572,12 @@
 
             item.minSpanX = appWidgetInfo.minSpanX;
             item.minSpanY = appWidgetInfo.minSpanY;
-            addAppWidgetToWorkspace(
-                    mAppWidgetHost.createView(this, item.appWidgetId, appWidgetInfo),
-                    item, appWidgetInfo, false);
+            view = mAppWidgetHost.createView(this, item.appWidgetId, appWidgetInfo);
         } else {
-            PendingAppWidgetHostView view = new PendingAppWidgetHostView(this, item, false);
-            view.updateIcon(mIconCache);
-            view.updateAppWidget(null);
-            view.setOnClickListener(this);
-            addAppWidgetToWorkspace(view, item, null, false);
+            view = new PendingAppWidgetHostView(this, item, mIconCache, false);
         }
+        prepareAppWidget(view, item);
+        mWorkspace.addInScreen(view, item);
         mWorkspace.requestLayout();
 
         if (DEBUG_WIDGETS) {
@@ -3915,7 +3602,7 @@
         info.restoreStatus = finalRestoreFlag;
 
         mWorkspace.reinflateWidgetsIfNecessary();
-        LauncherModel.updateItemInDatabase(this, info);
+        getModelWriter().updateItemInDatabase(info);
         return info;
     }
 
@@ -3981,14 +3668,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);
@@ -4001,6 +3680,8 @@
 
         InstallShortcutReceiver.disableAndFlushInstallQueue(this);
 
+        NotificationListener.setNotificationsChangedListener(mPopupDataProvider);
+
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.finishBindingItems(false);
         }
@@ -4070,21 +3751,7 @@
      */
     @Override
     public void bindDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMapCopy) {
-        mDeepShortcutMap = deepShortcutMapCopy;
-        if (LOGD) Log.d(TAG, "bindDeepShortcutMap: " + mDeepShortcutMap);
-    }
-
-    public List<String> getShortcutIdsForItem(ItemInfo info) {
-        if (!DeepShortcutManager.supportsShortcuts(info)) {
-            return Collections.EMPTY_LIST;
-        }
-        ComponentName component = info.getTargetComponent();
-        if (component == null) {
-            return Collections.EMPTY_LIST;
-        }
-
-        List<String> ids = mDeepShortcutMap.get(new ComponentKey(component, info.user));
-        return ids == null ? Collections.EMPTY_LIST : ids;
+        mPopupDataProvider.setDeepShortcutMap(deepShortcutMapCopy);
     }
 
     /**
@@ -4131,7 +3798,7 @@
      */
     @Override
     public void bindShortcutsChanged(final ArrayList<ShortcutInfo> updated,
-            final ArrayList<ShortcutInfo> removed, final UserHandleCompat user) {
+            final ArrayList<ShortcutInfo> removed, final UserHandle user) {
         Runnable r = new Runnable() {
             public void run() {
                 bindShortcutsChanged(updated, removed, user);
@@ -4151,7 +3818,7 @@
 
             for (ShortcutInfo si : removed) {
                 if (si.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
-                    removedDeepShortcuts.add(ShortcutKey.fromShortcutInfo(si));
+                    removedDeepShortcuts.add(ShortcutKey.fromItemInfo(si));
                 } else {
                     removedComponents.add(si.getTargetComponent());
                 }
@@ -4200,7 +3867,7 @@
     @Override
     public void bindWorkspaceComponentsRemoved(
             final HashSet<String> packageNames, final HashSet<ComponentName> components,
-            final UserHandleCompat user) {
+            final UserHandle user) {
         Runnable r = new Runnable() {
             public void run() {
                 bindWorkspaceComponentsRemoved(packageNames, components, user);
@@ -4236,76 +3903,56 @@
         // Update AllApps
         if (mAppsView != null) {
             mAppsView.removeApps(appInfos);
+            tryAndUpdatePredictedApps();
         }
     }
 
-    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;
         }
+
+        AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this);
+        if (topView != null) {
+            topView.onWidgetsBound();
+        }
+    }
+
+    public List<WidgetItem> getWidgetsForPackageUser(PackageUserKey packageUserKey) {
+        return mWidgetsView.getWidgetsForPackageUser(packageUserKey);
     }
 
     @Override
     public void notifyWidgetProvidersChanged() {
         if (mWorkspace.getState().shouldUpdateWidget) {
-            mModel.refreshAndBindWidgetsAndShortcuts(this, mWidgetsView.isEmpty());
+            refreshAndBindWidgetsForPackageUser(null);
         }
     }
 
-    private int mapConfigurationOriActivityInfoOri(int configOri) {
-        final Display d = getWindowManager().getDefaultDisplay();
-        int naturalOri = Configuration.ORIENTATION_LANDSCAPE;
-        switch (d.getRotation()) {
-        case Surface.ROTATION_0:
-        case Surface.ROTATION_180:
-            // We are currently in the same basic orientation as the natural orientation
-            naturalOri = configOri;
-            break;
-        case Surface.ROTATION_90:
-        case Surface.ROTATION_270:
-            // We are currently in the other basic orientation to the natural orientation
-            naturalOri = (configOri == Configuration.ORIENTATION_LANDSCAPE) ?
-                    Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE;
-            break;
-        }
-
-        int[] oriMap = {
-                ActivityInfo.SCREEN_ORIENTATION_PORTRAIT,
-                ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
-                ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT,
-                ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
-        };
-        // Since the map starts at portrait, we need to offset if this device's natural orientation
-        // is landscape.
-        int indexOffset = 0;
-        if (naturalOri == Configuration.ORIENTATION_LANDSCAPE) {
-            indexOffset = 1;
-        }
-        return oriMap[(d.getRotation() + indexOffset) % 4];
+    /**
+     * @param packageUser if null, refreshes all widgets and shortcuts, otherwise only
+     *                    refreshes the widgets and shortcuts associated with the given package/user
+     */
+    public void refreshAndBindWidgetsForPackageUser(@Nullable PackageUserKey packageUser) {
+        mModel.refreshAndBindWidgetsAndShortcuts(this, mWidgetsView.isEmpty(), packageUser);
     }
 
-    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
     public void lockScreenOrientation() {
         if (mRotationEnabled) {
-            if (Utilities.ATLEAST_JB_MR2) {
-                setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
-            } else {
-                setRequestedOrientation(mapConfigurationOriActivityInfoOri(getResources()
-                        .getConfiguration().orientation));
-            }
+            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
         }
     }
 
@@ -4346,117 +3993,118 @@
         return true;
     }
 
-    // TODO: These method should be a part of LauncherSearchCallback
-    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
-    public ItemInfo createAppDragInfo(Intent appLaunchIntent) {
-        // Called from search suggestion
-        UserHandleCompat user = null;
-        if (Utilities.ATLEAST_LOLLIPOP) {
-            UserHandle userHandle = appLaunchIntent.getParcelableExtra(Intent.EXTRA_USER);
-            if (userHandle != null) {
-                user = UserHandleCompat.fromUser(userHandle);
-            }
-        }
-        return createAppDragInfo(appLaunchIntent, user);
-    }
-
-    // TODO: This method should be a part of LauncherSearchCallback
-    public ItemInfo createAppDragInfo(Intent intent, UserHandleCompat user) {
-        if (user == null) {
-            user = UserHandleCompat.myUserHandle();
-        }
-
-        // Called from search suggestion, add the profile extra to the intent to ensure that we
-        // can launch it correctly
-        LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
-        LauncherActivityInfoCompat activityInfo = launcherApps.resolveActivity(intent, user);
-        if (activityInfo == null) {
-            return null;
-        }
-        return new AppInfo(this, activityInfo, user, mIconCache);
-    }
-
-    // TODO: This method should be a part of LauncherSearchCallback
-    public ItemInfo createShortcutDragInfo(Intent shortcutIntent, CharSequence caption,
-            Bitmap icon) {
-        return new ShortcutInfo(shortcutIntent, caption, caption, icon,
-                UserHandleCompat.myUserHandle());
-    }
-
     protected void moveWorkspaceToDefaultScreen() {
         mWorkspace.moveToDefaultScreen(false);
     }
 
     /**
-     * Returns a FastBitmapDrawable with the icon, accurately sized.
+     * $ adb shell dumpsys activity com.android.launcher3.Launcher [--all]
      */
-    public FastBitmapDrawable createIconDrawable(Bitmap icon) {
-        FastBitmapDrawable d = new FastBitmapDrawable(icon);
-        d.setFilterBitmap(true);
-        resizeIconDrawable(d);
-        return d;
-    }
-
-    /**
-     * Resizes an icon drawable to the correct icon size.
-     */
-    public Drawable resizeIconDrawable(Drawable icon) {
-        icon.setBounds(0, 0, mDeviceProfile.iconSizePx, mDeviceProfile.iconSizePx);
-        return icon;
-    }
-
-    /**
-     * Prints out out state for debugging.
-     */
-    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);
-        mModel.dumpState();
-        // TODO(hyunyoungs): add mWidgetsView.dumpState(); or mWidgetsModel.dumpState();
-
-        Log.d(TAG, "END launcher3 dump state");
-    }
-
     @Override
     public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
         super.dump(prefix, fd, writer, args);
-        // Dump workspace
-        writer.println(prefix + "Workspace Items");
-        for (int i = mWorkspace.numCustomPages(); i < mWorkspace.getPageCount(); i++) {
-            writer.println(prefix + "  Homescreen " + i);
 
-            ViewGroup layout = ((CellLayout) mWorkspace.getPageAt(i)).getShortcutsAndWidgets();
+        if (args.length > 0 && TextUtils.equals(args[0], "--all")) {
+            writer.println(prefix + "Workspace Items");
+            for (int i = mWorkspace.numCustomPages(); i < mWorkspace.getPageCount(); i++) {
+                writer.println(prefix + "  Homescreen " + i);
+
+                ViewGroup layout = ((CellLayout) mWorkspace.getPageAt(i)).getShortcutsAndWidgets();
+                for (int j = 0; j < layout.getChildCount(); j++) {
+                    Object tag = layout.getChildAt(j).getTag();
+                    if (tag != null) {
+                        writer.println(prefix + "    " + tag.toString());
+                    }
+                }
+            }
+
+            writer.println(prefix + "  Hotseat");
+            ViewGroup layout = mHotseat.getLayout().getShortcutsAndWidgets();
             for (int j = 0; j < layout.getChildCount(); j++) {
                 Object tag = layout.getChildAt(j).getTag();
                 if (tag != null) {
                     writer.println(prefix + "    " + tag.toString());
                 }
             }
-        }
 
-        writer.println(prefix + "  Hotseat");
-        ViewGroup layout = mHotseat.getLayout().getShortcutsAndWidgets();
-        for (int j = 0; j < layout.getChildCount(); j++) {
-            Object tag = layout.getChildAt(j).getTag();
-            if (tag != null) {
-                writer.println(prefix + "    " + tag.toString());
+            try {
+                FileLog.flushAll(writer);
+            } catch (Exception e) {
+                // Ignore
             }
         }
 
-        try {
-            FileLog.flushAll(writer);
-        } catch (Exception e) {
-            // Ignore
-        }
+        writer.println(prefix + "Misc:");
+        writer.print(prefix + "\tmWorkspaceLoading=" + mWorkspaceLoading);
+        writer.print(" mPendingRequestArgs=" + mPendingRequestArgs);
+        writer.println(" mPendingActivityResult=" + mPendingActivityResult);
+
+        mModel.dumpState(prefix, fd, writer, args);
 
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.dump(prefix, fd, writer, args);
         }
     }
 
+    @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)) {
+                        PopupContainerWithArrow.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 +4113,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;
@@ -4480,22 +4120,16 @@
         return ((Launcher) ((ContextWrapper) context).getBaseContext());
     }
 
-    private class RotationPrefChangeHandler implements OnSharedPreferenceChangeListener, Runnable {
+    private class RotationPrefChangeHandler implements OnSharedPreferenceChangeListener {
 
         @Override
         public void onSharedPreferenceChanged(
                 SharedPreferences sharedPreferences, String key) {
             if (Utilities.ALLOW_ROTATION_PREFERENCE_KEY.equals(key)) {
-                mRotationEnabled = Utilities.isAllowRotationPrefEnabled(getApplicationContext());
-                if (!waitUntilResume(this, true)) {
-                    run();
-                }
+                // Finish this instance of the activity. When the activity is recreated,
+                // it will initialize the rotation preference again.
+                finish();
             }
         }
-
-        @Override
-        public void run() {
-            setOrientation();
-        }
     }
 }
diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java
index 01e73d4..aa7f5ee 100644
--- a/src/com/android/launcher3/LauncherAnimUtils.java
+++ b/src/com/android/launcher3/LauncherAnimUtils.java
@@ -23,7 +23,9 @@
 import android.animation.ValueAnimator;
 import android.util.Property;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
+import android.widget.ViewAnimator;
 
 import java.util.HashSet;
 import java.util.WeakHashMap;
@@ -92,7 +94,7 @@
         return anim;
     }
 
-    public static ValueAnimator ofFloat(View target, float... values) {
+    public static ValueAnimator ofFloat(float... values) {
         ValueAnimator anim = new ValueAnimator();
         anim.setFloatValues(values);
         cancelOnDestroyActivity(anim);
@@ -127,4 +129,5 @@
         new FirstFrameAnimatorHelper(anim, view);
         return anim;
     }
+
 }
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 7861a10..180c202 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -16,10 +16,11 @@
 
 package com.android.launcher3;
 
-import android.content.BroadcastReceiver;
+import android.content.ContentProviderClient;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.os.Looper;
 import android.util.Log;
 
 import com.android.launcher3.compat.LauncherAppsCompat;
@@ -27,37 +28,43 @@
 import com.android.launcher3.compat.UserManagerCompat;
 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.Preconditions;
 import com.android.launcher3.util.TestingUtils;
-import com.android.launcher3.util.Thunk;
 
-import java.lang.ref.WeakReference;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
 
 public class LauncherAppState {
 
     public static final boolean PROFILE_STARTUP = ProviderConfig.IS_DOGFOOD_BUILD;
 
-    private final AppFilter mAppFilter;
-    @Thunk final LauncherModel mModel;
-    private final IconCache mIconCache;
-    private final WidgetPreviewLoader mWidgetCache;
-    private final DeepShortcutManager mDeepShortcutManager;
-
-    @Thunk boolean mWallpaperChangedSinceLastCheck;
-
-    private static WeakReference<LauncherProvider> sLauncherProvider;
-    private static Context sContext;
-
+    // We do not need any synchronization for this variable as its only written on UI thread.
     private static LauncherAppState INSTANCE;
 
-    private InvariantDeviceProfile mInvariantDeviceProfile;
+    private final Context mContext;
+    private final LauncherModel mModel;
+    private final IconCache mIconCache;
+    private final WidgetPreviewLoader mWidgetCache;
+    private final InvariantDeviceProfile mInvariantDeviceProfile;
 
-    public static LauncherAppState getInstance() {
+
+    public static LauncherAppState getInstance(final Context context) {
         if (INSTANCE == null) {
-            INSTANCE = new LauncherAppState();
+            if (Looper.myLooper() == Looper.getMainLooper()) {
+                INSTANCE = new LauncherAppState(context.getApplicationContext());
+            } else {
+                try {
+                    return new MainThreadExecutor().submit(new Callable<LauncherAppState>() {
+                        @Override
+                        public LauncherAppState call() throws Exception {
+                            return LauncherAppState.getInstance(context);
+                        }
+                    }).get();
+                } catch (InterruptedException|ExecutionException e) {
+                    throw new RuntimeException(e);
+                }
+            }
         }
         return INSTANCE;
     }
@@ -67,43 +74,30 @@
     }
 
     public Context getContext() {
-        return sContext;
+        return mContext;
     }
 
-    static void setLauncherProvider(LauncherProvider provider) {
-        if (sLauncherProvider != null) {
-            Log.w(Launcher.TAG, "setLauncherProvider called twice! old=" +
-                    sLauncherProvider.get() + " new=" + provider);
+    private LauncherAppState(Context context) {
+        if (getLocalProvider(context) == null) {
+            throw new RuntimeException(
+                    "Initializing LauncherAppState in the absence of LauncherProvider");
         }
-        sLauncherProvider = new WeakReference<>(provider);
-
-        // The content provider exists for the entire duration of the launcher main process and
-        // is the first component to get created. Initializing application context here ensures
-        // that LauncherAppState always exists in the main process.
-        sContext = provider.getContext().getApplicationContext();
-        FileLog.setDir(sContext.getFilesDir());
-    }
-
-    private LauncherAppState() {
-        if (sContext == null) {
-            throw new IllegalStateException("LauncherAppState inited before app context set");
-        }
-
-        Log.v(Launcher.TAG, "LauncherAppState inited");
+        Log.v(Launcher.TAG, "LauncherAppState initiated");
+        Preconditions.assertUIThread();
+        mContext = context;
 
         if (TestingUtils.MEMORY_DUMP_ENABLED) {
-            TestingUtils.startTrackingMemory(sContext);
+            TestingUtils.startTrackingMemory(mContext);
         }
 
-        mInvariantDeviceProfile = new InvariantDeviceProfile(sContext);
-        mIconCache = new IconCache(sContext, mInvariantDeviceProfile);
-        mWidgetCache = new WidgetPreviewLoader(sContext, mIconCache);
-        mDeepShortcutManager = new DeepShortcutManager(sContext, new ShortcutCache());
+        mInvariantDeviceProfile = new InvariantDeviceProfile(mContext);
+        mIconCache = new IconCache(mContext, mInvariantDeviceProfile);
+        mWidgetCache = new WidgetPreviewLoader(mContext, mIconCache);
 
-        mAppFilter = AppFilter.loadByName(sContext.getString(R.string.app_filter_class));
-        mModel = new LauncherModel(this, mIconCache, mAppFilter, mDeepShortcutManager);
+        mModel = new LauncherModel(this, mIconCache,
+                Utilities.getOverrideObject(AppFilter.class, mContext, R.string.app_filter_class));
 
-        LauncherAppsCompat.getInstance(sContext).addOnAppsChangedCallback(mModel);
+        LauncherAppsCompat.getInstance(mContext).addOnAppsChangedCallback(mModel);
 
         // Register intent receivers
         IntentFilter filter = new IntentFilter();
@@ -115,48 +109,30 @@
         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);
         }
 
-        sContext.registerReceiver(mModel, filter);
-        UserManagerCompat.getInstance(sContext).enableAndResetCache();
-        if (!Utilities.ATLEAST_KITKAT) {
-            sContext.registerReceiver(new BroadcastReceiver() {
+        mContext.registerReceiver(mModel, filter);
+        UserManagerCompat.getInstance(mContext).enableAndResetCache();
+        new ConfigMonitor(mContext).register();
 
-                @Override
-                public void onReceive(Context context, Intent intent) {
-                    mWallpaperChangedSinceLastCheck = true;
-                }
-            }, new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED));
-        }
-        new ConfigMonitor(sContext).register();
-
-        ExtractionUtils.startColorExtractionServiceIfNecessary(sContext);
+        ExtractionUtils.startColorExtractionServiceIfNecessary(mContext);
     }
 
     /**
      * Call from Application.onTerminate(), which is not guaranteed to ever be called.
      */
     public void onTerminate() {
-        sContext.unregisterReceiver(mModel);
-        final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(sContext);
+        mContext.unregisterReceiver(mModel);
+        final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(mContext);
         launcherApps.removeOnAppsChangedCallback(mModel);
-        PackageInstallerCompat.getInstance(sContext).onStop();
-    }
-
-    /**
-     * Reloads the workspace items from the DB and re-binds the workspace. This should generally
-     * not be called as DB updates are automatically followed by UI update
-     */
-    public void reloadWorkspace() {
-        mModel.resetLoadedState(false, true);
-        mModel.startLoaderFromBackground();
+        PackageInstallerCompat.getInstance(mContext).onStop();
     }
 
     LauncherModel setLauncher(Launcher launcher) {
-        sLauncherProvider.get().setLauncherProviderChangeListener(launcher);
+        getLocalProvider(mContext).setLauncherProviderChangeListener(launcher);
         mModel.initialize(launcher);
         return mModel;
     }
@@ -173,17 +149,21 @@
         return mWidgetCache;
     }
 
-    public DeepShortcutManager getShortcutManager() {
-        return mDeepShortcutManager;
-    }
-
-    public boolean hasWallpaperChangedSinceLastCheck() {
-        boolean result = mWallpaperChangedSinceLastCheck;
-        mWallpaperChangedSinceLastCheck = false;
-        return result;
-    }
-
     public InvariantDeviceProfile getInvariantDeviceProfile() {
         return mInvariantDeviceProfile;
     }
+
+    /**
+     * Shorthand for {@link #getInvariantDeviceProfile()}
+     */
+    public static InvariantDeviceProfile getIDP(Context context) {
+        return LauncherAppState.getInstance(context).getInvariantDeviceProfile();
+    }
+
+    private static LauncherProvider getLocalProvider(Context context) {
+        try (ContentProviderClient cl = context.getContentResolver()
+                .acquireContentProviderClient(LauncherProvider.AUTHORITY)) {
+            return (LauncherProvider) cl.getLocalContentProvider();
+        }
+    }
 }
diff --git a/src/com/android/launcher3/LauncherAppWidgetHost.java b/src/com/android/launcher3/LauncherAppWidgetHost.java
index d3e5350..6e8c59b 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;
+            }
         }
     }
 
@@ -112,6 +128,18 @@
         super.onProviderChanged(appWidgetId, info);
         // The super method updates the dimensions of the providerInfo. Update the
         // launcher spans accordingly.
-        info.initSpans();
+        info.initSpans(mLauncher);
+    }
+
+    @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..13cc7ba 100644
--- a/src/com/android/launcher3/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/LauncherAppWidgetHostView.java
@@ -19,7 +19,12 @@
 import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
+import android.graphics.PointF;
 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,22 +33,38 @@
 import android.view.ViewDebug;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.AdapterView;
+import android.widget.Advanceable;
 import android.widget.RemoteViews;
 
+import com.android.launcher3.dragndrop.DragLayer;
 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 {
+public class LauncherAppWidgetHostView extends AppWidgetHostView
+        implements TouchCompleteListener, View.OnLongClickListener {
 
-    LayoutInflater mInflater;
+    private static final String TAG = "LauncherWidgetHostView";
 
-    private CheckLongPressHelper mLongPressHelper;
-    private StylusEventHelper mStylusEventHelper;
-    private Context mContext;
+    // 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();
+
+    protected final LayoutInflater mInflater;
+
+    private final CheckLongPressHelper mLongPressHelper;
+    private final StylusEventHelper mStylusEventHelper;
+    private final Context mContext;
+
     @ViewDebug.ExportedProperty(category = "launcher")
     private int mPreviousOrientation;
 
@@ -52,21 +73,54 @@
     @ViewDebug.ExportedProperty(category = "launcher")
     private boolean mChildrenFocused;
 
-    protected int mErrorViewId = R.layout.appwidget_error;
+    private boolean mIsScrollable;
+    private boolean mIsAttachedToWindow;
+    private boolean mIsAutoAdvanceRegistered;
+    private Runnable mAutoAdvanceRunnable;
+
+    /**
+     * The scaleX and scaleY value such that the widget fits within its cellspans, scaleX = scaleY.
+     */
+    private float mScaleToFit = 1f;
+
+    /**
+     * The translation values to center the widget within its cellspans.
+     */
+    private final PointF mTranslationForCentering = new PointF(0, 0);
 
     public LauncherAppWidgetHostView(Context context) {
         super(context);
         mContext = context;
-        mLongPressHelper = new CheckLongPressHelper(this);
+        mLongPressHelper = new CheckLongPressHelper(this, 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("setExecutor", Executor.class);
+                asyncMethod.invoke(this, Utilities.THREAD_POOL_EXECUTOR);
+            } catch (Exception e) {
+                Log.e(TAG, "Unable to set async executor", e);
+            }
+        }
+    }
+
+    @Override
+    public boolean onLongClick(View view) {
+        if (mIsScrollable) {
+            DragLayer dragLayer = Launcher.getLauncher(getContext()).getDragLayer();
+            dragLayer.requestDisallowInterceptTouchEvent(false);
+        }
+        view.performLongClick();
+        return true;
     }
 
     @Override
     protected View getErrorView() {
-        return mInflater.inflate(mErrorViewId, this, false);
+        return mInflater.inflate(R.layout.appwidget_error, this, false);
     }
 
     public void updateLastInflationOrientation() {
@@ -78,6 +132,25 @@
         // Store the orientation in which the widget was inflated
         updateLastInflationOrientation();
         super.updateAppWidget(remoteViews);
+
+        // The provider info or the views might have changed.
+        checkIfAutoAdvance();
+    }
+
+    private boolean checkScrollableRecursively(ViewGroup viewGroup) {
+        if (viewGroup instanceof AdapterView) {
+            return true;
+        } else {
+            for (int i=0; i < viewGroup.getChildCount(); i++) {
+                View child = viewGroup.getChildAt(i);
+                if (child instanceof ViewGroup) {
+                    if (checkScrollableRecursively((ViewGroup) child)) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
     }
 
     public boolean isReinflateRequired() {
@@ -108,12 +181,18 @@
             mLongPressHelper.cancelLongPress();
             return true;
         }
+
         switch (ev.getAction()) {
             case MotionEvent.ACTION_DOWN: {
+                DragLayer dragLayer = Launcher.getLauncher(getContext()).getDragLayer();
+
+                if (mIsScrollable) {
+                     dragLayer.requestDisallowInterceptTouchEvent(true);
+                }
                 if (!mStylusEventHelper.inStylusButtonPressed()) {
                     mLongPressHelper.postCheckForLongPress();
                 }
-                Launcher.getLauncher(getContext()).getDragLayer().setTouchCompleteListener(this);
+                dragLayer.setTouchCompleteListener(this);
                 break;
             }
 
@@ -153,6 +232,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 +263,6 @@
         return info;
     }
 
-    public LauncherAppWidgetProviderInfo getLauncherAppWidgetProviderInfo() {
-        return (LauncherAppWidgetProviderInfo) getAppWidgetInfo();
-    }
-
     @Override
     public void onTouchComplete() {
         if (!mLongPressHelper.hasPerformedLongPress()) {
@@ -276,6 +364,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,11 +377,12 @@
             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();
                 }
             });
         }
+
+        mIsScrollable = checkScrollableRecursively(this);
     }
 
     @Override
@@ -296,4 +390,99 @@
         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();
+    }
+
+    public void setScaleToFit(float scale) {
+        mScaleToFit = scale;
+        setScaleX(scale);
+        setScaleY(scale);
+    }
+
+    public float getScaleToFit() {
+        return mScaleToFit;
+    }
+
+    public void setTranslationForCentering(float x, float y) {
+        mTranslationForCentering.set(x, y);
+        setTranslationX(x);
+        setTranslationY(y);
+    }
+
+    public PointF getTranslationForCentering() {
+        return mTranslationForCentering;
+    }
 }
diff --git a/src/com/android/launcher3/LauncherAppWidgetInfo.java b/src/com/android/launcher3/LauncherAppWidgetInfo.java
index 66d8957..1e0f285 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 android.os.Process;
 
-import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.util.ContentWriter;
 
 /**
  * Represents a widget (either instantiated or about to be) in the Launcher.
@@ -66,7 +65,7 @@
     /**
      * Indicates that the widget hasn't been instantiated yet.
      */
-    static final int NO_ID = -1;
+    public static final int NO_ID = -1;
 
     /**
      * Indicates that this is a locally defined widget and hence has no system allocated id.
@@ -77,19 +76,19 @@
      * Identifier for this widget when talking with
      * {@link android.appwidget.AppWidgetManager} for updates.
      */
-    int appWidgetId = NO_ID;
+    public int appWidgetId = NO_ID;
 
     public ComponentName providerName;
 
     /**
      * 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 {
@@ -113,22 +112,26 @@
         spanX = -1;
         spanY = -1;
         // We only support app widgets on current user.
-        user = UserHandleCompat.myUserHandle();
+        user = Process.myUserHandle();
         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));
+    public 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/LauncherAppWidgetProviderInfo.java b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
index 1a4153f..6cb703b 100644
--- a/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
+++ b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
@@ -1,6 +1,5 @@
 package com.android.launcher3;
 
-import android.annotation.TargetApi;
 import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.ComponentName;
@@ -9,8 +8,9 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
-import android.os.Build;
 import android.os.Parcel;
+import android.os.Process;
+import android.os.UserHandle;
 
 /**
  * This class is a thin wrapper around the framework AppWidgetProviderInfo class. This class affords
@@ -29,22 +29,27 @@
 
     public static LauncherAppWidgetProviderInfo fromProviderInfo(Context context,
             AppWidgetProviderInfo info) {
+        final LauncherAppWidgetProviderInfo launcherInfo;
+        if (info instanceof LauncherAppWidgetProviderInfo) {
+            launcherInfo = (LauncherAppWidgetProviderInfo) info;
+        } else {
 
-        // In lieu of a public super copy constructor, we first write the AppWidgetProviderInfo
-        // into a parcel, and then construct a new LauncherAppWidgetProvider info from the
-        // associated super parcel constructor. This allows us to copy non-public members without
-        // using reflection.
-        Parcel p = Parcel.obtain();
-        info.writeToParcel(p, 0);
-        p.setDataPosition(0);
-        LauncherAppWidgetProviderInfo lawpi = new LauncherAppWidgetProviderInfo(p);
-        p.recycle();
-        return lawpi;
+            // In lieu of a public super copy constructor, we first write the AppWidgetProviderInfo
+            // into a parcel, and then construct a new LauncherAppWidgetProvider info from the
+            // associated super parcel constructor. This allows us to copy non-public members without
+            // using reflection.
+            Parcel p = Parcel.obtain();
+            info.writeToParcel(p, 0);
+            p.setDataPosition(0);
+            launcherInfo = new LauncherAppWidgetProviderInfo(p);
+            p.recycle();
+        }
+        launcherInfo.initSpans(context);
+        return launcherInfo;
     }
 
-    public LauncherAppWidgetProviderInfo(Parcel in) {
+    private LauncherAppWidgetProviderInfo(Parcel in) {
         super(in);
-        initSpans();
     }
 
     public LauncherAppWidgetProviderInfo(Context context, CustomAppWidget widget) {
@@ -56,12 +61,11 @@
         previewImage = widget.getPreviewImage();
         initialLayout = widget.getWidgetLayout();
         resizeMode = widget.getResizeMode();
-        initSpans();
+        initSpans(context);
     }
 
-    public void initSpans() {
-        LauncherAppState app = LauncherAppState.getInstance();
-        InvariantDeviceProfile idp = app.getInvariantDeviceProfile();
+    public void initSpans(Context context) {
+        InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
 
         Point paddingLand = idp.landscapeProfile.getTotalWorkspacePadding();
         Point paddingPort = idp.portraitProfile.getTotalWorkspacePadding();
@@ -80,7 +84,7 @@
         // We want to account for the extra amount of padding that we are adding to the widget
         // to ensure that it gets the full amount of space that it has requested.
         Rect widgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(
-                app.getContext(), provider, null);
+                context, provider, null);
         spanX = Math.max(1, (int) Math.ceil(
                         (minWidth + widgetPadding.left + widgetPadding.right) / smallestCellWidth));
         spanY = Math.max(1, (int) Math.ceil(
@@ -92,7 +96,6 @@
                 (minResizeHeight + widgetPadding.top + widgetPadding.bottom) / smallestCellHeight));
     }
 
-    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
     public String getLabel(PackageManager packageManager) {
         if (isCustomWidget) {
             return Utilities.trim(label);
@@ -100,13 +103,11 @@
         return super.loadLabel(packageManager);
     }
 
-    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
     public Drawable getIcon(Context context, IconCache cache) {
         if (isCustomWidget) {
             return cache.getFullResIcon(provider.getPackageName(), icon);
         }
-        return super.loadIcon(context,
-                LauncherAppState.getInstance().getInvariantDeviceProfile().fillResIconDpi);
+        return super.loadIcon(context, LauncherAppState.getIDP(context).fillResIconDpi);
     }
 
     public String toString(PackageManager pm) {
@@ -122,4 +123,8 @@
                 (resizeMode & RESIZE_HORIZONTAL) != 0 ? minSpanX : -1,
                         (resizeMode & RESIZE_VERTICAL) != 0 ? minSpanY : -1);
     }
+
+    public UserHandle getUser() {
+        return isCustomWidget ? Process.myUserHandle() : getProfile();
+    }
  }
diff --git a/src/com/android/launcher3/LauncherBackupAgent.java b/src/com/android/launcher3/LauncherBackupAgent.java
index b3e73f7..140794b 100644
--- a/src/com/android/launcher3/LauncherBackupAgent.java
+++ b/src/com/android/launcher3/LauncherBackupAgent.java
@@ -5,11 +5,19 @@
 import android.app.backup.BackupDataOutput;
 import android.os.ParcelFileDescriptor;
 
+import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.provider.RestoreDbTask;
 
 public class LauncherBackupAgent extends BackupAgent {
 
     @Override
+    public void onCreate() {
+        super.onCreate();
+        // Set the log dir as LauncherAppState is not initialized during restore.
+        FileLog.setDir(getFilesDir());
+    }
+
+    @Override
     public void onRestore(
             BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) {
         // Doesn't do incremental backup/restore
diff --git a/src/com/android/launcher3/LauncherCallbacks.java b/src/com/android/launcher3/LauncherCallbacks.java
index 6394b90..2bac11f 100644
--- a/src/com/android/launcher3/LauncherCallbacks.java
+++ b/src/com/android/launcher3/LauncherCallbacks.java
@@ -92,7 +92,6 @@
     /*
      * Extensions points for adding / replacing some other aspects of the Launcher experience.
      */
-    public UserEventDispatcher getUserEventDispatcher();
     public boolean shouldMoveToDefaultScreenOnHomeIntent();
     public boolean hasSettings();
     public AllAppsSearchBarController getAllAppsSearchBarController();
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 0199d0c..2586764 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -24,41 +24,47 @@
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
-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.content.pm.LauncherActivityInfo;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
-import android.os.Parcelable;
 import android.os.Process;
 import android.os.SystemClock;
 import android.os.Trace;
-import android.provider.BaseColumns;
+import android.os.UserHandle;
+import android.support.annotation.Nullable;
 import android.text.TextUtils;
 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;
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.PackageInstallerCompat;
 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
-import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.config.ProviderConfig;
 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.BgDataModel;
+import com.android.launcher3.model.CacheDataUpdatedTask;
+import com.android.launcher3.model.ExtendedModelTask;
 import com.android.launcher3.model.GridSizeMigrationTask;
+import com.android.launcher3.model.LoaderCursor;
+import com.android.launcher3.model.ModelWriter;
+import com.android.launcher3.model.PackageInstallStateChangedTask;
+import com.android.launcher3.model.PackageItemInfo;
+import com.android.launcher3.model.PackageUpdatedTask;
+import com.android.launcher3.model.SdCardAvailableReceiver;
+import com.android.launcher3.model.ShortcutsChangedTask;
+import com.android.launcher3.model.UserLockStateChangedTask;
+import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.provider.ImportDataTask;
 import com.android.launcher3.provider.LauncherDbUtils;
@@ -66,23 +72,19 @@
 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.CursorIconInfo;
-import com.android.launcher3.util.FlagOp;
-import com.android.launcher3.util.GridOccupancy;
-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.PackageUserKey;
 import com.android.launcher3.util.Preconditions;
-import com.android.launcher3.util.StringFilter;
+import com.android.launcher3.util.Provider;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.ViewOnDrawExecutor;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
-import java.net.URISyntaxException;
-import java.security.InvalidParameterException;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
@@ -90,8 +92,8 @@
 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.CancellationException;
 import java.util.concurrent.Executor;
 
 /**
@@ -123,12 +125,16 @@
     }
     @Thunk static final Handler sWorker = new Handler(sWorkerThread.getLooper());
 
-    // We start off with everything not loaded.  After that, we assume that
+    // Indicates whether the current model data is valid or not.
+    // We start off with everything not loaded. After that, we assume that
     // our monitoring of the package manager provides all updates and we never
-    // need to do a requery.  These are only ever touched from the loader thread.
-    private boolean mWorkspaceLoaded;
-    private boolean mAllAppsLoaded;
-    private boolean mDeepShortcutsLoaded;
+    // need to do a requery. This is only ever touched from the loader thread.
+    private boolean mModelLoaded;
+    public boolean isModelLoaded() {
+        synchronized (mLock) {
+            return mModelLoaded && mLoaderTask == null;
+        }
+    }
 
     /**
      * Set of runnables to be called on the background thread after the workspace binding
@@ -143,60 +149,30 @@
     // 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();
+            if (mModelLoaded) {
+                boolean hasShortcutHostPermission =
+                        DeepShortcutManager.getInstance(mApp.getContext()).hasHostPermission();
                 if (hasShortcutHostPermission != mHasShortcutHostPermission) {
-                    mApp.reloadWorkspace();
+                    forceReload();
                 }
             }
         }
     };
 
-    // 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;
@@ -219,32 +195,26 @@
                                   ArrayList<AppInfo> addedApps);
         public void bindAppsUpdated(ArrayList<AppInfo> apps);
         public void bindShortcutsChanged(ArrayList<ShortcutInfo> updated,
-                ArrayList<ShortcutInfo> removed, UserHandleCompat user);
+                ArrayList<ShortcutInfo> removed, UserHandle user);
         public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
         public void bindRestoreItemsChange(HashSet<ItemInfo> updates);
         public void bindWorkspaceComponentsRemoved(
                 HashSet<String> packageNames, HashSet<ComponentName> components,
-                UserHandleCompat user);
+                UserHandle 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);
@@ -272,314 +242,42 @@
         }
     }
 
-    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, Process.myUserHandle(), packages));
     }
 
     /**
      * Adds the provided items to the workspace.
      */
-    public void addAndBindAddedWorkspaceItems(final Context context,
-            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);
+    public void addAndBindAddedWorkspaceItems(List<ItemInfo> workspaceApps) {
+        addAndBindAddedWorkspaceItems(Provider.of(workspaceApps));
     }
 
     /**
-     * Adds an item to the DB if it was not created previously, or move it to a new
-     * <container, screen, cellX, cellY>
+     * Adds the provided items to the workspace.
      */
-    public static void addOrMoveItemInDatabase(Context context, ItemInfo item, long container,
-            long screenId, int cellX, int cellY) {
-        if (item.container == ItemInfo.NO_ID) {
-            // From all apps
-            addItemToDatabase(context, item, container, screenId, cellX, cellY);
-        } else {
-            // From somewhere else
-            moveItemInDatabase(context, item, container, screenId, cellX, cellY);
-        }
+    public void addAndBindAddedWorkspaceItems(
+            Provider<List<ItemInfo>> appsProvider) {
+        enqueueModelUpdateTask(new AddWorkspaceItemsTask(appsProvider));
+    }
+
+    public ModelWriter getWriter(boolean hasVerticalHotseat) {
+        return new ModelWriter(mApp.getContext(), sBgDataModel, hasVerticalHotseat);
     }
 
     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) {
@@ -620,7 +318,7 @@
         final long itemId = item.id;
         Runnable r = new Runnable() {
             public void run() {
-                synchronized (sBgLock) {
+                synchronized (sBgDataModel) {
                     checkItemInfoLocked(itemId, item, stackTrace);
                 }
             }
@@ -628,439 +326,11 @@
         runOnWorkerThread(r);
     }
 
-    static void updateItemInDatabaseHelper(Context context, final ContentValues values,
-            final ItemInfo item, final String callingFunction) {
-        final long itemId = item.id;
-        final Uri uri = LauncherSettings.Favorites.getContentUri(itemId);
-        final ContentResolver cr = context.getContentResolver();
-
-        final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
-        Runnable r = new Runnable() {
-            public void run() {
-                cr.update(uri, values, null, null);
-                updateItemArrays(item, itemId, stackTrace);
-            }
-        };
-        runOnWorkerThread(r);
-    }
-
-    static void updateItemsInDatabaseHelper(Context context, final ArrayList<ContentValues> valuesList,
-            final ArrayList<ItemInfo> items, final String callingFunction) {
-        final ContentResolver cr = context.getContentResolver();
-
-        final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
-        Runnable r = new Runnable() {
-            public void run() {
-                ArrayList<ContentProviderOperation> ops =
-                        new ArrayList<ContentProviderOperation>();
-                int count = items.size();
-                for (int i = 0; i < count; i++) {
-                    ItemInfo item = items.get(i);
-                    final long itemId = item.id;
-                    final Uri uri = LauncherSettings.Favorites.getContentUri(itemId);
-                    ContentValues values = valuesList.get(i);
-
-                    ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build());
-                    updateItemArrays(item, itemId, stackTrace);
-
-                }
-                try {
-                    cr.applyBatch(LauncherProvider.AUTHORITY, ops);
-                } catch (Exception e) {
-                    e.printStackTrace();
-                }
-            }
-        };
-        runOnWorkerThread(r);
-    }
-
-    static void updateItemArrays(ItemInfo item, long itemId, StackTraceElement[] stackTrace) {
-        // Lock on mBgLock *after* the db operation
-        synchronized (sBgLock) {
-            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)) {
-                    // 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: " +
-                            item.container + ", not in the list of folders";
-                    Log.e(TAG, msg);
-                }
-            }
-
-            // 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);
-            if (modelItem != null &&
-                    (modelItem.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
-                     modelItem.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT)) {
-                switch (modelItem.itemType) {
-                    case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
-                    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);
-                        }
-                        break;
-                    default:
-                        break;
-                }
-            } else {
-                sBgWorkspaceItems.remove(modelItem);
-            }
-        }
-    }
-
-    /**
-     * Move an item in the DB to a new <container, screen, cellX, cellY>
-     */
-    public static void moveItemInDatabase(Context context, final ItemInfo item, final long container,
-            final long screenId, final int cellX, final int cellY) {
-        item.container = container;
-        item.cellX = cellX;
-        item.cellY = cellY;
-
-        // We store hotseat items in canonical form which is this orientation invariant position
-        // in the hotseat
-        if (context instanceof Launcher && screenId < 0 &&
-                container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
-            item.screenId = Launcher.getLauncher(context).getHotseat()
-                    .getOrderInHotseat(cellX, cellY);
-        } else {
-            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);
-
-        updateItemInDatabaseHelper(context, values, item, "moveItemInDatabase");
-    }
-
-    /**
-     * Move items in the DB to a new <container, screen, cellX, cellY>. We assume that the
-     * cellX, cellY have already been updated on the ItemInfos.
-     */
-    public static void moveItemsInDatabase(Context context, final ArrayList<ItemInfo> items,
-            final long container, final int screen) {
-
-        ArrayList<ContentValues> contentValues = new ArrayList<ContentValues>();
-        int count = items.size();
-
-        for (int i = 0; i < count; i++) {
-            ItemInfo item = items.get(i);
-            item.container = container;
-
-            // We store hotseat items in canonical form which is this orientation invariant position
-            // in the hotseat
-            if (context instanceof Launcher && screen < 0 &&
-                    container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
-                item.screenId = Launcher.getLauncher(context).getHotseat().getOrderInHotseat(item.cellX,
-                        item.cellY);
-            } else {
-                item.screenId = screen;
-            }
-
-            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);
-
-            contentValues.add(values);
-        }
-        updateItemsInDatabaseHelper(context, contentValues, items, "moveItemInDatabase");
-    }
-
-    /**
-     * Move and/or resize item in the DB to a new <container, screen, cellX, cellY, spanX, spanY>
-     */
-    static void modifyItemInDatabase(Context context, final ItemInfo item, final long container,
-            final long screenId, final int cellX, final int cellY, final int spanX, final int spanY) {
-        item.container = container;
-        item.cellX = cellX;
-        item.cellY = cellY;
-        item.spanX = spanX;
-        item.spanY = spanY;
-
-        // We store hotseat items in canonical form which is this orientation invariant position
-        // in the hotseat
-        if (context instanceof Launcher && screenId < 0 &&
-                container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
-            item.screenId = Launcher.getLauncher(context).getHotseat()
-                    .getOrderInHotseat(cellX, cellY);
-        } else {
-            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);
-
-        updateItemInDatabaseHelper(context, values, 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;
-    }
-
-    /**
-     * Add an item to the database in a specified container. Sets the container, screen, cellX and
-     * cellY fields of the item. Also assigns an ID to the item.
-     */
-    public static void addItemToDatabase(Context context, final ItemInfo item, final long container,
-            final long screenId, final int cellX, final int cellY) {
-        item.container = container;
-        item.cellX = cellX;
-        item.cellY = cellY;
-        // We store hotseat items in canonical form which is this orientation invariant position
-        // in the hotseat
-        if (context instanceof Launcher && screenId < 0 &&
-                container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
-            item.screenId = Launcher.getLauncher(context).getHotseat()
-                    .getOrderInHotseat(cellX, cellY);
-        } else {
-            item.screenId = screenId;
-        }
-
-        final ContentValues values = new ContentValues();
-        final ContentResolver cr = context.getContentResolver();
-        item.onAddToDatabase(context, values);
-
-        item.id = LauncherSettings.Settings.call(cr, LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
-                .getLong(LauncherSettings.Settings.EXTRA_VALUE);
-
-        values.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);
-
-                // Lock on mBgLock *after* the db operation
-                synchronized (sBgLock) {
-                    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;
-                    }
-                }
-            }
-        };
-        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>();
-        items.add(item);
-        deleteItemsFromDatabase(context, items);
-    }
-
-    /**
-     * Removes the specified items from the database
-     */
-    static void deleteItemsFromDatabase(Context context, final ArrayList<? extends ItemInfo> items) {
-        final ContentResolver cr = context.getContentResolver();
-        Runnable r = new Runnable() {
-            public void run() {
-                for (ItemInfo item : items) {
-                    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);
-                    }
-                }
-            }
-        };
-        runOnWorkerThread(r);
-    }
-
-    /**
-     * 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;
@@ -1095,38 +365,9 @@
                     throw new RuntimeException(ex);
                 }
 
-                synchronized (sBgLock) {
-                    sBgWorkspaceScreens.clear();
-                    sBgWorkspaceScreens.addAll(screensCopy);
-                }
-            }
-        };
-        runOnWorkerThread(r);
-    }
-
-    /**
-     * Remove the specified folder and all its contents from the database.
-     */
-    public static void deleteFolderAndContentsFromDatabase(Context context, final FolderInfo info) {
-        final ContentResolver cr = context.getContentResolver();
-
-        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);
-                    }
+                synchronized (sBgDataModel) {
+                    sBgDataModel.workspaceScreens.clear();
+                    sBgDataModel.workspaceScreens.addAll(screensCopy);
                 }
             }
         };
@@ -1146,66 +387,64 @@
     }
 
     @Override
-    public void onPackageChanged(String packageName, UserHandleCompat user) {
+    public void onPackageChanged(String packageName, UserHandle 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) {
+    public void onPackageRemoved(String packageName, UserHandle user) {
+        onPackagesRemoved(user, packageName);
+    }
+
+    public void onPackagesRemoved(UserHandle 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) {
+    public void onPackageAdded(String packageName, UserHandle 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,
+    public void onPackagesAvailable(String[] packageNames, UserHandle 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,
+    public void onPackagesUnavailable(String[] packageNames, UserHandle 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));
+    public void onPackagesSuspended(String[] packageNames, UserHandle 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));
+    public void onPackagesUnsuspended(String[] packageNames, UserHandle 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));
+            UserHandle user) {
+        enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, true));
     }
 
     public void updatePinnedShortcuts(String packageName, List<ShortcutInfoCompat> shortcuts,
-            UserHandleCompat user) {
-        enqueueItemUpdatedTask(new ShortcutsChangedTask(packageName, shortcuts, user, false));
+            UserHandle user) {
+        enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, false));
     }
 
     /**
@@ -1227,20 +466,19 @@
         } else if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) ||
                 Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) ||
                 Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) {
-            UserHandleCompat user = UserHandleCompat.fromIntent(intent);
+            UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
             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)) {
@@ -1248,8 +486,16 @@
         }
     }
 
-    void forceReload() {
-        resetLoadedState(true, true);
+    /**
+     * Reloads the workspace items from the DB and re-binds the workspace. This should generally
+     * not be called as DB updates are automatically followed by UI update
+     */
+    public void forceReload() {
+        synchronized (mLock) {
+            // Stop any existing loaders first, so they don't set mModelLoaded to true later
+            stopLoaderLocked();
+            mModelLoaded = false;
+        }
 
         // Do this here because if the launcher activity is running it will be restarted.
         // If it's not running startLoaderFromBackground will merely tell it that it needs
@@ -1257,19 +503,6 @@
         startLoaderFromBackground();
     }
 
-    public void resetLoadedState(boolean resetAllAppsLoaded, boolean resetWorkspaceLoaded) {
-        synchronized (mLock) {
-            // Stop any existing loaders first, so they don't set mAllAppsLoaded or
-            // mWorkspaceLoaded to true later
-            stopLoaderLocked();
-            if (resetAllAppsLoaded) mAllAppsLoaded = false;
-            if (resetWorkspaceLoaded) mWorkspaceLoaded = false;
-            // Always reset deep shortcuts loaded.
-            // TODO: why?
-            mDeepShortcutsLoaded = false;
-        }
-    }
-
     /**
      * When the launcher is in the background, it's possible for it to miss paired
      * configuration changes.  So whenever we trigger the loader from the background
@@ -1321,9 +554,8 @@
                 // If there is already one running, tell it to stop.
                 stopLoaderLocked();
                 mLoaderTask = new LoaderTask(mApp.getContext(), synchronousBindPage);
-                // TODO: mDeepShortcutsLoaded does not need to be true for synchronous bind.
-                if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE && mAllAppsLoaded
-                        && mWorkspaceLoaded && mDeepShortcutsLoaded && !mIsLoaderTaskRunning) {
+                if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE
+                        && mModelLoaded && !mIsLoaderTaskRunning) {
                     mLoaderTask.runBindSynchronousPage(synchronousBindPage);
                     return true;
                 } else {
@@ -1375,28 +607,6 @@
             mPageToBindFirst = pageToBindFirst;
         }
 
-        private void loadAndBindWorkspace() {
-            mIsLoadingAndBindingWorkspace = true;
-
-            // Load the workspace
-            if (DEBUG_LOADERS) {
-                Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded);
-            }
-
-            if (!mWorkspaceLoaded) {
-                loadWorkspace();
-                synchronized (LoaderTask.this) {
-                    if (mStopped) {
-                        return;
-                    }
-                    mWorkspaceLoaded = true;
-                }
-            }
-
-            // Bind the workspace
-            bindWorkspace(mPageToBindFirst);
-        }
-
         private void waitForIdle() {
             // Wait until the either we're stopped or the other threads are done.
             // This way we don't start loading all apps until the workspace has settled
@@ -1439,7 +649,7 @@
                 throw new RuntimeException("Should not call runBindSynchronousPage() without " +
                         "valid page index");
             }
-            if (!mAllAppsLoaded || !mWorkspaceLoaded) {
+            if (!mModelLoaded) {
                 // Ensure that we don't try and bind a specified page when the pages have not been
                 // loaded already (we should load everything asynchronously in that case)
                 throw new RuntimeException("Expecting AllApps and Workspace to be loaded");
@@ -1471,6 +681,14 @@
             bindDeepShortcuts();
         }
 
+        private void verifyNotStopped() throws CancellationException {
+            synchronized (LoaderTask.this) {
+                if (mStopped) {
+                    throw new CancellationException("Loader stopped");
+                }
+            }
+        }
+
         public void run() {
             synchronized (mLock) {
                 if (mStopped) {
@@ -1478,41 +696,72 @@
                 }
                 mIsLoaderTaskRunning = true;
             }
-            // Optimize for end-user experience: if the Launcher is up and // running with the
-            // All Apps interface in the foreground, load All Apps first. Otherwise, load the
-            // workspace first (default).
-            keep_running: {
-                if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
-                loadAndBindWorkspace();
 
-                if (mStopped) {
-                    break keep_running;
-                }
+            try {
+                if (DEBUG_LOADERS) Log.d(TAG, "step 1.1: loading workspace");
+                // Set to false in bindWorkspace()
+                mIsLoadingAndBindingWorkspace = true;
+                loadWorkspace();
 
+                verifyNotStopped();
+                if (DEBUG_LOADERS) Log.d(TAG, "step 1.2: bind workspace workspace");
+                bindWorkspace(mPageToBindFirst);
+
+                // Take a break
+                if (DEBUG_LOADERS) Log.d(TAG, "step 1 completed, wait for idle");
                 waitForIdle();
+                verifyNotStopped();
 
                 // second step
-                if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
-                loadAndBindAllApps();
+                if (DEBUG_LOADERS) Log.d(TAG, "step 2.1: loading all apps");
+                loadAllApps();
 
+                verifyNotStopped();
+                if (DEBUG_LOADERS) Log.d(TAG, "step 2.2: Update icon cache");
+                updateIconCache();
+
+                // Take a break
+                if (DEBUG_LOADERS) Log.d(TAG, "step 2 completed, wait for idle");
                 waitForIdle();
+                verifyNotStopped();
 
                 // third step
-                if (DEBUG_LOADERS) Log.d(TAG, "step 3: loading deep shortcuts");
-                loadAndBindDeepShortcuts();
-            }
+                if (DEBUG_LOADERS) Log.d(TAG, "step 3.1: loading deep shortcuts");
+                loadDeepShortcuts();
 
-            // Clear out this reference, otherwise we end up holding it until all of the
-            // callback runnables are done.
-            mContext = null;
+                verifyNotStopped();
+                if (DEBUG_LOADERS) Log.d(TAG, "step 3.2: bind deep shortcuts");
+                bindDeepShortcuts();
 
-            synchronized (mLock) {
-                // If we are still the last one to be scheduled, remove ourselves.
-                if (mLoaderTask == this) {
-                    mLoaderTask = null;
+                // Take a break
+                if (DEBUG_LOADERS) Log.d(TAG, "step 3 completed, wait for idle");
+                waitForIdle();
+                verifyNotStopped();
+
+                // fourth step
+                if (DEBUG_LOADERS) Log.d(TAG, "step 4.1: loading widgets");
+                refreshAndBindWidgetsAndShortcuts(getCallback(), false /* bindFirst */,
+                        null /* packageUser */);
+
+                synchronized (mLock) {
+                    // Everything loaded bind the data.
+                    mModelLoaded = true;
+                    mHasLoaderCompletedOnce = true;
                 }
-                mIsLoaderTaskRunning = false;
-                mHasLoaderCompletedOnce = true;
+            } catch (CancellationException e) {
+              // Loader stopped, ignore
+            } finally {
+                // Clear out this reference, otherwise we end up holding it until all of the
+                // callback runnables are done.
+                mContext = null;
+
+                synchronized (mLock) {
+                    // If we are still the last one to be scheduled, remove ourselves.
+                    if (mLoaderTask == this) {
+                        mLoaderTask = null;
+                    }
+                    mIsLoaderTaskRunning = false;
+                }
             }
         }
 
@@ -1553,125 +802,19 @@
             }
         }
 
-        // check & update map of what's occupied; used to discard overlapping/invalid items
-        private boolean checkItemPlacement(LongArrayMap<GridOccupancy> occupied, ItemInfo item,
-                   ArrayList<Long> workspaceScreens) {
-            LauncherAppState app = LauncherAppState.getInstance();
-            InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
-
-            long containerIndex = item.screenId;
-            if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
-                // Return early if we detect that an item is under the hotseat button
-                if (!FeatureFlags.NO_ALL_APPS_ICON &&
-                        profile.isAllAppsButtonRank((int) item.screenId)) {
-                    Log.e(TAG, "Error loading shortcut into hotseat " + item
-                            + " into position (" + item.screenId + ":" + item.cellX + ","
-                            + item.cellY + ") occupied by all apps");
-                    return false;
-                }
-
-                final GridOccupancy hotseatOccupancy =
-                        occupied.get((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT);
-
-                if (item.screenId >= profile.numHotseatIcons) {
-                    Log.e(TAG, "Error loading shortcut " + item
-                            + " into hotseat position " + item.screenId
-                            + ", position out of bounds: (0 to " + (profile.numHotseatIcons - 1)
-                            + ")");
-                    return false;
-                }
-
-                if (hotseatOccupancy != null) {
-                    if (hotseatOccupancy.cells[(int) item.screenId][0]) {
-                        Log.e(TAG, "Error loading shortcut into hotseat " + item
-                                + " into position (" + item.screenId + ":" + item.cellX + ","
-                                + item.cellY + ") already occupied");
-                            return false;
-                    } else {
-                        hotseatOccupancy.cells[(int) item.screenId][0] = true;
-                        return true;
-                    }
-                } else {
-                    final GridOccupancy occupancy = new GridOccupancy(profile.numHotseatIcons, 1);
-                    occupancy.cells[(int) item.screenId][0] = true;
-                    occupied.put((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT, occupancy);
-                    return true;
-                }
-            } else if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
-                if (!workspaceScreens.contains((Long) item.screenId)) {
-                    // The item has an invalid screen id.
-                    return false;
-                }
-            } else {
-                // Skip further checking if it is not the hotseat or workspace container
-                return true;
-            }
-
-            final int countX = profile.numColumns;
-            final int countY = profile.numRows;
-            if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
-                    item.cellX < 0 || item.cellY < 0 ||
-                    item.cellX + item.spanX > countX || item.cellY + item.spanY > countY) {
-                Log.e(TAG, "Error loading shortcut " + item
-                        + " into cell (" + containerIndex + "-" + item.screenId + ":"
-                        + item.cellX + "," + item.cellY
-                        + ") out of screen bounds ( " + countX + "x" + countY + ")");
-                return false;
-            }
-
-            if (!occupied.containsKey(item.screenId)) {
-                GridOccupancy screen = new GridOccupancy(countX + 1, countY + 1);
-                if (item.screenId == Workspace.FIRST_SCREEN_ID) {
-                    // Mark the first row as occupied (if the feature is enabled)
-                    // in order to account for the QSB.
-                    screen.markCells(0, 0, countX + 1, 1, FeatureFlags.QSB_ON_FIRST_SCREEN);
-                }
-                occupied.put(item.screenId, screen);
-            }
-            final GridOccupancy occupancy = occupied.get(item.screenId);
-
-            // Check if any workspace icons overlap with each other
-            if (occupancy.isRegionVacant(item.cellX, item.cellY, item.spanX, item.spanY)) {
-                occupancy.markCells(item, true);
-                return true;
-            } else {
-                Log.e(TAG, "Error loading shortcut " + item
-                        + " into cell (" + containerIndex + "-" + item.screenId + ":"
-                        + item.cellX + "," + item.cellX + "," + item.spanX + "," + item.spanY
-                        + ") already occupied");
-                return false;
-            }
-        }
-
-        /** 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");
             }
-            final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
 
             final Context context = mContext;
             final ContentResolver contentResolver = context.getContentResolver();
-            final PackageManager manager = context.getPackageManager();
-            final boolean isSafeMode = manager.isSafeMode();
+            final PackageManagerHelper pmHelper = new PackageManagerHelper(context);
+            final boolean isSafeMode = pmHelper.isSafeMode();
             final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
+            final DeepShortcutManager shortcutManager = DeepShortcutManager.getInstance(context);
             final boolean isSdCardReady = Utilities.isBootCompleted();
-
-            LauncherAppState app = LauncherAppState.getInstance();
-            InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
-            int countX = profile.numColumns;
-            int countY = profile.numRows;
+            final MultiHashMap<UserHandle, String> pendingPackages = new MultiHashMap<>();
 
             boolean clearDb = false;
             try {
@@ -1690,68 +833,44 @@
             if (clearDb) {
                 Log.d(TAG, "loadWorkspace: resetting launcher database");
                 LauncherSettings.Settings.call(contentResolver,
-                        LauncherSettings.Settings.METHOD_DELETE_DB);
+                        LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
             }
 
             Log.d(TAG, "loadWorkspace: loading default favorites");
             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<>();
                 Map<ShortcutKey, ShortcutInfoCompat> shortcutKeyToPinnedShortcuts = new HashMap<>();
-                final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI;
-                if (DEBUG_LOADERS) Log.d(TAG, "loading model from " + contentUri);
-                final Cursor c = contentResolver.query(contentUri, null, null, null, null);
+                final LoaderCursor c = new LoaderCursor(contentResolver.query(
+                        LauncherSettings.Favorites.CONTENT_URI, null, null, null, null), mApp);
 
-                // +1 for the hotseat (it can be larger than the workspace)
-                // Load workspace in reverse order to ensure that latest items are loaded first (and
-                // before any earlier duplicates)
-                final LongArrayMap<GridOccupancy> occupied = new LongArrayMap<>();
                 HashMap<ComponentKey, AppWidgetProviderInfo> widgetProvidersMap = null;
 
                 try {
-                    final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
-                    final int intentIndex = c.getColumnIndexOrThrow
-                            (LauncherSettings.Favorites.INTENT);
-                    final int containerIndex = c.getColumnIndexOrThrow(
-                            LauncherSettings.Favorites.CONTAINER);
-                    final int itemTypeIndex = c.getColumnIndexOrThrow(
-                            LauncherSettings.Favorites.ITEM_TYPE);
                     final int appWidgetIdIndex = c.getColumnIndexOrThrow(
                             LauncherSettings.Favorites.APPWIDGET_ID);
                     final int appWidgetProviderIndex = c.getColumnIndexOrThrow(
                             LauncherSettings.Favorites.APPWIDGET_PROVIDER);
-                    final int screenIndex = c.getColumnIndexOrThrow(
-                            LauncherSettings.Favorites.SCREEN);
-                    final int cellXIndex = c.getColumnIndexOrThrow
-                            (LauncherSettings.Favorites.CELLX);
-                    final int cellYIndex = c.getColumnIndexOrThrow
-                            (LauncherSettings.Favorites.CELLY);
                     final int spanXIndex = c.getColumnIndexOrThrow
                             (LauncherSettings.Favorites.SPANX);
                     final int spanYIndex = c.getColumnIndexOrThrow(
                             LauncherSettings.Favorites.SPANY);
                     final int rankIndex = c.getColumnIndexOrThrow(
                             LauncherSettings.Favorites.RANK);
-                    final int restoredIndex = c.getColumnIndexOrThrow(
-                            LauncherSettings.Favorites.RESTORED);
-                    final int profileIdIndex = c.getColumnIndexOrThrow(
-                            LauncherSettings.Favorites.PROFILE_ID);
                     final int optionsIndex = c.getColumnIndexOrThrow(
                             LauncherSettings.Favorites.OPTIONS);
-                    final CursorIconInfo cursorIconInfo = new CursorIconInfo(mContext, c);
 
-                    final LongSparseArray<UserHandleCompat> allUsers = new LongSparseArray<>();
+                    final LongSparseArray<UserHandle> allUsers = c.allUsers;
                     final LongSparseArray<Boolean> quietMode = new LongSparseArray<>();
                     final LongSparseArray<Boolean> unlockedUsers = new LongSparseArray<>();
-                    for (UserHandleCompat user : mUserManager.getUserProfiles()) {
+                    for (UserHandle user : mUserManager.getUserProfiles()) {
                         long serialNo = mUserManager.getSerialNumberForUser(user);
                         allUsers.put(serialNo, user);
                         quietMode.put(serialNo, mUserManager.isQuietModeEnabled(user));
@@ -1761,8 +880,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);
@@ -1778,215 +897,174 @@
                     }
 
                     ShortcutInfo info;
-                    String intentDescription;
                     LauncherAppWidgetInfo appWidgetInfo;
-                    int container;
-                    long id;
-                    long serialNumber;
                     Intent intent;
-                    UserHandleCompat user;
-                    String targetPackage;
+                    String targetPkg;
 
                     while (!mStopped && c.moveToNext()) {
                         try {
-                            int itemType = c.getInt(itemTypeIndex);
-                            boolean restored = 0 != c.getInt(restoredIndex);
-                            boolean allowMissingTarget = false;
-                            container = c.getInt(containerIndex);
+                            if (c.user == null) {
+                                // User has been deleted, remove the item.
+                                c.markDeleted("User has been deleted");
+                                continue;
+                            }
 
-                            switch (itemType) {
-                            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
+                            boolean allowMissingTarget = false;
+                            switch (c.itemType) {
                             case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+                            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
                             case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
-                                id = c.getLong(idIndex);
-                                intentDescription = c.getString(intentIndex);
-                                serialNumber = c.getInt(profileIdIndex);
-                                user = allUsers.get(serialNumber);
-                                int promiseType = c.getInt(restoredIndex);
-                                int disabledState = 0;
-                                boolean itemReplaced = false;
-                                targetPackage = null;
-                                if (user == null) {
-                                    // User has been deleted remove the item.
-                                    itemsToRemove.add(id);
+                                intent = c.parseIntent();
+                                if (intent == null) {
+                                    c.markDeleted("Invalid or null intent");
                                     continue;
                                 }
-                                try {
-                                    intent = Intent.parseUri(intentDescription, 0);
-                                    ComponentName cn = intent.getComponent();
-                                    if (cn != null && cn.getPackageName() != null) {
-                                        boolean validPkg = launcherApps.isPackageEnabledForProfile(
-                                                cn.getPackageName(), user);
-                                        boolean validComponent = validPkg &&
-                                                launcherApps.isActivityEnabledForProfile(cn, user);
-                                        if (validPkg) {
-                                            targetPackage = cn.getPackageName();
-                                        }
 
-                                        if (validComponent) {
-                                            if (restored) {
-                                                // no special handling necessary for this item
-                                                restoredRows.add(id);
-                                                restored = false;
-                                            }
-                                            if (quietMode.get(serialNumber)) {
-                                                disabledState = ShortcutInfo.FLAG_DISABLED_QUIET_USER;
-                                            }
-                                        } else if (validPkg) {
-                                            intent = null;
-                                            if ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) {
-                                                // We allow auto install apps to have their intent
-                                                // updated after an install.
-                                                intent = manager.getLaunchIntentForPackage(
-                                                        cn.getPackageName());
-                                                if (intent != null) {
-                                                    ContentValues values = new ContentValues();
-                                                    values.put(LauncherSettings.Favorites.INTENT,
-                                                            intent.toUri(0));
-                                                    updateItem(id, values);
-                                                }
-                                            }
+                                int disabledState = quietMode.get(c.serialNumber) ?
+                                        ShortcutInfo.FLAG_DISABLED_QUIET_USER : 0;
+                                ComponentName cn = intent.getComponent();
+                                targetPkg = cn == null ? intent.getPackage() : cn.getPackageName();
 
-                                            if (intent == null) {
-                                                // The app is installed but the component is no
-                                                // longer available.
-                                                FileLog.d(TAG, "Invalid component removed: " + cn);
-                                                itemsToRemove.add(id);
-                                                continue;
+                                if (!Process.myUserHandle().equals(c.user)) {
+                                    if (c.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
+                                        c.markDeleted("Legacy shortcuts are only allowed for default user");
+                                        continue;
+                                    } else if (c.restoreFlag != 0) {
+                                        // Don't restore items for other profiles.
+                                        c.markDeleted("Restore from managed profile not supported");
+                                        continue;
+                                    }
+                                }
+                                if (TextUtils.isEmpty(targetPkg) &&
+                                        c.itemType != LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
+                                    c.markDeleted("Only legacy shortcuts can have null package");
+                                    continue;
+                                }
+
+                                // If there is no target package, its an implicit intent
+                                // (legacy shortcut) which is always valid
+                                boolean validTarget = TextUtils.isEmpty(targetPkg) ||
+                                        launcherApps.isPackageEnabledForProfile(targetPkg, c.user);
+
+                                if (cn != null && validTarget) {
+                                    // If the apk is present and the shortcut points to a specific
+                                    // component.
+
+                                    // If the component is already present
+                                    if (launcherApps.isActivityEnabledForProfile(cn, c.user)) {
+                                        // no special handling necessary for this item
+                                        c.markRestored();
+                                    } else {
+                                        if (c.hasRestoreFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
+                                            // We allow auto install apps to have their intent
+                                            // updated after an install.
+                                            intent = pmHelper.getAppLaunchIntent(targetPkg, c.user);
+                                            if (intent != null) {
+                                                c.restoreFlag = 0;
+                                                c.updater().put(
+                                                        LauncherSettings.Favorites.INTENT,
+                                                        intent.toUri(0)).commit();
+                                                cn = intent.getComponent();
                                             } else {
-                                                // no special handling necessary for this item
-                                                restoredRows.add(id);
-                                                restored = false;
-                                            }
-                                        } else if (restored) {
-                                            // Package is not yet available but might be
-                                            // installed later.
-                                            FileLog.d(TAG, "package not yet restored: " + cn);
-
-                                            if ((promiseType & ShortcutInfo.FLAG_RESTORE_STARTED) != 0) {
-                                                // Restore has started once.
-                                            } else if (installingPkgs.containsKey(cn.getPackageName())) {
-                                                // App restore has started. Update the flag
-                                                promiseType |= ShortcutInfo.FLAG_RESTORE_STARTED;
-                                                ContentValues values = new ContentValues();
-                                                values.put(LauncherSettings.Favorites.RESTORED,
-                                                        promiseType);
-                                                updateItem(id, values);
-                                            } else if ((promiseType & ShortcutInfo.FLAG_RESTORED_APP_TYPE) != 0) {
-                                                // This is a common app. Try to replace this.
-                                                int appType = CommonAppTypeParser.decodeItemTypeFromFlag(promiseType);
-                                                CommonAppTypeParser parser = new CommonAppTypeParser(id, appType, context);
-                                                if (parser.findDefaultApp()) {
-                                                    // Default app found. Replace it.
-                                                    intent = parser.parsedIntent;
-                                                    cn = intent.getComponent();
-                                                    ContentValues values = parser.parsedValues;
-                                                    values.put(LauncherSettings.Favorites.RESTORED, 0);
-                                                    updateItem(id, values);
-                                                    restored = false;
-                                                    itemReplaced = true;
-
-                                                } else {
-                                                    FileLog.d(TAG, "Unrestored package removed: " + cn);
-                                                    itemsToRemove.add(id);
-                                                    continue;
-                                                }
-                                            } else {
-                                                FileLog.d(TAG, "Unrestored package removed: " + cn);
-                                                itemsToRemove.add(id);
+                                                c.markDeleted("Unable to find a launch target");
                                                 continue;
                                             }
-                                        } else if (PackageManagerHelper.isAppOnSdcard(
-                                                manager, cn.getPackageName())) {
-                                            // Package is present but not available.
-                                            allowMissingTarget = true;
-                                            disabledState = ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
-                                        } else if (!isSdCardReady) {
-                                            // 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());
-                                            allowMissingTarget = true;
-                                            // Add the icon on the workspace anyway.
-
                                         } else {
-                                            // Do not wait for external media load anymore.
-                                            // Log the invalid package, and remove it
-                                            FileLog.d(TAG, "Invalid package removed: " + cn);
-                                            itemsToRemove.add(id);
+                                            // The app is installed but the component is no
+                                            // longer available.
+                                            c.markDeleted("Invalid component removed: " + cn);
                                             continue;
                                         }
-                                    } else if (cn == null) {
-                                        // For shortcuts with no component, keep them as they are
-                                        restoredRows.add(id);
-                                        restored = false;
                                     }
-                                } catch (URISyntaxException e) {
-                                    FileLog.d(TAG, "Invalid uri: " + intentDescription);
-                                    itemsToRemove.add(id);
-                                    continue;
+                                }
+                                // else if cn == null => can't infer much, leave it
+                                // else if !validPkg => could be restored icon or missing sd-card
+
+                                if (!TextUtils.isEmpty(targetPkg) && !validTarget) {
+                                    // Points to a valid app (superset of cn != null) but the apk
+                                    // is not available.
+
+                                    if (c.restoreFlag != 0) {
+                                        // Package is not yet available but might be
+                                        // installed later.
+                                        FileLog.d(TAG, "package not yet restored: " + targetPkg);
+
+                                        if (c.hasRestoreFlag(ShortcutInfo.FLAG_RESTORE_STARTED)) {
+                                            // Restore has started once.
+                                        } else if (installingPkgs.containsKey(targetPkg)) {
+                                            // App restore has started. Update the flag
+                                            c.restoreFlag |= ShortcutInfo.FLAG_RESTORE_STARTED;
+                                            c.updater().commit();
+                                        } else {
+                                            c.markDeleted("Unrestored app removed: " + targetPkg);
+                                            continue;
+                                        }
+                                    } else if (pmHelper.isAppOnSdcard(targetPkg, c.user)) {
+                                        // Package is present but not available.
+                                        disabledState |= ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
+                                        // Add the icon on the workspace anyway.
+                                        allowMissingTarget = true;
+                                    } else if (!isSdCardReady) {
+                                        // SdCard is not ready yet. Package might get available,
+                                        // once it is ready.
+                                        Log.d(TAG, "Missing pkg, will check later: " + targetPkg);
+                                        pendingPackages.addToList(c.user, targetPkg);
+                                        // Add the icon on the workspace anyway.
+                                        allowMissingTarget = true;
+                                    } else {
+                                        // Do not wait for external media load anymore.
+                                        c.markDeleted("Invalid package removed: " + targetPkg);
+                                        continue;
+                                    }
                                 }
 
-                                boolean useLowResIcon = container >= 0 &&
+                                if (validTarget) {
+                                    // The shortcut points to a valid target (either no target
+                                    // or something which is ready to be used)
+                                    c.markRestored();
+                                }
+
+                                boolean useLowResIcon = !c.isOnWorkspaceOrHotseat() &&
                                         c.getInt(rankIndex) >= FolderIcon.NUM_ITEMS_IN_PREVIEW;
 
-                                if (itemReplaced) {
-                                    if (user.equals(UserHandleCompat.myUserHandle())) {
-                                        info = getAppShortcutInfo(intent, user, null,
-                                                cursorIconInfo, false, useLowResIcon);
-                                    } else {
-                                        // Don't replace items for other profiles.
-                                        itemsToRemove.add(id);
-                                        continue;
-                                    }
-                                } else if (restored) {
-                                    if (user.equals(UserHandleCompat.myUserHandle())) {
-                                        info = getRestoredItemInfo(c, intent,
-                                                promiseType, itemType, cursorIconInfo);
-                                        intent = getRestoredItemIntent(c, context, intent);
-                                    } else {
-                                        // Don't restore items for other profiles.
-                                        itemsToRemove.add(id);
-                                        continue;
-                                    }
-                                } else if (itemType ==
+                                if (c.restoreFlag != 0) {
+                                    // Already verified above that user is same as default user
+                                    info = c.getRestoredItemInfo(intent);
+                                } else if (c.itemType ==
                                         LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
-                                    info = getAppShortcutInfo(intent, user, c,
-                                            cursorIconInfo, allowMissingTarget, useLowResIcon);
-                                } else if (itemType ==
+                                    info = c.getAppShortcutInfo(
+                                            intent, allowMissingTarget, useLowResIcon);
+                                } else if (c.itemType ==
                                         LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
 
-                                    ShortcutKey key = ShortcutKey.fromIntent(intent, user);
-                                    if (unlockedUsers.get(serialNumber)) {
+                                    ShortcutKey key = ShortcutKey.fromIntent(intent, c.user);
+                                    if (unlockedUsers.get(c.serialNumber)) {
                                         ShortcutInfoCompat pinnedShortcut =
                                                 shortcutKeyToPinnedShortcuts.get(key);
                                         if (pinnedShortcut == null) {
                                             // The shortcut is no longer valid.
-                                            itemsToRemove.add(id);
+                                            c.markDeleted("Pinned shortcut not found");
                                             continue;
                                         }
                                         info = new ShortcutInfo(pinnedShortcut, context);
+                                        info.iconBitmap = LauncherIcons
+                                                .createShortcutIcon(pinnedShortcut, context);
+                                        if (pmHelper.isAppSuspended(
+                                                pinnedShortcut.getPackage(), info.user)) {
+                                            info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SUSPENDED;
+                                        }
                                         intent = info.intent;
                                     } else {
                                         // Create a shortcut info in disabled mode for now.
-                                        info = new ShortcutInfo();
-                                        info.user = user;
-                                        info.itemType = itemType;
-                                        loadInfoFromCursor(info, c, cursorIconInfo);
-
+                                        info = c.loadSimpleShortcut();
                                         info.isDisabled |= ShortcutInfo.FLAG_DISABLED_LOCKED_USER;
                                     }
-                                    incrementPinnedShortcutCount(key, false /* shouldPin */);
                                 } else { // item type == ITEM_TYPE_SHORTCUT
-                                    info = getShortcutInfo(c, cursorIconInfo);
+                                    info = c.loadSimpleShortcut();
 
                                     // Shortcuts are only available on the primary profile
-                                    if (PackageManagerHelper.isAppSuspended(manager, targetPackage)) {
+                                    if (!TextUtils.isEmpty(targetPkg)
+                                            && pmHelper.isAppSuspended(targetPkg, c.user)) {
                                         disabledState |= ShortcutInfo.FLAG_DISABLED_SUSPENDED;
                                     }
 
@@ -2004,121 +1082,64 @@
                                 }
 
                                 if (info != null) {
-                                    info.id = id;
+                                    c.applyCommonProperties(info);
+
                                     info.intent = intent;
-                                    info.container = container;
-                                    info.screenId = c.getInt(screenIndex);
-                                    info.cellX = c.getInt(cellXIndex);
-                                    info.cellY = c.getInt(cellYIndex);
                                     info.rank = c.getInt(rankIndex);
                                     info.spanX = 1;
                                     info.spanY = 1;
-                                    info.intent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber);
-                                    if (info.promisedIntent != null) {
-                                        info.promisedIntent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber);
-                                    }
                                     info.isDisabled |= disabledState;
                                     if (isSafeMode && !Utilities.isSystemApp(context, intent)) {
                                         info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SAFEMODE;
                                     }
 
-                                    // check & update map of what's occupied
-                                    if (!checkItemPlacement(occupied, info, sBgWorkspaceScreens)) {
-                                        itemsToRemove.add(id);
-                                        break;
-                                    }
-
-                                    if (restored) {
-                                        ComponentName cn = info.getTargetComponent();
-                                        if (cn != null) {
-                                            Integer progress = installingPkgs.get(cn.getPackageName());
-                                            if (progress != null) {
-                                                info.setInstallProgress(progress);
-                                            } else {
-                                                info.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
-                                            }
+                                    if (c.restoreFlag != 0 && !TextUtils.isEmpty(targetPkg)) {
+                                        Integer progress = installingPkgs.get(targetPkg);
+                                        if (progress != null) {
+                                            info.setInstallProgress(progress);
+                                        } else {
+                                            info.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
                                         }
                                     }
 
-                                    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);
+                                    c.checkAndAddItem(info, sBgDataModel);
                                 } else {
                                     throw new RuntimeException("Unexpected null ShortcutInfo");
                                 }
                                 break;
 
                             case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
-                                id = c.getLong(idIndex);
-                                FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id);
+                                FolderInfo folderInfo = sBgDataModel.findOrMakeFolder(c.id);
+                                c.applyCommonProperties(folderInfo);
 
                                 // Do not trim the folder label, as is was set by the user.
-                                folderInfo.title = c.getString(cursorIconInfo.titleIndex);
-                                folderInfo.id = id;
-                                folderInfo.container = container;
-                                folderInfo.screenId = c.getInt(screenIndex);
-                                folderInfo.cellX = c.getInt(cellXIndex);
-                                folderInfo.cellY = c.getInt(cellYIndex);
+                                folderInfo.title = c.getString(c.titleIndex);
                                 folderInfo.spanX = 1;
                                 folderInfo.spanY = 1;
                                 folderInfo.options = c.getInt(optionsIndex);
 
-                                // check & update map of what's occupied
-                                if (!checkItemPlacement(occupied, folderInfo, sBgWorkspaceScreens)) {
-                                    itemsToRemove.add(id);
-                                    break;
-                                }
+                                // no special handling required for restored folders
+                                c.markRestored();
 
-                                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);
+                                c.checkAndAddItem(folderInfo, sBgDataModel);
                                 break;
 
                             case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
                             case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
                                 // Read all Launcher-specific widget details
-                                boolean customWidget = itemType ==
+                                boolean customWidget = c.itemType ==
                                     LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
 
                                 int appWidgetId = c.getInt(appWidgetIdIndex);
-                                serialNumber = c.getLong(profileIdIndex);
                                 String savedProvider = c.getString(appWidgetProviderIndex);
-                                id = c.getLong(idIndex);
-                                user = allUsers.get(serialNumber);
-                                if (user == null) {
-                                    itemsToRemove.add(id);
-                                    continue;
-                                }
 
                                 final ComponentName component =
                                         ComponentName.unflattenFromString(savedProvider);
 
-                                final int restoreStatus = c.getInt(restoredIndex);
-                                final boolean isIdValid = (restoreStatus &
-                                        LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) == 0;
-                                final boolean wasProviderReady = (restoreStatus &
-                                        LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0;
+                                final boolean isIdValid = !c.hasRestoreFlag(
+                                        LauncherAppWidgetInfo.FLAG_ID_NOT_VALID);
+                                final boolean wasProviderReady = !c.hasRestoreFlag(
+                                        LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY);
 
                                 if (widgetProvidersMap == null) {
                                     widgetProvidersMap = AppWidgetManagerCompat
@@ -2127,14 +1148,14 @@
                                 final AppWidgetProviderInfo provider = widgetProvidersMap.get(
                                         new ComponentKey(
                                                 ComponentName.unflattenFromString(savedProvider),
-                                                user));
+                                                c.user));
 
                                 final boolean isProviderReady = isValidProvider(provider);
                                 if (!isSafeMode && !customWidget &&
                                         wasProviderReady && !isProviderReady) {
-                                    FileLog.d(TAG, "Deleting widget that isn't installed anymore: "
+                                    c.markDeleted(
+                                            "Deleting widget that isn't installed anymore: "
                                             + provider);
-                                    itemsToRemove.add(id);
                                 } else {
                                     if (isProviderReady) {
                                         appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
@@ -2143,7 +1164,7 @@
                                         // The provider is available. So the widget is either
                                         // available or not available. We do not need to track
                                         // any future restore updates.
-                                        int status = restoreStatus &
+                                        int status = c.restoreFlag &
                                                 ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
                                         if (!wasProviderReady) {
                                             // If provider was not previously ready, update the
@@ -2159,23 +1180,22 @@
                                         }
                                         appWidgetInfo.restoreStatus = status;
                                     } else {
-                                        Log.v(TAG, "Widget restore pending id=" + id
+                                        Log.v(TAG, "Widget restore pending id=" + c.id
                                                 + " appWidgetId=" + appWidgetId
-                                                + " status =" + restoreStatus);
+                                                + " status =" + c.restoreFlag);
                                         appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
                                                 component);
-                                        appWidgetInfo.restoreStatus = restoreStatus;
+                                        appWidgetInfo.restoreStatus = c.restoreFlag;
                                         Integer installProgress = installingPkgs.get(component.getPackageName());
 
-                                        if ((restoreStatus & LauncherAppWidgetInfo.FLAG_RESTORE_STARTED) != 0) {
+                                        if (c.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_RESTORE_STARTED)) {
                                             // Restore has started once.
                                         } else if (installProgress != null) {
                                             // App restore has started. Update the flag
                                             appWidgetInfo.restoreStatus |=
                                                     LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
                                         } else if (!isSafeMode) {
-                                            FileLog.d(TAG, "Unrestored widget removed: " + component);
-                                            itemsToRemove.add(id);
+                                            c.markDeleted("Unrestored widget removed: " + component);
                                             continue;
                                         }
 
@@ -2184,52 +1204,34 @@
                                     }
                                     if (appWidgetInfo.hasRestoreFlag(
                                             LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG)) {
-                                        intentDescription = c.getString(intentIndex);
-                                        if (!TextUtils.isEmpty(intentDescription)) {
-                                            appWidgetInfo.bindOptions =
-                                                    Intent.parseUri(intentDescription, 0);
-                                        }
+                                        appWidgetInfo.bindOptions = c.parseIntent();
                                     }
 
-                                    appWidgetInfo.id = id;
-                                    appWidgetInfo.screenId = c.getInt(screenIndex);
-                                    appWidgetInfo.cellX = c.getInt(cellXIndex);
-                                    appWidgetInfo.cellY = c.getInt(cellYIndex);
+                                    c.applyCommonProperties(appWidgetInfo);
                                     appWidgetInfo.spanX = c.getInt(spanXIndex);
                                     appWidgetInfo.spanY = c.getInt(spanYIndex);
-                                    appWidgetInfo.user = user;
+                                    appWidgetInfo.user = c.user;
 
-                                    if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
-                                        container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
-                                        Log.e(TAG, "Widget found where container != " +
+                                    if (!c.isOnWorkspaceOrHotseat()) {
+                                        c.markDeleted("Widget found where container != " +
                                                 "CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!");
-                                        itemsToRemove.add(id);
                                         continue;
                                     }
 
-                                    appWidgetInfo.container = container;
-                                    // check & update map of what's occupied
-                                    if (!checkItemPlacement(occupied, appWidgetInfo, sBgWorkspaceScreens)) {
-                                        itemsToRemove.add(id);
-                                        break;
-                                    }
-
                                     if (!customWidget) {
                                         String providerName =
                                                 appWidgetInfo.providerName.flattenToString();
                                         if (!providerName.equals(savedProvider) ||
-                                                (appWidgetInfo.restoreStatus != restoreStatus)) {
-                                            ContentValues values = new ContentValues();
-                                            values.put(
-                                                    LauncherSettings.Favorites.APPWIDGET_PROVIDER,
-                                                    providerName);
-                                            values.put(LauncherSettings.Favorites.RESTORED,
-                                                    appWidgetInfo.restoreStatus);
-                                            updateItem(id, values);
+                                                (appWidgetInfo.restoreStatus != c.restoreFlag)) {
+                                            c.updater()
+                                                    .put(LauncherSettings.Favorites.APPWIDGET_PROVIDER,
+                                                            providerName)
+                                                    .put(LauncherSettings.Favorites.RESTORED,
+                                                            appWidgetInfo.restoreStatus)
+                                                    .commit();
                                         }
                                     }
-                                    sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo);
-                                    sBgAppWidgets.add(appWidgetInfo);
+                                    c.checkAndAddItem(appWidgetInfo, sBgDataModel);
                                 }
                                 break;
                             }
@@ -2243,48 +1245,48 @@
 
                 // Break early if we've stopped loading
                 if (mStopped) {
-                    clearSBgDataStructures();
+                    sBgDataModel.clear();
                     return;
                 }
 
-                if (itemsToRemove.size() > 0) {
-                    // Remove dead items
-                    contentResolver.delete(LauncherSettings.Favorites.CONTENT_URI,
-                            Utilities.createDbSelectionQuery(
-                                    LauncherSettings.Favorites._ID, itemsToRemove), null);
-                    if (DEBUG_LOADERS) {
-                        Log.d(TAG, "Removed = " + Utilities.createDbSelectionQuery(
-                                LauncherSettings.Favorites._ID, itemsToRemove));
-                    }
-
+                // Remove dead items
+                if (c.commitDeleted()) {
                     // Remove any empty folder
                     ArrayList<Long> deletedFolderIds = (ArrayList<Long>) LauncherSettings.Settings
                             .call(contentResolver,
                                     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);
                     }
+
+                    // Remove any ghost widgets
+                    LauncherSettings.Settings.call(contentResolver,
+                            LauncherSettings.Settings.METHOD_REMOVE_GHOST_WIDGETS);
                 }
 
                 // Unpin shortcuts that don't exist on the workspace.
+                HashSet<ShortcutKey> pendingShortcuts =
+                        InstallShortcutReceiver.getPendingShortcuts(context);
                 for (ShortcutKey key : shortcutKeyToPinnedShortcuts.keySet()) {
-                    MutableInt numTimesPinned = sBgPinnedShortcutCounts.get(key);
-                    if (numTimesPinned == null || numTimesPinned.value == 0) {
+                    MutableInt numTimesPinned = sBgDataModel.pinnedShortcutCounts.get(key);
+                    if ((numTimesPinned == null || numTimesPinned.value == 0)
+                            && !pendingShortcuts.contains(key)) {
                         // 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) {
-                        if (info.usingLowResIcon) {
-                            info.updateIcon(mIconCache, false);
+                        if (info.usingLowResIcon &&
+                                info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
+                            mIconCache.getTitleAndIcon(info, false);
                         }
                         pos ++;
                         if (pos >= FolderIcon.NUM_ITEMS_IN_PREVIEW) {
@@ -2293,24 +1295,19 @@
                     }
                 }
 
-                if (restoredRows.size() > 0) {
-                    // Update restored items that no longer require special handling
-                    ContentValues values = new ContentValues();
-                    values.put(LauncherSettings.Favorites.RESTORED, 0);
-                    contentResolver.update(LauncherSettings.Favorites.CONTENT_URI, values,
-                            Utilities.createDbSelectionQuery(
-                                    LauncherSettings.Favorites._ID, restoredRows), null);
-                }
-
-                if (!isSdCardReady && !sPendingPackages.isEmpty()) {
-                    context.registerReceiver(new AppsAvailabilityCheck(),
+                c.commitRestoredItems();
+                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<>(sBgDataModel.workspaceScreens);
+                for (ItemInfo item: sBgDataModel.itemsIdMap) {
                     long screenId = item.screenId;
                     if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
                             unusedScreens.contains(screenId)) {
@@ -2320,25 +1317,8 @@
 
                 // If there are any empty screens remove them, and update.
                 if (unusedScreens.size() != 0) {
-                    sBgWorkspaceScreens.removeAll(unusedScreens);
-                    updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
-                }
-
-                if (DEBUG_LOADERS) {
-                    Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms");
-                    Log.d(TAG, "workspace layout: ");
-                    int nScreens = occupied.size();
-                    for (int y = 0; y < countY; y++) {
-                        String line = "";
-
-                        for (int i = 0; i < nScreens; i++) {
-                            long screenId = occupied.keyAt(i);
-                            if (screenId > 0) {
-                                line += " | ";
-                            }
-                        }
-                        Log.d(TAG, "[ " + line + " ]");
-                    }
+                    sBgDataModel.workspaceScreens.removeAll(unusedScreens);
+                    updateWorkspaceScreenOrder(context, sBgDataModel.workspaceScreens);
                 }
             }
             if (LauncherAppState.PROFILE_STARTUP) {
@@ -2346,17 +1326,6 @@
             }
         }
 
-        /**
-         * Partially updates the item without any notification. Must be called on the worker thread.
-         */
-        private void updateItem(long itemId, ContentValues update) {
-            mContext.getContentResolver().update(
-                    LauncherSettings.Favorites.CONTENT_URI,
-                    update,
-                    BaseColumns._ID + "= ?",
-                    new String[]{Long.toString(itemId)});
-        }
-
         /** Filters the set of items who are directly or indirectly (via another container) on the
          * specified screen. */
         private void filterCurrentWorkspaceItems(long currentScreenId,
@@ -2424,8 +1393,7 @@
         /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to
          * right) */
         private void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) {
-            final LauncherAppState app = LauncherAppState.getInstance();
-            final InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
+            final InvariantDeviceProfile profile = mApp.getInvariantDeviceProfile();
             final int screenCols = profile.numColumns;
             final int screenCellCount = profile.numColumns * profile.numRows;
             Collections.sort(workspaceItems, new Comparator<ItemInfo>() {
@@ -2534,10 +1502,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;
@@ -2655,34 +1623,11 @@
             }
         }
 
-        private void loadAndBindAllApps() {
-            if (DEBUG_LOADERS) {
-                Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded);
-            }
-            if (!mAllAppsLoaded) {
-                loadAllApps();
-                synchronized (LoaderTask.this) {
-                    if (mStopped) {
-                        return;
-                    }
-                }
-                updateIconCache();
-                synchronized (LoaderTask.this) {
-                    if (mStopped) {
-                        return;
-                    }
-                    mAllAppsLoaded = true;
-                }
-            } else {
-                onlyBindAllApps();
-            }
-        }
-
         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) {
@@ -2728,7 +1673,7 @@
         }
 
         private void scheduleManagedHeuristicRunnable(final ManagedProfileHeuristic heuristic,
-                final UserHandleCompat user, final List<LauncherActivityInfoCompat> apps) {
+                final UserHandle user, final List<LauncherActivityInfo> apps) {
             if (heuristic != null) {
                 // Assume the app lists now is updated.
                 mIsManagedHeuristicAppsUpdated = false;
@@ -2740,7 +1685,7 @@
                             // list will override everything in processUserApps().
                             sWorker.post(new Runnable() {
                                 public void run() {
-                                    final List<LauncherActivityInfoCompat> updatedApps =
+                                    final List<LauncherActivityInfo> updatedApps =
                                             mLauncherApps.getActivityList(null, user);
                                     scheduleManagedHeuristicRunnable(heuristic, user,
                                             updatedApps);
@@ -2778,14 +1723,14 @@
                 return;
             }
 
-            final List<UserHandleCompat> profiles = mUserManager.getUserProfiles();
+            final List<UserHandle> profiles = mUserManager.getUserProfiles();
 
             // Clear the list of apps
             mBgAllAppsList.clear();
-            for (UserHandleCompat user : profiles) {
+            for (UserHandle user : profiles) {
                 // Query for the set of apps
                 final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
-                final List<LauncherActivityInfoCompat> apps = mLauncherApps.getActivityList(null, user);
+                final List<LauncherActivityInfo> apps = mLauncherApps.getActivityList(null, user);
                 if (DEBUG_LOADERS) {
                     Log.d(TAG, "getActivityList took "
                             + (SystemClock.uptimeMillis()-qiaTime) + "ms for user " + user);
@@ -2799,9 +1744,9 @@
                 boolean quietMode = mUserManager.isQuietModeEnabled(user);
                 // Create the ApplicationInfos
                 for (int i = 0; i < apps.size(); i++) {
-                    LauncherActivityInfoCompat app = apps.get(i);
+                    LauncherActivityInfo app = apps.get(i);
                     // This builds the icon bitmaps.
-                    mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache, quietMode));
+                    mBgAllAppsList.add(new AppInfo(app, user, quietMode), app);
                 }
                 final ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(mContext, user);
                 if (heuristic != null) {
@@ -2837,72 +1782,27 @@
             }
         }
 
-        private void loadAndBindDeepShortcuts() {
-            if (DEBUG_LOADERS) {
-                Log.d(TAG, "loadAndBindDeepShortcuts mDeepShortcutsLoaded=" + mDeepShortcutsLoaded);
-            }
-            if (!mDeepShortcutsLoaded) {
-                mBgDeepShortcutMap.clear();
-                mHasShortcutHostPermission = mDeepShortcutManager.hasHostPermission();
+        private void loadDeepShortcuts() {
+            if (!mModelLoaded) {
+                sBgDataModel.deepShortcutMap.clear();
+                DeepShortcutManager shortcutManager = DeepShortcutManager.getInstance(mContext);
+                mHasShortcutHostPermission = shortcutManager.hasHostPermission();
                 if (mHasShortcutHostPermission) {
-                    for (UserHandleCompat user : mUserManager.getUserProfiles()) {
+                    for (UserHandle 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);
                         }
                     }
                 }
-                synchronized (LoaderTask.this) {
-                    if (mStopped) {
-                        return;
-                    }
-                    mDeepShortcutsLoaded = true;
-                }
-            }
-            bindDeepShortcuts();
-        }
-
-        public void dumpState() {
-            synchronized (sBgLock) {
-                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());
             }
         }
     }
 
     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() {
@@ -2921,7 +1821,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);
         }
@@ -2930,934 +1830,155 @@
     /**
      * 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<>();
-
+    public void onPackageIconsUpdated(HashSet<String> updatedPackages, UserHandle user) {
         // 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);
-        }
-
-        bindUpdatedShortcuts(updatedShortcuts, user);
-
-        if (!updatedApps.isEmpty()) {
-            mHandler.post(new Runnable() {
-
-                public void run() {
-                    Callbacks cb = getCallback();
-                    if (cb != null && callbacks == cb) {
-                        cb.bindAppsUpdated(updatedApps);
-                    }
-                }
-            });
-        }
+        enqueueModelUpdateTask(new CacheDataUpdatedTask(
+                CacheDataUpdatedTask.OP_CACHE_UPDATE, user, updatedPackages));
     }
 
-    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 enqueueModelUpdateTask(BaseModelUpdateTask task) {
+        if (!mModelLoaded && mLoaderTask == null) {
+            if (DEBUG_LOADERS) {
+                Log.d(TAG, "enqueueModelUpdateTask Ignoring task since loader is pending=" + task);
+            }
+            return;
         }
-    }
-
-    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) {
-                // Loader has not yet run.
-                return;
-            }
-            mIsManagedHeuristicAppsUpdated = true;
-            final Context context = mApp.getContext();
-
-            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);
-                    }
-
-                    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);
-                            }
-                        }
-                    }
-                }
-
-                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();
-                        }
-                    }
-                });
-            }
-        }
+        task.init(this);
+        runOnWorkerThread(task);
     }
 
     /**
-     * Repopulates the shortcut info, possibly updating any icon already on the workspace.
+     * A task to be executed on the current callbacks on the UI thread.
+     * If there is no current callbacks, the task is ignored.
      */
-    public void updateShortcutInfo(final ShortcutInfoCompat fullDetail, final ShortcutInfo info) {
-        enqueueItemUpdatedTask(new Runnable() {
+    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;
+        }
+
+        @Override
+        public void run() {
+            if (!mModel.mHasLoaderCompletedOnce) {
+                // Loader has not yet run.
+                return;
+            }
+            execute(mModel.mApp, sBgDataModel, mModel.mBgAllAppsList);
+        }
+
+        /**
+         * Execute the actual task. Called on the worker thread.
+         */
+        public abstract void execute(
+                LauncherAppState app, BgDataModel dataModel, AllAppsList apps);
+
+        /**
+         * 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);
+                    }
+                }
+            });
+        }
+
+        public ModelWriter getModelWriter() {
+            // Updates from model task, do not deal with icon position in hotseat.
+            return mModel.getWriter(false /* hasVerticalHotseat */);
+        }
+    }
+
+    public void updateAndBindShortcutInfo(final ShortcutInfo si, final ShortcutInfoCompat info) {
+        updateAndBindShortcutInfo(new Provider<ShortcutInfo>() {
             @Override
-            public void run() {
-                info.updateFromDeepShortcutInfo(
-                        fullDetail, LauncherAppState.getInstance().getContext());
-                ArrayList<ShortcutInfo> update = new ArrayList<ShortcutInfo>();
-                update.add(info);
-                bindUpdatedShortcuts(update, fullDetail.getUserHandle());
+            public ShortcutInfo get() {
+                si.updateFromDeepShortcutInfo(info, mApp.getContext());
+                si.iconBitmap = LauncherIcons.createShortcutIcon(info, mApp.getContext());
+                return si;
             }
         });
     }
 
-    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
+     * Utility method to update a shortcut on the background thread.
      */
-    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;
-                }
+    public void updateAndBindShortcutInfo(final Provider<ShortcutInfo> shortcutProvider) {
+        enqueueModelUpdateTask(new ExtendedModelTask() {
+            @Override
+            public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+                ShortcutInfo info = shortcutProvider.get();
+                ArrayList<ShortcutInfo> update = new ArrayList<>();
+                update.add(info);
+                bindUpdatedShortcuts(update, info.user);
             }
-
-            // 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);
                 }
             }
         });
     }
 
-    public void refreshAndBindWidgetsAndShortcuts(
-            final Callbacks callbacks, final boolean bindFirst) {
+    public void refreshAndBindWidgetsAndShortcuts(final Callbacks callbacks,
+            final boolean bindFirst, @Nullable final PackageUserKey packageUser) {
         runOnWorkerThread(new Runnable() {
             @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> widgets = mBgWidgetsModel.update(
+                        mApp.getContext(), packageUser);
+                bindWidgetsModel(callbacks);
+
                 // update the Widget entries inside DB on the worker thread.
-                LauncherAppState.getInstance().getWidgetCache().removeObsoletePreviews(
-                        model.getRawList());
+                mApp.getWidgetCache().removeObsoletePreviews(widgets, packageUser);
             }
         });
     }
 
-    @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.
-     */
-    public ShortcutInfo getRestoredItemInfo(Cursor c, Intent intent,
-            int promiseType, int itemType, CursorIconInfo iconInfo) {
-        final ShortcutInfo info = new ShortcutInfo();
-        info.user = UserHandleCompat.myUserHandle();
-
-        Bitmap icon = iconInfo.loadIcon(c, info);
-        // the fallback icon
-        if (icon == null) {
-            mIconCache.getTitleAndIcon(info, intent, info.user, false /* useLowResIcon */);
-        } else {
-            info.setIcon(icon);
-        }
-
-        if ((promiseType & ShortcutInfo.FLAG_RESTORED_ICON) != 0) {
-            String title = iconInfo.getTitle(c);
-            if (!TextUtils.isEmpty(title)) {
-                info.title = Utilities.trim(title);
-            }
-        } else if  ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) {
-            if (TextUtils.isEmpty(info.title)) {
-                info.title = iconInfo.getTitle(c);
-            }
-        } else {
-            throw new InvalidParameterException("Invalid restoreType " + promiseType);
-        }
-
-        info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
-        info.itemType = itemType;
-        info.promisedIntent = intent;
-        info.status = promiseType;
-        return info;
-    }
-
-    /**
-     * Make an Intent object for a restored application or shortcut item that points
-     * to the market page for the item.
-     */
-    @Thunk Intent getRestoredItemIntent(Cursor c, Context context, Intent intent) {
-        ComponentName componentName = intent.getComponent();
-        return getMarketIntent(componentName.getPackageName());
-    }
-
-    static Intent getMarketIntent(String packageName) {
-        return new Intent(Intent.ACTION_VIEW)
-            .setData(new Uri.Builder()
-                .scheme("market")
-                .authority("details")
-                .appendQueryParameter("id", packageName)
-                .build());
-    }
-
-    /**
-     * Make an ShortcutInfo object for a shortcut that is an application.
-     *
-     * If c is not null, then it will be used to fill in missing data like the title and icon.
-     */
-    public ShortcutInfo getAppShortcutInfo(Intent intent,
-            UserHandleCompat user, Cursor c, CursorIconInfo iconInfo,
-            boolean allowMissingTarget, boolean useLowResIcon) {
-        if (user == null) {
-            Log.d(TAG, "Null user found in getShortcutInfo");
-            return null;
-        }
-
-        ComponentName componentName = intent.getComponent();
-        if (componentName == null) {
-            Log.d(TAG, "Missing component found in getShortcutInfo");
-            return null;
-        }
-
-        Intent newIntent = new Intent(intent.getAction(), null);
-        newIntent.addCategory(Intent.CATEGORY_LAUNCHER);
-        newIntent.setComponent(componentName);
-        LauncherActivityInfoCompat lai = mLauncherApps.resolveActivity(newIntent, user);
-        if ((lai == null) && !allowMissingTarget) {
-            Log.d(TAG, "Missing activity found in getShortcutInfo: " + componentName);
-            return null;
-        }
-
-        final ShortcutInfo info = new ShortcutInfo();
-        mIconCache.getTitleAndIcon(info, componentName, lai, user, false, useLowResIcon);
-        if (mIconCache.isDefaultIcon(info.getIcon(mIconCache), user) && c != null) {
-            Bitmap icon = iconInfo.loadIcon(c);
-            info.setIcon(icon == null ? mIconCache.getDefaultIcon(user) : icon);
-        }
-
-        if (lai != null && PackageManagerHelper.isAppSuspended(lai.getApplicationInfo())) {
-            info.isDisabled = ShortcutInfo.FLAG_DISABLED_SUSPENDED;
-        }
-
-        // from the db
-        if (TextUtils.isEmpty(info.title) && c != null) {
-            info.title = iconInfo.getTitle(c);
-        }
-
-        // fall back to the class name of the activity
-        if (info.title == null) {
-            info.title = componentName.getClassName();
-        }
-
-        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.
-     */
-    @Thunk ShortcutInfo getShortcutInfo(Cursor c, CursorIconInfo iconInfo) {
-        final ShortcutInfo info = new ShortcutInfo();
-        // Non-app shortcuts are only supported for current user.
-        info.user = UserHandleCompat.myUserHandle();
-        info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
-
-        // TODO: If there's an explicit component and we can't install that, delete it.
-
-        loadInfoFromCursor(info, c, iconInfo);
-        return info;
-    }
-
-    /**
-     * Make an ShortcutInfo object for a shortcut that isn't an application.
-     */
-    public void loadInfoFromCursor(ShortcutInfo info, Cursor c, CursorIconInfo iconInfo) {
-        info.title = iconInfo.getTitle(c);
-        Bitmap icon = iconInfo.loadIcon(c, info);
-        // the fallback icon
-        if (icon == null) {
-            icon = mIconCache.getDefaultIcon(info.user);
-            info.usingFallbackIcon = true;
-        }
-        info.setIcon(icon);
-    }
-
-    ShortcutInfo infoFromShortcutIntent(Context context, Intent data) {
-        Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
-        String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
-        Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
-
-        if (intent == null) {
-            // If the intent is null, we can't construct a valid ShortcutInfo, so we return null
-            Log.e(TAG, "Can't construct ShorcutInfo with null intent");
-            return null;
-        }
-
-        Bitmap icon = null;
-        boolean customIcon = false;
-        ShortcutIconResource iconResource = null;
-
-        if (bitmap instanceof Bitmap) {
-            icon = Utilities.createIconBitmap((Bitmap) bitmap, context);
-            customIcon = true;
-        } else {
-            Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
-            if (extra instanceof ShortcutIconResource) {
-                iconResource = (ShortcutIconResource) extra;
-                icon = Utilities.createIconBitmap(iconResource.packageName,
-                        iconResource.resourceName, context);
-            }
-        }
-
-        final ShortcutInfo info = new ShortcutInfo();
-
-        // Only support intents for current user for now. Intents sent from other
-        // users wouldn't get here without intent forwarding anyway.
-        info.user = UserHandleCompat.myUserHandle();
-        if (icon == null) {
-            icon = mIconCache.getDefaultIcon(info.user);
-            info.usingFallbackIcon = true;
-        }
-        info.setIcon(icon);
-
-        info.title = Utilities.trim(name);
-        info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
-        info.intent = intent;
-        info.iconResource = iconResource;
-
-        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);
     }
 
-    public void dumpState() {
-        Log.d(TAG, "mCallbacks=" + mCallbacks);
-        AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.data", mBgAllAppsList.data);
-        AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.added", mBgAllAppsList.added);
-        AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.removed", mBgAllAppsList.removed);
-        AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.modified", mBgAllAppsList.modified);
-        if (mLoaderTask != null) {
-            mLoaderTask.dumpState();
-        } else {
-            Log.d(TAG, "mLoaderTask=null");
+    public void dumpState(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+        if (args.length > 0 && TextUtils.equals(args[0], "--all")) {
+            writer.println(prefix + "All apps list: size=" + mBgAllAppsList.data.size());
+            for (AppInfo info : mBgAllAppsList.data) {
+                writer.println(prefix + "   title=\"" + info.title + "\" iconBitmap=" + info.iconBitmap
+                        + " componentName=" + info.componentName.getPackageName());
+            }
         }
+        sBgDataModel.dump(prefix, fd, writer, args);
     }
 
     public Callbacks getCallback() {
@@ -3868,8 +1989,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..3150d5b 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -16,7 +16,6 @@
 
 package com.android.launcher3;
 
-import android.annotation.TargetApi;
 import android.appwidget.AppWidgetHost;
 import android.appwidget.AppWidgetManager;
 import android.content.ComponentName;
@@ -39,12 +38,12 @@
 import android.database.sqlite.SQLiteStatement;
 import android.net.Uri;
 import android.os.Binder;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
 import android.os.Process;
 import android.os.Trace;
+import android.os.UserHandle;
 import android.os.UserManager;
 import android.text.TextUtils;
 import android.util.Log;
@@ -52,11 +51,12 @@
 import com.android.launcher3.AutoInstallsLayout.LayoutParserCallback;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.LauncherSettings.WorkspaceScreens;
-import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.config.ProviderConfig;
 import com.android.launcher3.dynamicui.ExtractionUtils;
+import com.android.launcher3.graphics.IconShapeOverride;
+import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.provider.LauncherDbUtils;
 import com.android.launcher3.provider.RestoreDbTask;
 import com.android.launcher3.util.ManagedProfileHeuristic;
@@ -64,16 +64,30 @@
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.Thunk;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.lang.reflect.Method;
 import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.Locale;
+import java.util.HashSet;
 
 public class LauncherProvider extends ContentProvider {
     private static final String TAG = "LauncherProvider";
     private static final boolean LOGD = false;
 
-    private static final int DATABASE_VERSION = 27;
+    /**
+     * Represents the schema of the database. Changes in scheme need not be backwards compatible.
+     */
+    private static final int SCHEMA_VERSION = 27;
+    /**
+     * Represents the actual data. It could include additional validations and normalizations added
+     * overtime. These must be backwards compatible, else we risk breaking old devices during
+     * restore or binary version downgrade.
+     */
+    private static final int DATA_VERSION = 3;
+
+    private static final String PREF_KEY_DATA_VERISON = "provider_data_version";
 
     public static final String AUTHORITY = ProviderConfig.AUTHORITY;
 
@@ -86,6 +100,18 @@
 
     protected DatabaseHelper mOpenHelper;
 
+    /**
+     * $ adb shell dumpsys activity provider com.android.launcher3
+     */
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
+        if (appState == null || !appState.getModel().isModelLoaded()) {
+            return;
+        }
+        appState.getModel().dumpState("", fd, writer, args);
+    }
+
     @Override
     public boolean onCreate() {
         if (ProviderConfig.IS_DOGFOOD_BUILD) {
@@ -93,7 +119,12 @@
         }
         mListenerHandler = new Handler(mListenerWrapper);
 
-        LauncherAppState.setLauncherProvider(this);
+        // The content provider exists for the entire duration of the launcher main process and
+        // is the first component to get created. Initializing FileLog here ensures that it's
+        // always available in the main process.
+        FileLog.setDir(getContext().getApplicationContext().getFilesDir());
+        IconShapeOverride.apply(getContext());
+        SessionCommitReceiver.applyDefaultUserPrefs(getContext());
         return true;
     }
 
@@ -172,7 +203,7 @@
         if (Utilities.ATLEAST_MARSHMALLOW && Binder.getCallingPid() != Process.myPid()) {
             LauncherAppState app = LauncherAppState.getInstanceNoCreate();
             if (app != null) {
-                app.reloadWorkspace();
+                app.getModel().forceReload();
             }
         }
     }
@@ -203,7 +234,7 @@
             // Deprecated behavior to support legacy devices which rely on provider callbacks.
             LauncherAppState app = LauncherAppState.getInstanceNoCreate();
             if (app != null && "true".equals(uri.getQueryParameter("isExternalAdd"))) {
-                app.reloadWorkspace();
+                app.getModel().forceReload();
             }
 
             String notify = uri.getQueryParameter("notify");
@@ -232,10 +263,11 @@
 
             if (cn != null) {
                 try {
-                    int appWidgetId = new AppWidgetHost(getContext(), Launcher.APPWIDGET_HOST_ID)
-                            .allocateAppWidgetId();
+                    AppWidgetHost widgetHost = mOpenHelper.newLauncherWidgetHost();
+                    int appWidgetId = widgetHost.allocateAppWidgetId();
                     values.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId);
                     if (!appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,cn)) {
+                        widgetHost.deleteAppWidgetId(appWidgetId);
                         return false;
                     }
                 } catch (RuntimeException e) {
@@ -317,23 +349,7 @@
 
         if (Binder.getCallingPid() != Process.myPid()
                 && Favorites.TABLE_NAME.equalsIgnoreCase(args.table)) {
-            String widgetSelection = TextUtils.isEmpty(args.where) ? "1=1" : args.where;
-            widgetSelection = String.format(Locale.ENGLISH, "%1$s = %2$d AND ( %3$s )",
-                    Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET, widgetSelection);
-            try (Cursor c = db.query(Favorites.TABLE_NAME, new String[] { Favorites.APPWIDGET_ID },
-                    widgetSelection, args.args, null, null, null)) {
-                AppWidgetHost host = new AppWidgetHost(getContext(), Launcher.APPWIDGET_HOST_ID);
-                while (c.moveToNext()) {
-                    int widgetId = c.getInt(0);
-                    if (widgetId != AppWidgetManager.INVALID_APPWIDGET_ID) {
-                        try {
-                            host.deleteAppWidgetId(widgetId);
-                        } catch (RuntimeException e) {
-                            Log.e(TAG, "Error deleting widget id " + widgetId, e);
-                        }
-                    }
-                }
-            }
+            mOpenHelper.removeGhostWidgets(mOpenHelper.getWritableDatabase());
         }
         int count = db.delete(args.table, args.where, args.args);
         if (count > 0) {
@@ -404,16 +420,15 @@
                 return result;
             }
             case LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB: {
-                createEmptyDB();
+                mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
                 return null;
             }
             case LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES: {
                 loadDefaultFavoritesIfNecessary();
                 return null;
             }
-            case LauncherSettings.Settings.METHOD_DELETE_DB: {
-                // Are you sure? (y/n)
-                mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
+            case LauncherSettings.Settings.METHOD_REMOVE_GHOST_WIDGETS: {
+                mOpenHelper.removeGhostWidgets(mOpenHelper.getWritableDatabase());
                 return null;
             }
         }
@@ -467,13 +482,6 @@
         values.put(LauncherSettings.ChangeLogColumns.MODIFIED, System.currentTimeMillis());
     }
 
-    /**
-     * Clears all the data for a fresh start.
-     */
-    synchronized private void createEmptyDB() {
-        mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
-    }
-
     private void clearFlagEmptyDbCreated() {
         Utilities.getPrefs(getContext()).edit().remove(EMPTY_DATABASE_CREATED).commit();
     }
@@ -491,7 +499,7 @@
         if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) {
             Log.d(TAG, "loading default workspace");
 
-            AppWidgetHost widgetHost = new AppWidgetHost(getContext(), Launcher.APPWIDGET_HOST_ID);
+            AppWidgetHost widgetHost = mOpenHelper.newLauncherWidgetHost();
             AutoInstallsLayout loader = createWorkspaceLoaderFromAppRestriction(widgetHost);
             if (loader == null) {
                 loader = AutoInstallsLayout.get(getContext(),widgetHost, mOpenHelper);
@@ -516,12 +524,12 @@
 
             // There might be some partially restored DB items, due to buggy restore logic in
             // previous versions of launcher.
-            createEmptyDB();
+            mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
             // Populate favorites table with initial favorites
             if ((mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), loader) <= 0)
                     && usingExternallyProvidedLayout) {
                 // Unable to load external layout. Cleanup and load the internal layout.
-                createEmptyDB();
+                mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
                 mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(),
                         getDefaultLayoutParser(widgetHost));
             }
@@ -534,13 +542,7 @@
      *
      * @return the loader if the restrictions are set and the resource exists; null otherwise.
      */
-    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
     private AutoInstallsLayout createWorkspaceLoaderFromAppRestriction(AppWidgetHost widgetHost) {
-        // UserManager.getApplicationRestrictions() requires minSdkVersion >= 18
-        if (!Utilities.ATLEAST_JB_MR2) {
-            return null;
-        }
-
         Context ctx = getContext();
         UserManager um = (UserManager) ctx.getSystemService(Context.USER_SERVICE);
         Bundle bundle = um.getApplicationRestrictions(ctx.getPackageName());
@@ -564,8 +566,7 @@
     }
 
     private DefaultLayoutParser getDefaultLayoutParser(AppWidgetHost widgetHost) {
-        int defaultLayout = LauncherAppState.getInstance()
-                .getInvariantDeviceProfile().defaultLayoutId;
+        int defaultLayout = LauncherAppState.getIDP(getContext()).defaultLayoutId;
         return new DefaultLayoutParser(getContext(), widgetHost,
                 mOpenHelper, getContext().getResources(), defaultLayout);
     }
@@ -599,7 +600,7 @@
          */
         public DatabaseHelper(
                 Context context, Handler widgetHostResetHandler, String tableName) {
-            super(new NoLocaleSqliteContext(context), tableName, null, DATABASE_VERSION);
+            super(new NoLocaleSqliteContext(context), tableName, null, SCHEMA_VERSION);
             mContext = context;
             mWidgetHostResetHandler = widgetHostResetHandler;
         }
@@ -648,25 +649,22 @@
         protected void onEmptyDbCreated() {
             // 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
-                ));
+                newLauncherWidgetHost().deleteHost();
+                mWidgetHostResetHandler.sendEmptyMessage(
+                        ChangeListenerWrapper.MSG_APP_WIDGET_HOST_RESET);
             }
 
             // Set the flag for empty DB
             Utilities.getPrefs(mContext).edit().putBoolean(EMPTY_DATABASE_CREATED, true).commit();
 
             // When a new DB is created, remove all previously stored managed profile information.
-            ManagedProfileHeuristic.processAllUsers(Collections.<UserHandleCompat>emptyList(),
+            ManagedProfileHeuristic.processAllUsers(Collections.<UserHandle>emptyList(),
                     mContext);
         }
 
         public long getDefaultUserSerial() {
             return UserManagerCompat.getInstance(mContext).getSerialNumberForUser(
-                    UserHandleCompat.myUserHandle());
+                    Process.myUserHandle());
         }
 
         private void addFavoritesTable(SQLiteDatabase db, boolean optional) {
@@ -714,6 +712,56 @@
         }
 
         @Override
+        public void onOpen(SQLiteDatabase db) {
+            super.onOpen(db);
+            SharedPreferences prefs = mContext
+                    .getSharedPreferences(LauncherFiles.DEVICE_PREFERENCES_KEY, 0);
+            int oldVersion = prefs.getInt(PREF_KEY_DATA_VERISON, 0);
+            if (oldVersion != DATA_VERSION) {
+                // Only run the data upgrade path for an existing db.
+                if (!Utilities.getPrefs(mContext).getBoolean(EMPTY_DATABASE_CREATED, false)) {
+                    db.beginTransaction();
+                    try {
+                        onDataUpgrade(db, oldVersion);
+                        db.setTransactionSuccessful();
+                    } catch (Exception e) {
+                        Log.d(TAG, "Error updating data version, ignoring", e);
+                        return;
+                    } finally {
+                        db.endTransaction();
+                    }
+                }
+                prefs.edit().putInt(PREF_KEY_DATA_VERISON, DATA_VERSION).apply();
+            }
+        }
+
+        /**
+         * Called when the data is updated as part of app update. It can be called multiple times
+         * with old version, even though it had been run before. The changes made here must be
+         * backwards compatible, else we risk breaking old devices during restore or binary
+         * version downgrade.
+         */
+        protected void onDataUpgrade(SQLiteDatabase db, int oldVersion) {
+            switch (oldVersion) {
+                case 0:
+                case 1: {
+                    // Remove "profile extra"
+                    UserManagerCompat um = UserManagerCompat.getInstance(mContext);
+                    for (UserHandle user : um.getUserProfiles()) {
+                        long serial = um.getSerialNumberForUser(user);
+                        String sql = "update favorites set intent = replace(intent, "
+                                + "';l.profile=" + serial + ";', ';') where itemType = 0;";
+                        db.execSQL(sql);
+                    }
+                }
+                case 2:
+                case 3:
+                    // data updated
+                    return;
+            }
+        }
+
+        @Override
         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
             if (LOGD) Log.d(TAG, "onUpgrade triggered: " + oldVersion);
             switch (oldVersion) {
@@ -764,12 +812,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
@@ -812,13 +855,12 @@
                 case 26:
                     // QSB was moved to the grid. Clear the first row on screen 0.
                     if (FeatureFlags.QSB_ON_FIRST_SCREEN &&
-                            !LauncherDbUtils.prepareScreenZeroToHostQsb(db)) {
+                            !LauncherDbUtils.prepareScreenZeroToHostQsb(mContext, db)) {
                         break;
                     }
-                case 27: {
+                case 27:
                     // DB Upgraded successfully
                     return;
-                }
             }
 
             // DB was not upgraded
@@ -828,6 +870,11 @@
 
         @Override
         public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+            if (oldVersion == 28 && newVersion == 27) {
+                // TODO: remove this check. This is only applicable for internal development/testing
+                // and for any released version of Launcher.
+                return;
+            }
             // This shouldn't happen -- throw our hands up in the air and start over.
             Log.w(TAG, "Database version downgrade from: " + oldVersion + " to " + newVersion +
                     ". Wiping databse.");
@@ -838,9 +885,56 @@
          * Clears all the data for a fresh start.
          */
         public void createEmptyDB(SQLiteDatabase db) {
-            db.execSQL("DROP TABLE IF EXISTS " + Favorites.TABLE_NAME);
-            db.execSQL("DROP TABLE IF EXISTS " + WorkspaceScreens.TABLE_NAME);
-            onCreate(db);
+            db.beginTransaction();
+            try {
+                db.execSQL("DROP TABLE IF EXISTS " + Favorites.TABLE_NAME);
+                db.execSQL("DROP TABLE IF EXISTS " + WorkspaceScreens.TABLE_NAME);
+                onCreate(db);
+                db.setTransactionSuccessful();
+            } finally {
+                db.endTransaction();
+            }
+        }
+
+        /**
+         * Removes widgets which are registered to the Launcher's host, but are not present
+         * in our model.
+         */
+        public void removeGhostWidgets(SQLiteDatabase db) {
+            // Get all existing widget ids.
+            final AppWidgetHost host = newLauncherWidgetHost();
+            final int[] allWidgets;
+            try {
+                Method getter = AppWidgetHost.class.getDeclaredMethod("getAppWidgetIds");
+                getter.setAccessible(true);
+                allWidgets = (int[]) getter.invoke(host);
+            } catch (Exception e) {
+                Log.e(TAG, "getAppWidgetIds not supported", e);
+                return;
+            }
+            try {
+                Cursor c = db.query(Favorites.TABLE_NAME,
+                        new String[] {Favorites.APPWIDGET_ID },
+                        "itemType=" + Favorites.ITEM_TYPE_APPWIDGET, null, null, null, null);
+                HashSet<Integer> validWidgets = new HashSet<>();
+                while (c.moveToNext()) {
+                    validWidgets.add(c.getInt(0));
+                }
+                c.close();
+
+                for (int widgetId : allWidgets) {
+                    if (!validWidgets.contains(widgetId)) {
+                        try {
+                            FileLog.d(TAG, "Deleting invalid widget " + widgetId);
+                            host.deleteAppWidgetId(widgetId);
+                        } catch (RuntimeException e) {
+                            // Ignore
+                        }
+                    }
+                }
+            } catch (SQLException ex) {
+                Log.w(TAG, "Error getting widgets list", ex);
+            }
         }
 
         /**
@@ -1012,6 +1106,10 @@
             return mMaxItemId;
         }
 
+        public AppWidgetHost newLauncherWidgetHost() {
+            return new AppWidgetHost(mContext, Launcher.APPWIDGET_HOST_ID);
+        }
+
         @Override
         public long insertAndCheck(SQLiteDatabase db, ContentValues values) {
             return dbInsertAndCheck(this, db, Favorites.TABLE_NAME, null, values);
@@ -1019,7 +1117,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 +1239,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/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index e884393..b25b256 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -37,7 +37,7 @@
         public static final String MODIFIED = "modified";
     }
 
-    static interface BaseLauncherColumns extends ChangeLogColumns {
+    static public interface BaseLauncherColumns extends ChangeLogColumns {
         /**
          * Descriptive name of the gesture that can be displayed to the user.
          * <P>Type: TEXT</P>
@@ -291,7 +291,6 @@
         public static final String METHOD_NEW_SCREEN_ID = "generate_new_screen_id";
 
         public static final String METHOD_CREATE_EMPTY_DB = "create_empty_db";
-        public static final String METHOD_DELETE_DB = "delete_db";
 
         public static final String METHOD_LOAD_DEFAULT_FAVORITES = "load_default_favorites";
 
@@ -300,6 +299,8 @@
         public static final String EXTRA_EXTRACTED_COLORS = "extra_extractedColors";
         public static final String EXTRA_WALLPAPER_ID = "extra_wallpaperId";
 
+        public static final String METHOD_REMOVE_GHOST_WIDGETS = "remove_ghost_widgets";
+
         public static final String EXTRA_VALUE = "value";
 
         public static Bundle call(ContentResolver cr, String method) {
diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
index eb70650..39c466d 100644
--- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java
+++ b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
@@ -23,24 +23,19 @@
 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.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 +114,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 +131,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 +166,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,22 +219,18 @@
     /**
      * 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();
         final Resources res = mLauncher.getResources();
-        final boolean material = Utilities.ATLEAST_LOLLIPOP;
         final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime);
         final int revealDurationSlide = res.getInteger(R.integer.config_overlaySlideRevealTime);
 
         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 +239,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 +255,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;
         }
@@ -291,20 +271,11 @@
             revealView.setTranslationX(0f);
 
             // Calculate the final animation values
-            final float revealViewToAlpha;
-            final float revealViewToXDrift;
-            final float revealViewToYDrift;
-            if (material) {
-                int[] buttonViewToPanelDelta = Utilities.getCenterDeltaInScreenSpace(
-                        revealView, buttonView, null);
-                revealViewToAlpha = pCb.materialRevealViewFinalAlpha;
-                revealViewToYDrift = buttonViewToPanelDelta[1];
-                revealViewToXDrift = buttonViewToPanelDelta[0];
-            } else {
-                revealViewToAlpha = 0f;
-                revealViewToYDrift = 2 * height / 3;
-                revealViewToXDrift = 0;
-            }
+            int[] buttonViewToPanelDelta =
+                    Utilities.getCenterDeltaInScreenSpace(revealView, buttonView);
+            final float revealViewToAlpha = pCb.materialRevealViewFinalAlpha;
+            final float revealViewToXDrift = buttonViewToPanelDelta[0];
+            final float revealViewToYDrift = buttonViewToPanelDelta[1];
 
             // Create the animators
             PropertyValuesHolder panelAlpha =
@@ -319,14 +290,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",
@@ -342,36 +313,24 @@
             itemsAlpha.setStartDelay(itemsAlphaStagger);
             animation.play(itemsAlpha);
 
-            if (material) {
-                float startRadius = pCb.getMaterialRevealViewStartFinalRadius();
-                AnimatorListenerAdapter listener = pCb.getMaterialRevealViewAnimatorListener(
-                        revealView, buttonView);
-                Animator reveal = new CircleRevealOutlineProvider(width / 2, height / 2,
-                        startRadius, revealRadius).createRevealAnimator(revealView);
-                reveal.setDuration(revealDuration);
-                reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
-                if (listener != null) {
-                    reveal.addListener(listener);
-                }
-                animation.play(reveal);
+            float startRadius = pCb.getMaterialRevealViewStartFinalRadius();
+            AnimatorListenerAdapter listener = pCb.getMaterialRevealViewAnimatorListener(
+                    revealView, buttonView);
+            Animator reveal = new CircleRevealOutlineProvider(width / 2, height / 2,
+                    startRadius, revealRadius).createRevealAnimator(revealView);
+            reveal.setDuration(revealDuration);
+            reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
+            if (listener != null) {
+                reveal.addListener(listener);
             }
+            animation.play(reveal);
 
             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 +338,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 +372,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 +385,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 +424,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 +470,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();
@@ -680,17 +515,15 @@
             final PrivateTransitionCallbacks pCb) {
         final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
         final Resources res = mLauncher.getResources();
-        final boolean material = Utilities.ATLEAST_LOLLIPOP;
         final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime);
         final int revealDurationSlide = res.getInteger(R.integer.config_overlaySlideRevealTime);
-        final int itemsAlphaStagger =
-                res.getInteger(R.integer.config_overlayItemsAlphaStagger);
+        final int itemsAlphaStagger = res.getInteger(R.integer.config_overlayItemsAlphaStagger);
 
         final View toView = mLauncher.getWorkspace();
         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 +531,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 +539,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,27 +558,17 @@
                 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;
-                final float revealViewToYDrift;
-                if (material) {
-                    int[] buttonViewToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView,
-                            buttonView, null);
-                    revealViewToYDrift = buttonViewToPanelDelta[1];
-                    revealViewToXDrift = buttonViewToPanelDelta[0];
-                } else {
-                    revealViewToYDrift = 2 * height / 3;
-                    revealViewToXDrift = 0;
-                }
+                int[] buttonViewToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView, buttonView);
+                final float revealViewToXDrift = buttonViewToPanelDelta[0];
+                final float revealViewToYDrift = buttonViewToPanelDelta[1];
 
                 // The vertical motion of the apps panel should be delayed by one frame
                 // from the conceal animation in order to give the right feel. We correspondingly
                 // shorten the duration so that the slide and conceal end at the same time.
-                TimeInterpolator decelerateInterpolator = material ?
-                        new LogDecelerateInterpolator(100, 0) :
-                        new DecelerateInterpolator(1f);
+                TimeInterpolator decelerateInterpolator = new LogDecelerateInterpolator(100, 0);
                 ObjectAnimator panelDriftY = ObjectAnimator.ofFloat(revealView, "translationY",
                         0, revealViewToYDrift);
                 panelDriftY.setDuration(revealDuration - SINGLE_FRAME_DELAY);
@@ -769,19 +584,16 @@
                 animation.play(panelDriftX);
 
                 // Setup animation for the reveal panel alpha
-                final float revealViewToAlpha = !material ? 0f :
-                        pCb.materialRevealViewFinalAlpha;
-                if (revealViewToAlpha != 1f) {
+                if (pCb.materialRevealViewFinalAlpha != 1f) {
                     ObjectAnimator panelAlpha = ObjectAnimator.ofFloat(revealView, "alpha",
-                            1f, revealViewToAlpha);
-                    panelAlpha.setDuration(material ? revealDuration : 150);
-                    panelAlpha.setStartDelay(material ? 0 : itemsAlphaStagger + SINGLE_FRAME_DELAY);
+                            1f, pCb.materialRevealViewFinalAlpha);
+                    panelAlpha.setDuration(revealDuration);
                     panelAlpha.setInterpolator(decelerateInterpolator);
                     animation.play(panelAlpha);
                 }
 
                 // 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",
@@ -809,45 +621,30 @@
                 });
                 animation.play(invalidateScrim);
 
-                if (material) {
-                    // Animate the all apps button
-                    float finalRadius = pCb.getMaterialRevealViewStartFinalRadius();
-                    AnimatorListenerAdapter listener =
-                            pCb.getMaterialRevealViewAnimatorListener(revealView, buttonView);
-                    Animator reveal = new CircleRevealOutlineProvider(width / 2, height / 2,
-                            revealRadius, finalRadius).createRevealAnimator(revealView);
-                    reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
-                    reveal.setDuration(revealDuration);
-                    reveal.setStartDelay(itemsAlphaStagger);
-                    if (listener != null) {
-                        reveal.addListener(listener);
-                    }
-                    animation.play(reveal);
+                // Animate the all apps button
+                float finalRadius = pCb.getMaterialRevealViewStartFinalRadius();
+                AnimatorListenerAdapter listener =
+                        pCb.getMaterialRevealViewAnimatorListener(revealView, buttonView);
+                Animator reveal = new CircleRevealOutlineProvider(width / 2, height / 2,
+                        revealRadius, finalRadius).createRevealAnimator(revealView);
+                reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
+                reveal.setDuration(revealDuration);
+                reveal.setStartDelay(itemsAlphaStagger);
+                if (listener != null) {
+                    reveal.addListener(listener);
                 }
+                animation.play(reveal);
             }
 
-            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 +658,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 +675,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 +687,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 +700,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 +713,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/LauncherViewPropertyAnimator.java b/src/com/android/launcher3/LauncherViewPropertyAnimator.java
deleted file mode 100644
index 4406a2c..0000000
--- a/src/com/android/launcher3/LauncherViewPropertyAnimator.java
+++ /dev/null
@@ -1,275 +0,0 @@
-/*
- * Copyright (C) 2012 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.Animator.AnimatorListener;
-import android.animation.TimeInterpolator;
-import android.view.View;
-import android.view.ViewPropertyAnimator;
-
-import java.util.ArrayList;
-import java.util.EnumSet;
-
-public class LauncherViewPropertyAnimator extends Animator implements AnimatorListener {
-
-    enum Properties {
-            TRANSLATION_X,
-            TRANSLATION_Y,
-            SCALE_X,
-            SCALE_Y,
-            ROTATION_Y,
-            ALPHA,
-            START_DELAY,
-            DURATION,
-            INTERPOLATOR,
-            WITH_LAYER
-    }
-    EnumSet<Properties> mPropertiesToSet = EnumSet.noneOf(Properties.class);
-    ViewPropertyAnimator mViewPropertyAnimator;
-    View mTarget;
-
-    float mTranslationX;
-    float mTranslationY;
-    float mScaleX;
-    float mScaleY;
-    float mRotationY;
-    float mAlpha;
-    long mStartDelay;
-    long mDuration;
-    TimeInterpolator mInterpolator;
-    ArrayList<Animator.AnimatorListener> mListeners = new ArrayList<>();
-    boolean mRunning = false;
-    FirstFrameAnimatorHelper mFirstFrameHelper;
-
-    public LauncherViewPropertyAnimator(View target) {
-        mTarget = target;
-    }
-
-    @Override
-    public void addListener(Animator.AnimatorListener listener) {
-        mListeners.add(listener);
-    }
-
-    @Override
-    public void cancel() {
-        if (mViewPropertyAnimator != null) {
-            mViewPropertyAnimator.cancel();
-        }
-    }
-
-    @Override
-    public Animator clone() {
-        throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void end() {
-        throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public long getDuration() {
-        return mDuration;
-    }
-
-    @Override
-    public ArrayList<Animator.AnimatorListener> getListeners() {
-        return mListeners;
-    }
-
-    @Override
-    public long getStartDelay() {
-        return mStartDelay;
-    }
-
-    @Override
-    public void onAnimationCancel(Animator animation) {
-        for (int i = 0; i < mListeners.size(); i++) {
-            Animator.AnimatorListener listener = mListeners.get(i);
-            listener.onAnimationCancel(this);
-        }
-        mRunning = false;
-    }
-
-    @Override
-    public void onAnimationEnd(Animator animation) {
-        for (int i = 0; i < mListeners.size(); i++) {
-            Animator.AnimatorListener listener = mListeners.get(i);
-            listener.onAnimationEnd(this);
-        }
-        mRunning = false;
-    }
-
-    @Override
-    public void onAnimationRepeat(Animator animation) {
-        for (int i = 0; i < mListeners.size(); i++) {
-            Animator.AnimatorListener listener = mListeners.get(i);
-            listener.onAnimationRepeat(this);
-        }
-    }
-
-    @Override
-    public void onAnimationStart(Animator animation) {
-        // This is the first time we get a handle to the internal ValueAnimator
-        // used by the ViewPropertyAnimator.
-        mFirstFrameHelper.onAnimationStart(animation);
-
-        for (int i = 0; i < mListeners.size(); i++) {
-            Animator.AnimatorListener listener = mListeners.get(i);
-            listener.onAnimationStart(this);
-        }
-        mRunning = true;
-    }
-
-    @Override
-    public boolean isRunning() {
-        return mRunning;
-    }
-
-    @Override
-    public boolean isStarted() {
-        return mViewPropertyAnimator != null;
-    }
-
-    @Override
-    public void removeAllListeners() {
-        mListeners.clear();
-    }
-
-    @Override
-    public void removeListener(Animator.AnimatorListener listener) {
-        mListeners.remove(listener);
-    }
-
-    @Override
-    public Animator setDuration(long duration) {
-        mPropertiesToSet.add(Properties.DURATION);
-        mDuration = duration;
-        return this;
-    }
-
-    @Override
-    public void setInterpolator(TimeInterpolator value) {
-        mPropertiesToSet.add(Properties.INTERPOLATOR);
-        mInterpolator = value;
-    }
-
-    @Override
-    public void setStartDelay(long startDelay) {
-        mPropertiesToSet.add(Properties.START_DELAY);
-        mStartDelay = startDelay;
-    }
-
-    @Override
-    public void setTarget(Object target) {
-        throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void setupEndValues() {
-
-    }
-
-    @Override
-    public void setupStartValues() {
-    }
-
-    @Override
-    public void start() {
-        mViewPropertyAnimator = mTarget.animate();
-
-        // FirstFrameAnimatorHelper hooks itself up to the updates on the animator,
-        // and then adjusts the play time to keep the first two frames jank-free
-        mFirstFrameHelper = new FirstFrameAnimatorHelper(mViewPropertyAnimator, mTarget);
-
-        if (mPropertiesToSet.contains(Properties.TRANSLATION_X)) {
-            mViewPropertyAnimator.translationX(mTranslationX);
-        }
-        if (mPropertiesToSet.contains(Properties.TRANSLATION_Y)) {
-            mViewPropertyAnimator.translationY(mTranslationY);
-        }
-        if (mPropertiesToSet.contains(Properties.SCALE_X)) {
-            mViewPropertyAnimator.scaleX(mScaleX);
-        }
-        if (mPropertiesToSet.contains(Properties.ROTATION_Y)) {
-            mViewPropertyAnimator.rotationY(mRotationY);
-        }
-        if (mPropertiesToSet.contains(Properties.SCALE_Y)) {
-            mViewPropertyAnimator.scaleY(mScaleY);
-        }
-        if (mPropertiesToSet.contains(Properties.ALPHA)) {
-            mViewPropertyAnimator.alpha(mAlpha);
-        }
-        if (mPropertiesToSet.contains(Properties.START_DELAY)) {
-            mViewPropertyAnimator.setStartDelay(mStartDelay);
-        }
-        if (mPropertiesToSet.contains(Properties.DURATION)) {
-            mViewPropertyAnimator.setDuration(mDuration);
-        }
-        if (mPropertiesToSet.contains(Properties.INTERPOLATOR)) {
-            mViewPropertyAnimator.setInterpolator(mInterpolator);
-        }
-        if (mPropertiesToSet.contains(Properties.WITH_LAYER)) {
-            mViewPropertyAnimator.withLayer();
-        }
-        mViewPropertyAnimator.setListener(this);
-        mViewPropertyAnimator.start();
-        LauncherAnimUtils.cancelOnDestroyActivity(this);
-    }
-
-    public LauncherViewPropertyAnimator translationX(float value) {
-        mPropertiesToSet.add(Properties.TRANSLATION_X);
-        mTranslationX = value;
-        return this;
-    }
-
-    public LauncherViewPropertyAnimator translationY(float value) {
-        mPropertiesToSet.add(Properties.TRANSLATION_Y);
-        mTranslationY = value;
-        return this;
-    }
-
-    public LauncherViewPropertyAnimator scaleX(float value) {
-        mPropertiesToSet.add(Properties.SCALE_X);
-        mScaleX = value;
-        return this;
-    }
-
-    public LauncherViewPropertyAnimator scaleY(float value) {
-        mPropertiesToSet.add(Properties.SCALE_Y);
-        mScaleY = value;
-        return this;
-    }
-
-    public LauncherViewPropertyAnimator rotationY(float value) {
-        mPropertiesToSet.add(Properties.ROTATION_Y);
-        mRotationY = value;
-        return this;
-    }
-
-    public LauncherViewPropertyAnimator alpha(float value) {
-        mPropertiesToSet.add(Properties.ALPHA);
-        mAlpha = value;
-        return this;
-    }
-
-    public LauncherViewPropertyAnimator withLayer() {
-        mPropertiesToSet.add(Properties.WITH_LAYER);
-        return this;
-    }
-}
diff --git a/src/com/android/launcher3/MainThreadExecutor.java b/src/com/android/launcher3/MainThreadExecutor.java
index 866b17c..4ca0a59 100644
--- a/src/com/android/launcher3/MainThreadExecutor.java
+++ b/src/com/android/launcher3/MainThreadExecutor.java
@@ -16,65 +16,18 @@
 
 package com.android.launcher3;
 
-import android.os.Handler;
 import android.os.Looper;
 
-import java.util.List;
-import java.util.concurrent.AbstractExecutorService;
-import java.util.concurrent.TimeUnit;
+import com.android.launcher3.util.LooperExecuter;
 
 /**
  * An executor service that executes its tasks on the main thread.
  *
  * Shutting down this executor is not supported.
  */
-public class MainThreadExecutor extends AbstractExecutorService {
+public class MainThreadExecutor extends LooperExecuter {
 
-    private Handler mHandler = new Handler(Looper.getMainLooper());
-
-    @Override
-    public void execute(Runnable runnable) {
-        if (Looper.getMainLooper() == Looper.myLooper()) {
-            runnable.run();
-        } else {
-            mHandler.post(runnable);
-        }
-    }
-
-    /**
-     * Not supported and throws an exception when used.
-     */
-    @Override
-    @Deprecated
-    public void shutdown() {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * Not supported and throws an exception when used.
-     */
-    @Override
-    @Deprecated
-    public List<Runnable> shutdownNow() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public boolean isShutdown() {
-        return false;
-    }
-
-    @Override
-    public boolean isTerminated() {
-        return false;
-    }
-
-    /**
-     * Not supported and throws an exception when used.
-     */
-    @Override
-    @Deprecated
-    public boolean awaitTermination(long l, TimeUnit timeUnit) throws InterruptedException {
-        throw new UnsupportedOperationException();
+    public MainThreadExecutor() {
+        super(Looper.getMainLooper());
     }
 }
diff --git a/src/com/android/launcher3/OverviewButtonClickListener.java b/src/com/android/launcher3/OverviewButtonClickListener.java
new file mode 100644
index 0000000..dd670d2
--- /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.Action;
+
+/**
+ * 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 Action.Touch} */
+
+    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, Action.Touch.TAP);
+        }
+    }
+
+    @Override
+    public boolean onLongClick(View view) {
+        if (shouldPerformClick(view)) {
+            handleViewClick(view, Action.Touch.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..fb6a611 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -22,14 +22,11 @@
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
 import android.annotation.SuppressLint;
-import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.TypedArray;
 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;
 import android.os.Parcelable;
@@ -50,6 +47,7 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.animation.Interpolator;
 
+import com.android.launcher3.anim.PropertyListBuilder;
 import com.android.launcher3.pageindicators.PageIndicator;
 import com.android.launcher3.util.LauncherEdgeEffect;
 import com.android.launcher3.util.Thunk;
@@ -98,7 +96,6 @@
 
     @ViewDebug.ExportedProperty(category = "launcher")
     protected int mCurrentPage;
-    protected int mRestorePage = INVALID_RESTORE_PAGE;
     private int mChildCountOnLastLayout;
 
     @ViewDebug.ExportedProperty(category = "launcher")
@@ -119,7 +116,6 @@
     private float mLastMotionXRemainder;
     private float mLastMotionY;
     private float mTotalMotionX;
-    private int mLastScreenCenter = -1;
 
     private boolean mCancelTap;
 
@@ -132,7 +128,6 @@
     protected final static int TOUCH_STATE_REORDERING = 4;
 
     protected int mTouchState = TOUCH_STATE_REST;
-    private boolean mForceScreenScrolled = false;
 
     protected OnLongClickListener mLongClickListener;
 
@@ -145,7 +140,7 @@
 
     protected int mActivePointerId = INVALID_POINTER;
 
-    protected boolean mIsPageMoving = false;
+    protected boolean mIsPageInTransition = false;
 
     protected boolean mWasInOverscroll = false;
 
@@ -184,7 +179,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 +404,6 @@
         if (getChildCount() == 0) {
             return;
         }
-        mForceScreenScrolled = true;
         mCurrentPage = validateNewPage(currentPage);
         updateCurrentPageScroll();
         notifyPageSwitchListener();
@@ -418,17 +411,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 +427,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;
     }
 
@@ -576,7 +564,7 @@
     protected boolean computeScrollHelper(boolean shouldInvalidate) {
         if (mScroller.computeScrollOffset()) {
             // Don't bother scrolling if the page does not need to be moved
-            if (getScrollX() != mScroller.getCurrX()
+            if (getUnboundedScrollX() != mScroller.getCurrX()
                     || getScrollY() != mScroller.getCurrY()) {
                 float scaleX = mFreeScroll ? getScaleX() : 1f;
                 int scrollX = (int) (mScroller.getCurrX() * (1 / scaleX));
@@ -596,7 +584,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 +867,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 +899,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 +909,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 +972,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 +982,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 +996,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 +1011,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 +1067,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 +1225,7 @@
                     mTouchState = TOUCH_STATE_REST;
                     if (!mScroller.isFinished() && !mFreeScroll) {
                         setCurrentPage(getNextPage());
-                        pageEndMoving();
+                        pageEndTransition();
                     }
                 } else {
                     if (isTouchPointInViewportWithBuffer((int) mDownMotionX, (int) mDownMotionY)) {
@@ -1399,7 +1286,7 @@
             mLastMotionX = x;
             mLastMotionXRemainder = 0;
             onScrollInteractionBegin();
-            pageBeginMoving();
+            pageBeginTransition();
             // Stop listening for things like pinches.
             requestDisallowInterceptTouchEvent(true);
         }
@@ -1484,8 +1371,12 @@
         dampedOverScroll(amount);
     }
 
-    public void enableFreeScroll() {
+    /**
+     * return true if freescroll has been enabled, false otherwise
+     */
+    public boolean enableFreeScroll() {
         setEnableFreeScroll(true);
+        return true;
     }
 
     public void disableFreeScroll() {
@@ -1581,7 +1472,7 @@
 
             if (mTouchState == TOUCH_STATE_SCROLLING) {
                 onScrollInteractionBegin();
-                pageBeginMoving();
+                pageBeginTransition();
             }
             break;
 
@@ -2027,7 +1918,6 @@
 
         mNextPage = whichPage;
 
-        pageBeginMoving();
         awakenScrollBars(duration);
         if (immediate) {
             duration = 0;
@@ -2035,6 +1925,10 @@
             duration = Math.abs(delta);
         }
 
+        if (duration != 0) {
+            pageBeginTransition();
+        }
+
         if (!mScroller.isFinished()) {
             abortScrollerAnimation(false);
         }
@@ -2052,9 +1946,9 @@
         // Trigger a compute() to finish switching pages if necessary
         if (immediate) {
             computeScroll();
+            pageEndTransition();
         }
 
-        mForceScreenScrolled = true;
         invalidate();
     }
 
@@ -2105,11 +1999,12 @@
     // Animate the drag view back to the original position
     private void animateDragViewToOriginalPosition() {
         if (mDragView != null) {
-            Animator anim = new LauncherViewPropertyAnimator(mDragView)
-                    .translationX(0)
-                    .translationY(0)
-                    .scaleX(1)
-                    .scaleY(1)
+            Animator anim = LauncherAnimUtils.ofPropertyValuesHolder(mDragView,
+                    new PropertyListBuilder()
+                            .scale(1)
+                            .translationX(0)
+                            .translationY(0)
+                            .build())
                     .setDuration(REORDERING_DROP_REPOSITION_DURATION);
             anim.addListener(new AnimatorListenerAdapter() {
                 @Override
@@ -2184,20 +2079,14 @@
         if (!mReorderingStarted) return;
         mReorderingStarted = false;
 
-        // If we haven't flung-to-delete the current child, then we just animate the drag view
-        // back into position
-        final Runnable onCompleteRunnable = new Runnable() {
-            @Override
-            public void run() {
-                onEndReordering();
-            }
-        };
-
         mPostReorderingPreZoomInRunnable = new Runnable() {
             public void run() {
-                onCompleteRunnable.run();
+                // If we haven't flung-to-delete the current child,
+                // then we just animate the drag view back into position
+                onEndReordering();
+
                 enableFreeScroll();
-            };
+            }
         };
 
         mPostReorderingPreZoomInRemainingAnimationCount =
@@ -2210,7 +2099,6 @@
 
     /* Accessibility */
     @SuppressWarnings("deprecation")
-    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
     @Override
     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
         super.onInitializeAccessibilityNodeInfo(info);
@@ -2227,9 +2115,7 @@
         // Besides disabling the accessibility long-click, this also prevents this view from getting
         // accessibility focus.
         info.setLongClickable(false);
-        if (Utilities.ATLEAST_LOLLIPOP) {
-            info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK);
-        }
+        info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK);
     }
 
     @Override
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..b163464 100644
--- a/src/com/android/launcher3/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/PendingAppWidgetHostView.java
@@ -16,17 +16,13 @@
 
 package com.android.launcher3;
 
-import android.annotation.TargetApi;
 import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources.Theme;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
-import android.os.Build;
 import android.os.Bundle;
 import android.text.Layout;
 import android.text.StaticLayout;
@@ -36,18 +32,21 @@
 import android.view.View;
 import android.view.View.OnClickListener;
 
-public class PendingAppWidgetHostView extends LauncherAppWidgetHostView implements OnClickListener {
+import com.android.launcher3.IconCache.ItemInfoUpdateReceiver;
+import com.android.launcher3.graphics.DrawableFactory;
+import com.android.launcher3.model.PackageItemInfo;
+import com.android.launcher3.util.Themes;
+
+public class PendingAppWidgetHostView extends LauncherAppWidgetHostView
+        implements OnClickListener, ItemInfoUpdateReceiver {
     private static final float SETUP_ICON_SIZE_FACTOR = 2f / 5;
     private static final float MIN_SATUNATION = 0.7f;
 
-    private static Theme sPreloaderTheme;
-
     private final Rect mRect = new Rect();
     private View mDefaultView;
     private OnClickListener mClickListener;
     private final LauncherAppWidgetInfo mInfo;
     private final int mStartState;
-    private final Intent mIconLookupIntent;
     private final boolean mDisabledForSafeMode;
     private Launcher mLauncher;
 
@@ -61,27 +60,30 @@
     private final TextPaint mPaint;
     private Layout mSetupTextLayout;
 
-    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
     public PendingAppWidgetHostView(Context context, LauncherAppWidgetInfo info,
-            boolean disabledForSafeMode) {
+            IconCache cache, boolean disabledForSafeMode) {
         super(new ContextThemeWrapper(context, R.style.WidgetContainerTheme));
 
         mLauncher = Launcher.getLauncher(context);
         mInfo = info;
         mStartState = info.restoreStatus;
-        mIconLookupIntent = new Intent().setComponent(info.providerName);
         mDisabledForSafeMode = disabledForSafeMode;
 
         mPaint = new TextPaint();
-        mPaint.setColor(0xFFFFFFFF);
+        mPaint.setColor(Themes.getAttrColor(getContext(), android.R.attr.textColorPrimary));
         mPaint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX,
                 mLauncher.getDeviceProfile().iconTextSizePx, getResources().getDisplayMetrics()));
-        setBackgroundResource(R.drawable.quantum_panel_dark);
+        setBackgroundResource(R.drawable.pending_widget_bg);
         setWillNotDraw(false);
 
-        if (Utilities.ATLEAST_LOLLIPOP) {
-            setElevation(getResources().getDimension(R.dimen.pending_widget_elevation));
-        }
+        setElevation(getResources().getDimension(R.dimen.pending_widget_elevation));
+        updateAppWidget(null);
+        setOnClickListener(mLauncher);
+
+        // Load icon
+        PackageItemInfo item = new PackageItemInfo(info.providerName.getPackageName());
+        item.user = info.user;
+        cache.updateIconInBackground(this, item);
     }
 
     @Override
@@ -117,8 +119,9 @@
         mDrawableSizeChanged = true;
     }
 
-    public void updateIcon(IconCache cache) {
-        Bitmap icon = cache.getIcon(mIconLookupIntent, mInfo.user);
+    @Override
+    public void reapplyItemInfo(ItemInfoWithIcon info) {
+        Bitmap icon = info.iconBitmap;
         if (mIcon == icon) {
             return;
         }
@@ -132,30 +135,27 @@
             //   1) App icon in the center
             //   2) Preload icon in the center
             //   3) Setup icon in the center and app icon in the top right corner.
+            DrawableFactory drawableFactory = DrawableFactory.get(getContext());
             if (mDisabledForSafeMode) {
-                FastBitmapDrawable disabledIcon = mLauncher.createIconDrawable(mIcon);
-                disabledIcon.setState(FastBitmapDrawable.State.DISABLED);
+                FastBitmapDrawable disabledIcon = drawableFactory.newIcon(mIcon, mInfo);
+                disabledIcon.setIsDisabled(true);
                 mCenterDrawable = disabledIcon;
                 mSettingIconDrawable = null;
             } else if (isReadyForClickSetup()) {
-                mCenterDrawable = new FastBitmapDrawable(mIcon);
+                mCenterDrawable = drawableFactory.newIcon(mIcon, mInfo);
                 mSettingIconDrawable = getResources().getDrawable(R.drawable.ic_setting).mutate();
 
                 updateSettingColor();
             } else {
-                if (sPreloaderTheme == null) {
-                    sPreloaderTheme = getResources().newTheme();
-                    sPreloaderTheme.applyStyle(R.style.PreloadIcon, true);
-                }
-
-                FastBitmapDrawable drawable = mLauncher.createIconDrawable(mIcon);
-                mCenterDrawable = new PreloadIconDrawable(drawable, sPreloaderTheme);
+                mCenterDrawable = DrawableFactory.get(getContext())
+                        .newPendingIcon(mIcon, getContext());
                 mCenterDrawable.setCallback(this);
                 mSettingIconDrawable = null;
                 applyState();
             }
             mDrawableSizeChanged = true;
         }
+        invalidate();
     }
 
     private void updateSettingColor() {
@@ -219,13 +219,10 @@
         int availableHeight = getHeight() - paddingTop - paddingBottom - 2 * minPadding;
 
         if (mSettingIconDrawable == null) {
-            int outset = (mCenterDrawable instanceof PreloadIconDrawable) ?
-                    ((PreloadIconDrawable) mCenterDrawable).getOutset() : 0;
-            int maxSize = grid.iconSizePx + 2 * outset;
+            int maxSize = grid.iconSizePx;
             int size = Math.min(maxSize, Math.min(availableWidth, availableHeight));
 
             mRect.set(0, 0, size, size);
-            mRect.inset(outset, outset);
             mRect.offsetTo((getWidth() - mRect.width()) / 2, (getHeight() - mRect.height()) / 2);
             mCenterDrawable.setBounds(mRect);
         } else  {
diff --git a/src/com/android/launcher3/PinchAnimationManager.java b/src/com/android/launcher3/PinchAnimationManager.java
index baeb77c..f8196e5 100644
--- a/src/com/android/launcher3/PinchAnimationManager.java
+++ b/src/com/android/launcher3/PinchAnimationManager.java
@@ -24,6 +24,10 @@
 import android.view.View;
 import android.view.animation.LinearInterpolator;
 
+import com.android.launcher3.anim.AnimationLayerSet;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+
 import static com.android.launcher3.Workspace.State.NORMAL;
 import static com.android.launcher3.Workspace.State.OVERVIEW;
 
@@ -58,7 +62,6 @@
 
     private final Animator[] mAnimators = new Animator[4];
 
-    private final int[] mVisiblePageRange = new int[2];
     private Launcher mLauncher;
     private Workspace mWorkspace;
 
@@ -107,7 +110,7 @@
             public void onAnimationEnd(Animator animation) {
                 mIsAnimating = false;
                 thresholdManager.reset();
-                mWorkspace.onLauncherTransitionEnd(mLauncher, false, true);
+                mWorkspace.onEndStateTransition();
             }
         });
         animator.setDuration(duration).start();
@@ -162,9 +165,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(
+                        Action.Touch.PINCH, Action.Direction.NONE,
+                        ContainerType.OVERVIEW, mWorkspace.getCurrentPage());
                 mLauncher.showWorkspace(true);
                 mWorkspace.snapToPage(mWorkspace.getCurrentPage());
             } else if (startState == NORMAL && goingTowards == OVERVIEW) {
+                mLauncher.getUserEventDispatcher().logActionOnContainer(
+                        Action.Touch.PINCH, Action.Direction.NONE,
+                        ContainerType.WORKSPACE, mWorkspace.getCurrentPage());
                 mLauncher.showOverviewMode(true);
             }
         } else {
@@ -173,17 +182,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();
             }
         }
     }
@@ -207,14 +212,24 @@
     }
 
     private void animateShowHideView(int index, final View view, boolean show) {
-        Animator animator = new LauncherViewPropertyAnimator(view).alpha(show ? 1 : 0).withLayer();
+        Animator animator = ObjectAnimator.ofFloat(view, View.ALPHA, show ? 1 : 0);
+        animator.addListener(new AnimationLayerSet(view));
         if (show) {
             view.setVisibility(View.VISIBLE);
         } else {
             animator.addListener(new AnimatorListenerAdapter() {
+                private boolean mCancelled = false;
+
+                @Override
+                public void onAnimationCancel(Animator animation) {
+                    mCancelled = true;
+                }
+
                 @Override
                 public void onAnimationEnd(Animator animation) {
-                    view.setVisibility(View.INVISIBLE);
+                    if (!mCancelled) {
+                        view.setVisibility(View.INVISIBLE);
+                    }
                 }
             });
         }
@@ -223,7 +238,6 @@
 
     private void startAnimator(int index, Animator animator, long duration) {
         if (mAnimators[index] != null) {
-            mAnimators[index].removeAllListeners();
             mAnimators[index].cancel();
         }
         mAnimators[index] = animator;
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
deleted file mode 100644
index b064c47..0000000
--- a/src/com/android/launcher3/PreloadIconDrawable.java
+++ /dev/null
@@ -1,253 +0,0 @@
-package com.android.launcher3;
-
-import android.animation.ObjectAnimator;
-import android.content.res.Resources.Theme;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ColorFilter;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.Drawable;
-
-public class PreloadIconDrawable extends Drawable {
-
-    private static final float ANIMATION_PROGRESS_STOPPED = -1.0f;
-    private static final float ANIMATION_PROGRESS_STARTED = 0f;
-    private static final float ANIMATION_PROGRESS_COMPLETED = 1.0f;
-
-    private static final float MIN_SATUNATION = 0.2f;
-    private static final float MIN_LIGHTNESS = 0.6f;
-
-    private static final float ICON_SCALE_FACTOR = 0.5f;
-    private static final int DEFAULT_COLOR = 0xFF009688;
-
-    private static final Rect sTempRect = new Rect();
-
-    private final RectF mIndicatorRect = new RectF();
-    private boolean mIndicatorRectDirty;
-
-    private final Paint mPaint;
-    public final Drawable mIcon;
-
-    private Drawable mBgDrawable;
-    private int mRingOutset;
-
-    private int mIndicatorColor = 0;
-
-    /**
-     * Indicates the progress of the preloader [0-100]. If it goes above 100, only the icon
-     * is shown with no progress bar.
-     */
-    private int mProgress = 0;
-
-    private float mAnimationProgress = ANIMATION_PROGRESS_STOPPED;
-    private ObjectAnimator mAnimator;
-
-    public PreloadIconDrawable(Drawable icon, Theme theme) {
-        mIcon = icon;
-
-        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-        mPaint.setStyle(Paint.Style.STROKE);
-        mPaint.setStrokeCap(Paint.Cap.ROUND);
-
-        setBounds(icon.getBounds());
-        applyPreloaderTheme(theme);
-        onLevelChange(0);
-    }
-
-    public void applyPreloaderTheme(Theme t) {
-        TypedArray ta = t.obtainStyledAttributes(R.styleable.PreloadIconDrawable);
-        mBgDrawable = ta.getDrawable(R.styleable.PreloadIconDrawable_background);
-        mBgDrawable.setFilterBitmap(true);
-        mPaint.setStrokeWidth(ta.getDimension(R.styleable.PreloadIconDrawable_indicatorSize, 0));
-        mRingOutset = ta.getDimensionPixelSize(R.styleable.PreloadIconDrawable_ringOutset, 0);
-        ta.recycle();
-        onBoundsChange(getBounds());
-        invalidateSelf();
-    }
-
-    @Override
-    protected void onBoundsChange(Rect bounds) {
-        mIcon.setBounds(bounds);
-        if (mBgDrawable != null) {
-            sTempRect.set(bounds);
-            sTempRect.inset(-mRingOutset, -mRingOutset);
-            mBgDrawable.setBounds(sTempRect);
-        }
-        mIndicatorRectDirty = true;
-    }
-
-    public int getOutset() {
-        return mRingOutset;
-    }
-
-    /**
-     * The size of the indicator is same as the content region of the {@link #mBgDrawable} minus
-     * half the stroke size to accommodate the indicator.
-     */
-    private void initIndicatorRect() {
-        Drawable d = mBgDrawable;
-        Rect bounds = d.getBounds();
-
-        d.getPadding(sTempRect);
-        // Amount by which padding has to be scaled
-        float paddingScaleX = ((float) bounds.width()) / d.getIntrinsicWidth();
-        float paddingScaleY = ((float) bounds.height()) / d.getIntrinsicHeight();
-        mIndicatorRect.set(
-                bounds.left + sTempRect.left * paddingScaleX,
-                bounds.top + sTempRect.top * paddingScaleY,
-                bounds.right - sTempRect.right * paddingScaleX,
-                bounds.bottom - sTempRect.bottom * paddingScaleY);
-
-        float inset = mPaint.getStrokeWidth() / 2;
-        mIndicatorRect.inset(inset, inset);
-        mIndicatorRectDirty = false;
-    }
-
-    @Override
-    public void draw(Canvas canvas) {
-        final Rect r = new Rect(getBounds());
-        if (canvas.getClipBounds(sTempRect) && !Rect.intersects(sTempRect, r)) {
-            // The draw region has been clipped.
-            return;
-        }
-        if (mIndicatorRectDirty) {
-            initIndicatorRect();
-        }
-        final float iconScale;
-
-        if ((mAnimationProgress >= ANIMATION_PROGRESS_STARTED)
-                && (mAnimationProgress < ANIMATION_PROGRESS_COMPLETED)) {
-            mPaint.setAlpha((int) ((1 - mAnimationProgress) * 255));
-            mBgDrawable.setAlpha(mPaint.getAlpha());
-            mBgDrawable.draw(canvas);
-            canvas.drawOval(mIndicatorRect, mPaint);
-
-            iconScale = ICON_SCALE_FACTOR + (1 - ICON_SCALE_FACTOR) * mAnimationProgress;
-        } else if (mAnimationProgress == ANIMATION_PROGRESS_STOPPED) {
-            mPaint.setAlpha(255);
-            iconScale = ICON_SCALE_FACTOR;
-            mBgDrawable.setAlpha(255);
-            mBgDrawable.draw(canvas);
-
-            if (mProgress >= 100) {
-                canvas.drawOval(mIndicatorRect, mPaint);
-            } else if (mProgress > 0) {
-                canvas.drawArc(mIndicatorRect, -90, mProgress * 3.6f, false, mPaint);
-            }
-        } else {
-            iconScale = 1;
-        }
-
-        canvas.save();
-        canvas.scale(iconScale, iconScale, r.exactCenterX(), r.exactCenterY());
-        mIcon.draw(canvas);
-        canvas.restore();
-    }
-
-    @Override
-    public int getOpacity() {
-        return PixelFormat.TRANSLUCENT;
-    }
-
-    @Override
-    public void setAlpha(int alpha) {
-        mIcon.setAlpha(alpha);
-    }
-
-    @Override
-    public void setColorFilter(ColorFilter cf) {
-        mIcon.setColorFilter(cf);
-    }
-
-    @Override
-    protected boolean onLevelChange(int level) {
-        mProgress = level;
-
-        // Stop Animation
-        if (mAnimator != null) {
-            mAnimator.cancel();
-            mAnimator = null;
-        }
-        mAnimationProgress = ANIMATION_PROGRESS_STOPPED;
-        if (level > 0) {
-            // Set the paint color only when the level changes, so that the dominant color
-            // is only calculated when needed.
-            mPaint.setColor(getIndicatorColor());
-        }
-        if (mIcon instanceof FastBitmapDrawable) {
-            ((FastBitmapDrawable) mIcon).setState(level <= 0 ?
-                    FastBitmapDrawable.State.DISABLED : FastBitmapDrawable.State.NORMAL);
-        }
-
-        invalidateSelf();
-        return true;
-    }
-
-    /**
-     * Runs the finish animation if it is has not been run after last level change.
-     */
-    public void maybePerformFinishedAnimation() {
-        if (mAnimationProgress > ANIMATION_PROGRESS_STOPPED) {
-            return;
-        }
-        if (mAnimator != null) {
-            mAnimator.cancel();
-        }
-        setAnimationProgress(ANIMATION_PROGRESS_STARTED);
-        mAnimator = ObjectAnimator.ofFloat(this, "animationProgress",
-                ANIMATION_PROGRESS_STARTED, ANIMATION_PROGRESS_COMPLETED);
-        mAnimator.start();
-    }
-
-    public void setAnimationProgress(float progress) {
-        if (progress != mAnimationProgress) {
-            mAnimationProgress = progress;
-            invalidateSelf();
-        }
-    }
-
-    public float getAnimationProgress() {
-        return mAnimationProgress;
-    }
-
-    public boolean hasNotCompleted() {
-        return mAnimationProgress < ANIMATION_PROGRESS_COMPLETED;
-    }
-
-    @Override
-    public int getIntrinsicHeight() {
-        return mIcon.getIntrinsicHeight();
-    }
-
-    @Override
-    public int getIntrinsicWidth() {
-        return mIcon.getIntrinsicWidth();
-    }
-
-    private int getIndicatorColor() {
-        if (mIndicatorColor != 0) {
-            return mIndicatorColor;
-        }
-        if (!(mIcon instanceof FastBitmapDrawable)) {
-            mIndicatorColor = DEFAULT_COLOR;
-            return mIndicatorColor;
-        }
-        mIndicatorColor = Utilities.findDominantColorByHue(
-                ((FastBitmapDrawable) mIcon).getBitmap(), 20);
-
-        // Make sure that the dominant color has enough saturation to be visible properly.
-        float[] hsv = new float[3];
-        Color.colorToHSV(mIndicatorColor, hsv);
-        if (hsv[1] < MIN_SATUNATION) {
-            mIndicatorColor = DEFAULT_COLOR;
-            return mIndicatorColor;
-        }
-        hsv[2] = Math.max(MIN_LIGHTNESS, hsv[2]);
-        mIndicatorColor = Color.HSVToColor(hsv);
-        return mIndicatorColor;
-    }
-}
diff --git a/src/com/android/launcher3/SessionCommitReceiver.java b/src/com/android/launcher3/SessionCommitReceiver.java
new file mode 100644
index 0000000..61bcc17
--- /dev/null
+++ b/src/com/android/launcher3/SessionCommitReceiver.java
@@ -0,0 +1,156 @@
+/*
+ * 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.annotation.TargetApi;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.SessionInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.os.Process;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.launcher3.compat.LauncherAppsCompat;
+
+import java.util.List;
+
+/**
+ * BroadcastReceiver to handle session commit intent.
+ */
+@TargetApi(Build.VERSION_CODES.O)
+public class SessionCommitReceiver extends BroadcastReceiver {
+
+    private static final String TAG = "SessionCommitReceiver";
+
+    // The content provider for the add to home screen setting. It should be of the format:
+    // <package name>.addtohomescreen
+    private static final String MARKER_PROVIDER_PREFIX = ".addtohomescreen";
+
+    // Preference key for automatically adding icon to homescreen.
+    public static final String ADD_ICON_PREFERENCE_KEY = "pref_add_icon_to_home";
+    public static final String ADD_ICON_PREFERENCE_INITIALIZED_KEY =
+            "pref_add_icon_to_home_initialized";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (!isEnabled(context) || !Utilities.isAtLeastO()) {
+            // User has decided to not add icons on homescreen.
+            return;
+        }
+
+        SessionInfo info = intent.getParcelableExtra(PackageInstaller.EXTRA_SESSION);
+        UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
+
+        if (TextUtils.isEmpty(info.getAppPackageName()) ||
+                info.getInstallReason() != PackageManager.INSTALL_REASON_USER) {
+            return;
+        }
+
+        if (!Process.myUserHandle().equals(user)) {
+            // Managed profile is handled using ManagedProfileHeuristic
+            return;
+        }
+
+        List<LauncherActivityInfo> activities = LauncherAppsCompat.getInstance(context)
+                .getActivityList(info.getAppPackageName(), user);
+        if (activities == null || activities.isEmpty()) {
+            // no activity found
+            return;
+        }
+        InstallShortcutReceiver.queueActivityInfo(activities.get(0), context);
+    }
+
+    public static boolean isEnabled(Context context) {
+        return Utilities.getPrefs(context).getBoolean(ADD_ICON_PREFERENCE_KEY, true);
+    }
+
+    public static void applyDefaultUserPrefs(final Context context) {
+        if (!Utilities.isAtLeastO()) {
+            return;
+        }
+        SharedPreferences prefs = Utilities.getPrefs(context);
+        if (prefs.getAll().isEmpty()) {
+            // This logic assumes that the code is the first thing that is executed (before any
+            // shared preference is written).
+            // TODO: Move this logic to DB upgrade once we have proper support for db downgrade
+            // If it is a fresh start, just apply the default value. We use prefs.isEmpty() to infer
+            // a fresh start as put preferences always contain some values corresponding to current
+            // grid.
+            prefs.edit().putBoolean(ADD_ICON_PREFERENCE_KEY, true).apply();
+        } else if (!prefs.contains(ADD_ICON_PREFERENCE_INITIALIZED_KEY)) {
+            new PrefInitTask(context).executeOnExecutor(Utilities.THREAD_POOL_EXECUTOR);
+        }
+    }
+
+    private static class PrefInitTask extends AsyncTask<Void, Void, Void> {
+        private final Context mContext;
+
+        PrefInitTask(Context context) {
+            mContext = context;
+        }
+
+        @Override
+        protected Void doInBackground(Void... voids) {
+            boolean addIconToHomeScreenEnabled = readValueFromMarketApp();
+            Utilities.getPrefs(mContext).edit()
+                    .putBoolean(ADD_ICON_PREFERENCE_KEY, addIconToHomeScreenEnabled)
+                    .putBoolean(ADD_ICON_PREFERENCE_INITIALIZED_KEY, true)
+                    .apply();
+            return null;
+        }
+
+        public boolean readValueFromMarketApp() {
+            // Get the marget package
+            ResolveInfo ri = mContext.getPackageManager().resolveActivity(
+                    new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_APP_MARKET),
+                    PackageManager.MATCH_DEFAULT_ONLY | PackageManager.MATCH_SYSTEM_ONLY);
+            if (ri == null) {
+                return true;
+            }
+
+            Cursor c = null;
+            try {
+                c = mContext.getContentResolver().query(
+                        Uri.parse("content://" + ri.activityInfo.packageName
+                                + MARKER_PROVIDER_PREFIX),
+                        null, null, null, null);
+                if (c.moveToNext()) {
+                    return c.getInt(c.getColumnIndexOrThrow(Settings.NameValueTable.VALUE)) != 0;
+                }
+            } catch (Exception e) {
+                Log.d(TAG, "Error reading add to homescreen preference", e);
+            } finally {
+                if (c != null) {
+                    c.close();
+                }
+            }
+            return true;
+        }
+    }
+}
diff --git a/src/com/android/launcher3/SettingsActivity.java b/src/com/android/launcher3/SettingsActivity.java
index cedeb39..7ae6b26 100644
--- a/src/com/android/launcher3/SettingsActivity.java
+++ b/src/com/android/launcher3/SettingsActivity.java
@@ -21,10 +21,14 @@
 import android.database.ContentObserver;
 import android.os.Bundle;
 import android.os.Handler;
+import android.preference.ListPreference;
 import android.preference.Preference;
 import android.preference.PreferenceFragment;
 import android.provider.Settings;
 import android.provider.Settings.System;
+import android.support.v4.os.BuildCompat;
+
+import com.android.launcher3.graphics.IconShapeOverride;
 
 /**
  * Settings activity for Launcher. Currently implements the following setting: Allow rotation
@@ -72,6 +76,20 @@
                 mRotationLockObserver.onChange(true);
                 rotationPref.setDefaultValue(Utilities.getAllowRotationDefaultValue(getActivity()));
             }
+
+            if (!BuildCompat.isAtLeastO()) {
+                getPreferenceScreen().removePreference(
+                        findPreference(SessionCommitReceiver.ADD_ICON_PREFERENCE_KEY));
+            }
+
+            Preference iconShapeOverride = findPreference(IconShapeOverride.KEY_PREFERENCE);
+            if (iconShapeOverride != null) {
+                if (IconShapeOverride.isSupported(getActivity())) {
+                    IconShapeOverride.handlePreferenceUi((ListPreference) iconShapeOverride);
+                } else {
+                    getPreferenceScreen().removePreference(iconShapeOverride);
+                }
+            }
         }
 
         @Override
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index 37cbf98..f8742f8 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -18,46 +18,40 @@
 
 import android.app.WallpaperManager;
 import android.content.Context;
-import android.graphics.Paint;
 import android.graphics.Rect;
 import android.view.View;
 import android.view.ViewGroup;
 
+import com.android.launcher3.CellLayout.ContainerType;
+
 public class ShortcutAndWidgetContainer extends ViewGroup {
-    static final String TAG = "CellLayoutChildren";
+    static final String TAG = "ShortcutAndWidgetContainer";
 
     // 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 final int[] mTmpCellXY = new int[2];
 
+    @ContainerType private final int mContainerType;
     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;
-
     private boolean mInvertIfRtl = false;
 
-    public ShortcutAndWidgetContainer(Context context) {
+    public ShortcutAndWidgetContainer(Context context, @ContainerType int containerType) {
         super(context);
         mLauncher = Launcher.getLauncher(context);
         mWallpaperManager = WallpaperManager.getInstance(context);
+        mContainerType = containerType;
     }
 
-    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;
     }
 
@@ -91,9 +85,15 @@
         }
     }
 
-    public void setupLp(CellLayout.LayoutParams lp) {
-        lp.setup(mCellWidth, mCellHeight, mWidthGap, mHeightGap, invertLayoutHorizontally(),
-                mCountX);
+    public void setupLp(View child) {
+        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
+        if (child instanceof LauncherAppWidgetHostView) {
+            DeviceProfile profile = mLauncher.getDeviceProfile();
+            lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX,
+                    profile.appWidgetScale.x, profile.appWidgetScale.y);
+        } else {
+            lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX);
+        }
     }
 
     // Set whether or not to invert the layout horizontally if the layout is in RTL mode.
@@ -101,38 +101,26 @@
         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);
-    }
-
     int getCellContentHeight() {
-        final DeviceProfile grid = mLauncher.getDeviceProfile();
-        return Math.min(getMeasuredHeight(), mIsHotseatLayout ?
-                grid.hotseatCellHeightPx : grid.cellHeightPx);
+        return Math.min(getMeasuredHeight(),
+                mLauncher.getDeviceProfile().getCellHeight(mContainerType));
     }
 
     public void measureChild(View child) {
-        final DeviceProfile grid = mLauncher.getDeviceProfile();
-        final int cellWidth = mCellWidth;
-        final int cellHeight = mCellHeight;
         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
         if (!lp.isFullscreen) {
-            lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, invertLayoutHorizontally(),
-                    mCountX);
+            final DeviceProfile profile = mLauncher.getDeviceProfile();
 
             if (child instanceof LauncherAppWidgetHostView) {
-                // Widgets have their own padding, so skip
+                lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX,
+                        profile.appWidgetScale.x, profile.appWidgetScale.y);
+                // Widgets have their own padding
             } else {
-                // Otherwise, center the icon/folder
+                lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX);
+                // Center the icon/folder
                 int cHeight = getCellContentHeight();
                 int cellPaddingY = (int) Math.max(0, ((lp.height - cHeight) / 2f));
-                int cellPaddingX = (int) (grid.edgeMarginPx / 2f);
+                int cellPaddingX = (int) (profile.edgeMarginPx / 2f);
                 child.setPadding(cellPaddingX, cellPaddingY, cellPaddingX, 0);
             }
         } else {
@@ -157,6 +145,20 @@
             final View child = getChildAt(i);
             if (child.getVisibility() != GONE) {
                 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
+
+                if (child instanceof LauncherAppWidgetHostView) {
+                    LauncherAppWidgetHostView lahv = (LauncherAppWidgetHostView) child;
+
+                    // Scale and center the widget to fit within its cells.
+                    DeviceProfile profile = mLauncher.getDeviceProfile();
+                    float scaleX = profile.appWidgetScale.x;
+                    float scaleY = profile.appWidgetScale.y;
+
+                    lahv.setScaleToFit(Math.min(scaleX, scaleY));
+                    lahv.setTranslationForCentering(-(lp.width - (lp.width * scaleX)) / 2.0f,
+                            -(lp.height - (lp.height * scaleY)) / 2.0f);
+                }
+
                 int childLeft = lp.x;
                 int childTop = lp.y;
                 child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);
@@ -201,28 +203,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..6f0417c 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -17,26 +17,20 @@
 package com.android.launcher3;
 
 import android.annotation.TargetApi;
-import android.content.ComponentName;
-import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.text.TextUtils;
 
 import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.compat.LauncherActivityInfoCompat;
-import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
-import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+import com.android.launcher3.util.ContentWriter;
 
 /**
  * Represents a launchable icon on the workspaces and in folders.
  */
-public class ShortcutInfo extends ItemInfo {
+public class ShortcutInfo extends ItemInfoWithIcon {
 
     public static final int DEFAULT = 0;
 
@@ -68,6 +62,7 @@
      * Indicates if it represents a common type mentioned in {@link CommonAppTypeParser}.
      * Upto 15 different types supported.
      */
+    @Deprecated
     public static final int FLAG_RESTORED_APP_TYPE = 0B0011110000;
 
     /**
@@ -76,28 +71,12 @@
     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;
-
-    /**
      * If isShortcut=true and customIcon=false, this contains a reference to the
      * shortcut icon as an application's resource.
      */
     public Intent.ShortcutIconResource iconResource;
 
     /**
-     * The application icon.
-     */
-    private Bitmap mIcon;
-
-    /**
      * Indicates that the icon is disabled due to safe mode restrictions.
      */
     public static final int FLAG_DISABLED_SAFEMODE = 1 << 0;
@@ -131,7 +110,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,60 +118,25 @@
      */
     CharSequence disabledMessage;
 
-    int status;
+    public int status;
 
     /**
      * The installation progress [0-100] of the package that this shortcut represents.
      */
     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 ShortcutInfo() {
         itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT;
     }
 
-    @Override
-    public Intent getIntent() {
-        return intent;
-    }
-
-    /** Returns {@link #promisedIntent}, or {@link #intent} if promisedIntent is null. */
-    public Intent getPromisedIntent() {
-        return promisedIntent != null ? promisedIntent : intent;
-    }
-
-    ShortcutInfo(Intent intent, CharSequence title, CharSequence contentDescription,
-            Bitmap icon, UserHandleCompat user) {
-        this();
-        this.intent = intent;
-        this.title = Utilities.trim(title);
-        this.contentDescription = contentDescription;
-        mIcon = icon;
-        this.user = user;
-    }
-
     public ShortcutInfo(ShortcutInfo info) {
         super(info);
         title = info.title;
         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,20 +144,9 @@
         super(info);
         title = Utilities.trim(info.title);
         intent = new Intent(info.intent);
-        flags = info.flags;
         isDisabled = info.isDisabled;
     }
 
-    public ShortcutInfo(LauncherActivityInfoCompat info, Context context) {
-        user = info.getUser();
-        title = Utilities.trim(info.getLabel());
-        contentDescription = UserManagerCompat.getInstance(context)
-                .getBadgedLabelForUser(info.getLabel(), info.getUser());
-        intent = AppInfo.makeLaunchIntent(context, info, info.getUser());
-        itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
-        flags = AppInfo.initFlags(info);
-    }
-
     /**
      * Creates a {@link ShortcutInfo} from a {@link ShortcutInfoCompat}.
      */
@@ -221,57 +154,29 @@
     public ShortcutInfo(ShortcutInfoCompat shortcutInfo, Context context) {
         user = shortcutInfo.getUserHandle();
         itemType = LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
-        flags = 0;
         updateFromDeepShortcutInfo(shortcutInfo, context);
     }
 
-    public void setIcon(Bitmap b) {
-        mIcon = b;
-    }
+    @Override
+    public void onAddToDatabase(ContentWriter writer) {
+        super.onAddToDatabase(writer);
+        writer.put(LauncherSettings.BaseLauncherColumns.TITLE, title)
+                .put(LauncherSettings.BaseLauncherColumns.INTENT, getIntent())
+                .put(LauncherSettings.Favorites.RESTORED, status);
 
-    public Bitmap getIcon(IconCache iconCache) {
-        if (mIcon == null) {
-            updateIcon(iconCache);
+        if (!usingLowResIcon) {
+            writer.putIcon(iconBitmap, user);
         }
-        return mIcon;
-    }
-
-    public void updateIcon(IconCache iconCache, boolean useLowRes) {
-        if (itemType == Favorites.ITEM_TYPE_APPLICATION) {
-            iconCache.getTitleAndIcon(this, promisedIntent != null ? promisedIntent : intent, user,
-                    useLowRes);
+        if (iconResource != null) {
+            writer.put(LauncherSettings.BaseLauncherColumns.ICON_PACKAGE, iconResource.packageName)
+                    .put(LauncherSettings.BaseLauncherColumns.ICON_RESOURCE,
+                            iconResource.resourceName);
         }
     }
 
-    public void updateIcon(IconCache iconCache) {
-        updateIcon(iconCache, shouldUseLowResIcon());
-    }
-
     @Override
-    void onAddToDatabase(Context context, ContentValues values) {
-        super.onAddToDatabase(context, values);
-
-        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 (iconResource != null) {
-            values.put(LauncherSettings.BaseLauncherColumns.ICON_PACKAGE,
-                    iconResource.packageName);
-            values.put(LauncherSettings.BaseLauncherColumns.ICON_RESOURCE,
-                    iconResource.resourceName);
-        }
-    }
-
-    public ComponentName getTargetComponent() {
-        return promisedIntent != null ? promisedIntent.getComponent() : intent.getComponent();
+    public Intent getIntent() {
+        return intent;
     }
 
     public boolean hasStatusFlag(int flag) {
@@ -292,13 +197,9 @@
         status |= FLAG_INSTALL_SESSION_ACTIVE;
     }
 
-    public boolean shouldUseLowResIcon() {
-        return usingLowResIcon && container >= 0 && rank >= FolderIcon.NUM_ITEMS_IN_PREVIEW;
-    }
-
     public void updateFromDeepShortcutInfo(ShortcutInfoCompat shortcutInfo, Context context) {
         // {@link ShortcutInfoCompat#getActivity} can change during an update. Recreate the intent
-        intent = shortcutInfo.makeIntent(context);
+        intent = shortcutInfo.makeIntent();
         title = shortcutInfo.getShortLabel();
 
         CharSequence label = shortcutInfo.getLongLabel();
@@ -313,40 +214,12 @@
             isDisabled |= FLAG_DISABLED_BY_PUBLISHER;
         }
         disabledMessage = shortcutInfo.getDisabledMessage();
-
-        // TODO: Use cache for this
-        LauncherAppState launcherAppState = LauncherAppState.getInstance();
-        Drawable unbadgedDrawable = launcherAppState.getShortcutManager()
-                .getShortcutIconDrawable(shortcutInfo,
-                        launcherAppState.getInvariantDeviceProfile().fillResIconDpi);
-
-        IconCache cache = launcherAppState.getIconCache();
-        Bitmap unbadgedBitmap = unbadgedDrawable == null
-                ? cache.getDefaultIcon(UserHandleCompat.myUserHandle())
-                : Utilities.createScaledBitmapWithoutShadow(unbadgedDrawable, context);
-        setIcon(getBadgedIcon(unbadgedBitmap, shortcutInfo, cache, context));
-    }
-
-    protected Bitmap getBadgedIcon(Bitmap unbadgedBitmap, ShortcutInfoCompat shortcutInfo,
-            IconCache cache, Context context) {
-        unbadgedBitmap = Utilities.addShadowToIcon(unbadgedBitmap);
-        // Get the app info for the source activity.
-        AppInfo appInfo = new AppInfo();
-        appInfo.user = user;
-        appInfo.componentName = shortcutInfo.getActivity();
-        try {
-            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 Utilities.badgeWithBitmap(unbadgedBitmap, appInfo.iconBitmap, context);
     }
 
     /** Returns the ShortcutInfo id associated with the deep shortcut. */
     public String getDeepShortcutId() {
         return itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT ?
-                getPromisedIntent().getStringExtra(ShortcutInfoCompat.EXTRA_SHORTCUT_ID) : null;
+                getIntent().getStringExtra(ShortcutInfoCompat.EXTRA_SHORTCUT_ID) : null;
     }
 
     @Override
diff --git a/src/com/android/launcher3/UninstallDropTarget.java b/src/com/android/launcher3/UninstallDropTarget.java
index 9153943..0fac29f 100644
--- a/src/com/android/launcher3/UninstallDropTarget.java
+++ b/src/com/android/launcher3/UninstallDropTarget.java
@@ -1,18 +1,19 @@
 package com.android.launcher3;
 
-import android.annotation.TargetApi;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.PackageManager;
 import android.net.Uri;
-import android.os.Build;
 import android.os.Bundle;
+import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.AttributeSet;
-import android.util.Pair;
 import android.widget.Toast;
 
-import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.compat.LauncherAppsCompat;
 
 public class UninstallDropTarget extends ButtonDropTarget {
 
@@ -38,34 +39,42 @@
         return supportsDrop(getContext(), info);
     }
 
-    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
     public static boolean supportsDrop(Context context, Object info) {
-        if (Utilities.ATLEAST_JB_MR2) {
-            UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
-            Bundle restrictions = userManager.getUserRestrictions();
-            if (restrictions.getBoolean(UserManager.DISALLOW_APPS_CONTROL, false)
-                    || restrictions.getBoolean(UserManager.DISALLOW_UNINSTALL_APPS, false)) {
-                return false;
-            }
+        UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+        Bundle restrictions = userManager.getUserRestrictions();
+        if (restrictions.getBoolean(UserManager.DISALLOW_APPS_CONTROL, false)
+                || restrictions.getBoolean(UserManager.DISALLOW_UNINSTALL_APPS, false)) {
+            return false;
         }
 
-        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;
+        UserHandle 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) {
+            LauncherActivityInfo info = LauncherAppsCompat.getInstance(context)
+                    .resolveActivity(intent, user);
+            if (info != null
+                    && (info.getApplicationInfo().flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
+                return info.getComponentName();
             }
         }
         return null;
@@ -81,7 +90,7 @@
     }
 
     @Override
-    void completeDrop(final DragObject d) {
+    public void completeDrop(final DragObject d) {
         DropTargetResultCallback callback = d.dragSource instanceof DropTargetResultCallback
                 ? (DropTargetResultCallback) d.dragSource : null;
         startUninstallActivity(mLauncher, d.dragInfo, callback);
@@ -93,11 +102,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();
@@ -107,13 +115,12 @@
                     Uri.fromParts("package", cn.getPackageName(), cn.getClassName()))
                     .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                             | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-            info.user.addToIntent(intent, Intent.EXTRA_USER);
+            intent.putExtra(Intent.EXTRA_USER, info.user);
             launcher.startActivity(intent);
             isUninstallable = true;
         }
         if (callback != null) {
-            sendUninstallResult(
-                    launcher, isUninstallable, componentInfo.first, info.user, callback);
+            sendUninstallResult(launcher, isUninstallable, cn, info.user, callback);
         }
         return isUninstallable;
     }
@@ -127,15 +134,16 @@
      */
     protected static void sendUninstallResult(
             final Launcher launcher, boolean activityStarted,
-            final ComponentName cn, final UserHandleCompat user,
+            final ComponentName cn, final UserHandle user,
             final DropTargetResultCallback callback) {
         if (activityStarted)  {
             final Runnable checkIfUninstallWasSuccess = new Runnable() {
                 @Override
                 public void run() {
-                    String packageName = cn.getPackageName();
-                    boolean uninstallSuccessful = !AllAppsList.packageHasActivities(
-                            launcher, packageName, user);
+                    // We use MATCH_UNINSTALLED_PACKAGES as the app can be on SD card as well.
+                    boolean uninstallSuccessful = LauncherAppsCompat.getInstance(launcher)
+                            .getApplicationInfo(cn.getPackageName(),
+                                    PackageManager.MATCH_UNINSTALLED_PACKAGES, user) == null;
                     callback.onDragObjectRemoved(uninstallSuccessful);
                 }
             };
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index b0e096a..207a7d4 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -16,10 +16,7 @@
 
 package com.android.launcher3;
 
-import android.annotation.TargetApi;
-import android.app.Activity;
 import android.app.WallpaperManager;
-import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -30,23 +27,17 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 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.support.v4.os.BuildCompat;
 import android.text.Spannable;
 import android.text.SpannableString;
 import android.text.TextUtils;
@@ -56,24 +47,19 @@
 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.InvocationTargetException;
 import java.lang.reflect.Method;
-import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashSet;
 import java.util.Locale;
 import java.util.Set;
 import java.util.concurrent.Executor;
@@ -90,29 +76,24 @@
 
     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() {
+        return BuildCompat.isAtLeastO();
     }
 
-    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;
@@ -120,17 +101,11 @@
     public static final boolean ATLEAST_LOLLIPOP_MR1 =
             Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1;
 
-    public static final boolean ATLEAST_LOLLIPOP =
-            Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
-
-    public static final boolean ATLEAST_KITKAT =
-            Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
-
-    public static final boolean ATLEAST_JB_MR1 =
-            Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1;
-
-    public static final boolean ATLEAST_JB_MR2 =
-            Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2;
+    /**
+     * Indicates if the device has a debug build. Should only be used to store additional info or
+     * add extra logging and not for changing the app behavior.
+     */
+    public static final boolean IS_DEBUG_DEVICE = Build.TYPE.toLowerCase().contains("debug");
 
     // An intent extra to indicate the horizontal scroll of the wallpaper.
     public static final String EXTRA_WALLPAPER_OFFSET = "com.android.launcher3.WALLPAPER_OFFSET";
@@ -159,7 +134,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 +145,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 +160,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,31 +219,7 @@
                 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) {
+    public static int[] getCenterDeltaInScreenSpace(View v0, View v1) {
         v0.getLocationInWindow(sLoc0);
         v1.getLocationInWindow(sLoc1);
 
@@ -484,15 +227,7 @@
         sLoc0[1] += (v0.getMeasuredHeight() * v0.getScaleY()) / 2;
         sLoc1[0] += (v1.getMeasuredWidth() * v1.getScaleX()) / 2;
         sLoc1[1] += (v1.getMeasuredHeight() * v1.getScaleY()) / 2;
-
-        if (delta == null) {
-            delta = new int[2];
-        }
-
-        delta[0] = sLoc1[0] - sLoc0[0];
-        delta[1] = sLoc1[1] - sLoc0[1];
-
-        return delta;
+        return new int[] {sLoc1[0] - sLoc0[0], sLoc1[1] - sLoc0[1]};
     }
 
     public static void scaleRectAboutCenter(Rect r, float scale) {
@@ -510,18 +245,18 @@
         }
     }
 
-    public static void startActivityForResultSafely(
-            Activity activity, Intent intent, int requestCode) {
-        try {
-            activity.startActivityForResult(intent, requestCode);
-        } catch (ActivityNotFoundException e) {
-            Toast.makeText(activity, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
-        } catch (SecurityException e) {
-            Toast.makeText(activity, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
-            Log.e(TAG, "Launcher does not have the permission to launch " + intent +
-                    ". Make sure to create a MAIN intent-filter for the corresponding activity " +
-                    "or use the exported attribute for this activity.", e);
+    public static float shrinkRect(Rect r, float scaleX, float scaleY) {
+        float scale = Math.min(Math.min(scaleX, scaleY), 1.0f);
+        if (scale < 1.0f) {
+            int deltaX = (int) (r.width() * (scaleX - scale) * 0.5f);
+            r.left += deltaX;
+            r.right -= deltaX;
+
+            int deltaY = (int) (r.height() * (scaleY - scale) * 0.5f);
+            r.top += deltaY;
+            r.bottom -= deltaY;
         }
+        return scale;
     }
 
     static boolean isSystemApp(Context context, Intent intent) {
@@ -554,7 +289,7 @@
      * @param bitmap The bitmap to scan
      * @param samples The approximate max number of samples to use.
      */
-    static int findDominantColorByHue(Bitmap bitmap, int samples) {
+    public static int findDominantColorByHue(Bitmap bitmap, int samples) {
         final int height = bitmap.getHeight();
         final int width = bitmap.getWidth();
         int sampleStride = (int) Math.sqrt((height * width) / samples);
@@ -712,10 +447,8 @@
         System.out.println(b.toString());
     }
 
-    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
     public static boolean isRtl(Resources res) {
-        return ATLEAST_JB_MR1 &&
-                (res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
+        return res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
     }
 
     /**
@@ -735,13 +468,8 @@
                 && TextUtils.isEmpty(launchIntent.getDataString())) {
             // An app target can either have no extra or have ItemInfo.EXTRA_PROFILE.
             Bundle extras = launchIntent.getExtras();
-            if (extras == null) {
-                return true;
-            } else {
-                Set<String> keys = extras.keySet();
-                return keys.size() == 1 && keys.contains(ItemInfo.EXTRA_PROFILE);
-            }
-        };
+            return extras == null || extras.keySet().isEmpty();
+        }
         return false;
     }
 
@@ -802,16 +530,11 @@
      * @param msg original message
      * @param ttsMsg message to be spoken
      */
-    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
     public static CharSequence wrapForTts(CharSequence msg, String ttsMsg) {
-        if (Utilities.ATLEAST_LOLLIPOP) {
-            SpannableString spanned = new SpannableString(msg);
-            spanned.setSpan(new TtsSpan.TextBuilder(ttsMsg).build(),
-                    0, spanned.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
-            return spanned;
-        } else {
-            return msg;
-        }
+        SpannableString spanned = new SpannableString(msg);
+        spanned.setSpan(new TtsSpan.TextBuilder(ttsMsg).build(),
+                0, spanned.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
+        return spanned;
     }
 
     /**
@@ -826,14 +549,13 @@
                 LauncherFiles.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE);
     }
 
-    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
     public static boolean isPowerSaverOn(Context context) {
         PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
-        return ATLEAST_LOLLIPOP && powerManager.isPowerSaveMode();
+        return 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,35 +602,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);
-        ta.recycle();
-        return colorAccent;
-    }
-
     public static void sendCustomAccessibilityEvent(View target, int type, String text) {
         AccessibilityManager accessibilityManager = (AccessibilityManager)
                 target.getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
@@ -919,4 +612,38 @@
             accessibilityManager.sendAccessibilityEvent(event);
         }
     }
+
+    public static boolean isBinderSizeError(Exception e) {
+        return e.getCause() instanceof TransactionTooLargeException
+                || e.getCause() instanceof DeadObjectException;
+    }
+
+    public static <T> T getOverrideObject(Class<T> clazz, Context context, int resId) {
+        String className = context.getString(resId);
+        if (!TextUtils.isEmpty(className)) {
+            try {
+                Class<?> cls = Class.forName(className);
+                return (T) cls.getDeclaredConstructor(Context.class).newInstance(context);
+            } catch (ClassNotFoundException | InstantiationException | IllegalAccessException
+                    | ClassCastException | NoSuchMethodException | InvocationTargetException e) {
+                Log.e(TAG, "Bad overriden class", e);
+            }
+        }
+
+        try {
+            return clazz.newInstance();
+        } catch (InstantiationException|IllegalAccessException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Returns a HashSet with a single element. We use this instead of Collections.singleton()
+     * because HashSet ensures all operations, such as remove, are supported.
+     */
+    public static <T> HashSet<T> singletonHashSet(T elem) {
+        HashSet<T> hashSet = new HashSet<>(1);
+        hashSet.add(elem);
+        return hashSet;
+    }
 }
diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java
index 45e65b5..c525cd4 100644
--- a/src/com/android/launcher3/WidgetPreviewLoader.java
+++ b/src/com/android/launcher3/WidgetPreviewLoader.java
@@ -3,7 +3,6 @@
 import android.content.ComponentName;
 import android.content.ContentValues;
 import android.content.Context;
-import android.content.pm.ActivityInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
@@ -15,24 +14,31 @@
 import android.graphics.Bitmap.Config;
 import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
-import android.graphics.ColorMatrix;
-import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
 import android.graphics.RectF;
-import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.AsyncTask;
+import android.os.Build;
+import android.os.CancellationSignal;
 import android.os.Handler;
+import android.os.UserHandle;
+import android.support.annotation.Nullable;
+import android.support.v4.graphics.ColorUtils;
 import android.util.Log;
 import android.util.LongSparseArray;
 
 import com.android.launcher3.compat.AppWidgetManagerCompat;
-import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.compat.ShortcutConfigActivityInfo;
 import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.graphics.LauncherIcons;
+import com.android.launcher3.graphics.ShadowGenerator;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.SQLiteCacheHelper;
 import com.android.launcher3.util.Thunk;
@@ -52,8 +58,6 @@
     private static final String TAG = "WidgetPreviewLoader";
     private static final boolean DEBUG = false;
 
-    private static final float WIDGET_PREVIEW_ICON_PADDING_PERCENTAGE = 0.25f;
-
     private final HashMap<String, long[]> mPackageVersions = new HashMap<>();
 
     /**
@@ -70,7 +74,6 @@
     private final UserManagerCompat mUserManager;
     private final AppWidgetManagerCompat mWidgetManager;
     private final CacheDb mDb;
-    private final int mProfileBadgeMargin;
 
     private final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
     @Thunk final Handler mWorkerHandler;
@@ -82,8 +85,6 @@
         mUserManager = UserManagerCompat.getInstance(context);
         mDb = new CacheDb(context);
         mWorkerHandler = new Handler(LauncherModel.getWorkerLooper());
-        mProfileBadgeMargin = context.getResources()
-                .getDimensionPixelSize(R.dimen.profile_badge_margin);
     }
 
     /**
@@ -92,14 +93,18 @@
      *
      * @return a request id which can be used to cancel the request.
      */
-    public PreviewLoadRequest getPreview(WidgetItem item, int previewWidth,
-            int previewHeight, WidgetCell caller) {
+    public CancellationSignal getPreview(WidgetItem item, int previewWidth,
+            int previewHeight, WidgetCell caller, boolean animate) {
         String size = previewWidth + "x" + previewHeight;
         WidgetCacheKey key = new WidgetCacheKey(item.componentName, item.user, size);
 
-        PreviewLoadTask task = new PreviewLoadTask(key, item, previewWidth, previewHeight, caller);
+        PreviewLoadTask task = new PreviewLoadTask(key, item, previewWidth, previewHeight, caller,
+                animate);
         task.executeOnExecutor(Utilities.THREAD_POOL_EXECUTOR);
-        return new PreviewLoadRequest(task);
+
+        CancellationSignal signal = new CancellationSignal();
+        signal.setOnCancelListener(task);
+        return signal;
     }
 
     /**
@@ -107,7 +112,7 @@
      * sizes (landscape vs portrait).
      */
     private static class CacheDb extends SQLiteCacheHelper {
-        private static final int DB_VERSION = 4;
+        private static final int DB_VERSION = 6;
 
         private static final String TABLE_NAME = "shortcut_and_widget_previews";
         private static final String COLUMN_COMPONENT = "componentName";
@@ -149,11 +154,11 @@
         mDb.insertOrReplace(values);
     }
 
-    public void removePackage(String packageName, UserHandleCompat user) {
+    public void removePackage(String packageName, UserHandle user) {
         removePackage(packageName, user, mUserManager.getSerialNumberForUser(user));
     }
 
-    private void removePackage(String packageName, UserHandleCompat user, long userSerial) {
+    private void removePackage(String packageName, UserHandle user, long userSerial) {
         synchronized(mPackageVersions) {
             mPackageVersions.remove(packageName);
         }
@@ -168,8 +173,12 @@
      *   1. Any preview generated for an old package version is removed
      *   2. Any preview for an absent package is removed
      * This ensures that we remove entries for packages which changed while the launcher was dead.
+     *
+     * @param packageUser if provided, specifies that list only contains previews for the
+     *                    given package/user, otherwise the list contains all previews
      */
-    public void removeObsoletePreviews(ArrayList<? extends ComponentKey> list) {
+    public void removeObsoletePreviews(ArrayList<? extends ComponentKey> list,
+            @Nullable PackageUserKey packageUser) {
         Preconditions.assertWorkerThread();
 
         LongSparseArray<HashSet<String>> validPackages = new LongSparseArray<>();
@@ -185,6 +194,8 @@
         }
 
         LongSparseArray<HashSet<String>> packagesToDelete = new LongSparseArray<>();
+        long passedUserId = packageUser == null ? 0
+                : mUserManager.getSerialNumberForUser(packageUser.mUser);
         Cursor c = null;
         try {
             c = mDb.query(
@@ -197,6 +208,12 @@
                 long lastUpdated = c.getLong(2);
                 long version = c.getLong(3);
 
+                if (packageUser != null && (!pkg.equals(packageUser.mPackageName)
+                        || userId != passedUserId)) {
+                    // This preview is associated with a different package/user, no need to remove.
+                    continue;
+                }
+
                 HashSet<String> packages = validPackages.get(userId);
                 if (packages != null && packages.contains(pkg)) {
                     long[] versions = getPackageVersion(pkg);
@@ -217,7 +234,7 @@
 
             for (int i = 0; i < packagesToDelete.size(); i++) {
                 long userId = packagesToDelete.keyAt(i);
-                UserHandleCompat user = mUserManager.getUserForSerialNumber(userId);
+                UserHandle user = mUserManager.getUserForSerialNumber(userId);
                 for (String pkg : packagesToDelete.valueAt(i)) {
                     removePackage(pkg, user, userId);
                 }
@@ -242,7 +259,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
                     });
@@ -272,7 +289,7 @@
         return null;
     }
 
-    private Bitmap generatePreview(Launcher launcher, WidgetItem item, Bitmap recycle,
+    private Bitmap generatePreview(BaseActivity launcher, WidgetItem item, Bitmap recycle,
             int previewWidth, int previewHeight) {
         if (item.widgetInfo != null) {
             return generateWidgetPreview(launcher, item.widgetInfo,
@@ -294,14 +311,21 @@
      * @param preScaledWidthOut           return the width of the returned bitmap
      * @return
      */
-    public Bitmap generateWidgetPreview(Launcher launcher, LauncherAppWidgetProviderInfo info,
+    public Bitmap generateWidgetPreview(BaseActivity launcher, LauncherAppWidgetProviderInfo info,
             int maxPreviewWidth, Bitmap preview, int[] preScaledWidthOut) {
         // Load the preview image if possible
         if (maxPreviewWidth < 0) maxPreviewWidth = Integer.MAX_VALUE;
 
         Drawable drawable = null;
         if (info.previewImage != 0) {
-            drawable = mWidgetManager.loadPreview(info);
+            try {
+                drawable = info.loadPreviewImage(mContext, 0);
+            } catch (OutOfMemoryError e) {
+                Log.w(TAG, "Error loading widget preview for: " + info.provider, e);
+                // During OutOfMemoryError, the previous heap stack is not affected. Catching
+                // an OOM error here should be safe & not affect other parts of launcher.
+                drawable = null;
+            }
             if (drawable != null) {
                 drawable = mutateOnMainThread(drawable);
             } else {
@@ -317,17 +341,14 @@
         int previewWidth;
         int previewHeight;
 
-        Bitmap tileBitmap = null;
-
         if (widgetPreviewExists) {
             previewWidth = drawable.getIntrinsicWidth();
             previewHeight = drawable.getIntrinsicHeight();
         } else {
-            // Generate a preview image if we couldn't load one
-            tileBitmap = ((BitmapDrawable) mContext.getResources().getDrawable(
-                    R.drawable.widget_tile)).getBitmap();
-            previewWidth = tileBitmap.getWidth() * spanX;
-            previewHeight = tileBitmap.getHeight() * spanY;
+            DeviceProfile dp = launcher.getDeviceProfile();
+            int tileSize = Math.min(dp.cellWidthPx, dp.cellHeightPx);
+            previewWidth = tileSize * spanX;
+            previewHeight = tileSize * spanY;
         }
 
         // Scale to fit width only - let the widget preview be clipped in the
@@ -337,7 +358,7 @@
             preScaledWidthOut[0] = previewWidth;
         }
         if (previewWidth > maxPreviewWidth) {
-            scale = (maxPreviewWidth - 2 * mProfileBadgeMargin) / (float) (previewWidth);
+            scale = maxPreviewWidth / (float) (previewWidth);
         }
         if (scale != 1f) {
             previewWidth = (int) (scale * previewWidth);
@@ -350,6 +371,12 @@
             preview = Bitmap.createBitmap(previewWidth, previewHeight, Config.ARGB_8888);
             c.setBitmap(preview);
         } else {
+            // We use the preview bitmap height to determine where the badge will be drawn in the
+            // UI. If its larger than what we need, resize the preview bitmap so that there are
+            // no transparent pixels between the preview and the badge.
+            if (preview.getHeight() > previewHeight) {
+                preview.reconfigure(preview.getWidth(), previewHeight, preview.getConfig());
+            }
             // Reusing bitmap. Clear it.
             c.setBitmap(preview);
             c.drawColor(0, PorterDuff.Mode.CLEAR);
@@ -361,93 +388,106 @@
             drawable.setBounds(x, 0, x + previewWidth, previewHeight);
             drawable.draw(c);
         } else {
-            final Paint p = new Paint();
-            p.setFilterBitmap(true);
-            int appIconSize = launcher.getDeviceProfile().iconSizePx;
+            final Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
+            RectF boxRect = drawBoxWithShadow(c, p, previewWidth, previewHeight);
 
-            // draw the spanX x spanY tiles
-            final Rect src = new Rect(0, 0, tileBitmap.getWidth(), tileBitmap.getHeight());
+            // Draw horizontal and vertical lines to represent individual columns.
+            p.setStyle(Paint.Style.STROKE);
+            p.setStrokeWidth(mContext.getResources()
+                    .getDimension(R.dimen.widget_preview_cell_divider_width));
+            p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
 
-            float tileW = scale * tileBitmap.getWidth();
-            float tileH = scale * tileBitmap.getHeight();
-            final RectF dst = new RectF(0, 0, tileW, tileH);
-
-            float tx = x;
-            for (int i = 0; i < spanX; i++, tx += tileW) {
-                float ty = 0;
-                for (int j = 0; j < spanY; j++, ty += tileH) {
-                    dst.offsetTo(tx, ty);
-                    c.drawBitmap(tileBitmap, src, dst, p);
-                }
+            float t = boxRect.left;
+            float tileSize = boxRect.width() / spanX;
+            for (int i = 1; i < spanX; i++) {
+                t += tileSize;
+                c.drawLine(t, 0, t, previewHeight, p);
             }
 
-            // Draw the icon in the top left corner
-            // TODO: use top right for RTL
-            int minOffset = (int) (appIconSize * WIDGET_PREVIEW_ICON_PADDING_PERCENTAGE);
-            int smallestSide = Math.min(previewWidth, previewHeight);
-            float iconScale = Math.min((float) smallestSide / (appIconSize + 2 * minOffset), scale);
+            t = boxRect.top;
+            tileSize = boxRect.height() / spanY;
+            for (int i = 1; i < spanY; i++) {
+                t += tileSize;
+                c.drawLine(0, t, previewWidth, t, p);
+            }
 
+            // Draw icon in the center.
             try {
-                Drawable icon = mWidgetManager.loadIcon(info, mIconCache);
+                Drawable icon = info.getIcon(launcher, mIconCache);
                 if (icon != null) {
+                    int appIconSize = launcher.getDeviceProfile().iconSizePx;
+                    int iconSize = (int) Math.min(appIconSize * scale,
+                            Math.min(boxRect.width(), boxRect.height()));
+
                     icon = mutateOnMainThread(icon);
-                    int hoffset = (int) ((tileW - appIconSize * iconScale) / 2) + x;
-                    int yoffset = (int) ((tileH - appIconSize * iconScale) / 2);
-                    icon.setBounds(hoffset, yoffset,
-                            hoffset + (int) (appIconSize * iconScale),
-                            yoffset + (int) (appIconSize * iconScale));
+                    int hoffset = (previewWidth - iconSize) / 2;
+                    int yoffset = (previewHeight - iconSize) / 2;
+                    icon.setBounds(hoffset, yoffset, hoffset + iconSize, yoffset + iconSize);
                     icon.draw(c);
                 }
-            } catch (Resources.NotFoundException e) {
-            }
+            } catch (Resources.NotFoundException e) { }
             c.setBitmap(null);
         }
-        int imageWidth = Math.min(preview.getWidth(), previewWidth + mProfileBadgeMargin);
-        int imageHeight = Math.min(preview.getHeight(), previewHeight + mProfileBadgeMargin);
-        return mWidgetManager.getBadgeBitmap(info, preview, imageWidth, imageHeight);
+        return preview;
     }
 
-    private Bitmap generateShortcutPreview(
-            Launcher launcher, ActivityInfo info, int maxWidth, int maxHeight, Bitmap preview) {
+    private RectF drawBoxWithShadow(Canvas c, Paint p, int width, int height) {
+        Resources res = mContext.getResources();
+        float shadowBlur = res.getDimension(R.dimen.widget_preview_shadow_blur);
+        float keyShadowDistance = res.getDimension(R.dimen.widget_preview_key_shadow_distance);
+        float corner = res.getDimension(R.dimen.widget_preview_corner_radius);
+
+        RectF bounds = new RectF(shadowBlur, shadowBlur,
+                width - shadowBlur, height - shadowBlur - keyShadowDistance);
+        p.setColor(Color.WHITE);
+
+        // Key shadow
+        p.setShadowLayer(shadowBlur, 0, keyShadowDistance,
+                ShadowGenerator.KEY_SHADOW_ALPHA << 24);
+        c.drawRoundRect(bounds, corner, corner, p);
+
+        // Ambient shadow
+        p.setShadowLayer(shadowBlur, 0, 0,
+                ColorUtils.setAlphaComponent(Color.BLACK, ShadowGenerator.AMBIENT_SHADOW_ALPHA));
+        c.drawRoundRect(bounds, corner, corner, p);
+
+        p.clearShadowLayer();
+        return bounds;
+    }
+
+    private Bitmap generateShortcutPreview(BaseActivity launcher, ShortcutConfigActivityInfo info,
+            int maxWidth, int maxHeight, Bitmap preview) {
+        int iconSize = launcher.getDeviceProfile().iconSizePx;
+        int padding = launcher.getResources()
+                .getDimensionPixelSize(R.dimen.widget_preview_shortcut_padding);
+
+        int size = iconSize + 2 * padding;
+        if (maxHeight < size || maxWidth < size) {
+            throw new RuntimeException("Max size is too small for preview");
+        }
         final Canvas c = new Canvas();
-        if (preview == null) {
-            preview = Bitmap.createBitmap(maxWidth, maxHeight, Config.ARGB_8888);
+        if (preview == null || preview.getWidth() < size || preview.getHeight() < size) {
+            preview = Bitmap.createBitmap(size, size, Config.ARGB_8888);
             c.setBitmap(preview);
-        } else if (preview.getWidth() != maxWidth || preview.getHeight() != maxHeight) {
-            throw new RuntimeException("Improperly sized bitmap passed as argument");
         } else {
+            if (preview.getWidth() > size || preview.getHeight() > size) {
+                preview.reconfigure(size, size, preview.getConfig());
+            }
+
             // Reusing bitmap. Clear it.
             c.setBitmap(preview);
             c.drawColor(0, PorterDuff.Mode.CLEAR);
         }
+        Paint p = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+        RectF boxRect = drawBoxWithShadow(c, p, size, size);
 
-        Drawable icon = mutateOnMainThread(mIconCache.getFullResIcon(info));
-        icon.setFilterBitmap(true);
+        Bitmap icon = LauncherIcons.createScaledBitmapWithoutShadow(
+                mutateOnMainThread(info.getFullResIcon(mIconCache)), mContext, Build.VERSION_CODES.O);
+        Rect src = new Rect(0, 0, icon.getWidth(), icon.getHeight());
 
-        // Draw a desaturated/scaled version of the icon in the background as a watermark
-        ColorMatrix colorMatrix = new ColorMatrix();
-        colorMatrix.setSaturation(0);
-        icon.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
-        icon.setAlpha((int) (255 * 0.06f));
-
-        Resources res = mContext.getResources();
-        int paddingTop = res.getDimensionPixelOffset(R.dimen.shortcut_preview_padding_top);
-        int paddingLeft = res.getDimensionPixelOffset(R.dimen.shortcut_preview_padding_left);
-        int paddingRight = res.getDimensionPixelOffset(R.dimen.shortcut_preview_padding_right);
-        int scaledIconWidth = (maxWidth - paddingLeft - paddingRight);
-        icon.setBounds(paddingLeft, paddingTop,
-                paddingLeft + scaledIconWidth, paddingTop + scaledIconWidth);
-        icon.draw(c);
-
-        // Draw the final icon at top left corner.
-        // TODO: use top right for RTL
-        int appIconSize = launcher.getDeviceProfile().iconSizePx;
-
-        icon.setAlpha(255);
-        icon.setColorFilter(null);
-        icon.setBounds(0, 0, appIconSize, appIconSize);
-        icon.draw(c);
-
+        boxRect.set(0, 0, iconSize, iconSize);
+        boxRect.offset(padding, padding);
+        c.drawBitmap(icon, src, boxRect, p);
         c.setBitmap(null);
         return preview;
     }
@@ -490,57 +530,27 @@
         }
     }
 
-    /**
-     * A request Id which can be used by the client to cancel any request.
-     */
-    public class PreviewLoadRequest {
-
-        @Thunk final PreviewLoadTask mTask;
-
-        public PreviewLoadRequest(PreviewLoadTask task) {
-            mTask = task;
-        }
-
-        public void cleanup() {
-            if (mTask != null) {
-                mTask.cancel(true);
-            }
-
-            // This only handles the case where the PreviewLoadTask is cancelled after the task has
-            // successfully completed (including having written to disk when necessary).  In the
-            // other cases where it is cancelled while the task is running, it will be cleaned up
-            // in the tasks's onCancelled() call, and if cancelled while the task is writing to
-            // disk, it will be cancelled in the task's onPostExecute() call.
-            if (mTask.mBitmapToRecycle != null) {
-                mWorkerHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        synchronized (mUnusedBitmaps) {
-                            mUnusedBitmaps.add(mTask.mBitmapToRecycle);
-                        }
-                        mTask.mBitmapToRecycle = null;
-                    }
-                });
-            }
-        }
-    }
-
-    public class PreviewLoadTask extends AsyncTask<Void, Void, Bitmap> {
+    public class PreviewLoadTask extends AsyncTask<Void, Void, Bitmap>
+            implements CancellationSignal.OnCancelListener {
         @Thunk final WidgetCacheKey mKey;
         private final WidgetItem mInfo;
         private final int mPreviewHeight;
         private final int mPreviewWidth;
         private final WidgetCell mCaller;
+        private final boolean mAnimatePreviewIn;
+        private final BaseActivity mActivity;
         @Thunk long[] mVersions;
         @Thunk Bitmap mBitmapToRecycle;
 
         PreviewLoadTask(WidgetCacheKey key, WidgetItem info, int previewWidth,
-                int previewHeight, WidgetCell caller) {
+                int previewHeight, WidgetCell caller, boolean animate) {
             mKey = key;
             mInfo = info;
             mPreviewHeight = previewHeight;
             mPreviewWidth = previewWidth;
             mCaller = caller;
+            mAnimatePreviewIn = animate;
+            mActivity = BaseActivity.fromContext(mCaller.getContext());
             if (DEBUG) {
                 Log.d(TAG, String.format("%s, %s, %d, %d",
                         mKey, mInfo, mPreviewHeight, mPreviewWidth));
@@ -582,19 +592,20 @@
                 // Fetch the version info before we generate the preview, so that, in-case the
                 // app was updated while we are generating the preview, we use the old version info,
                 // which would gets re-written next time.
-                mVersions = getPackageVersion(mKey.componentName.getPackageName());
-
-                Launcher launcher = Launcher.getLauncher(mCaller.getContext());
+                boolean persistable = mInfo.activityInfo == null
+                        || mInfo.activityInfo.isPersistable();
+                mVersions = persistable ? getPackageVersion(mKey.componentName.getPackageName())
+                        : null;
 
                 // it's not in the db... we need to generate it
-                preview = generatePreview(launcher, mInfo, unusedBitmap, mPreviewWidth, mPreviewHeight);
+                preview = generatePreview(mActivity, mInfo, unusedBitmap, mPreviewWidth, mPreviewHeight);
             }
             return preview;
         }
 
         @Override
         protected void onPostExecute(final Bitmap preview) {
-            mCaller.applyPreview(preview);
+            mCaller.applyPreview(preview, mAnimatePreviewIn);
 
             // Write the generated preview to the DB in the worker thread
             if (mVersions != null) {
@@ -638,14 +649,35 @@
                 });
             }
         }
+
+        @Override
+        public void onCancel() {
+            cancel(true);
+
+            // This only handles the case where the PreviewLoadTask is cancelled after the task has
+            // successfully completed (including having written to disk when necessary).  In the
+            // other cases where it is cancelled while the task is running, it will be cleaned up
+            // in the tasks's onCancelled() call, and if cancelled while the task is writing to
+            // disk, it will be cancelled in the task's onPostExecute() call.
+            if (mBitmapToRecycle != null) {
+                mWorkerHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        synchronized (mUnusedBitmaps) {
+                            mUnusedBitmaps.add(mBitmapToRecycle);
+                        }
+                        mBitmapToRecycle = null;
+                    }
+                });
+            }
+        }
     }
 
     private static final class WidgetCacheKey extends ComponentKey {
 
-        // TODO: remove dependency on size
         @Thunk final String size;
 
-        public WidgetCacheKey(ComponentName componentName, UserHandleCompat user, String size) {
+        public WidgetCacheKey(ComponentName componentName, UserHandle user, String size) {
             super(componentName, user);
             this.size = size;
         }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 340177d..369d80d 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -28,19 +28,17 @@
 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;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Parcelable;
+import android.os.UserHandle;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.Property;
@@ -53,32 +51,37 @@
 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.badge.FolderBadgeInfo;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
-import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.config.ProviderConfig;
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.dragndrop.DragScroller;
 import com.android.launcher3.dragndrop.DragView;
 import com.android.launcher3.dragndrop.SpringLoadedDragController;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.graphics.DragPreviewProvider;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.graphics.PreloadIconDrawable;
+import com.android.launcher3.popup.PopupContainerWithArrow;
+import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.LongArrayMap;
 import com.android.launcher3.util.MultiStateAlphaController;
+import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.VerticalFlingDetector;
 import com.android.launcher3.util.WallpaperOffsetInterpolator;
@@ -86,8 +89,8 @@
 import com.android.launcher3.widget.PendingAddWidgetInfo;
 
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Set;
 
 /**
  * The workspace is a wide area with a wallpaper and a finite number of pages.
@@ -95,11 +98,19 @@
  * interact with. A workspace is meant to be used with a fixed width only.
  */
 public class Workspace extends PagedView
-        implements DropTarget, DragSource, DragScroller, View.OnTouchListener,
-        DragController.DragListener, LauncherTransitionable, ViewGroup.OnHierarchyChangeListener,
+        implements DropTarget, DragSource, View.OnTouchListener,
+        DragController.DragListener, ViewGroup.OnHierarchyChangeListener,
         Insettable, DropTargetSource {
     private static final String TAG = "Launcher.Workspace";
 
+    /** The value that {@link #mTransitionProgress} must be greater than for
+     * {@link #transitionStateShouldAllowDrop()} to return true. */
+    private static final float ALLOW_DROP_TRANSITION_PROGRESS = 0.25f;
+
+    /** The value that {@link #mTransitionProgress} must be greater than for
+     * {@link #isFinishedSwitchingState()} ()} to return true. */
+    private static final float FINISHED_SWITCHING_STATE_TRANSITION_PROGRESS = 0.5f;
+
     private static boolean ENFORCE_DRAG_EVENT_ORDER = false;
 
     private static final int SNAP_OFF_EMPTY_SCREEN_DURATION = 400;
@@ -164,17 +175,15 @@
     private CellLayout mDropToLayout = null;
 
     @Thunk Launcher mLauncher;
-    @Thunk IconCache mIconCache;
     @Thunk DragController mDragController;
 
     // 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 float[] mTempTouchCoordinates = new float[2];
 
     private SpringLoadedDragController mSpringLoadedDragController;
     private float mOverviewModeShrinkFactor;
@@ -183,18 +192,20 @@
     // in all apps or customize mode)
 
     public enum State {
-        NORMAL          (false, false),
-        NORMAL_HIDDEN   (false, false),
-        SPRING_LOADED   (false, true),
-        OVERVIEW        (true, true),
-        OVERVIEW_HIDDEN (true, false);
+        NORMAL          (false, false, ContainerType.WORKSPACE),
+        NORMAL_HIDDEN   (false, false, ContainerType.ALLAPPS),
+        SPRING_LOADED   (false, true, ContainerType.WORKSPACE),
+        OVERVIEW        (true, true, ContainerType.OVERVIEW),
+        OVERVIEW_HIDDEN (true, false, ContainerType.WIDGETS);
 
         public final boolean shouldUpdateWidget;
         public final boolean hasMultipleVisiblePages;
+        public final int containerType;
 
-        State(boolean shouldUpdateWidget, boolean hasMultipleVisiblePages) {
+        State(boolean shouldUpdateWidget, boolean hasMultipleVisiblePages, int containerType) {
             this.shouldUpdateWidget = shouldUpdateWidget;
             this.hasMultipleVisiblePages = hasMultipleVisiblePages;
+            this.containerType = containerType;
         }
     }
 
@@ -240,11 +251,7 @@
 
     private boolean mStripScreensOnPageStopMoving = false;
 
-    /** Is the user is dragging an item near the edge of a page? */
-    private boolean mInScrollArea = false;
-
     private DragPreviewProvider mOutlineProvider = null;
-    public static final int DRAG_BITMAP_PADDING = DragPreviewProvider.DRAG_BITMAP_PADDING;
     private boolean mWorkspaceFadeInAdjacentScreens;
 
     final WallpaperOffsetInterpolator mWallpaperOffset;
@@ -303,7 +310,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;
@@ -375,17 +382,36 @@
         mOnStateChangeListener = listener;
     }
 
-    // estimate the size of a widget with spans hSpan, vSpan. return MAX_VALUE for each
-    // dimension if unsuccessful
-    public int[] estimateItemSize(ItemInfo itemInfo, boolean springLoaded) {
+    /**
+     * Estimates the size of an item using spans: hSpan, vSpan.
+     *
+     * @param springLoaded True if we are in spring loaded mode.
+     * @param unscaledSize True if caller wants to return the unscaled size
+     * @return MAX_VALUE for each dimension if unsuccessful.
+     */
+    public int[] estimateItemSize(ItemInfo itemInfo, boolean springLoaded, boolean unscaledSize) {
         float shrinkFactor = mLauncher.getDeviceProfile().workspaceSpringLoadShrinkFactor;
         int[] size = new int[2];
         if (getChildCount() > 0) {
             // Use the first non-custom page to estimate the child position
             CellLayout cl = (CellLayout) getChildAt(numCustomPages());
+            boolean isWidget = itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
+
             Rect r = estimateItemPosition(cl, 0, 0, itemInfo.spanX, itemInfo.spanY);
+
+            float scale = 1;
+            if (isWidget) {
+                DeviceProfile profile = mLauncher.getDeviceProfile();
+                scale = Utilities.shrinkRect(r, profile.appWidgetScale.x, profile.appWidgetScale.y);
+            }
             size[0] = r.width();
             size[1] = r.height();
+
+            if (isWidget && unscaledSize) {
+                size[0] /= scale;
+                size[1] /= scale;
+            }
+
             if (springLoaded) {
                 size[0] *= shrinkFactor;
                 size[1] *= shrinkFactor;
@@ -407,7 +433,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) {
@@ -451,10 +482,8 @@
             }
         }
 
-        if (!FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND) {
-            // Always enter the spring loaded mode
-            mLauncher.enterSpringLoadedDragMode();
-        }
+        // Always enter the spring loaded mode
+        mLauncher.enterSpringLoadedDragMode();
     }
 
     public void deferRemoveExtraEmptyScreen() {
@@ -464,7 +493,7 @@
     @Override
     public void onDragEnd() {
         if (ENFORCE_DRAG_EVENT_ORDER) {
-            enfoceDragParity("onDragEnd", 0, 0);
+            enforceDragParity("onDragEnd", 0, 0);
         }
 
         if (!mDeferRemoveExtraEmptyScreen) {
@@ -477,6 +506,8 @@
         // Re-enable any Un/InstallShortcutReceiver and now process any queued items
         InstallShortcutReceiver.disableAndFlushInstallQueue(getContext());
 
+        mOutlineProvider = null;
+        mDragInfo = null;
         mDragSourceInternal = null;
         mLauncher.onInteractionEnd();
     }
@@ -486,13 +517,10 @@
      */
     protected void initWorkspace() {
         mCurrentPage = getDefaultPage();
-        LauncherAppState app = LauncherAppState.getInstance();
         DeviceProfile grid = mLauncher.getDeviceProfile();
-        mIconCache = app.getIconCache();
         setWillNotDraw(false);
         setClipChildren(false);
         setClipToPadding(false);
-        setChildrenDrawnWithCacheEnabled(true);
 
         setMinScale(mOverviewModeShrinkFactor);
         setupLayoutTransition();
@@ -545,32 +573,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 +584,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) {
@@ -642,7 +644,8 @@
         // of workspace despite that it's not a true child.
         // Note that it relies on the strict ordering of measuring the workspace before the QSB
         // at the dragLayer level.
-        if (getChildCount() > 0) {
+        // Only measure the QSB when the view is enabled
+        if (FeatureFlags.QSB_ON_FIRST_SCREEN && getChildCount() > 0) {
             CellLayout firstPage = (CellLayout) getChildAt(0);
             int cellHeight = firstPage.getCellHeight();
 
@@ -737,11 +740,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 +761,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 +1007,7 @@
             return;
         }
 
-        if (isPageMoving()) {
+        if (isPageInTransition()) {
             mStripScreensOnPageStopMoving = true;
             return;
         }
@@ -1070,23 +1065,28 @@
         }
     }
 
-    // See implementation for parameter definition.
-    void addInScreen(View child, long container, long screenId,
-            int x, int y, int spanX, int spanY) {
-        addInScreen(child, container, screenId, x, y, spanX, spanY, false, false);
+    /**
+     * At bind time, we use the rank (screenId) to compute x and y for hotseat items.
+     * See {@link #addInScreen}.
+     */
+    public void addInScreenFromBind(View child, ItemInfo info) {
+        int x = info.cellX;
+        int y = info.cellY;
+        if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+            int screenId = (int) info.screenId;
+            x = mLauncher.getHotseat().getCellXFromOrder(screenId);
+            y = mLauncher.getHotseat().getCellYFromOrder(screenId);
+        }
+        addInScreen(child, info.container, info.screenId, x, y, info.spanX, info.spanY);
     }
 
-    // At bind time, we use the rank (screenId) to compute x and y for hotseat items.
-    // See implementation for parameter definition.
-    public void addInScreenFromBind(View child, long container, long screenId, int x, int y,
-            int spanX, int spanY) {
-        addInScreen(child, container, screenId, x, y, spanX, spanY, false, true);
-    }
-
-    // See implementation for parameter definition.
-    void addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY,
-            boolean insert) {
-        addInScreen(child, container, screenId, x, y, spanX, spanY, insert, false);
+    /**
+     * Adds the specified child in the specified screen based on the {@param info}
+     * See {@link #addInScreen}.
+     */
+    public void addInScreen(View child, ItemInfo info) {
+        addInScreen(child, info.container, info.screenId, info.cellX, info.cellY,
+                info.spanX, info.spanY);
     }
 
     /**
@@ -1099,13 +1099,9 @@
      * @param y The Y position of the child in the screen's grid.
      * @param spanX The number of cells spanned horizontally by the child.
      * @param spanY The number of cells spanned vertically by the child.
-     * @param insert When true, the child is inserted at the beginning of the children list.
-     * @param computeXYFromRank When true, we use the rank (stored in screenId) to compute
-     *                          the x and y position in which to place hotseat items. Otherwise
-     *                          we use the x and y position to compute the rank.
      */
-    void addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY,
-            boolean insert, boolean computeXYFromRank) {
+    private void addInScreen(View child, long container, long screenId, int x, int y,
+            int spanX, int spanY) {
         if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
             if (getScreenWithId(screenId) == null) {
                 Log.e(TAG, "Skipping child, screenId " + screenId + " not found");
@@ -1128,13 +1124,6 @@
             if (child instanceof FolderIcon) {
                 ((FolderIcon) child).setTextVisible(false);
             }
-
-            if (computeXYFromRank) {
-                x = mLauncher.getHotseat().getCellXFromOrder((int) screenId);
-                y = mLauncher.getHotseat().getCellYFromOrder((int) screenId);
-            } else {
-                screenId = mLauncher.getHotseat().getOrderInHotseat(x, y);
-            }
         } else {
             // Show folder title if not in the hotseat
             if (child instanceof FolderIcon) {
@@ -1165,7 +1154,7 @@
         int childId = mLauncher.getViewIdForItem(info);
 
         boolean markCellsAsOccupied = !(child instanceof Folder);
-        if (!layout.addViewToCellLayout(child, insert ? 0 : -1, childId, lp, markCellsAsOccupied)) {
+        if (!layout.addViewToCellLayout(child, -1, childId, lp, markCellsAsOccupied)) {
             // TODO: This branch occurs when the workspace is adding views
             // outside of the defined grid
             // maybe we should be deleting these items from the LauncherModel?
@@ -1193,7 +1182,7 @@
     }
 
     private boolean shouldConsumeTouch(View v) {
-        return (workspaceInModalState() || !isFinishedSwitchingState())
+        return !workspaceIconsCanBeDragged()
                 || (!workspaceInModalState() && indexOfChild(v) != mCurrentPage);
     }
 
@@ -1204,7 +1193,8 @@
     /** This differs from isSwitchingState in that we take into account how far the transition
      *  has completed. */
     public boolean isFinishedSwitchingState() {
-        return !mIsSwitchingState || (mTransitionProgress > 0.5f);
+        return !mIsSwitchingState
+                || (mTransitionProgress > FINISHED_SWITCHING_STATE_TRANSITION_PROGRESS);
     }
 
     protected void onWindowVisibilityChanged (int visibility) {
@@ -1328,31 +1318,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()) {
@@ -1419,12 +1392,10 @@
         // it's own settling, and every gesture to the overlay should be self-contained and start
         // from 0, so we zero it out here.
         if (isScrollingOverlay()) {
-            int finalScroll = mIsRtl ? mMaxScrollX : 0;
-
             // We reset mWasInOverscroll so that PagedView doesn't zero out the overscroll
-            // interaction when we call scrollTo.
+            // interaction when we call snapToPageImmediately.
             mWasInOverscroll = false;
-            scrollTo(finalScroll, getScrollY());
+            snapToPageImmediately(0);
         } else {
             super.snapToDestination();
         }
@@ -1454,6 +1425,10 @@
         if (!isTransitioning) {
             showPageIndicatorAtCurrentScroll();
         }
+
+        updatePageAlphaValues();
+        updateStateForCustomContent();
+        enableHwLayersOnVisiblePages();
     }
 
     private void showPageIndicatorAtCurrentScroll() {
@@ -1470,7 +1445,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 +1454,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 +1587,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();
@@ -1656,8 +1619,7 @@
         Utilities.THREAD_POOL_EXECUTOR.execute(new Runnable() {
             @Override
             public void run() {
-                final Point size = LauncherAppState.getInstance()
-                        .getInvariantDeviceProfile().defaultWallpaperSize;
+                final Point size = LauncherAppState.getIDP(getContext()).defaultWallpaperSize;
                 if (size.x != mWallpaperManager.getDesiredMinimumWidth()
                         || size.y != mWallpaperManager.getDesiredMinimumHeight()) {
                     mWallpaperManager.suggestDesiredDimensions(size.x, size.y);
@@ -1723,15 +1685,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 +1719,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 +1770,6 @@
         }
     }
 
-    @Override
-    protected void screenScrolled(int screenCenter) {
-        updatePageAlphaValues(screenCenter);
-        updateStateForCustomContent(screenCenter);
-        enableHwLayersOnVisiblePages();
-    }
-
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         IBinder windowToken = getWindowToken();
@@ -1828,11 +1784,6 @@
     }
 
     protected void onResume() {
-        // Update wallpaper dimensions if they were changed since last onResume
-        // (we also always set the wallpaper dimensions in the constructor)
-        if (LauncherAppState.getInstance().hasWallpaperChangedSinceLastCheck()) {
-            setWallpaperDimension();
-        }
         mWallpaperOffset.onResume();
     }
 
@@ -1875,7 +1826,7 @@
                 }
             });
         }
-
+        updatePageAlphaValues();
     }
 
     @Override
@@ -1890,40 +1841,14 @@
         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);
-            }
-        }
+    /** Returns whether a drag should be allowed to be started from the current workspace state. */
+    public boolean workspaceIconsCanBeDragged() {
+        return mState == State.NORMAL || mState == State.SPRING_LOADED;
     }
 
     @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 +1866,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 +1906,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 +1928,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 +1948,7 @@
 
     public void exitWidgetResizeMode() {
         DragLayer dragLayer = mLauncher.getDragLayer();
-        dragLayer.clearAllResizeFrames();
+        dragLayer.clearResizeFrame();
     }
 
     @Override
@@ -2045,7 +1984,7 @@
             CellLayout cl = ((CellLayout) getChildAt(i));
             mScreenOrder.add(getIdForScreen(cl));
         }
-
+        mLauncher.getUserEventDispatcher().logOverviewReorder();
         mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
 
         // Re-enable auto layout transitions for page deletion.
@@ -2106,15 +2045,19 @@
      * to that new state.
      */
     public Animator setStateWithAnimation(State toState, boolean animated,
-            HashMap<View, Integer> layerViews) {
-        // Create the animation to the new state
-        AnimatorSet workspaceAnim =  mStateTransitionAnimation.getAnimationToState(mState,
-                toState, animated, layerViews);
+            AnimationLayerSet layerViews) {
+        final State fromState = mState;
 
-        boolean shouldNotifyWidgetChange = !mState.shouldUpdateWidget
-                && toState.shouldUpdateWidget;
         // Update the current state
         mState = toState;
+
+        // Create the animation to the new state
+        AnimatorSet workspaceAnim =  mStateTransitionAnimation.getAnimationToState(fromState,
+                toState, animated, layerViews);
+
+        boolean shouldNotifyWidgetChange = !fromState.shouldUpdateWidget
+                && toState.shouldUpdateWidget;
+
         updateAccessibilityFlags();
 
         if (shouldNotifyWidgetChange) {
@@ -2125,6 +2068,20 @@
             mOnStateChangeListener.prepareStateChange(toState, animated ? workspaceAnim : null);
         }
 
+        onPrepareStateTransition(mState.hasMultipleVisiblePages);
+
+        StateTransitionListener listener = new StateTransitionListener();
+        if (animated) {
+            ValueAnimator stepAnimator = ValueAnimator.ofFloat(0, 1);
+            stepAnimator.addUpdateListener(listener);
+
+            workspaceAnim.play(stepAnimator);
+            workspaceAnim.addListener(listener);
+        } else {
+            listener.onAnimationStart(null);
+            listener.onAnimationEnd(null);
+        }
+
         return workspaceAnim;
     }
 
@@ -2135,20 +2092,13 @@
     public void updateAccessibilityFlags() {
         // TODO: Update the accessibility flags appropriately when dragging.
         if (!mLauncher.getAccessibilityDelegate().isInAccessibleDrag()) {
-            if (Utilities.ATLEAST_LOLLIPOP) {
-                int total = getPageCount();
-                for (int i = numCustomPages(); i < total; i++) {
-                    updateAccessibilityFlags((CellLayout) getPageAt(i), i);
-                }
-                setImportantForAccessibility((mState == State.NORMAL || mState == State.OVERVIEW)
-                        ? IMPORTANT_FOR_ACCESSIBILITY_AUTO
-                        : IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
-            } else {
-                int accessible = mState == State.NORMAL ?
-                        IMPORTANT_FOR_ACCESSIBILITY_AUTO :
-                        IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
-                setImportantForAccessibility(accessible);
+            int total = getPageCount();
+            for (int i = numCustomPages(); i < total; i++) {
+                updateAccessibilityFlags((CellLayout) getPageAt(i), i);
             }
+            setImportantForAccessibility((mState == State.NORMAL || mState == State.OVERVIEW)
+                    ? IMPORTANT_FOR_ACCESSIBILITY_AUTO
+                    : IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
         }
     }
 
@@ -2177,9 +2127,7 @@
         }
     }
 
-    @Override
-    public void onLauncherTransitionPrepare(Launcher l, boolean animated,
-            boolean multiplePagesVisible) {
+    public void onPrepareStateTransition(boolean multiplePagesVisible) {
         mIsSwitchingState = true;
         mTransitionProgress = 0;
 
@@ -2192,32 +2140,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 +2198,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) {
@@ -2322,20 +2248,18 @@
         Point dragVisualizeOffset = null;
         Rect dragRect = null;
         if (child instanceof BubbleTextView) {
-            int iconSize = grid.iconSizePx;
-            int top = child.getPaddingTop();
-            int left = (b.getWidth() - iconSize) / 2;
-            int right = left + iconSize;
-            int bottom = top + iconSize;
-            dragLayerY += top;
-            // Note: The drag region is used to calculate drag layer offsets, but the
+            dragRect = new Rect();
+            ((BubbleTextView) child).getIconBounds(dragRect);
+            dragLayerY += dragRect.top;
+            // Note: The dragRect is used to calculate drag layer offsets, but the
             // dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
             dragVisualizeOffset = new Point(- halfPadding, halfPadding);
-            dragRect = new Rect(left, top, right, bottom);
         } else if (child instanceof FolderIcon) {
             int previewSize = grid.folderIconSizePx;
             dragVisualizeOffset = new Point(- halfPadding, halfPadding - child.getPaddingTop());
             dragRect = new Rect(0, child.getPaddingTop(), child.getWidth(), previewSize);
+        } else if (previewProvider instanceof ShortcutDragPreviewProvider) {
+            dragVisualizeOffset = new Point(- halfPadding, halfPadding);
         }
 
         // Clear the pressed state if necessary
@@ -2348,6 +2272,16 @@
             mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
         }
 
+        if (child instanceof BubbleTextView) {
+            PopupContainerWithArrow popupContainer = PopupContainerWithArrow
+                    .showForIcon((BubbleTextView) child);
+            if (popupContainer != null) {
+                dragOptions.preDragCondition = popupContainer.createPreDragCondition();
+
+                mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
+            }
+        }
+
         DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source,
                 dragObject, dragVisualizeOffset, dragRect, scale, dragOptions);
         dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
@@ -2355,8 +2289,8 @@
         return dv;
     }
 
-    public boolean transitionStateShouldAllowDrop() {
-        return ((!isSwitchingState() || mTransitionProgress > 0.5f) &&
+    private boolean transitionStateShouldAllowDrop() {
+        return ((!isSwitchingState() || mTransitionProgress > ALLOW_DROP_TRANSITION_PROGRESS) &&
                 (mState == State.NORMAL || mState == State.SPRING_LOADED));
     }
 
@@ -2423,18 +2357,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;
             }
         }
@@ -2606,9 +2529,10 @@
         if (d.dragSource != this) {
             final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0],
                     (int) mDragViewVisualCenter[1] };
-            onDropExternal(touchXY, d.dragInfo, dropTargetLayout, false, d);
+            onDropExternal(touchXY, dropTargetLayout, d);
         } else if (mDragInfo != null) {
             final View cell = mDragInfo.cell;
+            boolean droppedOnOriginalCellDuringTransition = false;
 
             if (dropTargetLayout != null && !d.cancelled) {
                 // Move internally
@@ -2631,7 +2555,7 @@
 
                 // If the item being dropped is a shortcut and the nearest drop
                 // cell also contains a shortcut, then create a folder with the two shortcuts.
-                if (!mInScrollArea && createUserFolderIfNecessary(cell, container,
+                if (createUserFolderIfNecessary(cell, container,
                         dropTargetLayout, mTargetCell, distance, false, d.dragView, null)) {
                     return;
                 }
@@ -2651,10 +2575,23 @@
                     minSpanY = item.minSpanY;
                 }
 
+                droppedOnOriginalCellDuringTransition = mIsSwitchingState
+                        && item.screenId == screenId && item.container == container
+                        && item.cellX == mTargetCell[0] && item.cellY == mTargetCell[1];
+
+                // When quickly moving an item, a user may accidentally rearrange their
+                // workspace. So instead we move the icon back safely to its original position.
+                boolean returnToOriginalCellToPreventShuffling = !isFinishedSwitchingState()
+                        && !droppedOnOriginalCellDuringTransition && !dropTargetLayout
+                        .isRegionVacant(mTargetCell[0], mTargetCell[1], spanX, spanY);
                 int[] resultSpan = new int[2];
-                mTargetCell = dropTargetLayout.performReorder((int) mDragViewVisualCenter[0],
-                        (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, cell,
-                        mTargetCell, resultSpan, CellLayout.MODE_ON_DROP);
+                if (returnToOriginalCellToPreventShuffling) {
+                    mTargetCell[0] = mTargetCell[1] = -1;
+                } else {
+                    mTargetCell = dropTargetLayout.performReorder((int) mDragViewVisualCenter[0],
+                            (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, cell,
+                            mTargetCell, resultSpan, CellLayout.MODE_ON_DROP);
+                }
 
                 boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
 
@@ -2669,12 +2606,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,18 +2645,22 @@
                                 && !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);
                                     }
                                 }
                             };
                         }
                     }
 
-                    LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId, lp.cellX,
-                            lp.cellY, item.spanX, item.spanY);
+                    mLauncher.getModelWriter().modifyItemInDatabase(info, container, screenId,
+                            lp.cellX, lp.cellY, item.spanX, item.spanY);
                 } else {
+                    if (!returnToOriginalCellToPreventShuffling) {
+                        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 +2682,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 +2717,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,64 +2762,47 @@
     @Override
     public void onDragEnter(DragObject d) {
         if (ENFORCE_DRAG_EVENT_ORDER) {
-            enfoceDragParity("onDragEnter", 1, 1);
+            enforceDragParity("onDragEnter", 1, 1);
         }
 
         mCreateUserFolderOnDrop = false;
         mAddToExistingFolderOnDrop = false;
 
         mDropToLayout = null;
-        setDropLayoutForDragObject(d);
-
-        if (!workspaceInModalState() && FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND) {
-            mLauncher.getDragLayer().showPageHints();
-        }
+        mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter);
+        setDropLayoutForDragObject(d, mDragViewVisualCenter[0], mDragViewVisualCenter[1]);
     }
 
     @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 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());
-            } else {
-                mDropToLayout = mDragOverlappingLayout;
-            }
-        } else {
-            mDropToLayout = mDragTargetLayout;
-        }
-
+        mDropToLayout = mDragTargetLayout;
         if (mDragMode == DRAG_MODE_CREATE_FOLDER) {
             mCreateUserFolderOnDrop = true;
         } else if (mDragMode == DRAG_MODE_ADD_TO_FOLDER) {
             mAddToExistingFolderOnDrop = true;
         }
 
-        // Reset the scroll area and previous drag target
-        onResetScrollArea();
+        // Reset the previous drag target
         setCurrentDropLayout(null);
         setCurrentDragOverlappingLayout(null);
 
         mSpringLoadedDragController.cancel();
-
-        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 +2923,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];
@@ -2985,68 +2940,6 @@
        xy[1] += v.getTop();
    }
 
-   static private float squaredDistance(float[] point1, float[] point2) {
-        float distanceX = point1[0] - point2[0];
-        float distanceY = point2[1] - point2[1];
-        return distanceX * distanceX + distanceY * distanceY;
-   }
-
-    /*
-     *
-     * This method returns the CellLayout that is currently being dragged to. In order to drag
-     * to a CellLayout, either the touch point must be directly over the CellLayout, or as a second
-     * strategy, we see if the dragView is overlapping any CellLayout and choose the closest one
-     *
-     * Return null if no CellLayout is currently being dragged over
-     *
-     */
-    private CellLayout findMatchingPageForDragOver(
-            DragView dragView, float originX, float originY, boolean exact) {
-        // We loop through all the screens (ie CellLayouts) and see which ones overlap
-        // with the item being dragged and then choose the one that's closest to the touch point
-        final int screenCount = getChildCount();
-        CellLayout bestMatchingScreen = null;
-        float smallestDistSoFar = Float.MAX_VALUE;
-
-        for (int i = 0; i < screenCount; i++) {
-            // The custom content screen is not a valid drag over option
-            if (mScreenOrder.get(i) == CUSTOM_CONTENT_SCREEN_ID) {
-                continue;
-            }
-
-            CellLayout cl = (CellLayout) getChildAt(i);
-
-            final float[] touchXy = {originX, originY};
-            mapPointFromSelfToChild(cl, touchXy);
-
-            if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() &&
-                    touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) {
-                return cl;
-            }
-
-            if (!exact) {
-                // Get the center of the cell layout in screen coordinates
-                final float[] cellLayoutCenter = mTempCellLayoutCenterCoordinates;
-                cellLayoutCenter[0] = cl.getWidth()/2;
-                cellLayoutCenter[1] = cl.getHeight()/2;
-                mapPointFromChildToSelf(cl, cellLayoutCenter);
-
-                touchXy[0] = originX;
-                touchXy[1] = originY;
-
-                // Calculate the distance between the center of the CellLayout
-                // and the touch point
-                float dist = squaredDistance(touchXy, cellLayoutCenter);
-
-                if (dist < smallestDistSoFar) {
-                    smallestDistSoFar = dist;
-                    bestMatchingScreen = cl;
-                }
-            }
-        }
-        return bestMatchingScreen;
-    }
-
     private boolean isDragWidget(DragObject d) {
         return (d.dragInfo instanceof LauncherAppWidgetInfo ||
                 d.dragInfo instanceof PendingAddWidgetInfo);
@@ -3054,7 +2947,7 @@
 
     public void onDragOver(DragObject d) {
         // Skip drag over events while we are dragging over side pages
-        if (mInScrollArea || !transitionStateShouldAllowDrop()) return;
+        if (!transitionStateShouldAllowDrop()) return;
 
         ItemInfo item = d.dragInfo;
         if (item == null) {
@@ -3069,14 +2962,11 @@
         mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter);
 
         final View child = (mDragInfo == null) ? null : mDragInfo.cell;
-        if (setDropLayoutForDragObject(d)) {
-            boolean isInSpringLoadedMode = (mState == State.SPRING_LOADED);
-            if (isInSpringLoadedMode) {
-                if (mLauncher.isHotseatLayout(mDragTargetLayout)) {
-                    mSpringLoadedDragController.cancel();
-                } else {
-                    mSpringLoadedDragController.setAlarm(mDragTargetLayout);
-                }
+        if (setDropLayoutForDragObject(d, mDragViewVisualCenter[0], mDragViewVisualCenter[1])) {
+            if (mLauncher.isHotseatLayout(mDragTargetLayout)) {
+                mSpringLoadedDragController.cancel();
+            } else {
+                mSpringLoadedDragController.setAlarm(mDragTargetLayout);
             }
         }
 
@@ -3153,7 +3043,7 @@
      *
      * @return whether the layout is different from the current {@link #mDragTargetLayout}.
      */
-    private boolean setDropLayoutForDragObject(DragObject d) {
+    private boolean setDropLayoutForDragObject(DragObject d, float centerX, float centerY) {
         CellLayout layout = null;
         // Test to see if we are over the hotseat first
         if (mLauncher.getHotseat() != null && !isDragWidget(d)) {
@@ -3161,12 +3051,25 @@
                 layout = mLauncher.getHotseat().getLayout();
             }
         }
-        if (layout == null) {
-            // Identify whether we have dragged over a side page,
-            // otherwise just use the current page
-            layout = workspaceInModalState() ?
-                    findMatchingPageForDragOver(d.dragView, d.x, d.y, false)
-                    : getCurrentDropLayout();
+
+        int nextPage = getNextPage();
+        if (layout == null && !isPageInTransition()) {
+            // Check if the item is dragged over left page
+            mTempTouchCoordinates[0] = Math.min(centerX, d.x);
+            mTempTouchCoordinates[1] = d.y;
+            layout = verifyInsidePage(nextPage + (mIsRtl ? 1 : -1), mTempTouchCoordinates);
+        }
+
+        if (layout == null && !isPageInTransition()) {
+            // Check if the item is dragged over right page
+            mTempTouchCoordinates[0] = Math.max(centerX, d.x);
+            mTempTouchCoordinates[1] = d.y;
+            layout = verifyInsidePage(nextPage + (mIsRtl ? -1 : 1), mTempTouchCoordinates);
+        }
+
+        // Always pick the current page.
+        if (layout == null && nextPage >= numCustomPages() && nextPage < getPageCount()) {
+            layout = (CellLayout) getChildAt(nextPage);
         }
         if (layout != mDragTargetLayout) {
             setCurrentDropLayout(layout);
@@ -3176,6 +3079,22 @@
         return false;
     }
 
+    /**
+     * Returns the child CellLayout if the point is inside the page coordinates, null otherwise.
+     */
+    private CellLayout verifyInsidePage(int pageNo, float[] touchXy)  {
+        if (pageNo >= numCustomPages() && pageNo < getPageCount()) {
+            CellLayout cl = (CellLayout) getChildAt(pageNo);
+            mapPointFromSelfToChild(cl, touchXy);
+            if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() &&
+                    touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) {
+                // This point is inside the cell layout
+                return cl;
+            }
+        }
+        return null;
+    }
+
     private void manageFolderFeedback(CellLayout targetLayout,
             int[] targetCell, float distance, DragObject dragObject) {
         if (distance > mMaxDistanceForFolderCreation) return;
@@ -3313,8 +3232,7 @@
      * NOTE: This can also be called when we are outside of a drag event, when we want
      * to add an item to one of the workspace screens.
      */
-    private void onDropExternal(final int[] touchXY, final ItemInfo dragInfo,
-            final CellLayout cellLayout, boolean insertAtFirst, DragObject d) {
+    private void onDropExternal(final int[] touchXY, final CellLayout cellLayout, DragObject d) {
         final Runnable exitSpringLoadedRunnable = new Runnable() {
             @Override
             public void run() {
@@ -3323,7 +3241,15 @@
             }
         };
 
-        ItemInfo info = dragInfo;
+        if (d.dragInfo instanceof PendingAddShortcutInfo) {
+            ShortcutInfo si = ((PendingAddShortcutInfo) d.dragInfo)
+                    .activityInfo.createShortcutInfo();
+            if (si != null) {
+                d.dragInfo = si;
+            }
+        }
+
+        ItemInfo info = d.dragInfo;
         int spanX = info.spanX;
         int spanY = info.spanY;
         if (mDragInfo != null) {
@@ -3342,7 +3268,7 @@
         }
 
         if (info instanceof PendingAddItemInfo) {
-            final PendingAddItemInfo pendingInfo = (PendingAddItemInfo) dragInfo;
+            final PendingAddItemInfo pendingInfo = (PendingAddItemInfo) info;
 
             boolean findNearestVacantCell = true;
             if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
@@ -3405,7 +3331,7 @@
 
             int animationStyle = ANIMATE_INTO_POSITION_AND_DISAPPEAR;
             if (isWidget && ((PendingAddWidgetInfo) pendingInfo).info != null &&
-                    ((PendingAddWidgetInfo) pendingInfo).info.configure != null) {
+                    ((PendingAddWidgetInfo) pendingInfo).getHandler().needsConfigure()) {
                 animationStyle = ANIMATE_INTO_POSITION_AND_REMAIN;
             }
             animateWidgetDrop(info, cellLayout, d.dragView, onAnimationCompleteRunnable,
@@ -3427,7 +3353,7 @@
                 break;
             case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
                 view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout,
-                        (FolderInfo) info, mIconCache);
+                        (FolderInfo) info);
                 break;
             default:
                 throw new IllegalStateException("Unknown item type: " + info.itemType);
@@ -3461,11 +3387,11 @@
             }
             // Add the item to DB before adding to screen ensures that the container and other
             // values of the info is properly updated.
-            LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screenId,
+            mLauncher.getModelWriter().addOrMoveItemInDatabase(info, container, screenId,
                     mTargetCell[0], mTargetCell[1]);
 
-            addInScreen(view, container, screenId, mTargetCell[0], mTargetCell[1], info.spanX,
-                    info.spanY, insertAtFirst);
+            addInScreen(view, container, screenId, mTargetCell[0], mTargetCell[1],
+                    info.spanX, info.spanY);
             cellLayout.onDropChild(view);
             cellLayout.getShortcutsAndWidgets().measureChild(view);
 
@@ -3482,7 +3408,7 @@
     }
 
     public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) {
-        int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(widgetInfo, false);
+        int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(widgetInfo, false, true);
         int visibility = layout.getVisibility();
         layout.setVisibility(VISIBLE);
 
@@ -3508,6 +3434,10 @@
         int spanY = info.spanY;
 
         Rect r = estimateItemPosition(layout, targetCell[0], targetCell[1], spanX, spanY);
+        if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET) {
+            DeviceProfile profile = mLauncher.getDeviceProfile();
+            Utilities.shrinkRect(r, profile.appWidgetScale.x, profile.appWidgetScale.y);
+        }
         loc[0] = r.left;
         loc[1] = r.top;
 
@@ -3516,24 +3446,32 @@
                 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, loc, true);
         resetTransitionTransform(layout);
 
-        float dragViewScaleX;
-        float dragViewScaleY;
         if (scale) {
-            dragViewScaleX = (1.0f * r.width()) / dragView.getMeasuredWidth();
-            dragViewScaleY = (1.0f * r.height()) / dragView.getMeasuredHeight();
+            float dragViewScaleX = (1.0f * r.width()) / dragView.getMeasuredWidth();
+            float dragViewScaleY = (1.0f * r.height()) / dragView.getMeasuredHeight();
+
+            // The animation will scale the dragView about its center, so we need to center about
+            // the final location.
+            loc[0] -= (dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2
+                    - Math.ceil(layout.getUnusedHorizontalSpace() / 2f);
+            loc[1] -= (dragView.getMeasuredHeight() - cellLayoutScale * r.height()) / 2;
+            scaleXY[0] = dragViewScaleX * cellLayoutScale;
+            scaleXY[1] = dragViewScaleY * cellLayoutScale;
         } else {
-            dragViewScaleX = 1f;
-            dragViewScaleY = 1f;
+            // Since we are not cross-fading the dragView, align the drag view to the
+            // final cell position.
+            float dragScale = dragView.getInitialScale() * cellLayoutScale;
+            loc[0] += (dragScale - 1) * dragView.getWidth() / 2;
+            loc[1] += (dragScale - 1) * dragView.getHeight() / 2;
+            scaleXY[0] = scaleXY[1] = dragScale;
+
+            // If a dragRegion was provided, offset the final position accordingly.
+            Rect dragRegion = dragView.getDragRegion();
+            if (dragRegion != null) {
+                loc[0] += cellLayoutScale * dragRegion.left;
+                loc[1] += cellLayoutScale * dragRegion.top;
+            }
         }
-
-        // The animation will scale the dragView about its center, so we need to center about
-        // the final location.
-        loc[0] -= (dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2
-                - Math.ceil(layout.getUnusedHorizontalSpace() / 2f);
-        loc[1] -= (dragView.getMeasuredHeight() - cellLayoutScale * r.height()) / 2;
-
-        scaleXY[0] = dragViewScaleX * cellLayoutScale;
-        scaleXY[1] = dragViewScaleY * cellLayoutScale;
     }
 
     public void animateWidgetDrop(ItemInfo info, CellLayout cellLayout, final DragView dragView,
@@ -3609,14 +3547,6 @@
     }
 
     /**
-     * Return the current {@link CellLayout}, correctly picking the destination
-     * screen while a scroll is in progress.
-     */
-    public CellLayout getCurrentDropLayout() {
-        return (CellLayout) getChildAt(getNextPage());
-    }
-
-    /**
      * Return the current CellInfo describing our current drag; this method exists
      * so that Launcher can sync this object with the correct info when the activity is created/
      * destroyed
@@ -3656,8 +3586,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 +3617,6 @@
                 && mDragInfo.cell != null) {
             mDragInfo.cell.setVisibility(VISIBLE);
         }
-        mOutlineProvider = null;
         mDragInfo = null;
 
         if (!isFlingToDelete) {
@@ -3751,13 +3682,8 @@
     }
 
     @Override
-    public boolean supportsFlingToDelete() {
-        return true;
-    }
-
-    @Override
     public boolean supportsAppInfoDropTarget() {
-        return !FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND;
+        return true;
     }
 
     @Override
@@ -3765,16 +3691,6 @@
         return true;
     }
 
-    @Override
-    public void onFlingToDelete(DragObject d, PointF vec) {
-        // Do nothing
-    }
-
-    @Override
-    public void onFlingToDeleteCompleted() {
-        // Do nothing
-    }
-
     public boolean isDropEnabled() {
         return true;
     }
@@ -3813,7 +3729,7 @@
         if (!workspaceInModalState() && !mIsSwitchingState) {
             super.scrollLeft();
         }
-        Folder openFolder = getOpenFolder();
+        Folder openFolder = Folder.getOpen(mLauncher);
         if (openFolder != null) {
             openFolder.completeDragExit();
         }
@@ -3824,71 +3740,12 @@
         if (!workspaceInModalState() && !mIsSwitchingState) {
             super.scrollRight();
         }
-        Folder openFolder = getOpenFolder();
+        Folder openFolder = Folder.getOpen(mLauncher);
         if (openFolder != null) {
             openFolder.completeDragExit();
         }
     }
 
-    @Override
-    public boolean onEnterScrollArea(int x, int y, int direction) {
-        // Ignore the scroll area if we are dragging over the hot seat
-        boolean isPortrait = !mLauncher.getDeviceProfile().isLandscape;
-        if (mLauncher.getHotseat() != null && isPortrait) {
-            Rect r = new Rect();
-            mLauncher.getHotseat().getHitRect(r);
-            if (r.contains(x, y)) {
-                return false;
-            }
-        }
-
-        boolean result = false;
-        if (!workspaceInModalState() && !mIsSwitchingState && getOpenFolder() == null) {
-            mInScrollArea = true;
-
-            final int page = getNextPage() +
-                       (direction == DragController.SCROLL_LEFT ? -1 : 1);
-            // We always want to exit the current layout to ensure parity of enter / exit
-            setCurrentDropLayout(null);
-
-            if (0 <= page && page < getChildCount()) {
-                // Ensure that we are not dragging over to the custom content screen
-                if (getScreenIdForPageIndex(page) == CUSTOM_CONTENT_SCREEN_ID) {
-                    return false;
-                }
-
-                CellLayout layout = (CellLayout) getChildAt(page);
-                setCurrentDragOverlappingLayout(layout);
-
-                // Workspace is responsible for drawing the edge glow on adjacent pages,
-                // so we need to redraw the workspace when this may have changed.
-                invalidate();
-                result = true;
-            }
-        }
-        return result;
-    }
-
-    @Override
-    public boolean onExitScrollArea() {
-        boolean result = false;
-        if (mInScrollArea) {
-            invalidate();
-            CellLayout layout = getCurrentDropLayout();
-            setCurrentDropLayout(layout);
-            setCurrentDragOverlappingLayout(layout);
-
-            result = true;
-            mInScrollArea = false;
-        }
-        return result;
-    }
-
-    private void onResetScrollArea() {
-        setCurrentDragOverlappingLayout(null);
-        mInScrollArea = false;
-    }
-
     /**
      * Returns a specific CellLayout
      */
@@ -4003,63 +3860,36 @@
         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 folderInfo = (FolderInfo) parent.getTag();
+                        folderInfo.prepareAutoUpdate();
+                        folderInfo.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();
             }
         }
 
@@ -4136,8 +3966,7 @@
                     Drawable oldIcon = getTextViewIcon(shortcut);
                     boolean oldPromiseState = (oldIcon instanceof PreloadIconDrawable)
                             && ((PreloadIconDrawable) oldIcon).hasNotCompleted();
-                    shortcut.applyFromShortcutInfo(si, mIconCache,
-                            si.isPromise() != oldPromiseState);
+                    shortcut.applyFromShortcutInfo(si, si.isPromise() != oldPromiseState);
                 }
                 // process all the shortcuts
                 return false;
@@ -4157,11 +3986,49 @@
         });
     }
 
-    public void removeAbandonedPromise(String packageName, UserHandleCompat user) {
+    public void updateIconBadges(final Set<PackageUserKey> updatedBadges) {
+        final PackageUserKey packageUserKey = new PackageUserKey(null, null);
+        final HashSet<Long> folderIds = new HashSet<>();
+        mapOverItems(MAP_RECURSE, new ItemOperator() {
+            @Override
+            public boolean evaluate(ItemInfo info, View v) {
+                if (info instanceof ShortcutInfo && v instanceof BubbleTextView
+                        && packageUserKey.updateFromItemInfo(info)) {
+                    if (updatedBadges.contains(packageUserKey)) {
+                        ((BubbleTextView) v).applyBadgeState(info, true /* animate */);
+                        folderIds.add(info.container);
+                    }
+                }
+                // process all the shortcuts
+                return false;
+            }
+        });
+
+        // Update folder icons
+        mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
+            @Override
+            public boolean evaluate(ItemInfo info, View v) {
+                if (info instanceof FolderInfo && folderIds.contains(info.id)
+                        && v instanceof FolderIcon) {
+                    FolderBadgeInfo folderBadgeInfo = new FolderBadgeInfo();
+                    for (ShortcutInfo si : ((FolderInfo) info).contents) {
+                        folderBadgeInfo.addBadgeInfo(mLauncher.getPopupDataProvider()
+                                .getBadgeInfoForItem(si));
+                    }
+                    ((FolderIcon) v).setBadgeInfo(folderBadgeInfo);
+                }
+                // process all the shortcuts
+                return false;
+            }
+        });
+    }
+
+    public void removeAbandonedPromise(String packageName, UserHandle user) {
         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);
+        mLauncher.getModelWriter().deleteItemsFromDatabase(matcher);
+        removeItemsByMatcher(matcher);
     }
 
     public void updateRestoreItems(final HashSet<ItemInfo> updates) {
@@ -4170,7 +4037,7 @@
             public boolean evaluate(ItemInfo info, View v) {
                 if (info instanceof ShortcutInfo && v instanceof BubbleTextView
                         && updates.contains(info)) {
-                    ((BubbleTextView) v).applyState(false);
+                    ((BubbleTextView) v).applyPromiseState(false /* promiseStateChanged */);
                 } else if (v instanceof PendingAppWidgetHostView
                         && info instanceof LauncherAppWidgetInfo
                         && updates.contains(info)) {
@@ -4286,16 +4153,26 @@
     }
 
     @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();
-        targetParent.containerType = LauncherLogProto.WORKSPACE;
+        targetParent.containerType = ContainerType.WORKSPACE;
         if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
             target.rank = info.rank;
-            targetParent.containerType = LauncherLogProto.HOTSEAT;
+            targetParent.containerType = ContainerType.HOTSEAT;
         } else if (info.container >= 0) {
-            targetParent.containerType = LauncherLogProto.FOLDER;
+            targetParent.containerType = ContainerType.FOLDER;
+        }
+    }
+
+    @Override
+    public boolean enableFreeScroll() {
+        if (getState() == State.OVERVIEW) {
+            return super.enableFreeScroll();
+        } else {
+            Log.w(TAG, "enableFreeScroll called but not in overview: state=" + getState());
+            return false;
         }
     }
 
@@ -4338,7 +4215,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 +4238,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..482a2c9 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -30,12 +30,12 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.animation.DecelerateInterpolator;
 
+import com.android.launcher3.anim.AnimationLayerSet;
+import com.android.launcher3.anim.PropertyListBuilder;
 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 +226,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 +262,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) {
@@ -339,10 +338,9 @@
             if (animated) {
                 float oldBackgroundAlpha = cl.getBackgroundAlpha();
                 if (initialAlpha != finalAlpha) {
-                    LauncherViewPropertyAnimator alphaAnim =
-                            new LauncherViewPropertyAnimator(cl.getShortcutsAndWidgets());
-                    alphaAnim.alpha(finalAlpha)
-                            .setDuration(duration)
+                    Animator alphaAnim = ObjectAnimator.ofFloat(
+                            cl.getShortcutsAndWidgets(), View.ALPHA, finalAlpha);
+                    alphaAnim.setDuration(duration)
                             .setInterpolator(mZoomInInterpolator);
                     mStateAnimator.play(alphaAnim);
                 }
@@ -358,7 +356,8 @@
                 cl.setShortcutAndWidgetAlpha(finalAlpha);
             }
 
-            if (Workspace.isQsbContainerPage(i)) {
+            if (Workspace.isQsbContainerPage(i) &&
+                    states.stateIsNormal && mWorkspaceFadeInAdjacentScreens) {
                 if (animated) {
                     Animator anim = mWorkspace.mQsbAlphaController
                             .animateAlphaAtIndex(finalAlpha, Workspace.QSB_ALPHA_INDEX_PAGE_SCROLL);
@@ -374,34 +373,29 @@
 
         final ViewGroup overviewPanel = mLauncher.getOverviewPanel();
 
-        final View qsbContainer = mLauncher.getQsbContainer();
-
         Animator qsbAlphaAnimation = mWorkspace.mQsbAlphaController
                 .animateAlphaAtIndex(finalQsbAlpha, Workspace.QSB_ALPHA_INDEX_STATE_CHANGE);
 
         if (animated) {
-            LauncherViewPropertyAnimator scale = new LauncherViewPropertyAnimator(mWorkspace);
-            scale.scaleX(mNewScale)
-                    .scaleY(mNewScale)
-                    .translationY(finalWorkspaceTranslationY)
-                    .setDuration(duration)
-                    .setInterpolator(mZoomInInterpolator);
+            Animator scale = LauncherAnimUtils.ofPropertyValuesHolder(mWorkspace,
+                    new PropertyListBuilder().scale(mNewScale)
+                            .translationY(finalWorkspaceTranslationY).build())
+                    .setDuration(duration);
+            scale.setInterpolator(mZoomInInterpolator);
             mStateAnimator.play(scale);
             Animator hotseatAlpha = mWorkspace.createHotseatAlphaAnimator(finalHotseatAlpha);
 
-            LauncherViewPropertyAnimator overviewPanelAlpha =
-                    new LauncherViewPropertyAnimator(overviewPanel).alpha(finalOverviewPanelAlpha);
+            Animator overviewPanelAlpha = ObjectAnimator.ofFloat(
+                    overviewPanel, View.ALPHA, finalOverviewPanelAlpha);
             overviewPanelAlpha.addListener(new AlphaUpdateListener(overviewPanel,
                     accessibilityEnabled));
 
             // 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(mLauncher.getQsbContainer());
+            layerViews.addView(mLauncher.getHotseat());
+            layerViews.addView(mWorkspace.getPageIndicator());
 
             if (states.workspaceToOverview) {
                 hotseatAlpha.setInterpolator(new DecelerateInterpolator(2));
@@ -426,6 +420,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 +437,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..a476650 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -1,11 +1,9 @@
 package com.android.launcher3.accessibility;
 
-import android.annotation.TargetApi;
 import android.app.AlertDialog;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.DialogInterface;
 import android.graphics.Rect;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.text.TextUtils;
@@ -20,11 +18,9 @@
 import com.android.launcher3.AppWidgetResizeFrame;
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.CellLayout;
+import com.android.launcher3.popup.PopupContainerWithArrow;
 import com.android.launcher3.DeleteDropTarget;
-import com.android.launcher3.DragSource;
 import com.android.launcher3.DropTarget.DragObject;
-import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.folder.Folder;
 import com.android.launcher3.FolderInfo;
 import com.android.launcher3.InfoDropTarget;
 import com.android.launcher3.ItemInfo;
@@ -39,13 +35,12 @@
 import com.android.launcher3.UninstallDropTarget;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.dragndrop.DragController.DragListener;
-import com.android.launcher3.shortcuts.DeepShortcutTextView;
-import com.android.launcher3.shortcuts.DeepShortcutsContainer;
+import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.folder.Folder;
 import com.android.launcher3.util.Thunk;
 
 import java.util.ArrayList;
 
-@TargetApi(Build.VERSION_CODES.LOLLIPOP)
 public class LauncherAccessibilityDelegate extends AccessibilityDelegate implements DragListener {
 
     private static final String TAG = "LauncherAccessibilityDelegate";
@@ -57,7 +52,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 +95,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));
         }
 
@@ -117,13 +115,14 @@
         if (UninstallDropTarget.supportsDrop(host.getContext(), item)) {
             info.addAction(mActions.get(UNINSTALL));
         }
-        if (InfoDropTarget.supportsDrop(item)) {
+        if (InfoDropTarget.supportsDrop(host.getContext(), item)) {
             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) {
@@ -169,7 +168,7 @@
                 public void run() {
                     if (item instanceof AppInfo) {
                         ShortcutInfo info = ((AppInfo) item).makeShortcut();
-                        LauncherModel.addItemToDatabase(mLauncher, info,
+                        mLauncher.getModelWriter().addItemToDatabase(info,
                                 LauncherSettings.Favorites.CONTAINER_DESKTOP,
                                 screenId, coordinates[0], coordinates[1]);
 
@@ -188,14 +187,14 @@
             });
             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);
 
             final int[] coordinates = new int[2];
             final long screenId = findSpaceOnWorkspace(item, coordinates);
-            LauncherModel.moveItemInDatabase(mLauncher, info,
+            mLauncher.getModelWriter().moveItemInDatabase(info,
                     LauncherSettings.Favorites.CONTAINER_DESKTOP,
                     screenId, coordinates[0], coordinates[1]);
 
@@ -232,7 +231,7 @@
                 .show();
             return true;
         } else if (action == DEEP_SHORTCUTS) {
-            return DeepShortcutsContainer.showForIcon((BubbleTextView) host) != null;
+            return PopupContainerWithArrow.showForIcon((BubbleTextView) host) != null;
         }
         return false;
     }
@@ -305,7 +304,7 @@
         ((LauncherAppWidgetHostView) host).updateAppWidgetSize(null,
                 sizeRange.left, sizeRange.top, sizeRange.right, sizeRange.bottom);
         host.requestLayout();
-        LauncherModel.updateItemInDatabase(mLauncher, info);
+        mLauncher.getModelWriter().updateItemInDatabase(info);
         announceConfirmation(mLauncher.getString(R.string.widget_resized, info.spanX, info.spanY));
     }
 
@@ -369,12 +368,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 +383,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/OverviewScreenAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java
index 5f68f90..edb0b16 100644
--- a/src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java
@@ -16,21 +16,19 @@
 
 package com.android.launcher3.accessibility;
 
-import android.annotation.TargetApi;
 import android.content.Context;
-import android.os.Build;
 import android.os.Bundle;
 import android.util.SparseArray;
 import android.view.View;
 import android.view.View.AccessibilityDelegate;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.config.FeatureFlags;
 
-@TargetApi(Build.VERSION_CODES.LOLLIPOP)
 public class OverviewScreenAccessibilityDelegate extends AccessibilityDelegate {
 
     private static final int MOVE_BACKWARD = R.id.action_move_screen_backwards;
diff --git a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
index 0baa8f3..b784fe7 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
@@ -56,13 +59,13 @@
             Runnable onComplete = new Runnable() {
                 @Override
                 public void run() {
-                    LauncherModel.addItemToDatabase(mLauncher, info,
+                    mLauncher.getModelWriter().addItemToDatabase(info,
                             LauncherSettings.Favorites.CONTAINER_DESKTOP,
                             screenId, coordinates[0], coordinates[1]);
                     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/accessibility/WorkspaceAccessibilityHelper.java b/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
index c71307d..9a23aa8 100644
--- a/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
+++ b/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
@@ -57,7 +57,7 @@
         int y = id / mCountX;
         LauncherAccessibilityDelegate.DragInfo dragInfo = mDelegate.getDragInfo();
 
-        if (dragInfo.dragType == DragType.WIDGET && mView.isHotseat()) {
+        if (dragInfo.dragType == DragType.WIDGET && !mView.acceptsWidget()) {
             return INVALID_POSITION;
         }
 
@@ -161,11 +161,7 @@
 
         View child = mView.getChildAt(x, y);
         if (child == null || child == dragInfo.item) {
-            if (mView.isHotseat()) {
-                return mContext.getString(R.string.move_to_hotseat_position, id + 1);
-            } else {
-                return mContext.getString(R.string.move_to_empty_cell, y + 1, x + 1);
-            }
+            return mView.getItemMoveDescription(x, y);
         } else {
             return getDescriptionForDropOver(child, mContext);
         }
diff --git a/src/com/android/launcher3/allapps/AllAppsBackgroundDrawable.java b/src/com/android/launcher3/allapps/AllAppsBackgroundDrawable.java
index cfd07e6..c71bc31 100644
--- a/src/com/android/launcher3/allapps/AllAppsBackgroundDrawable.java
+++ b/src/com/android/launcher3/allapps/AllAppsBackgroundDrawable.java
@@ -23,71 +23,73 @@
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
-
 import android.view.Gravity;
+
 import com.android.launcher3.R;
 
 /**
- * A helper class to positon and orient a drawable to be drawn.
- */
-class TransformedImageDrawable {
-    private Drawable mImage;
-    private float mXPercent;
-    private float mYPercent;
-    private int mGravity;
-    private int mAlpha;
-
-    /**
-     * @param gravity If one of the Gravity center values, the x and y offset will take the width
-     *                and height of the image into account to center the image to the offset.
-     */
-    public TransformedImageDrawable(Resources res, int resourceId, float xPct, float yPct,
-            int gravity) {
-        mImage = res.getDrawable(resourceId);
-        mXPercent = xPct;
-        mYPercent = yPct;
-        mGravity = gravity;
-    }
-
-    public void setAlpha(int alpha) {
-        mImage.setAlpha(alpha);
-        mAlpha = alpha;
-    }
-
-    public int getAlpha() {
-        return mAlpha;
-    }
-
-    public void updateBounds(Rect bounds) {
-        int width = mImage.getIntrinsicWidth();
-        int height = mImage.getIntrinsicHeight();
-        int left = bounds.left + (int) (mXPercent * bounds.width());
-        int top = bounds.top + (int) (mYPercent * bounds.height());
-        if ((mGravity & Gravity.CENTER_HORIZONTAL) == Gravity.CENTER_HORIZONTAL) {
-            left -= (width / 2);
-        }
-        if ((mGravity & Gravity.CENTER_VERTICAL) == Gravity.CENTER_VERTICAL) {
-            top -= (height / 2);
-        }
-        mImage.setBounds(left, top, left + width, top + height);
-    }
-
-    public void draw(Canvas canvas) {
-        int c = canvas.save(Canvas.MATRIX_SAVE_FLAG);
-        mImage.draw(canvas);
-        canvas.restoreToCount(c);
-    }
-}
-
-/**
  * This is a custom composite drawable that has a fixed virtual size and dynamically lays out its
  * children images relatively within its bounds.  This way, we can reduce the memory usage of a
  * single, large sparsely populated image.
  */
 public class AllAppsBackgroundDrawable extends Drawable {
 
-    private final TransformedImageDrawable mHand;
-    private final TransformedImageDrawable[] mIcons;
+    /**
+     * A helper class to positon and orient a drawable to be drawn.
+     */
+    protected static class TransformedImageDrawable {
+        private Drawable mImage;
+        private float mXPercent;
+        private float mYPercent;
+        private int mGravity;
+        private int mAlpha;
+
+        /**
+         * @param gravity If one of the Gravity center values, the x and y offset will take the width
+         *                and height of the image into account to center the image to the offset.
+         */
+        public TransformedImageDrawable(Resources res, int resourceId, float xPct, float yPct,
+                int gravity) {
+            mImage = res.getDrawable(resourceId);
+            mXPercent = xPct;
+            mYPercent = yPct;
+            mGravity = gravity;
+        }
+
+        public void setAlpha(int alpha) {
+            mImage.setAlpha(alpha);
+            mAlpha = alpha;
+        }
+
+        public int getAlpha() {
+            return mAlpha;
+        }
+
+        public void updateBounds(Rect bounds) {
+            int width = mImage.getIntrinsicWidth();
+            int height = mImage.getIntrinsicHeight();
+            int left = bounds.left + (int) (mXPercent * bounds.width());
+            int top = bounds.top + (int) (mYPercent * bounds.height());
+            if ((mGravity & Gravity.CENTER_HORIZONTAL) == Gravity.CENTER_HORIZONTAL) {
+                left -= (width / 2);
+            }
+            if ((mGravity & Gravity.CENTER_VERTICAL) == Gravity.CENTER_VERTICAL) {
+                top -= (height / 2);
+            }
+            mImage.setBounds(left, top, left + width, top + height);
+        }
+
+        public void draw(Canvas canvas) {
+            mImage.draw(canvas);
+        }
+
+        public Rect getBounds() {
+            return mImage.getBounds();
+        }
+    }
+
+    protected final TransformedImageDrawable mHand;
+    protected final TransformedImageDrawable[] mIcons;
     private final int mWidth;
     private final int mHeight;
 
@@ -188,7 +190,6 @@
 
     private ObjectAnimator cancelAnimator(ObjectAnimator animator) {
         if (animator != null) {
-            animator.removeAllListeners();
             animator.cancel();
         }
         return null;
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 77ef642..2c7d156 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -15,11 +15,13 @@
  */
 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.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.v7.widget.RecyclerView;
 import android.text.Selection;
 import android.text.Spannable;
@@ -31,139 +33,60 @@
 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.discovery.AppDiscoveryItem;
+import com.android.launcher3.discovery.AppDiscoveryUpdateState;
+import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.graphics.TintedDrawableSpan;
 import com.android.launcher3.keyboard.FocusedItemDecorator;
-import com.android.launcher3.shortcuts.DeepShortcutsContainer;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.PackageUserKey;
 
-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;
-    }
-}
+import java.util.Set;
 
 /**
  * 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 +98,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 +201,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.
@@ -281,14 +214,9 @@
             return false;
         }
 
-        // IF a shortcuts container is open, container should not be pulled down.
-        if (mLauncher.getOpenShortcutsContainer() != 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.getCurrentScrollY() == 0) {
             return true;
         }
         return false;
@@ -340,12 +268,7 @@
                 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);
+        mElevationController = new HeaderElevationController(mSearchContainer);
 
         // Load the all apps recycler view
         mAppsRecyclerView = (AllAppsRecyclerView) findViewById(R.id.apps_list_view);
@@ -356,10 +279,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 +292,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 +311,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 +328,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 +365,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,37 +377,24 @@
         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);
-        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)
-            mLauncher.enterSpringLoadedDragMode();
-        }
 
+            @Override
+            public void onDragEnd() {
+                v.setVisibility(VISIBLE);
+                dragController.removeDragListener(this);
+            }
+        });
+        mLauncher.getWorkspace().beginDragShared(v, this, new DragOptions());
         return false;
     }
 
     @Override
-    public boolean supportsFlingToDelete() {
-        return true;
-    }
-
-    @Override
     public boolean supportsAppInfoDropTarget() {
         return true;
     }
@@ -596,14 +411,6 @@
     }
 
     @Override
-    public void onFlingToDeleteCompleted() {
-        // We just dismiss the drag when we fling, so cleanup here
-        mLauncher.exitSpringLoadedDragModeDelayed(true,
-                Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
-        mLauncher.unlockScreenOrientation(false);
-    }
-
-    @Override
     public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete,
             boolean success) {
         if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() &&
@@ -615,111 +422,30 @@
         }
         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)) {
-                mAppsRecyclerView.onSearchResultsChanged();
-            }
+            mApps.setOrderedFilter(apps);
+            mAppsRecyclerView.onSearchResultsChanged();
             mAdapter.setLastSearchQuery(query);
         }
     }
 
     @Override
+    public void onAppDiscoverySearchUpdate(@Nullable AppDiscoveryItem app,
+            @NonNull AppDiscoveryUpdateState state) {
+        if (!mLauncher.isDestroyed()) {
+            mApps.onAppDiscoverySearchUpdate(app, state);
+            mAppsRecyclerView.onSearchResultsChanged();
+        }
+    }
+
+    @Override
     public void clearSearchResult() {
         if (mApps.setOrderedFilter(null)) {
             mAppsRecyclerView.onSearchResultsChanged();
@@ -732,11 +458,44 @@
     }
 
     @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);
+        }
+    }
+
+    public void updateIconBadges(Set<PackageUserKey> updatedBadges) {
+        final PackageUserKey packageUserKey = new PackageUserKey(null, null);
+        final int n = mAppsRecyclerView.getChildCount();
+        for (int i = 0; i < n; i++) {
+            View child = mAppsRecyclerView.getChildAt(i);
+            if (!(child instanceof BubbleTextView) || !(child.getTag() instanceof ItemInfo)) {
+                continue;
+            }
+            ItemInfo info = (ItemInfo) child.getTag();
+            if (packageUserKey.updateFromItemInfo(info) && updatedBadges.contains(packageUserKey)) {
+                ((BubbleTextView) child).applyBadgeState(info, true /* animate */);
+            }
+        }
+    }
 }
diff --git a/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java b/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
index 76934af..a1ff822 100644
--- a/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
+++ b/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
@@ -19,6 +19,7 @@
 import android.view.View;
 
 import com.android.launcher3.BaseRecyclerViewFastScrollBar;
+import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.util.Thunk;
 
@@ -29,12 +30,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;
@@ -46,8 +46,7 @@
 
     // Set of all views animated during fast scroll.  We keep track of these ourselves since there
     // is no way to reset a view once it gets scrapped or recycled without other hacks
-    private HashSet<BaseRecyclerViewFastScrollBar.FastScrollFocusableView> mTrackedFastScrollViews =
-            new HashSet<>();
+    private HashSet<RecyclerView.ViewHolder> mTrackedFastScrollViews = new HashSet<>();
 
     // Smooth fast-scroll animation frames
     @Thunk int mFastScrollFrameIndex;
@@ -187,12 +186,7 @@
     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) {
-                BaseRecyclerViewFastScrollBar.FastScrollFocusableView v =
-                        (BaseRecyclerViewFastScrollBar.FastScrollFocusableView) holder.mContent;
-                updateViewFastScrollFocusState(v, holder.getPosition(), false /* animated */);
-                mTrackedFastScrollViews.add(v);
-            }
+            mTrackedFastScrollViews.add(holder);
         }
     }
 
@@ -202,9 +196,9 @@
     private void trackAllChildViews() {
         int childCount = mRv.getChildCount();
         for (int i = 0; i < childCount; i++) {
-            View v = mRv.getChildAt(i);
-            if (v instanceof BaseRecyclerViewFastScrollBar.FastScrollFocusableView) {
-                mTrackedFastScrollViews.add((BaseRecyclerViewFastScrollBar.FastScrollFocusableView) v);
+            RecyclerView.ViewHolder viewHolder = mRv.getChildViewHolder(mRv.getChildAt(i));
+            if (viewHolder != null) {
+                mTrackedFastScrollViews.add(viewHolder);
             }
         }
     }
@@ -213,27 +207,16 @@
      * Updates the fast scroll focus on all the children.
      */
     private void updateTrackedViewsFastScrollFocusState() {
-        for (BaseRecyclerViewFastScrollBar.FastScrollFocusableView v : mTrackedFastScrollViews) {
-            RecyclerView.ViewHolder viewHolder = mRv.getChildViewHolder((View) v);
-            int pos = (viewHolder != null) ? viewHolder.getPosition() : -1;
-            updateViewFastScrollFocusState(v, pos, true);
+        for (RecyclerView.ViewHolder viewHolder : mTrackedFastScrollViews) {
+            int pos = viewHolder.getAdapterPosition();
+            boolean isActive = false;
+            if (mCurrentFastScrollSection != null && pos > -1) {
+                AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(pos);
+                isActive = item != null &&
+                        mCurrentFastScrollSection.equals(item.sectionName) &&
+                        item.position == mTargetFastScrollPosition;
+            }
+            viewHolder.itemView.setActivated(isActive);
         }
     }
-
-    /**
-     * Updates the fast scroll focus on all a given view.
-     */
-    private void updateViewFastScrollFocusState(BaseRecyclerViewFastScrollBar.FastScrollFocusableView v,
-                                                int pos, boolean animated) {
-        FastBitmapDrawable.State newState = FastBitmapDrawable.State.NORMAL;
-        if (mCurrentFastScrollSection != null && pos > -1) {
-            AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(pos);
-            boolean highlight = item.sectionName.equals(mCurrentFastScrollSection) &&
-                    item.position == mTargetFastScrollPosition;
-            newState = highlight ?
-                    FastBitmapDrawable.State.FAST_SCROLL_HIGHLIGHTED :
-                    FastBitmapDrawable.State.FAST_SCROLL_UNHIGHLIGHTED;
-        }
-        v.setFastScrollFocusState(newState, animated);
-    }
 }
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 7b6aef1..59cac8d 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -18,12 +18,9 @@
 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.AccessibilityNodeInfoCompat;
 import android.support.v4.view.accessibility.AccessibilityRecordCompat;
 import android.support.v7.widget.GridLayoutManager;
 import android.support.v7.widget.RecyclerView;
@@ -36,14 +33,14 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.TextView;
 
+import com.android.launcher3.discovery.AppDiscoveryAppInfo;
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
+import com.android.launcher3.allapps.AlphabeticalAppsList.AdapterItem;
+import com.android.launcher3.discovery.AppDiscoveryItemView;
 
-import java.util.HashMap;
 import java.util.List;
 
 /**
@@ -52,10 +49,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
@@ -74,29 +68,29 @@
     public static final int VIEW_TYPE_SEARCH_DIVIDER = 1 << 6;
     // The divider that separates prediction icons from the app list
     public static final int VIEW_TYPE_PREDICTION_DIVIDER = 1 << 7;
+    public static final int VIEW_TYPE_APPS_LOADING_DIVIDER = 1 << 8;
+    public static final int VIEW_TYPE_DISCOVERY_ITEM = 1 << 9;
 
     // 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 static final int VIEW_TYPE_MASK_CONTENT = VIEW_TYPE_MASK_ICON
+            | VIEW_TYPE_DISCOVERY_ITEM;
 
 
     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;
         }
     }
 
@@ -118,17 +112,53 @@
             final AccessibilityRecordCompat record = AccessibilityEventCompat
                     .asRecord(event);
             record.setItemCount(mApps.getNumFilteredApps());
+            record.setFromIndex(Math.max(0,
+                    record.getFromIndex() - getRowsNotForAccessibility(record.getFromIndex())));
+            record.setToIndex(Math.max(0,
+                    record.getToIndex() - getRowsNotForAccessibility(record.getToIndex())));
         }
 
         @Override
         public int getRowCountForAccessibility(RecyclerView.Recycler recycler,
                 RecyclerView.State state) {
-            if (mApps.hasNoFilteredResults()) {
-                // Disregard the no-search-results text as a list item for accessibility
-                return 0;
-            } else {
-                return super.getRowCountForAccessibility(recycler, state);
+            return super.getRowCountForAccessibility(recycler, state) -
+                    getRowsNotForAccessibility(mApps.getAdapterItems().size() - 1);
+        }
+
+        @Override
+        public void onInitializeAccessibilityNodeInfoForItem(RecyclerView.Recycler recycler,
+                RecyclerView.State state, View host, AccessibilityNodeInfoCompat info) {
+            super.onInitializeAccessibilityNodeInfoForItem(recycler, state, host, info);
+
+            ViewGroup.LayoutParams lp = host.getLayoutParams();
+            AccessibilityNodeInfoCompat.CollectionItemInfoCompat cic = info.getCollectionItemInfo();
+            if (!(lp instanceof LayoutParams) || (cic == null)) {
+                return;
             }
+            LayoutParams glp = (LayoutParams) lp;
+            info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(
+                    cic.getRowIndex() - getRowsNotForAccessibility(glp.getViewAdapterPosition()),
+                    cic.getRowSpan(),
+                    cic.getColumnIndex(),
+                    cic.getColumnSpan(),
+                    cic.isHeading(),
+                    cic.isSelected()));
+        }
+
+        /**
+         * Returns the number of rows before {@param adapterPosition}, including this position
+         * which should not be counted towards the collection info.
+         */
+        private int getRowsNotForAccessibility(int adapterPosition) {
+            List<AdapterItem> items = mApps.getAdapterItems();
+            adapterPosition = Math.max(adapterPosition, mApps.getAdapterItems().size() - 1);
+            int extraRows = 0;
+            for (int i = 0; i <= adapterPosition; i++) {
+                if (!isViewType(items.get(i).viewType, VIEW_TYPE_MASK_CONTENT)) {
+                    extraRows++;
+                }
+            }
+            return extraRows;
         }
 
         @Override
@@ -158,189 +188,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 +216,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,36 +267,17 @@
     }
 
     /**
-     * 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: {
+            case VIEW_TYPE_PREDICTION_ICON:
                 BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
                         R.layout.all_apps_icon, parent, false);
                 icon.setOnClickListener(mIconClickListener);
@@ -460,14 +287,14 @@
                 icon.setOnFocusChangeListener(mIconFocusListener);
 
                 // Ensure the all apps icon height matches the workspace icons
-                DeviceProfile profile = mLauncher.getDeviceProfile();
-                Point cellSize = profile.getCellSize();
-                GridLayoutManager.LayoutParams lp =
-                        (GridLayoutManager.LayoutParams) icon.getLayoutParams();
-                lp.height = cellSize.y;
-                icon.setLayoutParams(lp);
+                icon.getLayoutParams().height = getCellSize().y;
                 return new ViewHolder(icon);
-            }
+            case VIEW_TYPE_DISCOVERY_ITEM:
+                AppDiscoveryItemView appDiscoveryItemView = (AppDiscoveryItemView) mLayoutInflater
+                        .inflate(R.layout.all_apps_discovery_item, parent, false);
+                appDiscoveryItemView.init(mIconClickListener, mLauncher.getAccessibilityDelegate(),
+                        mIconLongClickListener);
+                return new ViewHolder(appDiscoveryItemView);
             case VIEW_TYPE_EMPTY_SEARCH:
                 return new ViewHolder(mLayoutInflater.inflate(R.layout.all_apps_empty_search,
                         parent, false));
@@ -484,8 +311,11 @@
             case VIEW_TYPE_SEARCH_DIVIDER:
                 return new ViewHolder(mLayoutInflater.inflate(
                         R.layout.all_apps_search_divider, parent, false));
+            case VIEW_TYPE_APPS_LOADING_DIVIDER:
+                View loadingDividerView = mLayoutInflater.inflate(
+                        R.layout.all_apps_discovery_loading_divider, parent, false);
+                return new ViewHolder(loadingDividerView);
             case VIEW_TYPE_PREDICTION_DIVIDER:
-                /* falls through */
             case VIEW_TYPE_SEARCH_MARKET_DIVIDER:
                 return new ViewHolder(mLayoutInflater.inflate(
                         R.layout.all_apps_divider, parent, false));
@@ -494,37 +324,49 @@
         }
     }
 
+    private Point getCellSize() {
+        return mLauncher.getDeviceProfile().getCellSize();
+    }
+
     @Override
     public void onBindViewHolder(ViewHolder holder, int position) {
         switch (holder.getItemViewType()) {
-            case VIEW_TYPE_ICON: {
+            case VIEW_TYPE_ICON:
+            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_PREDICTION_ICON: {
-                AppInfo info = mApps.getAdapterItems().get(position).appInfo;
-                BubbleTextView icon = (BubbleTextView) holder.mContent;
-                icon.applyFromApplicationInfo(info);
-                icon.setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
+            case VIEW_TYPE_DISCOVERY_ITEM:
+                AppDiscoveryAppInfo appDiscoveryAppInfo = (AppDiscoveryAppInfo)
+                        mApps.getAdapterItems().get(position).appInfo;
+                AppDiscoveryItemView view = (AppDiscoveryItemView) holder.itemView;
+                view.apply(appDiscoveryAppInfo);
                 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 {
                     searchView.setVisibility(View.GONE);
                 }
                 break;
+            case VIEW_TYPE_APPS_LOADING_DIVIDER:
+                int visLoading = mApps.isAppDiscoveryRunning() ? View.VISIBLE : View.GONE;
+                int visLoaded = !mApps.isAppDiscoveryRunning() ? View.VISIBLE : View.GONE;
+                holder.itemView.findViewById(R.id.loadingProgressBar).setVisibility(visLoading);
+                holder.itemView.findViewById(R.id.loadedDivider).setVisibility(visLoaded);
+                break;
+            case VIEW_TYPE_SEARCH_MARKET_DIVIDER:
+                // nothing to do
+                break;
         }
         if (mBindViewCallback != null) {
             mBindViewCallback.onBindView(holder);
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 0173847..64e2fcb 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -22,6 +22,7 @@
 import android.support.v7.widget.RecyclerView;
 import android.util.AttributeSet;
 import android.util.SparseIntArray;
+import android.view.MotionEvent;
 import android.view.View;
 
 import com.android.launcher3.BaseRecyclerView;
@@ -29,7 +30,9 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.graphics.DrawableFactory;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 
 import java.util.List;
 
@@ -101,7 +104,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);
     }
 
     /**
@@ -109,47 +111,40 @@
      * all the different view types.
      */
     public void preMeasureViews(AllAppsGridAdapter adapter) {
+        View icon = adapter.onCreateViewHolder(this, AllAppsGridAdapter.VIEW_TYPE_ICON).itemView;
+        final int iconHeight = icon.getLayoutParams().height;
+        mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_ICON, iconHeight);
+        mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_PREDICTION_ICON, iconHeight);
+
         final int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(
                 getResources().getDisplayMetrics().widthPixels, View.MeasureSpec.AT_MOST);
         final int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(
                 getResources().getDisplayMetrics().heightPixels, View.MeasureSpec.AT_MOST);
 
-        // Icons
-        BubbleTextView icon = (BubbleTextView) adapter.onCreateViewHolder(this,
-                AllAppsGridAdapter.VIEW_TYPE_ICON).mContent;
-        int iconHeight = icon.getLayoutParams().height;
-        mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_ICON, iconHeight);
-        mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_PREDICTION_ICON, iconHeight);
+        putSameHeightFor(adapter, widthMeasureSpec, heightMeasureSpec,
+                AllAppsGridAdapter.VIEW_TYPE_PREDICTION_DIVIDER,
+                AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET_DIVIDER);
+        putSameHeightFor(adapter, widthMeasureSpec, heightMeasureSpec,
+                AllAppsGridAdapter.VIEW_TYPE_SEARCH_DIVIDER);
+        putSameHeightFor(adapter, widthMeasureSpec, heightMeasureSpec,
+                AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET);
+        putSameHeightFor(adapter, widthMeasureSpec, heightMeasureSpec,
+                AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH);
 
-        // Search divider
-        View searchDivider = adapter.onCreateViewHolder(this,
-                AllAppsGridAdapter.VIEW_TYPE_SEARCH_DIVIDER).mContent;
-        searchDivider.measure(widthMeasureSpec, heightMeasureSpec);
-        int searchDividerHeight = searchDivider.getMeasuredHeight();
-        mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_SEARCH_DIVIDER, searchDividerHeight);
+        if (FeatureFlags.DISCOVERY_ENABLED) {
+            putSameHeightFor(adapter, widthMeasureSpec, heightMeasureSpec,
+                    AllAppsGridAdapter.VIEW_TYPE_APPS_LOADING_DIVIDER);
+            putSameHeightFor(adapter, widthMeasureSpec, heightMeasureSpec,
+                    AllAppsGridAdapter.VIEW_TYPE_DISCOVERY_ITEM);
+        }
+    }
 
-        // Generic dividers
-        View divider = adapter.onCreateViewHolder(this,
-                AllAppsGridAdapter.VIEW_TYPE_PREDICTION_DIVIDER).mContent;
-        divider.measure(widthMeasureSpec, heightMeasureSpec);
-        int dividerHeight = divider.getMeasuredHeight();
-        mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_PREDICTION_DIVIDER, dividerHeight);
-        mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET_DIVIDER, dividerHeight);
-
-        // Search views
-        View emptySearch = adapter.onCreateViewHolder(this,
-                AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH).mContent;
-        emptySearch.measure(widthMeasureSpec, heightMeasureSpec);
-        mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH,
-                emptySearch.getMeasuredHeight());
-        View searchMarket = adapter.onCreateViewHolder(this,
-                AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET).mContent;
-        searchMarket.measure(widthMeasureSpec, heightMeasureSpec);
-        mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET,
-                searchMarket.getMeasuredHeight());
-
-        // Section breaks
-        mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_SECTION_BREAK, 0);
+    private void putSameHeightFor(AllAppsGridAdapter adapter, int w, int h, int... viewTypes) {
+        View view = adapter.onCreateViewHolder(this, viewTypes[0]).itemView;
+        view.measure(w, h);
+        for (int viewType : viewTypes) {
+            mViewHeights.put(viewType, view.getMeasuredHeight());
+        }
     }
 
     /**
@@ -166,27 +161,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);
         }
 
@@ -205,7 +183,7 @@
 
     public int getContainerType(View v) {
         if (mApps.hasFilter()) {
-            return LauncherLogProto.SEARCHRESULT;
+            return ContainerType.SEARCHRESULT;
         } else {
             if (v instanceof BubbleTextView) {
                 BubbleTextView icon = (BubbleTextView) v;
@@ -214,11 +192,11 @@
                     List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
                     AlphabeticalAppsList.AdapterItem item = items.get(position);
                     if (item.viewType == AllAppsGridAdapter.VIEW_TYPE_PREDICTION_ICON) {
-                        return LauncherLogProto.PREDICTION;
+                        return ContainerType.PREDICTION;
                     }
                 }
             }
-            return LauncherLogProto.ALLAPPS;
+            return ContainerType.ALLAPPS;
         }
     }
 
@@ -226,9 +204,10 @@
         // Always scroll the view to the top so the user can see the changed results
         scrollToTop();
 
-        if (mApps.hasNoFilteredResults()) {
+        if (mApps.shouldShowEmptySearch()) {
             if (mEmptySearchBackground == null) {
-                mEmptySearchBackground = new AllAppsBackgroundDrawable(getContext());
+                mEmptySearchBackground = DrawableFactory.get(getContext())
+                        .getAllAppsBackground(getContext());
                 mEmptySearchBackground.setAlpha(0);
                 mEmptySearchBackground.setCallback(this);
                 updateEmptySearchBackgroundBounds();
@@ -241,6 +220,16 @@
         }
     }
 
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent e) {
+        boolean result = super.onInterceptTouchEvent(e);
+        if (!result && e.getAction() == MotionEvent.ACTION_DOWN
+                && mEmptySearchBackground != null && mEmptySearchBackground.getAlpha() > 0) {
+            mEmptySearchBackground.setHotspot(e.getX(), e.getY());
+        }
+        return result;
+    }
+
     /**
      * Maps the touch (from 0..1) to the adapter position that should be visible.
      */
@@ -299,14 +288,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 +303,7 @@
         int availableScrollBarHeight = getAvailableScrollBarHeight();
         int availableScrollHeight = getAvailableScrollHeight();
         if (availableScrollHeight <= 0) {
-            mScrollbar.setThumbOffset(-1, -1);
+            mScrollbar.setThumbOffsetY(-1);
             return;
         }
 
@@ -323,11 +312,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 +332,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 +340,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 +404,8 @@
     }
 
     @Override
-    protected int getVisibleHeight() {
-        return super.getVisibleHeight()
+    protected int getScrollbarTrackHeight() {
+        return super.getScrollbarTrackHeight()
                 - Launcher.getLauncher(getContext()).getDragLayer().getInsets().bottom;
     }
 
@@ -429,7 +417,7 @@
     protected int getAvailableScrollHeight() {
         int paddedHeight = getCurrentScrollY(mApps.getAdapterItems().size(), 0);
         int totalHeight = paddedHeight + getPaddingBottom();
-        return totalHeight - getVisibleHeight();
+        return totalHeight - getScrollbarTrackHeight();
     }
 
     /**
@@ -447,4 +435,5 @@
                 x + mEmptySearchBackground.getIntrinsicWidth(),
                 y + mEmptySearchBackground.getIntrinsicHeight());
     }
+
 }
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/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/AllAppsSearchBarController.java
index 365ab31..c7ba3ab 100644
--- a/src/com/android/launcher3/allapps/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/AllAppsSearchBarController.java
@@ -19,6 +19,8 @@
 import android.content.Intent;
 import android.graphics.Rect;
 import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.text.Editable;
 import android.text.TextUtils;
 import android.text.TextWatcher;
@@ -32,6 +34,8 @@
 import com.android.launcher3.ExtendedEditText;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.discovery.AppDiscoveryItem;
+import com.android.launcher3.discovery.AppDiscoveryUpdateState;
 import com.android.launcher3.util.ComponentKey;
 
 import java.util.ArrayList;
@@ -46,7 +50,7 @@
     protected AlphabeticalAppsList mApps;
     protected Callbacks mCb;
     protected ExtendedEditText mInput;
-    private String mQuery;
+    protected String mQuery;
 
     protected DefaultAppSearchAlgorithm mSearchAlgorithm;
     protected InputMethodManager mInputMethodManager;
@@ -73,6 +77,14 @@
                 mInput.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
 
         mSearchAlgorithm = onInitializeSearch();
+
+        onInitialized();
+    }
+
+    /**
+     * You can override this method to perform custom initialization.
+     */
+    protected void onInitialized() {
     }
 
     /**
@@ -117,6 +129,7 @@
         if (actionId != EditorInfo.IME_ACTION_SEARCH) {
             return false;
         }
+
         // Skip if the query is empty
         String query = v.getText().toString();
         if (query.isEmpty()) {
@@ -206,5 +219,19 @@
          * Called when the search results should be cleared.
          */
         void clearSearchResult();
+
+
+        /**
+         * Called when the app discovery is providing an update of search, which can either be
+         * START for starting a new discovery,
+         * UPDATE for providing a new search result, can be called multiple times,
+         * END for indicating the end of results.
+         *
+         * @param app result item if UPDATE, else null
+         * @param app the update state, START, UPDATE or END
+         */
+        void onAppDiscoverySearchUpdate(@Nullable AppDiscoveryItem app,
+                @NonNull AppDiscoveryUpdateState state);
     }
+
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index b129bb0..30ed180 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -7,24 +7,24 @@
 import android.animation.ArgbEvaluator;
 import android.animation.ObjectAnimator;
 import android.graphics.Color;
-import android.support.v4.content.ContextCompat;
 import android.support.v4.graphics.ColorUtils;
 import android.support.v4.view.animation.FastOutSlowInInterpolator;
-import android.util.Log;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.animation.AccelerateInterpolator;
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
 
-import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Hotseat;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.TouchController;
 
 /**
@@ -46,11 +46,10 @@
     private final Interpolator mAccelInterpolator = new AccelerateInterpolator(2f);
     private final Interpolator mDecelInterpolator = new DecelerateInterpolator(3f);
     private final Interpolator mFastOutSlowInInterpolator = new FastOutSlowInInterpolator();
-    private final ScrollInterpolator mScrollInterpolator = new ScrollInterpolator();
+    private final VerticalPullDetector.ScrollInterpolator mScrollInterpolator
+            = new VerticalPullDetector.ScrollInterpolator();
 
-    private static final float ANIMATION_DURATION = 1200;
     private static final float PARALLAX_COEFFICIENT = .125f;
-    private static final float FAST_FLING_PX_MS = 10;
     private static final int SINGLE_FRAME_MS = 16;
 
     private AllAppsContainerView mAppsView;
@@ -84,7 +83,6 @@
 
     private static final float RECATCH_REJECTION_FRACTION = .0875f;
 
-    private int mBezelSwipeUpHeight;
     private long mAnimationDuration;
 
     private AnimatorSet mCurrentAnimation;
@@ -100,15 +98,13 @@
         mDetector.setListener(this);
         mShiftRange = DEFAULT_SHIFT_RANGE;
         mProgress = 1f;
-        mBezelSwipeUpHeight = l.getResources().getDimensionPixelSize(
-                R.dimen.all_apps_bezel_swipe_height);
 
         mEvaluator = new ArgbEvaluator();
-        mAllAppsBackgroundColor = ContextCompat.getColor(l, R.color.all_apps_container_color);
+        mAllAppsBackgroundColor = Themes.getAttrColor(l, android.R.attr.colorPrimary);
     }
 
     @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()) {
@@ -116,7 +112,7 @@
             } else if (mLauncher.isAllAppsVisible() &&
                     !mAppsView.shouldContainerScroll(ev)) {
                 mNoIntercept = true;
-            } else if (!mLauncher.isAllAppsVisible() && !shouldPossiblyIntercept(ev)) {
+            } else if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
                 mNoIntercept = true;
             } else {
                 // Now figure out which direction scroll events the controller will start
@@ -144,6 +140,7 @@
                         ignoreSlopWhenSettling);
             }
         }
+
         if (mNoIntercept) {
             return false;
         }
@@ -154,27 +151,8 @@
         return mDetector.isDraggingOrSettling();
     }
 
-    private boolean shouldPossiblyIntercept(MotionEvent ev) {
-        DeviceProfile grid = mLauncher.getDeviceProfile();
-        if (mDetector.isIdleState()) {
-            if (grid.isVerticalBarLayout()) {
-                if (ev.getY() > mLauncher.getDeviceProfile().heightPx - mBezelSwipeUpHeight) {
-                    return true;
-                }
-            } else {
-                if (mLauncher.getDragLayer().isEventOverHotseat(ev) ||
-                        mLauncher.getDragLayer().isEventOverPageIndicator(ev)) {
-                    return true;
-                }
-            }
-            return false;
-        } else {
-            return true;
-        }
-    }
-
     @Override
-    public boolean onTouchEvent(MotionEvent ev) {
+    public boolean onControllerTouchEvent(MotionEvent ev) {
         return mDetector.onTouchEvent(ev);
     }
 
@@ -221,9 +199,9 @@
 
                 if (!mLauncher.isAllAppsVisible()) {
                     mLauncher.getUserEventDispatcher().logActionOnContainer(
-                            LauncherLogProto.Action.FLING,
-                            LauncherLogProto.Action.UP,
-                            LauncherLogProto.HOTSEAT);
+                            Action.Touch.FLING,
+                            Action.Direction.UP,
+                            ContainerType.HOTSEAT);
                 }
                 mLauncher.showAppsView(true /* animated */,
                         false /* updatePredictedApps */,
@@ -241,9 +219,9 @@
                 calculateDuration(velocity, Math.abs(mAppsView.getTranslationY()));
                 if (!mLauncher.isAllAppsVisible()) {
                     mLauncher.getUserEventDispatcher().logActionOnContainer(
-                            LauncherLogProto.Action.SWIPE,
-                            LauncherLogProto.Action.UP,
-                            LauncherLogProto.HOTSEAT);
+                            Action.Touch.SWIPE,
+                            Action.Direction.UP,
+                            ContainerType.HOTSEAT);
                 }
                 mLauncher.showAppsView(true, /* animated */
                         false /* updatePredictedApps */,
@@ -282,7 +260,7 @@
         // Use a light status bar (dark icons) if all apps is behind at least half of the status
         // bar. If the status bar is already light due to wallpaper extraction, keep it that way.
         boolean forceLight = shift <= mStatusBarHeight / 2;
-        mLauncher.activateLightStatusBar(forceLight);
+        mLauncher.activateLightSystemBars(forceLight, true /* statusBar */, true /* navBar */);
     }
 
     /**
@@ -335,13 +313,7 @@
     }
 
     private void calculateDuration(float velocity, float disp) {
-        // TODO: make these values constants after tuning.
-        float velocityDivisor = Math.max(2f, Math.abs(0.5f * velocity));
-        float travelDistance = Math.max(0.2f, disp / mShiftRange);
-        mAnimationDuration = (long) Math.max(100, ANIMATION_DURATION / velocityDivisor * travelDistance);
-        if (DBG) {
-            Log.d(TAG, String.format("calculateDuration=%d, v=%f, d=%f", mAnimationDuration, velocity, disp));
-        }
+        mAnimationDuration = mDetector.calculateDuration(velocity, disp / mShiftRange);
     }
 
     public boolean animateToAllApps(AnimatorSet animationOut, long duration) {
@@ -531,21 +503,4 @@
         setProgress(mProgress);
     }
 
-    static class ScrollInterpolator implements Interpolator {
-
-        boolean mSteeper;
-
-        public void setVelocityAtZero(float velocity) {
-            mSteeper = velocity > FAST_FLING_PX_MS;
-        }
-
-        public float getInterpolation(float t) {
-            t -= 1.0f;
-            float output = t * t * t;
-            if (mSteeper) {
-                output *= t * t; // Make interpolation initial slope steeper
-            }
-            return output + 1;
-        }
-    }
 }
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 173065b..f5cf7ef 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -16,15 +16,20 @@
 package com.android.launcher3.allapps;
 
 import android.content.Context;
+import android.os.Process;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.util.Log;
 
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.Launcher;
 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.discovery.AppDiscoveryAppInfo;
+import com.android.launcher3.discovery.AppDiscoveryItem;
+import com.android.launcher3.discovery.AppDiscoveryUpdateState;
 import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.LabelComparator;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -48,17 +53,7 @@
 
     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;
-    }
+    private AppDiscoveryUpdateState mAppDiscoveryUpdateState;
 
     /**
      * Info about a fast scroller section, depending if sections are merged, the fast scroller
@@ -87,16 +82,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 +95,30 @@
         // 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;
+        }
+
+        public static AdapterItem asDiscoveryItem(int pos, String sectionName, AppInfo appInfo,
+                int appIndex) {
+            AdapterItem item = new AdapterItem();
+            item.viewType = AllAppsGridAdapter.VIEW_TYPE_DISCOVERY_ITEM;
+            item.position = pos;
+            item.sectionName = sectionName;
             item.appInfo = appInfo;
             item.appIndex = appIndex;
             return item;
@@ -149,7 +138,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;
@@ -163,6 +152,13 @@
             return item;
         }
 
+        public static AdapterItem asLoadingDivider(int pos) {
+            AdapterItem item = new AdapterItem();
+            item.viewType = AllAppsGridAdapter.VIEW_TYPE_APPS_LOADING_DIVIDER;
+            item.position = pos;
+            return item;
+        }
+
         public static AdapterItem asMarketSearch(int pos) {
             AdapterItem item = new AdapterItem();
             item.viewType = AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET;
@@ -171,39 +167,30 @@
         }
     }
 
-    /**
-     * Common interface for different merging strategies.
-     */
-    public interface MergeAlgorithm {
-        boolean continueMerging(SectionInfo section, SectionInfo withSection,
-                int sectionAppCount, int numAppsPerRow, int mergeCount);
-    }
-
-    private Launcher mLauncher;
+    private final Launcher mLauncher;
 
     // The set of apps from the system not including predictions
     private final List<AppInfo> mApps = new ArrayList<>();
     private final HashMap<ComponentKey, AppInfo> mComponentToAppMap = new HashMap<>();
 
     // The set of filtered apps with the current filter
-    private List<AppInfo> mFilteredApps = new ArrayList<>();
+    private final 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<>();
+    private final ArrayList<AdapterItem> mAdapterItems = new ArrayList<>();
     // The set of sections that we allow fast-scrolling to (includes non-merged sections)
-    private List<FastScrollSectionInfo> mFastScrollerSections = new ArrayList<>();
+    private final List<FastScrollSectionInfo> mFastScrollerSections = new ArrayList<>();
     // The set of predicted app component names
-    private List<ComponentKey> mPredictedAppComponents = new ArrayList<>();
+    private final List<ComponentKey> mPredictedAppComponents = new ArrayList<>();
     // The set of predicted apps resolved from the component names and the current set of apps
-    private List<AppInfo> mPredictedApps = new ArrayList<>();
+    private final List<AppInfo> mPredictedApps = new ArrayList<>();
+    private final List<AppDiscoveryAppInfo> mDiscoveredApps = new ArrayList<>();
+
     // The of ordered component names as a result of a search query
     private ArrayList<ComponentKey> mSearchResults;
     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 +198,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 +226,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() {
@@ -289,6 +267,10 @@
         return (mSearchResults != null) && mFilteredApps.isEmpty();
     }
 
+    boolean shouldShowEmptySearch() {
+        return hasNoFilteredResults() && !isAppDiscoveryRunning() && mDiscoveredApps.isEmpty();
+    }
+
     /**
      * Sets the sorted list of filtered components.
      */
@@ -302,6 +284,20 @@
         return false;
     }
 
+    public void onAppDiscoverySearchUpdate(@Nullable AppDiscoveryItem app,
+                @NonNull AppDiscoveryUpdateState state) {
+        mAppDiscoveryUpdateState = state;
+        switch (state) {
+            case START:
+                mDiscoveredApps.clear();
+                break;
+            case UPDATE:
+                mDiscoveredApps.add(new AppDiscoveryAppInfo(app));
+                break;
+        }
+        updateAdapterItems();
+    }
+
     /**
      * Sets the current set of predicted apps.  Since this can be called before we get the full set
      * of applications, we should merge the results only in onAppsUpdated() which is idempotent.
@@ -354,17 +350,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 +374,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 +395,17 @@
      * mCachedSectionNames to have been calculated for the set of all apps in mApps.
      */
     private void updateAdapterItems() {
-        SectionInfo lastSectionInfo = null;
+        refillAdapterItems();
+        refreshRecyclerView();
+    }
+
+    private void refreshRecyclerView() {
+        if (mAdapter != null) {
+            mAdapter.notifyDataSetChanged();
+        }
+    }
+
+    private void refillAdapterItems() {
         String lastSectionName = null;
         FastScrollSectionInfo lastFastScrollerSectionInfo = null;
         int position = 0;
@@ -413,23 +415,22 @@
         mFilteredApps.clear();
         mFastScrollerSections.clear();
         mAdapterItems.clear();
-        mSections.clear();
 
         if (DEBUG_PREDICTIONS) {
             if (mPredictedAppComponents.isEmpty() && !mApps.isEmpty()) {
                 mPredictedAppComponents.add(new ComponentKey(mApps.get(0).componentName,
-                        UserHandleCompat.myUserHandle()));
+                        Process.myUserHandle()));
                 mPredictedAppComponents.add(new ComponentKey(mApps.get(0).componentName,
-                        UserHandleCompat.myUserHandle()));
+                        Process.myUserHandle()));
                 mPredictedAppComponents.add(new ComponentKey(mApps.get(0).componentName,
-                        UserHandleCompat.myUserHandle()));
+                        Process.myUserHandle()));
                 mPredictedAppComponents.add(new ComponentKey(mApps.get(0).componentName,
-                        UserHandleCompat.myUserHandle()));
+                        Process.myUserHandle()));
             }
         }
 
         // Add the search divider
-        mAdapterItems.add(AdapterItem.asSearchDivder(position++));
+        mAdapterItems.add(AdapterItem.asSearchDivider(position++));
 
         // Process the predicted app components
         mPredictedApps.clear();
@@ -451,19 +452,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,43 +476,49 @@
             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);
             mFilteredApps.add(info);
         }
 
-        // Append the search market item if we are currently searching
         if (hasFilter()) {
-            if (hasNoFilteredResults()) {
-                mAdapterItems.add(AdapterItem.asEmptySearch(position++));
-            } else {
-                mAdapterItems.add(AdapterItem.asMarketDivider(position++));
-            }
-            mAdapterItems.add(AdapterItem.asMarketSearch(position++));
-        }
+            if (isAppDiscoveryRunning() || mDiscoveredApps.size() > 0) {
+                mAdapterItems.add(AdapterItem.asLoadingDivider(position++));
+                // Append all app discovery results
+                for (int i = 0; i < mDiscoveredApps.size(); i++) {
+                    AppDiscoveryAppInfo appDiscoveryAppInfo = mDiscoveredApps.get(i);
+                    if (appDiscoveryAppInfo.isRecent) {
+                        // already handled in getFilteredAppInfos()
+                        continue;
+                    }
+                    AdapterItem item = AdapterItem.asDiscoveryItem(position++,
+                            "", appDiscoveryAppInfo, appIndex++);
+                    mAdapterItems.add(item);
+                }
 
-        // Merge multiple sections together as requested by the merge strategy for this device
-        mergeSections();
+                if (!isAppDiscoveryRunning()) {
+                    mAdapterItems.add(AdapterItem.asMarketSearch(position++));
+                }
+            } else {
+                // Append the search market item
+                if (hasNoFilteredResults()) {
+                    mAdapterItems.add(AdapterItem.asEmptySearch(position++));
+                } else {
+                    mAdapterItems.add(AdapterItem.asMarketDivider(position++));
+                }
+                mAdapterItems.add(AdapterItem.asMarketSearch(position++));
+            }
+        }
 
         if (mNumAppsPerRow != 0) {
             // Update the number of rows in the adapter after we do all the merging (otherwise, we
@@ -571,11 +573,11 @@
                     break;
             }
         }
+    }
 
-        // Refresh the recycler view
-        if (mAdapter != null) {
-            mAdapter.notifyDataSetChanged();
-        }
+    public boolean isAppDiscoveryRunning() {
+        return mAppDiscoveryUpdateState == AppDiscoveryUpdateState.START
+                || mAppDiscoveryUpdateState == AppDiscoveryUpdateState.UPDATE;
     }
 
     private List<AppInfo> getFiltersAppInfos() {
@@ -590,62 +592,18 @@
                 result.add(match);
             }
         }
-        return result;
-    }
 
-    /**
-     * 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++;
+        // adding recently used instant apps
+        if (mDiscoveredApps.size() > 0) {
+            for (int i = 0; i < mDiscoveredApps.size(); i++) {
+                AppDiscoveryAppInfo discoveryAppInfo = mDiscoveredApps.get(i);
+                if (discoveryAppInfo.isRecent) {
+                    result.add(discoveryAppInfo);
                 }
             }
+            Collections.sort(result, mAppNameComparator);
         }
+        return result;
     }
 
     /**
@@ -660,4 +618,5 @@
         }
         return sectionName;
     }
+
 }
diff --git a/src/com/android/launcher3/allapps/AppInfoComparator.java b/src/com/android/launcher3/allapps/AppInfoComparator.java
new file mode 100644
index 0000000..80577a7
--- /dev/null
+++ b/src/com/android/launcher3/allapps/AppInfoComparator.java
@@ -0,0 +1,65 @@
+/*
+ * 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.allapps;
+
+import android.content.Context;
+import android.os.Process;
+import android.os.UserHandle;
+
+import com.android.launcher3.AppInfo;
+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 class AppInfoComparator implements Comparator<AppInfo> {
+
+    private final UserManagerCompat mUserManager;
+    private final UserHandle mMyUser;
+    private final LabelComparator mLabelComparator;
+
+    public AppInfoComparator(Context context) {
+        mUserManager = UserManagerCompat.getInstance(context);
+        mMyUser = Process.myUserHandle();
+        mLabelComparator = new LabelComparator();
+    }
+
+    @Override
+    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(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..b167fed 100644
--- a/src/com/android/launcher3/allapps/HeaderElevationController.java
+++ b/src/com/android/launcher3/allapps/HeaderElevationController.java
@@ -1,27 +1,52 @@
 package com.android.launcher3.allapps;
 
-import android.annotation.TargetApi;
 import android.content.res.Resources;
 import android.graphics.Outline;
-import android.graphics.Rect;
-import android.graphics.drawable.GradientDrawable;
-import android.os.Build;
 import android.support.v7.widget.RecyclerView;
 import android.view.View;
-import android.view.ViewGroup;
 import android.view.ViewOutlineProvider;
-import android.widget.FrameLayout;
 
 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.
  */
-public abstract class HeaderElevationController extends RecyclerView.OnScrollListener {
+public class HeaderElevationController extends RecyclerView.OnScrollListener {
+
+    private final View mHeader;
+    private final float mMaxElevation;
+    private final float mScrollToElevation;
 
     private int mCurrentY = 0;
 
+    public HeaderElevationController(View header) {
+        mHeader = header;
+        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);
+
+        // We need to provide a custom outline so the shadow only appears on the bottom edge.
+        // The top, left and right edges are all extended out, and the shadow is clipped
+        // by the parent.
+        final ViewOutlineProvider vop = new ViewOutlineProvider() {
+            @Override
+            public void getOutline(View view, Outline outline) {
+                final View parent = (View) mHeader.getParent();
+
+                final int left = parent.getLeft(); // Use the parent to account for offsets
+                final int top = view.getTop();
+                final int right = left + view.getWidth();
+                final int bottom = view.getBottom();
+
+                final int offset = Utilities.pxFromDp(mMaxElevation, res.getDisplayMetrics());
+                outline.setRect(left - offset, top - offset, right + offset, bottom);
+            }
+        };
+        mHeader.setOutlineProvider(vop);
+    }
+
     public void reset() {
         mCurrentY = 0;
         onScroll(mCurrentY);
@@ -33,91 +58,12 @@
         onScroll(mCurrentY);
     }
 
-    public void updateBackgroundPadding(Rect bgPadding) { }
-
-    abstract void onScroll(int scrollY);
-
-    public static class ControllerV16 extends HeaderElevationController {
-
-        private final View mShadow;
-        private final float mScrollToElevation;
-
-        public ControllerV16(View header) {
-            Resources res = header.getContext().getResources();
-            mScrollToElevation = res.getDimension(R.dimen.all_apps_header_scroll_to_elevation);
-
-            mShadow = new View(header.getContext());
-            mShadow.setBackground(new GradientDrawable(
-                    GradientDrawable.Orientation.TOP_BOTTOM, new int[] {0x1E000000, 0x00000000}));
-            mShadow.setAlpha(0);
-
-            FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
-                    FrameLayout.LayoutParams.MATCH_PARENT,
-                    res.getDimensionPixelSize(R.dimen.all_apps_header_shadow_height));
-            lp.topMargin = ((FrameLayout.LayoutParams) header.getLayoutParams()).height;
-
-            ((ViewGroup) header.getParent()).addView(mShadow, lp);
-        }
-
-        @Override
-        public void onScroll(int scrollY) {
-            float elevationPct = (float) Math.min(scrollY, mScrollToElevation) /
-                    mScrollToElevation;
-            mShadow.setAlpha(elevationPct);
-        }
-
-        @Override
-        public void updateBackgroundPadding(Rect bgPadding) {
-            FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mShadow.getLayoutParams();
-            lp.leftMargin = bgPadding.left;
-            lp.rightMargin = bgPadding.right;
-            mShadow.requestLayout();
+    private void onScroll(int scrollY) {
+        float elevationPct = Math.min(scrollY, mScrollToElevation) / mScrollToElevation;
+        float newElevation = mMaxElevation * elevationPct;
+        if (Float.compare(mHeader.getElevation(), newElevation) != 0) {
+            mHeader.setElevation(newElevation);
         }
     }
 
-    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
-    public static class ControllerVL extends HeaderElevationController {
-
-        private final View mHeader;
-        private final float mMaxElevation;
-        private final float mScrollToElevation;
-
-        public ControllerVL(View header) {
-            mHeader = header;
-            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);
-
-            // We need to provide a custom outline so the shadow only appears on the bottom edge.
-            // The top, left and right edges are all extended out, and the shadow is clipped
-            // by the parent.
-            final ViewOutlineProvider vop = new ViewOutlineProvider() {
-                @Override
-                public void getOutline(View view, Outline outline) {
-                    final View parent = (View) mHeader.getParent();
-
-                    final int left = parent.getLeft(); // Use the parent to account for offsets
-                    final int top = view.getTop();
-                    final int right = left + view.getWidth();
-                    final int bottom = view.getBottom();
-
-                    outline.setRect(
-                            left - (int) mMaxElevation,
-                            top - (int) mMaxElevation,
-                            right + (int) mMaxElevation,
-                            bottom);
-                }
-            };
-            mHeader.setOutlineProvider(vop);
-        }
-
-        @Override
-        public void onScroll(int scrollY) {
-            float elevationPct = Math.min(scrollY, mScrollToElevation) / mScrollToElevation;
-            float newElevation = mMaxElevation * elevationPct;
-            if (Float.compare(mHeader.getElevation(), newElevation) != 0) {
-                mHeader.setElevation(newElevation);
-            }
-        }
-    }
 }
diff --git a/src/com/android/launcher3/allapps/VerticalPullDetector.java b/src/com/android/launcher3/allapps/VerticalPullDetector.java
index ab2b6ed..7800c01 100644
--- a/src/com/android/launcher3/allapps/VerticalPullDetector.java
+++ b/src/com/android/launcher3/allapps/VerticalPullDetector.java
@@ -4,6 +4,7 @@
 import android.util.Log;
 import android.view.MotionEvent;
 import android.view.ViewConfiguration;
+import android.view.animation.Interpolator;
 
 /**
  * One dimensional scroll gesture detector for all apps container pull up interaction.
@@ -23,6 +24,9 @@
     public static final int DIRECTION_DOWN = 1 << 1;
     public static final int DIRECTION_BOTH = DIRECTION_DOWN | DIRECTION_UP;
 
+    private static final float ANIMATION_DURATION = 1200;
+    private static final float FAST_FLING_PX_MS = 10;
+
     /**
      * The minimum release velocity in pixels per millisecond that triggers fling..
      */
@@ -112,7 +116,7 @@
         mListener = l;
     }
 
-    interface Listener {
+    public interface Listener {
         void onDragStart(boolean start);
 
         boolean onDrag(float displacement, float velocity);
@@ -230,7 +234,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);
@@ -272,4 +276,33 @@
     private static float interpolate(float from, float to, float alpha) {
         return (1.0f - alpha) * from + alpha * to;
     }
+
+    public long calculateDuration(float velocity, float progressNeeded) {
+        // TODO: make these values constants after tuning.
+        float velocityDivisor = Math.max(2f, Math.abs(0.5f * velocity));
+        float travelDistance = Math.max(0.2f, progressNeeded);
+        long duration = (long) Math.max(100, ANIMATION_DURATION / velocityDivisor * travelDistance);
+        if (DBG) {
+            Log.d(TAG, String.format("calculateDuration=%d, v=%f, d=%f", duration, velocity, progressNeeded));
+        }
+        return duration;
+    }
+
+    public static class ScrollInterpolator implements Interpolator {
+
+        boolean mSteeper;
+
+        public void setVelocityAtZero(float velocity) {
+            mSteeper = velocity > FAST_FLING_PX_MS;
+        }
+
+        public float getInterpolation(float t) {
+            t -= 1.0f;
+            float output = t * t * t;
+            if (mSteeper) {
+                output *= t * t; // Make interpolation initial slope steeper
+            }
+            return output + 1;
+        }
+    }
 }
diff --git a/src/com/android/launcher3/anim/AnimationLayerSet.java b/src/com/android/launcher3/anim/AnimationLayerSet.java
new file mode 100644
index 0000000..14bcd17
--- /dev/null
+++ b/src/com/android/launcher3/anim/AnimationLayerSet.java
@@ -0,0 +1,70 @@
+/*
+ * 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.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Helper class to automatically build view hardware layers for the duration of an animation.
+ */
+public class AnimationLayerSet extends AnimatorListenerAdapter {
+
+    private final HashMap<View, Integer> mViewsToLayerTypeMap;
+
+    public AnimationLayerSet() {
+        mViewsToLayerTypeMap = new HashMap<>();
+    }
+
+    public AnimationLayerSet(View v) {
+        mViewsToLayerTypeMap = new HashMap<>(1);
+        addView(v);
+    }
+
+    public void addView(View v) {
+        mViewsToLayerTypeMap.put(v, v.getLayerType());
+    }
+
+    @Override
+    public void onAnimationStart(Animator animation) {
+        // Enable all necessary layers
+        Iterator<Map.Entry<View, Integer>> itr = mViewsToLayerTypeMap.entrySet().iterator();
+        while (itr.hasNext()) {
+            Map.Entry<View, Integer> entry = itr.next();
+            View v = entry.getKey();
+            entry.setValue(v.getLayerType());
+            v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+            if (v.isAttachedToWindow() && v.getVisibility() == View.VISIBLE) {
+                v.buildLayer();
+            }
+        }
+    }
+
+    @Override
+    public void onAnimationEnd(Animator animation) {
+        Iterator<Map.Entry<View, Integer>> itr = mViewsToLayerTypeMap.entrySet().iterator();
+        while (itr.hasNext()) {
+            Map.Entry<View, Integer> entry = itr.next();
+            entry.getKey().setLayerType(entry.getValue(), null);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/anim/PillHeightRevealOutlineProvider.java b/src/com/android/launcher3/anim/PillHeightRevealOutlineProvider.java
new file mode 100644
index 0000000..be1e2d6
--- /dev/null
+++ b/src/com/android/launcher3/anim/PillHeightRevealOutlineProvider.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.anim;
+
+import android.graphics.Rect;
+
+import com.android.launcher3.util.PillRevealOutlineProvider;
+
+/**
+ * Extension of {@link PillRevealOutlineProvider} which only changes the height of the pill.
+ * For now, we assume the height is added/removed from the bottom.
+ */
+public class PillHeightRevealOutlineProvider extends PillRevealOutlineProvider {
+
+    private final int mNewHeight;
+
+    public PillHeightRevealOutlineProvider(Rect pillRect, float radius, int newHeight) {
+        super(0, 0, pillRect, radius);
+        mOutline.set(pillRect);
+        mNewHeight = newHeight;
+    }
+
+    @Override
+    public void setProgress(float progress) {
+        mOutline.top = 0;
+        int heightDifference = mPillRect.height() - mNewHeight;
+        mOutline.bottom = (int) (mPillRect.bottom - heightDifference * (1 - progress));
+    }
+}
diff --git a/src/com/android/launcher3/anim/PropertyListBuilder.java b/src/com/android/launcher3/anim/PropertyListBuilder.java
new file mode 100644
index 0000000..33e7f66
--- /dev/null
+++ b/src/com/android/launcher3/anim/PropertyListBuilder.java
@@ -0,0 +1,50 @@
+package com.android.launcher3.anim;
+
+import android.animation.PropertyValuesHolder;
+import android.view.View;
+
+import java.util.ArrayList;
+
+/**
+ * Helper class to build a list of {@link PropertyValuesHolder} for view properties
+ */
+public class PropertyListBuilder {
+
+    private final ArrayList<PropertyValuesHolder> mProperties = new ArrayList<>();
+
+    public PropertyListBuilder translationX(float value) {
+        mProperties.add(PropertyValuesHolder.ofFloat(View.TRANSLATION_X, value));
+        return this;
+    }
+
+    public PropertyListBuilder translationY(float value) {
+        mProperties.add(PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, value));
+        return this;
+    }
+
+    public PropertyListBuilder scaleX(float value) {
+        mProperties.add(PropertyValuesHolder.ofFloat(View.SCALE_X, value));
+        return this;
+    }
+
+    public PropertyListBuilder scaleY(float value) {
+        mProperties.add(PropertyValuesHolder.ofFloat(View.SCALE_Y, value));
+        return this;
+    }
+
+    /**
+     * Helper method to set both scaleX and scaleY
+     */
+    public PropertyListBuilder scale(float value) {
+        return scaleX(value).scaleY(value);
+    }
+
+    public PropertyListBuilder alpha(float value) {
+        mProperties.add(PropertyValuesHolder.ofFloat(View.ALPHA, value));
+        return this;
+    }
+
+    public PropertyValuesHolder[] build() {
+        return mProperties.toArray(new PropertyValuesHolder[mProperties.size()]);
+    }
+}
diff --git a/src/com/android/launcher3/anim/PropertyResetListener.java b/src/com/android/launcher3/anim/PropertyResetListener.java
new file mode 100644
index 0000000..eefb014
--- /dev/null
+++ b/src/com/android/launcher3/anim/PropertyResetListener.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.anim;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.util.Property;
+
+/**
+ * An AnimatorListener that sets the given property to the given value at the end of the animation.
+ */
+public class PropertyResetListener<T, V> extends AnimatorListenerAdapter {
+
+    private Property<T, V> mPropertyToReset;
+    private V mResetToValue;
+
+    public PropertyResetListener(Property<T, V> propertyToReset, V resetToValue) {
+        mPropertyToReset = propertyToReset;
+        mResetToValue = resetToValue;
+    }
+
+    @Override
+    public void onAnimationEnd(Animator animation) {
+        mPropertyToReset.set((T) ((ObjectAnimator) animation).getTarget(), mResetToValue);
+    }
+}
diff --git a/src/com/android/launcher3/badge/BadgeInfo.java b/src/com/android/launcher3/badge/BadgeInfo.java
new file mode 100644
index 0000000..08d8ad4
--- /dev/null
+++ b/src/com/android/launcher3/badge/BadgeInfo.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.badge;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Shader;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.Nullable;
+
+import com.android.launcher3.notification.NotificationInfo;
+import com.android.launcher3.notification.NotificationKeyData;
+import com.android.launcher3.util.PackageUserKey;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Contains data to be used in an icon badge.
+ */
+public class BadgeInfo {
+
+    public static final int MAX_COUNT = 999;
+
+    /** Used to link this BadgeInfo to icons on the workspace and all apps */
+    private PackageUserKey mPackageUserKey;
+
+    /**
+     * The keys of the notifications that this badge represents. These keys can later be
+     * used to retrieve {@link NotificationInfo}'s.
+     */
+    private List<NotificationKeyData> mNotificationKeys;
+
+    /**
+     * The current sum of the counts in {@link #mNotificationKeys},
+     * updated whenever a key is added or removed.
+     */
+    private int mTotalCount;
+
+    /** This will only be initialized if the badge should display the notification icon. */
+    private NotificationInfo mNotificationInfo;
+
+    /**
+     * When retrieving the notification icon, we draw it into this shader, which can be clipped
+     * as necessary when drawn in a badge.
+     */
+    private Shader mNotificationIcon;
+
+    public BadgeInfo(PackageUserKey packageUserKey) {
+        mPackageUserKey = packageUserKey;
+        mNotificationKeys = new ArrayList<>();
+    }
+
+    /**
+     * Returns whether the notification was added or its count changed.
+     */
+    public boolean addOrUpdateNotificationKey(NotificationKeyData notificationKey) {
+        int indexOfPrevKey = mNotificationKeys.indexOf(notificationKey);
+        NotificationKeyData prevKey = indexOfPrevKey == -1 ? null
+                : mNotificationKeys.get(indexOfPrevKey);
+        if (prevKey != null) {
+            if (prevKey.count == notificationKey.count) {
+                return false;
+            }
+            // Notification was updated with a new count.
+            mTotalCount -= prevKey.count;
+            mTotalCount += notificationKey.count;
+            prevKey.count = notificationKey.count;
+            return true;
+        }
+        boolean added = mNotificationKeys.add(notificationKey);
+        if (added) {
+            mTotalCount += notificationKey.count;
+        }
+        return added;
+    }
+
+    /**
+     * Returns whether the notification was removed (false if it didn't exist).
+     */
+    public boolean removeNotificationKey(NotificationKeyData notificationKey) {
+        boolean removed = mNotificationKeys.remove(notificationKey);
+        if (removed) {
+            mTotalCount -= notificationKey.count;
+        }
+        return removed;
+    }
+
+    public List<NotificationKeyData> getNotificationKeys() {
+        return mNotificationKeys;
+    }
+
+    public int getNotificationCount() {
+        return Math.min(mTotalCount, MAX_COUNT);
+    }
+
+    public void setNotificationToShow(@Nullable NotificationInfo notificationInfo) {
+        mNotificationInfo = notificationInfo;
+        mNotificationIcon = null;
+    }
+
+    public boolean hasNotificationToShow() {
+        return mNotificationInfo != null;
+    }
+
+    /**
+     * Returns a shader to set on a Paint that will draw the notification icon in a badge.
+     *
+     * The shader is cached until {@link #setNotificationToShow(NotificationInfo)} is called.
+     */
+    public @Nullable Shader getNotificationIconForBadge(Context context, int badgeColor,
+            int badgeSize, int badgePadding) {
+        if (mNotificationInfo == null) {
+            return null;
+        }
+        if (mNotificationIcon == null) {
+            Drawable icon = mNotificationInfo.getIconForBackground(context, badgeColor)
+                    .getConstantState().newDrawable();
+            int iconSize = badgeSize - badgePadding * 2;
+            icon.setBounds(0, 0, iconSize, iconSize);
+            Bitmap iconBitmap = Bitmap.createBitmap(badgeSize, badgeSize, Bitmap.Config.ARGB_8888);
+            Canvas canvas = new Canvas(iconBitmap);
+            canvas.translate(badgePadding, badgePadding);
+            icon.draw(canvas);
+            mNotificationIcon = new BitmapShader(iconBitmap, Shader.TileMode.CLAMP,
+                    Shader.TileMode.CLAMP);
+        }
+        return mNotificationIcon;
+    }
+
+    public boolean isIconLarge() {
+        return mNotificationInfo != null && mNotificationInfo.isIconLarge();
+    }
+
+    /**
+     * Whether newBadge represents the same PackageUserKey as this badge, and icons with
+     * this badge should be invalidated. So, for instance, if a badge has 3 notifications
+     * and one of those notifications is updated, this method should return false because
+     * the badge still says "3" and the contents of those notifications are only retrieved
+     * upon long-click. This method always returns true when adding or removing notifications,
+     * or if the badge has a notification icon to show.
+     */
+    public boolean shouldBeInvalidated(BadgeInfo newBadge) {
+        return mPackageUserKey.equals(newBadge.mPackageUserKey)
+                && (getNotificationCount() != newBadge.getNotificationCount()
+                    || hasNotificationToShow());
+    }
+}
diff --git a/src/com/android/launcher3/badge/BadgeRenderer.java b/src/com/android/launcher3/badge/BadgeRenderer.java
new file mode 100644
index 0000000..ba1977a
--- /dev/null
+++ b/src/com/android/launcher3/badge/BadgeRenderer.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.badge;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.Shader;
+import android.support.annotation.Nullable;
+import android.util.SparseArray;
+
+import com.android.launcher3.R;
+import com.android.launcher3.graphics.IconPalette;
+import com.android.launcher3.graphics.ShadowGenerator;
+
+/**
+ * Contains parameters necessary to draw a badge for an icon (e.g. the size of the badge).
+ * @see BadgeInfo for the data to draw
+ */
+public class BadgeRenderer {
+
+    private static final boolean DOTS_ONLY = true;
+
+    // The badge sizes are defined as percentages of the app icon size.
+    private static final float SIZE_PERCENTAGE = 0.38f;
+    // Used to expand the width of the badge for each additional digit.
+    private static final float CHAR_SIZE_PERCENTAGE = 0.12f;
+    private static final float TEXT_SIZE_PERCENTAGE = 0.26f;
+    private static final float OFFSET_PERCENTAGE = 0.02f;
+    private static final float STACK_OFFSET_PERCENTAGE_X = 0.05f;
+    private static final float STACK_OFFSET_PERCENTAGE_Y = 0.06f;
+    private static final float DOT_SCALE = 0.6f;
+
+    private final Context mContext;
+    private final int mSize;
+    private final int mCharSize;
+    private final int mTextHeight;
+    private final int mOffset;
+    private final int mStackOffsetX;
+    private final int mStackOffsetY;
+    private final IconDrawer mLargeIconDrawer;
+    private final IconDrawer mSmallIconDrawer;
+    private final Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+    private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG
+            | Paint.FILTER_BITMAP_FLAG);
+    private final SparseArray<Bitmap> mBackgroundsWithShadow;
+
+    public BadgeRenderer(Context context, int iconSizePx) {
+        mContext = context;
+        Resources res = context.getResources();
+        mSize = (int) (SIZE_PERCENTAGE * iconSizePx);
+        mCharSize = (int) (CHAR_SIZE_PERCENTAGE * iconSizePx);
+        mOffset = (int) (OFFSET_PERCENTAGE * iconSizePx);
+        mStackOffsetX = (int) (STACK_OFFSET_PERCENTAGE_X * iconSizePx);
+        mStackOffsetY = (int) (STACK_OFFSET_PERCENTAGE_Y * iconSizePx);
+        mTextPaint.setTextSize(iconSizePx * TEXT_SIZE_PERCENTAGE);
+        mTextPaint.setTextAlign(Paint.Align.CENTER);
+        mLargeIconDrawer = new IconDrawer(res.getDimensionPixelSize(R.dimen.badge_small_padding));
+        mSmallIconDrawer = new IconDrawer(res.getDimensionPixelSize(R.dimen.badge_large_padding));
+        // Measure the text height.
+        Rect tempTextHeight = new Rect();
+        mTextPaint.getTextBounds("0", 0, 1, tempTextHeight);
+        mTextHeight = tempTextHeight.height();
+
+        mBackgroundsWithShadow = new SparseArray<>(3);
+    }
+
+    /**
+     * Draw a circle in the top right corner of the given bounds, and draw
+     * {@link BadgeInfo#getNotificationCount()} on top of the circle.
+     * @param palette The colors (based on the icon) to use for the badge.
+     * @param badgeInfo Contains data to draw on the badge. Could be null if we are animating out.
+     * @param iconBounds The bounds of the icon being badged.
+     * @param badgeScale The progress of the animation, from 0 to 1.
+     * @param spaceForOffset How much space is available to offset the badge up and to the right.
+     */
+    public void draw(Canvas canvas, IconPalette palette, @Nullable BadgeInfo badgeInfo,
+            Rect iconBounds, float badgeScale, Point spaceForOffset) {
+        mTextPaint.setColor(palette.textColor);
+        IconDrawer iconDrawer = badgeInfo != null && badgeInfo.isIconLarge()
+                ? mLargeIconDrawer : mSmallIconDrawer;
+        Shader icon = badgeInfo == null ? null : badgeInfo.getNotificationIconForBadge(
+                mContext, palette.backgroundColor, mSize, iconDrawer.mPadding);
+        String notificationCount = badgeInfo == null ? "0"
+                : String.valueOf(badgeInfo.getNotificationCount());
+        int numChars = notificationCount.length();
+        int width = DOTS_ONLY ? mSize : mSize + mCharSize * (numChars - 1);
+        // Lazily load the background with shadow.
+        Bitmap backgroundWithShadow = mBackgroundsWithShadow.get(numChars);
+        if (backgroundWithShadow == null) {
+            backgroundWithShadow = ShadowGenerator.createPillWithShadow(Color.WHITE, width, mSize);
+            mBackgroundsWithShadow.put(numChars, backgroundWithShadow);
+        }
+        canvas.save(Canvas.MATRIX_SAVE_FLAG);
+        // We draw the badge relative to its center.
+        int badgeCenterX = iconBounds.right - width / 2;
+        int badgeCenterY = iconBounds.top + mSize / 2;
+        boolean isText = !DOTS_ONLY && badgeInfo != null && badgeInfo.getNotificationCount() != 0;
+        boolean isIcon = !DOTS_ONLY && icon != null;
+        boolean isDot = !(isText || isIcon);
+        if (isDot) {
+            badgeScale *= DOT_SCALE;
+        }
+        int offsetX = Math.min(mOffset, spaceForOffset.x);
+        int offsetY = Math.min(mOffset, spaceForOffset.y);
+        canvas.translate(badgeCenterX + offsetX, badgeCenterY - offsetY);
+        canvas.scale(badgeScale, badgeScale);
+        // Prepare the background and shadow and possible stacking effect.
+        mBackgroundPaint.setColorFilter(palette.backgroundColorMatrixFilter);
+        int backgroundWithShadowSize = backgroundWithShadow.getHeight(); // Same as width.
+        boolean shouldStack = !isDot && badgeInfo != null
+                && badgeInfo.getNotificationKeys().size() > 1;
+        if (shouldStack) {
+            int offsetDiffX = mStackOffsetX - mOffset;
+            int offsetDiffY = mStackOffsetY - mOffset;
+            canvas.translate(offsetDiffX, offsetDiffY);
+            canvas.drawBitmap(backgroundWithShadow, -backgroundWithShadowSize / 2,
+                    -backgroundWithShadowSize / 2, mBackgroundPaint);
+            canvas.translate(-offsetDiffX, -offsetDiffY);
+        }
+
+        if (isText) {
+            canvas.drawBitmap(backgroundWithShadow, -backgroundWithShadowSize / 2,
+                    -backgroundWithShadowSize / 2, mBackgroundPaint);
+            canvas.drawText(notificationCount, 0, mTextHeight / 2, mTextPaint);
+        } else if (isIcon) {
+            canvas.drawBitmap(backgroundWithShadow, -backgroundWithShadowSize / 2,
+                    -backgroundWithShadowSize / 2, mBackgroundPaint);
+            iconDrawer.drawIcon(icon, canvas);
+        } else if (isDot) {
+            mBackgroundPaint.setColorFilter(palette.saturatedBackgroundColorMatrixFilter);
+            canvas.drawBitmap(backgroundWithShadow, -backgroundWithShadowSize / 2,
+                    -backgroundWithShadowSize / 2, mBackgroundPaint);
+        }
+        canvas.restore();
+    }
+
+    /** Draws the notification icon with padding of a given size. */
+    private class IconDrawer {
+
+        private final int mPadding;
+        private final Bitmap mCircleClipBitmap;
+        private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG |
+                Paint.FILTER_BITMAP_FLAG);
+
+        public IconDrawer(int padding) {
+            mPadding = padding;
+            mCircleClipBitmap = Bitmap.createBitmap(mSize, mSize, Bitmap.Config.ALPHA_8);
+            Canvas canvas = new Canvas();
+            canvas.setBitmap(mCircleClipBitmap);
+            canvas.drawCircle(mSize / 2, mSize / 2, mSize / 2 - padding, mPaint);
+        }
+
+        public void drawIcon(Shader icon, Canvas canvas) {
+            mPaint.setShader(icon);
+            canvas.drawBitmap(mCircleClipBitmap, -mSize / 2, -mSize / 2, mPaint);
+            mPaint.setShader(null);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/badge/FolderBadgeInfo.java b/src/com/android/launcher3/badge/FolderBadgeInfo.java
new file mode 100644
index 0000000..3a1bf60
--- /dev/null
+++ b/src/com/android/launcher3/badge/FolderBadgeInfo.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.badge;
+
+import com.android.launcher3.Utilities;
+
+/**
+ * Subclass of BadgeInfo that only contains the badge count, which is
+ * the sum of all the Folder's items' notifications (each counts as 1).
+ */
+public class FolderBadgeInfo extends BadgeInfo {
+
+    private static final int MIN_COUNT = 0;
+
+    private int mNumNotifications;
+
+    public FolderBadgeInfo() {
+        super(null);
+    }
+
+    public void addBadgeInfo(BadgeInfo badgeToAdd) {
+        if (badgeToAdd == null) {
+            return;
+        }
+        mNumNotifications += badgeToAdd.getNotificationKeys().size();
+        mNumNotifications = Utilities.boundToRange(
+                mNumNotifications, MIN_COUNT, BadgeInfo.MAX_COUNT);
+    }
+
+    public void subtractBadgeInfo(BadgeInfo badgeToSubtract) {
+        if (badgeToSubtract == null) {
+            return;
+        }
+        mNumNotifications -= badgeToSubtract.getNotificationKeys().size();
+        mNumNotifications = Utilities.boundToRange(
+                mNumNotifications, MIN_COUNT, BadgeInfo.MAX_COUNT);
+    }
+
+    @Override
+    public int getNotificationCount() {
+        // This forces the folder badge to always show up as a dot.
+        return 0;
+    }
+
+    public boolean hasBadge() {
+        return mNumNotifications > 0;
+    }
+}
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/AppWidgetManagerCompat.java b/src/com/android/launcher3/compat/AppWidgetManagerCompat.java
index 811cacf..3efbbfb 100644
--- a/src/com/android/launcher3/compat/AppWidgetManagerCompat.java
+++ b/src/com/android/launcher3/compat/AppWidgetManagerCompat.java
@@ -22,14 +22,15 @@
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.ComponentName;
 import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
 import android.os.Bundle;
+import android.os.UserHandle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 
-import com.android.launcher3.IconCache;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.PackageUserKey;
 
 import java.util.HashMap;
 import java.util.List;
@@ -39,14 +40,13 @@
     private static final Object sInstanceLock = new Object();
     private static AppWidgetManagerCompat sInstance;
 
-
     public static AppWidgetManagerCompat getInstance(Context context) {
         synchronized (sInstanceLock) {
             if (sInstance == null) {
-                if (Utilities.ATLEAST_LOLLIPOP) {
-                    sInstance = new AppWidgetManagerCompatVL(context.getApplicationContext());
+                if (Utilities.isAtLeastO()) {
+                    sInstance = new AppWidgetManagerCompatVO(context.getApplicationContext());
                 } else {
-                    sInstance = new AppWidgetManagerCompatV16(context.getApplicationContext());
+                    sInstance = new AppWidgetManagerCompatVL(context.getApplicationContext());
                 }
             }
             return sInstance;
@@ -70,27 +70,17 @@
         return info == null ? null : LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, info);
     }
 
-    public abstract List<AppWidgetProviderInfo> getAllProviders();
-
-    public abstract String loadLabel(LauncherAppWidgetProviderInfo info);
+    public abstract List<AppWidgetProviderInfo> getAllProviders(
+            @Nullable PackageUserKey packageUser);
 
     public abstract boolean bindAppWidgetIdIfAllowed(
             int appWidgetId, AppWidgetProviderInfo info, Bundle options);
 
-    public abstract UserHandleCompat getUser(LauncherAppWidgetProviderInfo info);
-
     public abstract void startConfigActivity(AppWidgetProviderInfo info, int widgetId,
             Activity activity, AppWidgetHost host, int requestCode);
 
-    public abstract Drawable loadPreview(AppWidgetProviderInfo info);
-
-    public abstract Drawable loadIcon(LauncherAppWidgetProviderInfo info, IconCache cache);
-
-    public abstract Bitmap getBadgeBitmap(LauncherAppWidgetProviderInfo info, Bitmap bitmap,
-            int imageWidth, int imageHeight);
-
     public abstract LauncherAppWidgetProviderInfo findProvider(
-            ComponentName provider, UserHandleCompat user);
+            ComponentName provider, UserHandle user);
 
     public abstract HashMap<ComponentKey, AppWidgetProviderInfo> getAllProvidersMap();
 }
diff --git a/src/com/android/launcher3/compat/AppWidgetManagerCompatV16.java b/src/com/android/launcher3/compat/AppWidgetManagerCompatV16.java
deleted file mode 100644
index de9414e..0000000
--- a/src/com/android/launcher3/compat/AppWidgetManagerCompatV16.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (C) 2014 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.compat;
-
-import android.annotation.TargetApi;
-import android.app.Activity;
-import android.appwidget.AppWidgetHost;
-import android.appwidget.AppWidgetManager;
-import android.appwidget.AppWidgetProviderInfo;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
-import android.os.Bundle;
-
-import com.android.launcher3.IconCache;
-import com.android.launcher3.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.util.ComponentKey;
-
-import java.util.HashMap;
-import java.util.List;
-
-class AppWidgetManagerCompatV16 extends AppWidgetManagerCompat {
-
-    AppWidgetManagerCompatV16(Context context) {
-        super(context);
-    }
-
-    @Override
-    public List<AppWidgetProviderInfo> getAllProviders() {
-        return mAppWidgetManager.getInstalledProviders();
-    }
-
-    @Override
-    public String loadLabel(LauncherAppWidgetProviderInfo info) {
-        return Utilities.trim(info.label);
-    }
-
-    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
-    @Override
-    public boolean bindAppWidgetIdIfAllowed(int appWidgetId, AppWidgetProviderInfo info,
-            Bundle options) {
-        if (Utilities.ATLEAST_JB_MR1) {
-            return mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, info.provider, options);
-        } else {
-            return mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, info.provider);
-        }
-    }
-
-    @Override
-    public UserHandleCompat getUser(LauncherAppWidgetProviderInfo info) {
-        return UserHandleCompat.myUserHandle();
-    }
-
-    @Override
-    public void startConfigActivity(AppWidgetProviderInfo info, int widgetId, Activity activity,
-            AppWidgetHost host, int requestCode) {
-        Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE);
-        intent.setComponent(info.configure);
-        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
-        Utilities.startActivityForResultSafely(activity, intent, requestCode);
-    }
-
-    @Override
-    public Drawable loadPreview(AppWidgetProviderInfo info) {
-        return mContext.getPackageManager().getDrawable(
-                info.provider.getPackageName(), info.previewImage, null);
-    }
-
-    @Override
-    public Drawable loadIcon(LauncherAppWidgetProviderInfo info, IconCache cache) {
-        return cache.getFullResIcon(info.provider.getPackageName(), info.icon);
-    }
-
-    @Override
-    public Bitmap getBadgeBitmap(LauncherAppWidgetProviderInfo info, Bitmap bitmap,
-            int imageWidth, int imageHeight) {
-        return bitmap;
-    }
-
-    @Override
-    public LauncherAppWidgetProviderInfo findProvider(
-            ComponentName provider, UserHandleCompat user) {
-        for (AppWidgetProviderInfo info : mAppWidgetManager.getInstalledProviders()) {
-            if (info.provider.equals(provider)) {
-                return LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, info);
-            }
-        }
-        return null;
-    }
-
-    @Override
-    public HashMap<ComponentKey, AppWidgetProviderInfo> getAllProvidersMap() {
-        HashMap<ComponentKey, AppWidgetProviderInfo> result = new HashMap<>();
-        UserHandleCompat user = UserHandleCompat.myUserHandle();
-        for (AppWidgetProviderInfo info : mAppWidgetManager.getInstalledProviders()) {
-            result.put(new ComponentKey(info.provider, user), info);
-        }
-        return result;
-    }
-}
diff --git a/src/com/android/launcher3/compat/AppWidgetManagerCompatVL.java b/src/com/android/launcher3/compat/AppWidgetManagerCompatVL.java
index a1570e6..f239f5c 100644
--- a/src/com/android/launcher3/compat/AppWidgetManagerCompatVL.java
+++ b/src/com/android/launcher3/compat/AppWidgetManagerCompatVL.java
@@ -16,64 +16,59 @@
 
 package com.android.launcher3.compat;
 
-import android.annotation.TargetApi;
 import android.app.Activity;
 import android.appwidget.AppWidgetHost;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.view.View;
+import android.support.annotation.Nullable;
 import android.widget.Toast;
 
-import com.android.launcher3.IconCache;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.R;
 import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.PackageUserKey;
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 
-@TargetApi(Build.VERSION_CODES.LOLLIPOP)
 class AppWidgetManagerCompatVL extends AppWidgetManagerCompat {
 
     private final UserManager mUserManager;
-    private final PackageManager mPm;
 
     AppWidgetManagerCompatVL(Context context) {
         super(context);
-        mPm = context.getPackageManager();
         mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
     }
 
     @Override
-    public List<AppWidgetProviderInfo> getAllProviders() {
-        ArrayList<AppWidgetProviderInfo> providers = new ArrayList<AppWidgetProviderInfo>();
-        for (UserHandle user : mUserManager.getUserProfiles()) {
-            providers.addAll(mAppWidgetManager.getInstalledProvidersForProfile(user));
+    public List<AppWidgetProviderInfo> getAllProviders(@Nullable PackageUserKey packageUser) {
+        if (packageUser == null) {
+            ArrayList<AppWidgetProviderInfo> providers = new ArrayList<AppWidgetProviderInfo>();
+            for (UserHandle user : mUserManager.getUserProfiles()) {
+                providers.addAll(mAppWidgetManager.getInstalledProvidersForProfile(user));
+            }
+            return providers;
+        }
+        // Only get providers for the given package/user.
+        List<AppWidgetProviderInfo> providers = new ArrayList<>(mAppWidgetManager
+                .getInstalledProvidersForProfile(packageUser.mUser));
+        Iterator<AppWidgetProviderInfo> iterator = providers.iterator();
+        while (iterator.hasNext()) {
+            if (!iterator.next().provider.getPackageName().equals(packageUser.mPackageName)) {
+                iterator.remove();
+            }
         }
         return providers;
     }
 
     @Override
-    public String loadLabel(LauncherAppWidgetProviderInfo info) {
-        return info.getLabel(mPm);
-    }
-
-    @Override
     public boolean bindAppWidgetIdIfAllowed(int appWidgetId, AppWidgetProviderInfo info,
             Bundle options) {
         return mAppWidgetManager.bindAppWidgetIdIfAllowed(
@@ -81,14 +76,6 @@
     }
 
     @Override
-    public UserHandleCompat getUser(LauncherAppWidgetProviderInfo info) {
-        if (info.isCustomWidget) {
-            return UserHandleCompat.myUserHandle();
-        }
-        return UserHandleCompat.fromUser(info.getProfile());
-    }
-
-    @Override
     public void startConfigActivity(AppWidgetProviderInfo info, int widgetId, Activity activity,
             AppWidgetHost host, int requestCode) {
         try {
@@ -99,60 +86,9 @@
     }
 
     @Override
-    public Drawable loadPreview(AppWidgetProviderInfo info) {
-        return info.loadPreviewImage(mContext, 0);
-    }
-
-    @Override
-    public Drawable loadIcon(LauncherAppWidgetProviderInfo info, IconCache cache) {
-        return info.getIcon(mContext, cache);
-    }
-
-    @Override
-    public Bitmap getBadgeBitmap(LauncherAppWidgetProviderInfo info, Bitmap bitmap,
-            int imageWidth, int imageHeight) {
-        if (info.isCustomWidget || info.getProfile().equals(android.os.Process.myUserHandle())) {
-            return bitmap;
-        }
-
-        // Add a user badge in the bottom right of the image.
-        final Resources res = mContext.getResources();
-        final int badgeMinTop = res.getDimensionPixelSize(R.dimen.profile_badge_minimum_top);
-
-        // choose min between badge size defined for widget tray versus width, height of the image.
-        // Width, height of the image can be smaller than widget tray badge size when being dropped
-        // to the workspace.
-        final int badgeSize = Math.min(res.getDimensionPixelSize(R.dimen.profile_badge_size),
-                Math.min(imageWidth, imageHeight - badgeMinTop));
-        final Rect badgeLocation = new Rect(0, 0, badgeSize, badgeSize);
-
-        final int top = Math.max(imageHeight - badgeSize, badgeMinTop);
-
-        if (res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
-            badgeLocation.offset(0, top);
-        } else {
-            badgeLocation.offset(bitmap.getWidth() - badgeSize, top);
-        }
-
-        Drawable drawable = mPm.getUserBadgedDrawableForDensity(
-                new BitmapDrawable(res, bitmap), info.getProfile(), badgeLocation, 0);
-
-        if (drawable instanceof BitmapDrawable) {
-            return ((BitmapDrawable) drawable).getBitmap();
-        }
-
-        bitmap.eraseColor(Color.TRANSPARENT);
-        Canvas c = new Canvas(bitmap);
-        drawable.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight());
-        drawable.draw(c);
-        c.setBitmap(null);
-        return bitmap;
-    }
-
-    @Override
-    public LauncherAppWidgetProviderInfo findProvider(ComponentName provider, UserHandleCompat user) {
+    public LauncherAppWidgetProviderInfo findProvider(ComponentName provider, UserHandle user) {
         for (AppWidgetProviderInfo info : mAppWidgetManager
-                .getInstalledProvidersForProfile(user.getUser())) {
+                .getInstalledProvidersForProfile(user)) {
             if (info.provider.equals(provider)) {
                 return LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, info);
             }
@@ -164,10 +100,9 @@
     public HashMap<ComponentKey, AppWidgetProviderInfo> getAllProvidersMap() {
         HashMap<ComponentKey, AppWidgetProviderInfo> result = new HashMap<>();
         for (UserHandle user : mUserManager.getUserProfiles()) {
-            UserHandleCompat userHandle = UserHandleCompat.fromUser(user);
             for (AppWidgetProviderInfo info :
                     mAppWidgetManager.getInstalledProvidersForProfile(user)) {
-                result.put(new ComponentKey(info.provider, userHandle), info);
+                result.put(new ComponentKey(info.provider, user), info);
             }
         }
         return result;
diff --git a/src/com/android/launcher3/compat/AppWidgetManagerCompatVO.java b/src/com/android/launcher3/compat/AppWidgetManagerCompatVO.java
new file mode 100644
index 0000000..1c48a13
--- /dev/null
+++ b/src/com/android/launcher3/compat/AppWidgetManagerCompatVO.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.compat;
+
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.Context;
+import android.support.annotation.Nullable;
+
+import com.android.launcher3.util.PackageUserKey;
+
+import java.util.List;
+
+class AppWidgetManagerCompatVO extends AppWidgetManagerCompatVL {
+
+    AppWidgetManagerCompatVO(Context context) {
+        super(context);
+    }
+
+    @Override
+    public List<AppWidgetProviderInfo> getAllProviders(@Nullable PackageUserKey packageUser) {
+        if (packageUser == null) {
+            return super.getAllProviders(null);
+        }
+        return mAppWidgetManager.getInstalledProvidersForPackage(packageUser.mPackageName,
+                packageUser.mUser);
+    }
+}
diff --git a/src/com/android/launcher3/compat/DeferredLauncherActivityInfo.java b/src/com/android/launcher3/compat/DeferredLauncherActivityInfo.java
deleted file mode 100644
index 46d36d1..0000000
--- a/src/com/android/launcher3/compat/DeferredLauncherActivityInfo.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.compat;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.graphics.drawable.Drawable;
-import android.util.Log;
-
-/**
- * {@link LauncherActivityInfoCompat} which loads its data only when needed.
- */
-public class DeferredLauncherActivityInfo extends LauncherActivityInfoCompat {
-
-    private final ComponentName mComponent;
-    private final UserHandleCompat mUser;
-    private final Context mContext;
-
-    private LauncherActivityInfoCompat mActualInfo;
-
-    public DeferredLauncherActivityInfo(
-            ComponentName component, UserHandleCompat user, Context context) {
-        mComponent = component;
-        mUser = user;
-        mContext = context;
-    }
-
-    @Override
-    public ComponentName getComponentName() {
-        return mComponent;
-    }
-
-    @Override
-    public UserHandleCompat getUser() {
-        return mUser;
-    }
-
-    private synchronized LauncherActivityInfoCompat getActualInfo() {
-        if (mActualInfo == null) {
-            Intent intent = new Intent(Intent.ACTION_MAIN)
-                    .addCategory(Intent.CATEGORY_LAUNCHER)
-                    .setComponent(mComponent);
-            mActualInfo = LauncherAppsCompat.getInstance(mContext).resolveActivity(intent, mUser);
-        }
-        return mActualInfo;
-    }
-
-    @Override
-    public CharSequence getLabel() {
-        return getActualInfo().getLabel();
-    }
-
-    @Override
-    public Drawable getIcon(int density) {
-        return getActualInfo().getIcon(density);
-    }
-
-    @Override
-    public ApplicationInfo getApplicationInfo() {
-        return getActualInfo().getApplicationInfo();
-    }
-
-    @Override
-    public long getFirstInstallTime() {
-        return getActualInfo().getFirstInstallTime();
-    }
-}
diff --git a/src/com/android/launcher3/compat/LauncherActivityInfoCompat.java b/src/com/android/launcher3/compat/LauncherActivityInfoCompat.java
deleted file mode 100644
index 0bc9588..0000000
--- a/src/com/android/launcher3/compat/LauncherActivityInfoCompat.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2014 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.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 {
-
-    LauncherActivityInfoCompat() {
-    }
-
-    public abstract ComponentName getComponentName();
-    public abstract UserHandleCompat getUser();
-    public abstract CharSequence getLabel();
-    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/LauncherActivityInfoCompatV16.java b/src/com/android/launcher3/compat/LauncherActivityInfoCompatV16.java
deleted file mode 100644
index fee0376..0000000
--- a/src/com/android/launcher3/compat/LauncherActivityInfoCompatV16.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2014 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.compat;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageInfo;
-import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
-
-
-public class LauncherActivityInfoCompatV16 extends LauncherActivityInfoCompat {
-    private final ResolveInfo mResolveInfo;
-    private final ActivityInfo mActivityInfo;
-    private final ComponentName mComponentName;
-    private final PackageManager mPm;
-
-    LauncherActivityInfoCompatV16(Context context, ResolveInfo info) {
-        super();
-        mResolveInfo = info;
-        mActivityInfo = info.activityInfo;
-        mComponentName = new ComponentName(mActivityInfo.packageName, mActivityInfo.name);
-        mPm = context.getPackageManager();
-    }
-
-    public ComponentName getComponentName() {
-        return mComponentName;
-    }
-
-    public UserHandleCompat getUser() {
-        return UserHandleCompat.myUserHandle();
-    }
-
-    public CharSequence getLabel() {
-        return mResolveInfo.loadLabel(mPm);
-    }
-
-    public Drawable getIcon(int density) {
-        int iconRes = mResolveInfo.getIconResource();
-        Resources resources = null;
-        Drawable icon = null;
-        // Get the preferred density icon from the app's resources
-        if (density != 0 && iconRes != 0) {
-            try {
-                resources = mPm.getResourcesForApplication(mActivityInfo.applicationInfo);
-                icon = resources.getDrawableForDensity(iconRes, density);
-            } catch (NameNotFoundException | Resources.NotFoundException exc) {
-            }
-        }
-        // Get the default density icon
-        if (icon == null) {
-            icon = mResolveInfo.loadIcon(mPm);
-        }
-        if (icon == null) {
-            resources = Resources.getSystem();
-            icon = resources.getDrawableForDensity(android.R.mipmap.sym_def_app_icon, density);
-        }
-        return icon;
-    }
-
-    public ApplicationInfo getApplicationInfo() {
-        return mActivityInfo.applicationInfo;
-    }
-
-    public long getFirstInstallTime() {
-        try {
-            PackageInfo info = mPm.getPackageInfo(mActivityInfo.packageName, 0);
-            return info != null ? info.firstInstallTime : 0;
-        } catch (NameNotFoundException e) {
-            return 0;
-        }
-    }
-
-    public String getName() {
-        return mActivityInfo.name;
-    }
-}
diff --git a/src/com/android/launcher3/compat/LauncherActivityInfoCompatVL.java b/src/com/android/launcher3/compat/LauncherActivityInfoCompatVL.java
deleted file mode 100644
index 67c5c27..0000000
--- a/src/com/android/launcher3/compat/LauncherActivityInfoCompatVL.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2014 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.compat;
-
-import android.annotation.TargetApi;
-import android.content.ComponentName;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.LauncherActivityInfo;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
-
-@TargetApi(Build.VERSION_CODES.LOLLIPOP)
-public class LauncherActivityInfoCompatVL extends LauncherActivityInfoCompat {
-    private LauncherActivityInfo mLauncherActivityInfo;
-
-    LauncherActivityInfoCompatVL(LauncherActivityInfo launcherActivityInfo) {
-        super();
-        mLauncherActivityInfo = launcherActivityInfo;
-    }
-
-    public ComponentName getComponentName() {
-        return mLauncherActivityInfo.getComponentName();
-    }
-
-    public UserHandleCompat getUser() {
-        return UserHandleCompat.fromUser(mLauncherActivityInfo.getUser());
-    }
-
-    public CharSequence getLabel() {
-        return mLauncherActivityInfo.getLabel();
-    }
-
-    public Drawable getIcon(int density) {
-        return mLauncherActivityInfo.getIcon(density);
-    }
-
-    public ApplicationInfo getApplicationInfo() {
-        return mLauncherActivityInfo.getApplicationInfo();
-    }
-
-    public long getFirstInstallTime() {
-        return mLauncherActivityInfo.getFirstInstallTime();
-    }
-}
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompat.java b/src/com/android/launcher3/compat/LauncherAppsCompat.java
index 645e68a..e997a99 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompat.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompat.java
@@ -19,28 +19,36 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.LauncherApps;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherActivityInfo;
 import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
 import android.os.Bundle;
+import android.os.UserHandle;
+import android.support.annotation.Nullable;
 
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.graphics.LauncherIcons;
 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+import com.android.launcher3.util.LooperExecuter;
+import com.android.launcher3.util.PackageUserKey;
 
 import java.util.List;
 
 public abstract class LauncherAppsCompat {
 
     public interface OnAppsChangedCallbackCompat {
-        void onPackageRemoved(String packageName, UserHandleCompat user);
-        void onPackageAdded(String packageName, UserHandleCompat user);
-        void onPackageChanged(String packageName, UserHandleCompat user);
-        void onPackagesAvailable(String[] packageNames, UserHandleCompat user, boolean replacing);
-        void onPackagesUnavailable(String[] packageNames, UserHandleCompat user, boolean replacing);
-        void onPackagesSuspended(String[] packageNames, UserHandleCompat user);
-        void onPackagesUnsuspended(String[] packageNames, UserHandleCompat user);
+        void onPackageRemoved(String packageName, UserHandle user);
+        void onPackageAdded(String packageName, UserHandle user);
+        void onPackageChanged(String packageName, UserHandle user);
+        void onPackagesAvailable(String[] packageNames, UserHandle user, boolean replacing);
+        void onPackagesUnavailable(String[] packageNames, UserHandle user, boolean replacing);
+        void onPackagesSuspended(String[] packageNames, UserHandle user);
+        void onPackagesUnsuspended(String[] packageNames, UserHandle user);
         void onShortcutsChanged(String packageName, List<ShortcutInfoCompat> shortcuts,
-                UserHandleCompat user);
+                UserHandle user);
     }
 
     protected LauncherAppsCompat() {
@@ -52,27 +60,91 @@
     public static LauncherAppsCompat getInstance(Context context) {
         synchronized (sInstanceLock) {
             if (sInstance == null) {
-                if (Utilities.ATLEAST_LOLLIPOP) {
-                    sInstance = new LauncherAppsCompatVL(context.getApplicationContext());
+                if (Utilities.isAtLeastO()) {
+                    sInstance = new LauncherAppsCompatVO(context.getApplicationContext());
                 } else {
-                    sInstance = new LauncherAppsCompatV16(context.getApplicationContext());
+                    sInstance = new LauncherAppsCompatVL(context.getApplicationContext());
                 }
             }
             return sInstance;
         }
     }
 
-    public abstract List<LauncherActivityInfoCompat> getActivityList(String packageName,
-            UserHandleCompat user);
-    public abstract LauncherActivityInfoCompat resolveActivity(Intent intent,
-            UserHandleCompat user);
-    public abstract void startActivityForProfile(ComponentName component, UserHandleCompat user,
+    public abstract List<LauncherActivityInfo> getActivityList(String packageName,
+            UserHandle user);
+    public abstract LauncherActivityInfo resolveActivity(Intent intent,
+            UserHandle user);
+    public abstract void startActivityForProfile(ComponentName component, UserHandle user,
             Rect sourceBounds, Bundle opts);
-    public abstract void showAppDetailsForProfile(ComponentName component, UserHandleCompat user);
+    public abstract ApplicationInfo getApplicationInfo(
+            String packageName, int flags, UserHandle user);
+    public abstract void showAppDetailsForProfile(ComponentName component, UserHandle user,
+            Rect sourceBounds, Bundle opts);
     public abstract void addOnAppsChangedCallback(OnAppsChangedCallbackCompat listener);
     public abstract void removeOnAppsChangedCallback(OnAppsChangedCallbackCompat listener);
-    public abstract boolean isPackageEnabledForProfile(String packageName, UserHandleCompat user);
+    public abstract boolean isPackageEnabledForProfile(String packageName, UserHandle user);
     public abstract boolean isActivityEnabledForProfile(ComponentName component,
-            UserHandleCompat user);
-    public abstract boolean isPackageSuspendedForProfile(String packageName, UserHandleCompat user);
+            UserHandle user);
+    public abstract List<ShortcutConfigActivityInfo> getCustomShortcutActivityList(
+            @Nullable PackageUserKey packageUser);
+
+    /**
+     * request.accept() will initiate the following flow:
+     *      -> go-to-system-process for actual processing (a)
+     *      -> callback-to-launcher on UI thread (b)
+     *      -> post callback on the worker thread (c)
+     *      -> Update model and unpin (in system) any shortcut not in out model. (d)
+     *
+     * Note that (b) will take at-least one frame as it involves posting callback from binder
+     * thread to UI thread.
+     * If (d) happens before we add this shortcut to our model, we will end up unpinning
+     * the shortcut in the system.
+     * Here its the caller's responsibility to add the newly created ShortcutInfo immediately
+     * to the model (which may involves a single post-to-worker-thread). That will guarantee
+     * that (d) happens after model is updated.
+     */
+    @Nullable
+    public static ShortcutInfo createShortcutInfoFromPinItemRequest(
+            Context context, final PinItemRequestCompat request, final long acceptDelay) {
+        if (request != null &&
+                request.getRequestType() == PinItemRequestCompat.REQUEST_TYPE_SHORTCUT &&
+                request.isValid()) {
+
+            if (acceptDelay <= 0) {
+                if (!request.accept()) {
+                    return null;
+                }
+            } else {
+                // Block the worker thread until the accept() is called.
+                new LooperExecuter(LauncherModel.getWorkerLooper()).execute(new Runnable() {
+                    @Override
+                    public void run() {
+                        try {
+                            Thread.sleep(acceptDelay);
+                        } catch (InterruptedException e) {
+                            // Ignore
+                        }
+                        if (request.isValid()) {
+                            request.accept();
+                        }
+                    }
+                });
+            }
+
+            ShortcutInfoCompat compat = new ShortcutInfoCompat(request.getShortcutInfo());
+            ShortcutInfo info = new ShortcutInfo(compat, context);
+            // Apply the unbadged icon and fetch the actual icon asynchronously.
+            info.iconBitmap = LauncherIcons
+                    .createShortcutIcon(compat, context, false /* badged */);
+            LauncherAppState.getInstance(context).getModel()
+                    .updateAndBindShortcutInfo(info, compat);
+            return info;
+        } else {
+            return null;
+        }
+    }
+
+    public void showAppDetailsForProfile(ComponentName component, UserHandle user) {
+        showAppDetailsForProfile(component, user, null, null);
+    }
 }
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatV16.java b/src/com/android/launcher3/compat/LauncherAppsCompatV16.java
deleted file mode 100644
index 49a0df6..0000000
--- a/src/com/android/launcher3/compat/LauncherAppsCompatV16.java
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
- * Copyright (C) 2014 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.compat;
-
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ActivityInfo;
-import android.content.pm.LauncherApps;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ResolveInfo;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Bundle;
-import android.provider.Settings;
-
-import com.android.launcher3.Utilities;
-import com.android.launcher3.shortcuts.ShortcutInfoCompat;
-import com.android.launcher3.util.PackageManagerHelper;
-import com.android.launcher3.util.Thunk;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Version of {@link LauncherAppsCompat} for devices with API level 16.
- * Devices Pre-L don't support multiple profiles in one launcher so
- * user parameters are ignored and all methods operate on the current user.
- */
-public class LauncherAppsCompatV16 extends LauncherAppsCompat {
-
-    private PackageManager mPm;
-    private Context mContext;
-    private List<OnAppsChangedCallbackCompat> mCallbacks
-            = new ArrayList<OnAppsChangedCallbackCompat>();
-    private PackageMonitor mPackageMonitor;
-
-    LauncherAppsCompatV16(Context context) {
-        mPm = context.getPackageManager();
-        mContext = context;
-        mPackageMonitor = new PackageMonitor();
-   }
-
-    public List<LauncherActivityInfoCompat> getActivityList(String packageName,
-            UserHandleCompat user) {
-        final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
-        mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
-        mainIntent.setPackage(packageName);
-        List<ResolveInfo> infos = mPm.queryIntentActivities(mainIntent, 0);
-        List<LauncherActivityInfoCompat> list =
-                new ArrayList<LauncherActivityInfoCompat>(infos.size());
-        for (ResolveInfo info : infos) {
-            list.add(new LauncherActivityInfoCompatV16(mContext, info));
-        }
-        return list;
-    }
-
-    public LauncherActivityInfoCompat resolveActivity(Intent intent, UserHandleCompat user) {
-        ResolveInfo info = mPm.resolveActivity(intent, 0);
-        if (info != null) {
-            return new LauncherActivityInfoCompatV16(mContext, info);
-        }
-        return null;
-    }
-
-    public void startActivityForProfile(ComponentName component, UserHandleCompat user,
-            Rect sourceBounds, Bundle opts) {
-        Intent launchIntent = new Intent(Intent.ACTION_MAIN);
-        launchIntent.addCategory(Intent.CATEGORY_LAUNCHER);
-        launchIntent.setComponent(component);
-        launchIntent.setSourceBounds(sourceBounds);
-        launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        mContext.startActivity(launchIntent, opts);
-    }
-
-    public void showAppDetailsForProfile(ComponentName component, UserHandleCompat user) {
-        String packageName = component.getPackageName();
-        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
-                Uri.fromParts("package", packageName, null));
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK |
-                Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-        mContext.startActivity(intent, null);
-    }
-
-    public synchronized void addOnAppsChangedCallback(OnAppsChangedCallbackCompat callback) {
-        if (callback != null && !mCallbacks.contains(callback)) {
-            mCallbacks.add(callback);
-            if (mCallbacks.size() == 1) {
-                registerForPackageIntents();
-            }
-        }
-    }
-
-    public synchronized void removeOnAppsChangedCallback(OnAppsChangedCallbackCompat callback) {
-        mCallbacks.remove(callback);
-        if (mCallbacks.size() == 0) {
-            unregisterForPackageIntents();
-        }
-    }
-
-    public boolean isPackageEnabledForProfile(String packageName, UserHandleCompat user) {
-        return PackageManagerHelper.isAppEnabled(mPm, packageName);
-    }
-
-    public boolean isActivityEnabledForProfile(ComponentName component, UserHandleCompat user) {
-        try {
-            ActivityInfo info = mPm.getActivityInfo(component, 0);
-            return info != null && info.isEnabled();
-        } catch (NameNotFoundException e) {
-            return false;
-        }
-    }
-
-    public boolean isPackageSuspendedForProfile(String packageName, UserHandleCompat user) {
-        return false;
-    }
-
-    private void unregisterForPackageIntents() {
-        mContext.unregisterReceiver(mPackageMonitor);
-    }
-
-    private void registerForPackageIntents() {
-        IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
-        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
-        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
-        filter.addDataScheme("package");
-        mContext.registerReceiver(mPackageMonitor, filter);
-        filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
-        filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
-        mContext.registerReceiver(mPackageMonitor, filter);
-    }
-
-    @Thunk synchronized List<OnAppsChangedCallbackCompat> getCallbacks() {
-        return new ArrayList<OnAppsChangedCallbackCompat>(mCallbacks);
-    }
-
-    @Thunk class PackageMonitor extends BroadcastReceiver {
-        public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            final UserHandleCompat user = UserHandleCompat.myUserHandle();
-
-            if (Intent.ACTION_PACKAGE_CHANGED.equals(action)
-                    || Intent.ACTION_PACKAGE_REMOVED.equals(action)
-                    || Intent.ACTION_PACKAGE_ADDED.equals(action)) {
-                final String packageName = intent.getData().getSchemeSpecificPart();
-                final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
-
-                if (packageName == null || packageName.length() == 0) {
-                    // they sent us a bad intent
-                    return;
-                }
-                if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
-                    for (OnAppsChangedCallbackCompat callback : getCallbacks()) {
-                        callback.onPackageChanged(packageName, user);
-                    }
-                } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
-                    if (!replacing) {
-                        for (OnAppsChangedCallbackCompat callback : getCallbacks()) {
-                            callback.onPackageRemoved(packageName, user);
-                        }
-                    }
-                    // else, we are replacing the package, so a PACKAGE_ADDED will be sent
-                    // later, we will update the package at this time
-                } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
-                    if (!replacing) {
-                        for (OnAppsChangedCallbackCompat callback : getCallbacks()) {
-                            callback.onPackageAdded(packageName, user);
-                        }
-                    } else {
-                        for (OnAppsChangedCallbackCompat callback : getCallbacks()) {
-                            callback.onPackageChanged(packageName, user);
-                        }
-                    }
-                }
-            } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
-                // EXTRA_REPLACING is available Kitkat onwards. For lower devices, it is broadcasted
-                // when moving a package or mounting/un-mounting external storage. Assume that
-                // it is a replacing operation.
-                final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING,
-                        !Utilities.ATLEAST_KITKAT);
-                String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
-                for (OnAppsChangedCallbackCompat callback : getCallbacks()) {
-                    callback.onPackagesAvailable(packages, user, replacing);
-                }
-            } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
-                // This intent is broadcasted when moving a package or mounting/un-mounting
-                // external storage.
-                // However on Kitkat this is also sent when a package is being updated, and
-                // contains an extra Intent.EXTRA_REPLACING=true for that case.
-                // Using false as default for Intent.EXTRA_REPLACING gives correct value on
-                // lower devices as the intent is not sent when the app is updating/replacing.
-                final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
-                String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
-                for (OnAppsChangedCallbackCompat callback : getCallbacks()) {
-                    callback.onPackagesUnavailable(packages, user, replacing);
-                }
-            }
-        }
-    }
-}
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVL.java b/src/com/android/launcher3/compat/LauncherAppsCompatVL.java
index d97bf2f..647c315 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompatVL.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompatVL.java
@@ -16,72 +16,91 @@
 
 package com.android.launcher3.compat;
 
-import android.annotation.TargetApi;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.content.pm.ShortcutInfo;
 import android.graphics.Rect;
-import android.os.Build;
 import android.os.Bundle;
+import android.os.Process;
 import android.os.UserHandle;
+import android.support.annotation.Nullable;
 
+import com.android.launcher3.compat.ShortcutConfigActivityInfo.ShortcutConfigActivityInfoVL;
 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+import com.android.launcher3.util.PackageUserKey;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
-@TargetApi(Build.VERSION_CODES.LOLLIPOP)
-public class LauncherAppsCompatVL extends LauncherAppsCompatV16 {
+public class LauncherAppsCompatVL extends LauncherAppsCompat {
 
-    protected LauncherApps mLauncherApps;
+    protected final LauncherApps mLauncherApps;
+    protected final Context mContext;
 
-    private Map<OnAppsChangedCallbackCompat, WrappedCallback> mCallbacks
-            = new HashMap<OnAppsChangedCallbackCompat, WrappedCallback>();
+    private Map<OnAppsChangedCallbackCompat, WrappedCallback> mCallbacks = new HashMap<>();
 
     LauncherAppsCompatVL(Context context) {
-        super(context);
-        mLauncherApps = (LauncherApps) context.getSystemService("launcherapps");
+        mContext = context;
+        mLauncherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
     }
 
-    public List<LauncherActivityInfoCompat> getActivityList(String packageName,
-            UserHandleCompat user) {
-        List<LauncherActivityInfo> list = mLauncherApps.getActivityList(packageName,
-                user.getUser());
-        if (list.size() == 0) {
-            return Collections.emptyList();
-        }
-        ArrayList<LauncherActivityInfoCompat> compatList =
-                new ArrayList<LauncherActivityInfoCompat>(list.size());
-        for (LauncherActivityInfo info : list) {
-            compatList.add(new LauncherActivityInfoCompatVL(info));
-        }
-        return compatList;
+    @Override
+    public List<LauncherActivityInfo> getActivityList(String packageName, UserHandle user) {
+        return mLauncherApps.getActivityList(packageName, user);
     }
 
-    public LauncherActivityInfoCompat resolveActivity(Intent intent, UserHandleCompat user) {
-        LauncherActivityInfo activity = mLauncherApps.resolveActivity(intent, user.getUser());
-        if (activity != null) {
-            return new LauncherActivityInfoCompatVL(activity);
-        } else {
+    @Override
+    public LauncherActivityInfo resolveActivity(Intent intent, UserHandle user) {
+        return mLauncherApps.resolveActivity(intent, user);
+    }
+
+    @Override
+    public void startActivityForProfile(ComponentName component, UserHandle user,
+            Rect sourceBounds, Bundle opts) {
+        mLauncherApps.startMainActivity(component, user, sourceBounds, opts);
+    }
+
+    @Override
+    public ApplicationInfo getApplicationInfo(String packageName, int flags, UserHandle user) {
+        final boolean isPrimaryUser = Process.myUserHandle().equals(user);
+        if (!isPrimaryUser && (flags == 0)) {
+            // We are looking for an installed app on a secondary profile. Prior to O, the only
+            // entry point for work profiles is through the LauncherActivity.
+            List<LauncherActivityInfo> activityList =
+                    mLauncherApps.getActivityList(packageName, user);
+            return activityList.size() > 0 ? activityList.get(0).getApplicationInfo() : null;
+        }
+        try {
+            ApplicationInfo info =
+                    mContext.getPackageManager().getApplicationInfo(packageName, flags);
+            // There is no way to check if the app is installed for managed profile. But for
+            // primary profile, we can still have this check.
+            if (isPrimaryUser && ((info.flags & ApplicationInfo.FLAG_INSTALLED) == 0)
+                    || !info.enabled) {
+                return null;
+            }
+            return info;
+        } catch (PackageManager.NameNotFoundException e) {
+            // Package not found
             return null;
         }
     }
 
-    public void startActivityForProfile(ComponentName component, UserHandleCompat user,
+    @Override
+    public void showAppDetailsForProfile(ComponentName component, UserHandle user,
             Rect sourceBounds, Bundle opts) {
-        mLauncherApps.startMainActivity(component, user.getUser(), sourceBounds, opts);
+        mLauncherApps.startAppDetailsActivity(component, user, sourceBounds, opts);
     }
 
-    public void showAppDetailsForProfile(ComponentName component, UserHandleCompat user) {
-        mLauncherApps.startAppDetailsActivity(component, user.getUser(), null, null);
-    }
-
+    @Override
     public void addOnAppsChangedCallback(LauncherAppsCompat.OnAppsChangedCallbackCompat callback) {
         WrappedCallback wrappedCallback = new WrappedCallback(callback);
         synchronized (mCallbacks) {
@@ -90,9 +109,9 @@
         mLauncherApps.registerCallback(wrappedCallback);
     }
 
-    public void removeOnAppsChangedCallback(
-            LauncherAppsCompat.OnAppsChangedCallbackCompat callback) {
-        WrappedCallback wrappedCallback = null;
+    @Override
+    public void removeOnAppsChangedCallback(OnAppsChangedCallbackCompat callback) {
+        final WrappedCallback wrappedCallback;
         synchronized (mCallbacks) {
             wrappedCallback = mCallbacks.remove(callback);
         }
@@ -101,16 +120,14 @@
         }
     }
 
-    public boolean isPackageEnabledForProfile(String packageName, UserHandleCompat user) {
-        return mLauncherApps.isPackageEnabled(packageName, user.getUser());
+    @Override
+    public boolean isPackageEnabledForProfile(String packageName, UserHandle user) {
+        return mLauncherApps.isPackageEnabled(packageName, user);
     }
 
-    public boolean isActivityEnabledForProfile(ComponentName component, UserHandleCompat user) {
-        return mLauncherApps.isActivityEnabled(component, user.getUser());
-    }
-
-    public boolean isPackageSuspendedForProfile(String packageName, UserHandleCompat user) {
-        return false;
+    @Override
+    public boolean isActivityEnabledForProfile(ComponentName component, UserHandle user) {
+        return mLauncherApps.isActivityEnabled(component, user);
     }
 
     private static class WrappedCallback extends LauncherApps.Callback {
@@ -121,36 +138,34 @@
         }
 
         public void onPackageRemoved(String packageName, UserHandle user) {
-            mCallback.onPackageRemoved(packageName, UserHandleCompat.fromUser(user));
+            mCallback.onPackageRemoved(packageName, user);
         }
 
         public void onPackageAdded(String packageName, UserHandle user) {
-            mCallback.onPackageAdded(packageName, UserHandleCompat.fromUser(user));
+            mCallback.onPackageAdded(packageName, user);
         }
 
         public void onPackageChanged(String packageName, UserHandle user) {
-            mCallback.onPackageChanged(packageName, UserHandleCompat.fromUser(user));
+            mCallback.onPackageChanged(packageName, user);
         }
 
         public void onPackagesAvailable(String[] packageNames, UserHandle user, boolean replacing) {
-            mCallback.onPackagesAvailable(packageNames, UserHandleCompat.fromUser(user), replacing);
+            mCallback.onPackagesAvailable(packageNames, user, replacing);
         }
 
         public void onPackagesUnavailable(String[] packageNames, UserHandle user,
                 boolean replacing) {
-            mCallback.onPackagesUnavailable(packageNames, UserHandleCompat.fromUser(user),
-                    replacing);
+            mCallback.onPackagesUnavailable(packageNames, user, replacing);
         }
 
         public void onPackagesSuspended(String[] packageNames, UserHandle user) {
-            mCallback.onPackagesSuspended(packageNames, UserHandleCompat.fromUser(user));
+            mCallback.onPackagesSuspended(packageNames, user);
         }
 
         public void onPackagesUnsuspended(String[] packageNames, UserHandle user) {
-            mCallback.onPackagesUnsuspended(packageNames, UserHandleCompat.fromUser(user));
+            mCallback.onPackagesUnsuspended(packageNames, user);
         }
 
-        @Override
         public void onShortcutsChanged(String packageName, List<ShortcutInfo> shortcuts,
                 UserHandle user) {
             List<ShortcutInfoCompat> shortcutInfoCompats = new ArrayList<>(shortcuts.size());
@@ -158,9 +173,26 @@
                 shortcutInfoCompats.add(new ShortcutInfoCompat(shortcutInfo));
             }
 
-            mCallback.onShortcutsChanged(packageName, shortcutInfoCompats,
-                    UserHandleCompat.fromUser(user));
+            mCallback.onShortcutsChanged(packageName, shortcutInfoCompats, user);
         }
     }
+
+    @Override
+    public List<ShortcutConfigActivityInfo> getCustomShortcutActivityList(
+            @Nullable PackageUserKey packageUser) {
+        List<ShortcutConfigActivityInfo> result = new ArrayList<>();
+        if (packageUser != null && !packageUser.mUser.equals(Process.myUserHandle())) {
+            return result;
+        }
+        PackageManager pm = mContext.getPackageManager();
+        for (ResolveInfo info :
+                pm.queryIntentActivities(new Intent(Intent.ACTION_CREATE_SHORTCUT), 0)) {
+            if (packageUser == null || packageUser.mPackageName
+                    .equals(info.activityInfo.packageName)) {
+                result.add(new ShortcutConfigActivityInfoVL(info.activityInfo, pm));
+            }
+        }
+        return result;
+    }
 }
 
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVO.java b/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
new file mode 100644
index 0000000..d145539
--- /dev/null
+++ b/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.compat;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.Process;
+import android.os.UserHandle;
+import android.support.annotation.Nullable;
+import android.util.Log;
+
+import com.android.launcher3.compat.ShortcutConfigActivityInfo.ShortcutConfigActivityInfoVO;
+import com.android.launcher3.util.PackageUserKey;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+@TargetApi(26)
+public class LauncherAppsCompatVO extends LauncherAppsCompatVL {
+
+    LauncherAppsCompatVO(Context context) {
+        super(context);
+    }
+
+    @Override
+    public ApplicationInfo getApplicationInfo(String packageName, int flags, UserHandle user) {
+        try {
+            // TODO: Temporary workaround until the API signature is updated
+            if (false) {
+                throw new PackageManager.NameNotFoundException();
+            }
+
+            ApplicationInfo info = mLauncherApps.getApplicationInfo(packageName, flags, user);
+            return (info.flags & ApplicationInfo.FLAG_INSTALLED) == 0 || !info.enabled
+                    ? null : info;
+        } catch (PackageManager.NameNotFoundException e) {
+            return null;
+        }
+    }
+
+    @Override
+    public List<ShortcutConfigActivityInfo> getCustomShortcutActivityList(
+            @Nullable PackageUserKey packageUser) {
+        List<ShortcutConfigActivityInfo> result = new ArrayList<>();
+        UserHandle myUser = Process.myUserHandle();
+
+        try {
+            Method m = LauncherApps.class.getDeclaredMethod("getShortcutConfigActivityList",
+                    String.class, UserHandle.class);
+            final List<UserHandle> users;
+            final String packageName;
+            if (packageUser == null) {
+                users = UserManagerCompat.getInstance(mContext).getUserProfiles();
+                packageName = null;
+            } else {
+                users = new ArrayList<>(1);
+                users.add(packageUser.mUser);
+                packageName = packageUser.mPackageName;
+            }
+            for (UserHandle user : users) {
+                boolean ignoreTargetSdk = myUser.equals(user);
+                List<LauncherActivityInfo> activities =
+                        (List<LauncherActivityInfo>) m.invoke(mLauncherApps, packageName, user);
+                for (LauncherActivityInfo activityInfo : activities) {
+                    if (ignoreTargetSdk || activityInfo.getApplicationInfo().targetSdkVersion >=
+                            Build.VERSION_CODES.O) {
+                        result.add(new ShortcutConfigActivityInfoVO(activityInfo));
+                    }
+                }
+            }
+        } catch (Exception e) {
+            Log.e("LauncherAppsCompatVO", "Error calling new API", e);
+        }
+
+        return result;
+    }
+}
diff --git a/src/com/android/launcher3/compat/PackageInstallerCompat.java b/src/com/android/launcher3/compat/PackageInstallerCompat.java
index ec5014d..c7fe0ce 100644
--- a/src/com/android/launcher3/compat/PackageInstallerCompat.java
+++ b/src/com/android/launcher3/compat/PackageInstallerCompat.java
@@ -18,8 +18,6 @@
 
 import android.content.Context;
 
-import com.android.launcher3.Utilities;
-
 import java.util.HashMap;
 
 public abstract class PackageInstallerCompat {
@@ -34,11 +32,7 @@
     public static PackageInstallerCompat getInstance(Context context) {
         synchronized (sInstanceLock) {
             if (sInstance == null) {
-                if (Utilities.ATLEAST_LOLLIPOP) {
-                    sInstance = new PackageInstallerCompatVL(context);
-                } else {
-                    sInstance = new PackageInstallerCompatV16();
-                }
+                sInstance = new PackageInstallerCompatVL(context);
             }
             return sInstance;
         }
diff --git a/src/com/android/launcher3/compat/PackageInstallerCompatV16.java b/src/com/android/launcher3/compat/PackageInstallerCompatV16.java
deleted file mode 100644
index 654e349..0000000
--- a/src/com/android/launcher3/compat/PackageInstallerCompatV16.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2014 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.compat;
-
-import java.util.HashMap;
-
-public class PackageInstallerCompatV16 extends PackageInstallerCompat {
-
-    PackageInstallerCompatV16() { }
-
-    @Override
-    public void onStop() { }
-
-    @Override
-    public HashMap<String, Integer> updateAndGetActiveSessionCache() {
-        return new HashMap<>();
-    }
-}
diff --git a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
index 4aa667e..b87582f 100644
--- a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
+++ b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
@@ -16,13 +16,13 @@
 
 package com.android.launcher3.compat;
 
-import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageInstaller.SessionCallback;
 import android.content.pm.PackageInstaller.SessionInfo;
-import android.os.Build;
 import android.os.Handler;
+import android.os.Process;
+import android.os.UserHandle;
 import android.util.SparseArray;
 
 import com.android.launcher3.IconCache;
@@ -32,7 +32,6 @@
 
 import java.util.HashMap;
 
-@TargetApi(Build.VERSION_CODES.LOLLIPOP)
 public class PackageInstallerCompatVL extends PackageInstallerCompat {
 
     @Thunk final SparseArray<String> mActiveSessions = new SparseArray<>();
@@ -43,7 +42,7 @@
 
     PackageInstallerCompatVL(Context context) {
         mInstaller = context.getPackageManager().getPackageInstaller();
-        mCache = LauncherAppState.getInstance().getIconCache();
+        mCache = LauncherAppState.getInstance(context).getIconCache();
         mWorker = new Handler(LauncherModel.getWorkerLooper());
 
         mInstaller.registerSessionCallback(mCallback, mWorker);
@@ -52,9 +51,9 @@
     @Override
     public HashMap<String, Integer> updateAndGetActiveSessionCache() {
         HashMap<String, Integer> activePackages = new HashMap<>();
-        UserHandleCompat user = UserHandleCompat.myUserHandle();
+        UserHandle user = Process.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 +62,7 @@
         return activePackages;
     }
 
-    @Thunk void addSessionInfoToCahce(SessionInfo info, UserHandleCompat user) {
+    @Thunk void addSessionInfoToCache(SessionInfo info, UserHandle user) {
         String packageName = info.getAppPackageName();
         if (packageName != null) {
             mCache.cachePackageInstallInfo(packageName, user, info.getAppIcon(),
@@ -124,7 +123,7 @@
         private void pushSessionDisplayToLauncher(int sessionId) {
             SessionInfo session = mInstaller.getSessionInfo(sessionId);
             if (session != null && session.getAppPackageName() != null) {
-                addSessionInfoToCahce(session, UserHandleCompat.myUserHandle());
+                addSessionInfoToCache(session, Process.myUserHandle());
                 LauncherAppState app = LauncherAppState.getInstanceNoCreate();
 
                 if (app != null) {
diff --git a/src/com/android/launcher3/compat/PinItemRequestCompat.java b/src/com/android/launcher3/compat/PinItemRequestCompat.java
new file mode 100644
index 0000000..1308cba
--- /dev/null
+++ b/src/com/android/launcher3/compat/PinItemRequestCompat.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.compat;
+
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.launcher3.Utilities;
+
+/**
+ * A wrapper around platform implementation of PinItemRequestCompat until the
+ * updated SDK is available.
+ */
+public class PinItemRequestCompat implements Parcelable {
+
+    public static final String EXTRA_PIN_ITEM_REQUEST = "android.content.pm.extra.PIN_ITEM_REQUEST";
+
+    public static final int REQUEST_TYPE_SHORTCUT = 1;
+    public static final int REQUEST_TYPE_APPWIDGET = 2;
+
+    private final Parcelable mObject;
+
+    private PinItemRequestCompat(Parcelable object) {
+        mObject = object;
+    }
+
+    public int getRequestType() {
+        return (Integer) invokeMethod("getRequestType");
+    }
+
+    public ShortcutInfo getShortcutInfo() {
+        return (ShortcutInfo) invokeMethod("getShortcutInfo");
+    }
+
+    public AppWidgetProviderInfo getAppWidgetProviderInfo(Context context) {
+        try {
+            return (AppWidgetProviderInfo) mObject.getClass()
+                    .getDeclaredMethod("getAppWidgetProviderInfo", Context.class)
+                    .invoke(mObject, context);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public boolean isValid() {
+        return (Boolean) invokeMethod("isValid");
+    }
+
+    public boolean accept() {
+        return (Boolean) invokeMethod("accept");
+    }
+
+    public boolean accept(Bundle options) {
+        try {
+            return (Boolean) mObject.getClass().getDeclaredMethod("accept", Bundle.class)
+                    .invoke(mObject, options);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public Bundle getExtras() {
+        try {
+            return (Bundle) mObject.getClass().getDeclaredMethod("getExtras").invoke(mObject);
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    private Object invokeMethod(String methodName) {
+        try {
+            return mObject.getClass().getDeclaredMethod(methodName).invoke(mObject);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int i) {
+        parcel.writeParcelable(mObject, i);
+    }
+
+    public static final Parcelable.Creator<PinItemRequestCompat> CREATOR =
+            new Parcelable.Creator<PinItemRequestCompat>() {
+                public PinItemRequestCompat createFromParcel(Parcel source) {
+                    Parcelable object = source.readParcelable(null);
+                    return new PinItemRequestCompat(object);
+                }
+
+                public PinItemRequestCompat[] newArray(int size) {
+                    return new PinItemRequestCompat[size];
+                }
+            };
+
+    public static PinItemRequestCompat getPinItemRequest(Intent intent) {
+        if (!Utilities.isAtLeastO()) {
+            return null;
+        }
+        Parcelable extra = intent.getParcelableExtra(EXTRA_PIN_ITEM_REQUEST);
+        return extra == null ? null : new PinItemRequestCompat(extra);
+    }
+}
diff --git a/src/com/android/launcher3/compat/ShortcutConfigActivityInfo.java b/src/com/android/launcher3/compat/ShortcutConfigActivityInfo.java
new file mode 100644
index 0000000..4a55e8c
--- /dev/null
+++ b/src/com/android/launcher3/compat/ShortcutConfigActivityInfo.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.compat;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.content.pm.ActivityInfo;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.os.Process;
+import android.os.UserHandle;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.android.launcher3.IconCache;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.R;
+import com.android.launcher3.ShortcutInfo;
+
+import java.lang.reflect.Method;
+
+/**
+ * Wrapper class for representing a shortcut configure activity.
+ */
+public abstract class ShortcutConfigActivityInfo {
+
+    private static final String TAG = "SCActivityInfo";
+
+    private final ComponentName mCn;
+    private final UserHandle mUser;
+
+    protected ShortcutConfigActivityInfo(ComponentName cn, UserHandle user) {
+        mCn = cn;
+        mUser = user;
+    }
+
+    public ComponentName getComponent() {
+        return mCn;
+    }
+
+    public UserHandle getUser() {
+        return mUser;
+    }
+
+    public int getItemType() {
+        return LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
+    }
+
+    public abstract CharSequence getLabel();
+
+    public abstract Drawable getFullResIcon(IconCache cache);
+
+    /**
+     * Return a shortcut info, if it can be created directly on drop, without requiring any
+     * {@link #startConfigActivity(Activity, int)}.
+     */
+    public ShortcutInfo createShortcutInfo() {
+        return null;
+    }
+
+    public boolean startConfigActivity(Activity activity, int requestCode) {
+        Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT)
+                .setComponent(getComponent());
+        try {
+            activity.startActivityForResult(intent, requestCode);
+            return true;
+        } catch (ActivityNotFoundException e) {
+            Toast.makeText(activity, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
+        } catch (SecurityException e) {
+            Toast.makeText(activity, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
+            Log.e(TAG, "Launcher does not have the permission to launch " + intent +
+                    ". Make sure to create a MAIN intent-filter for the corresponding activity " +
+                    "or use the exported attribute for this activity.", e);
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if various properties ({@link #getLabel()}, {@link #getFullResIcon}) can
+     * be safely persisted.
+     */
+    public boolean isPersistable() {
+        return true;
+    }
+
+    static class ShortcutConfigActivityInfoVL extends ShortcutConfigActivityInfo {
+
+        private final ActivityInfo mInfo;
+        private final PackageManager mPm;
+
+
+        public ShortcutConfigActivityInfoVL(ActivityInfo info, PackageManager pm) {
+            super(new ComponentName(info.packageName, info.name), Process.myUserHandle());
+            mInfo = info;
+            mPm = pm;
+        }
+
+        @Override
+        public CharSequence getLabel() {
+            return mInfo.loadLabel(mPm);
+        }
+
+        @Override
+        public Drawable getFullResIcon(IconCache cache) {
+            return cache.getFullResIcon(mInfo);
+        }
+    }
+
+    @TargetApi(26)
+    static class ShortcutConfigActivityInfoVO extends ShortcutConfigActivityInfo {
+
+        private final LauncherActivityInfo mInfo;
+
+        public ShortcutConfigActivityInfoVO(LauncherActivityInfo info) {
+            super(info.getComponentName(), info.getUser());
+            mInfo = info;
+        }
+
+        @Override
+        public CharSequence getLabel() {
+            return mInfo.getLabel();
+        }
+
+        @Override
+        public Drawable getFullResIcon(IconCache cache) {
+            return cache.getFullResIcon(mInfo);
+        }
+
+        @Override
+        public boolean startConfigActivity(Activity activity, int requestCode) {
+            if (getUser().equals(Process.myUserHandle())) {
+                return super.startConfigActivity(activity, requestCode);
+            }
+            try {
+                Method m = LauncherApps.class.getDeclaredMethod(
+                        "getShortcutConfigActivityIntent", LauncherActivityInfo.class);
+                IntentSender is = (IntentSender) m.invoke(
+                        activity.getSystemService(LauncherApps.class), mInfo);
+                activity.startIntentSenderForResult(is, requestCode, null, 0, 0, 0);
+                return true;
+            } catch (Exception e) {
+                Log.e(TAG, "Error calling new API", e);
+                return false;
+            }
+        }
+    }
+}
diff --git a/src/com/android/launcher3/compat/UserHandleCompat.java b/src/com/android/launcher3/compat/UserHandleCompat.java
deleted file mode 100644
index 50af21b..0000000
--- a/src/com/android/launcher3/compat/UserHandleCompat.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (C) 2014 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.compat;
-
-import android.annotation.TargetApi;
-import android.content.Intent;
-import android.os.Build;
-import android.os.UserHandle;
-import com.android.launcher3.Utilities;
-
-public class UserHandleCompat {
-    private UserHandle mUser;
-
-    private UserHandleCompat(UserHandle user) {
-        mUser = user;
-    }
-
-    private UserHandleCompat() {
-    }
-
-    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
-    public static UserHandleCompat myUserHandle() {
-        if (Utilities.ATLEAST_JB_MR1) {
-            return new UserHandleCompat(android.os.Process.myUserHandle());
-        } else {
-            return new UserHandleCompat();
-        }
-    }
-
-    public static UserHandleCompat fromUser(UserHandle user) {
-        if (user == null) {
-            return null;
-        } else {
-            return new UserHandleCompat(user);
-        }
-    }
-
-    public UserHandle getUser() {
-        return mUser;
-    }
-
-    @Override
-    public String toString() {
-        if (Utilities.ATLEAST_JB_MR1) {
-            return mUser.toString();
-        } else {
-            return "";
-        }
-    }
-
-    @Override
-    public boolean equals(Object other) {
-        if (!(other instanceof UserHandleCompat)) {
-            return false;
-        }
-        if (Utilities.ATLEAST_JB_MR1) {
-            return mUser.equals(((UserHandleCompat) other).mUser);
-        } else {
-            return true;
-        }
-    }
-
-    @Override
-    public int hashCode() {
-        if (Utilities.ATLEAST_JB_MR1) {
-            return mUser.hashCode();
-        } else {
-            return 0;
-        }
-    }
-
-    /**
-     * Adds {@link UserHandle} to the intent in for L or above.
-     * Pre-L the launcher doesn't support showing apps for multiple
-     * profiles so this is a no-op.
-     */
-    public void addToIntent(Intent intent, String name) {
-        if (Utilities.ATLEAST_LOLLIPOP && mUser != null) {
-            intent.putExtra(name, mUser);
-        }
-    }
-
-    public static UserHandleCompat fromIntent(Intent intent) {
-        if (Utilities.ATLEAST_LOLLIPOP) {
-            UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
-            if (user != null) {
-                return UserHandleCompat.fromUser(user);
-            }
-        }
-        return null;
-    }
-}
diff --git a/src/com/android/launcher3/compat/UserManagerCompat.java b/src/com/android/launcher3/compat/UserManagerCompat.java
index a5f8dd2..25808d2 100644
--- a/src/com/android/launcher3/compat/UserManagerCompat.java
+++ b/src/com/android/launcher3/compat/UserManagerCompat.java
@@ -17,6 +17,7 @@
 package com.android.launcher3.compat;
 
 import android.content.Context;
+import android.os.UserHandle;
 
 import com.android.launcher3.Utilities;
 
@@ -32,18 +33,14 @@
     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());
-                } else if (Utilities.ATLEAST_LOLLIPOP) {
-                    sInstance = new UserManagerCompatVL(context.getApplicationContext());
-                } else if (Utilities.ATLEAST_JB_MR1) {
-                    sInstance = new UserManagerCompatV17(context.getApplicationContext());
                 } else {
-                    sInstance = new UserManagerCompatV16();
+                    sInstance = new UserManagerCompatVL(context.getApplicationContext());
                 }
             }
             return sInstance;
@@ -55,13 +52,13 @@
      */
     public abstract void enableAndResetCache();
 
-    public abstract List<UserHandleCompat> getUserProfiles();
-    public abstract long getSerialNumberForUser(UserHandleCompat user);
-    public abstract UserHandleCompat getUserForSerialNumber(long serialNumber);
-    public abstract CharSequence getBadgedLabelForUser(CharSequence label, UserHandleCompat user);
-    public abstract long getUserCreationTime(UserHandleCompat user);
-    public abstract boolean isQuietModeEnabled(UserHandleCompat user);
-    public abstract boolean isUserUnlocked(UserHandleCompat user);
+    public abstract List<UserHandle> getUserProfiles();
+    public abstract long getSerialNumberForUser(UserHandle user);
+    public abstract UserHandle getUserForSerialNumber(long serialNumber);
+    public abstract CharSequence getBadgedLabelForUser(CharSequence label, UserHandle user);
+    public abstract long getUserCreationTime(UserHandle user);
+    public abstract boolean isQuietModeEnabled(UserHandle user);
+    public abstract boolean isUserUnlocked(UserHandle user);
 
     public abstract boolean isDemoUser();
 }
diff --git a/src/com/android/launcher3/compat/UserManagerCompatV16.java b/src/com/android/launcher3/compat/UserManagerCompatV16.java
deleted file mode 100644
index 9bd4567..0000000
--- a/src/com/android/launcher3/compat/UserManagerCompatV16.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2014 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.compat;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class UserManagerCompatV16 extends UserManagerCompat {
-
-    UserManagerCompatV16() {
-    }
-
-    public List<UserHandleCompat> getUserProfiles() {
-        List<UserHandleCompat> profiles = new ArrayList<UserHandleCompat>(1);
-        profiles.add(UserHandleCompat.myUserHandle());
-        return profiles;
-    }
-
-    public UserHandleCompat getUserForSerialNumber(long serialNumber) {
-        return UserHandleCompat.myUserHandle();
-    }
-
-    public long getSerialNumberForUser(UserHandleCompat user) {
-        return 0;
-    }
-
-    public CharSequence getBadgedLabelForUser(CharSequence label, UserHandleCompat user) {
-        return label;
-    }
-
-    @Override
-    public long getUserCreationTime(UserHandleCompat user) {
-        return 0;
-    }
-
-    @Override
-    public void enableAndResetCache() {
-    }
-
-    @Override
-    public boolean isQuietModeEnabled(UserHandleCompat user) {
-        return false;
-    }
-
-    @Override
-    public boolean isUserUnlocked(UserHandleCompat user) {
-        return true;
-    }
-
-    @Override
-    public boolean isDemoUser() {
-        return false;
-    }
-}
diff --git a/src/com/android/launcher3/compat/UserManagerCompatV17.java b/src/com/android/launcher3/compat/UserManagerCompatV17.java
deleted file mode 100644
index 75203b7..0000000
--- a/src/com/android/launcher3/compat/UserManagerCompatV17.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2014 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.compat;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.os.Build;
-import android.os.UserManager;
-
-import com.android.launcher3.util.LongArrayMap;
-
-import java.util.HashMap;
-
-@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
-public class UserManagerCompatV17 extends UserManagerCompatV16 {
-
-    protected LongArrayMap<UserHandleCompat> mUsers;
-    // Create a separate reverse map as LongArrayMap.indexOfValue checks if objects are same
-    // and not {@link Object#equals}
-    protected HashMap<UserHandleCompat, Long> mUserToSerialMap;
-
-    protected UserManager mUserManager;
-
-    UserManagerCompatV17(Context context) {
-        mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
-    }
-
-    public long getSerialNumberForUser(UserHandleCompat user) {
-        synchronized (this) {
-            if (mUserToSerialMap != null) {
-                Long serial = mUserToSerialMap.get(user);
-                return serial == null ? 0 : serial;
-            }
-        }
-        return mUserManager.getSerialNumberForUser(user.getUser());
-    }
-
-    public UserHandleCompat getUserForSerialNumber(long serialNumber) {
-        synchronized (this) {
-            if (mUsers != null) {
-                return mUsers.get(serialNumber);
-            }
-        }
-        return UserHandleCompat.fromUser(mUserManager.getUserForSerialNumber(serialNumber));
-    }
-
-    @Override
-    public void enableAndResetCache() {
-        synchronized (this) {
-            mUsers = new LongArrayMap<>();
-            mUserToSerialMap = new HashMap<>();
-            UserHandleCompat myUser = UserHandleCompat.myUserHandle();
-            long serial = mUserManager.getSerialNumberForUser(myUser.getUser());
-            mUsers.put(serial, myUser);
-            mUserToSerialMap.put(myUser, serial);
-        }
-    }
-}
-
diff --git a/src/com/android/launcher3/compat/UserManagerCompatVL.java b/src/com/android/launcher3/compat/UserManagerCompatVL.java
index 2552b0c..45525f5 100644
--- a/src/com/android/launcher3/compat/UserManagerCompatVL.java
+++ b/src/com/android/launcher3/compat/UserManagerCompatVL.java
@@ -16,12 +16,11 @@
 
 package com.android.launcher3.compat;
 
-import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
-import android.os.Build;
 import android.os.UserHandle;
+import android.os.UserManager;
 
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.LongArrayMap;
@@ -31,20 +30,61 @@
 import java.util.HashMap;
 import java.util.List;
 
-@TargetApi(Build.VERSION_CODES.LOLLIPOP)
-public class UserManagerCompatVL extends UserManagerCompatV17 {
+public class UserManagerCompatVL extends UserManagerCompat {
     private static final String USER_CREATION_TIME_KEY = "user_creation_time_";
 
+    protected final UserManager mUserManager;
     private final PackageManager mPm;
     private final Context mContext;
 
+    protected LongArrayMap<UserHandle> mUsers;
+    // Create a separate reverse map as LongArrayMap.indexOfValue checks if objects are same
+    // and not {@link Object#equals}
+    protected HashMap<UserHandle, Long> mUserToSerialMap;
+
     UserManagerCompatVL(Context context) {
-        super(context);
+        mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
         mPm = context.getPackageManager();
         mContext = context;
     }
 
     @Override
+    public long getSerialNumberForUser(UserHandle user) {
+        synchronized (this) {
+            if (mUserToSerialMap != null) {
+                Long serial = mUserToSerialMap.get(user);
+                return serial == null ? 0 : serial;
+            }
+        }
+        return mUserManager.getSerialNumberForUser(user);
+    }
+
+    @Override
+    public UserHandle getUserForSerialNumber(long serialNumber) {
+        synchronized (this) {
+            if (mUsers != null) {
+                return mUsers.get(serialNumber);
+            }
+        }
+        return mUserManager.getUserForSerialNumber(serialNumber);
+    }
+
+    @Override
+    public boolean isQuietModeEnabled(UserHandle user) {
+        return false;
+    }
+
+    @Override
+    public boolean isUserUnlocked(UserHandle user) {
+        return true;
+    }
+
+    @Override
+    public boolean isDemoUser() {
+        return false;
+    }
+
+    @Override
     public void enableAndResetCache() {
         synchronized (this) {
             mUsers = new LongArrayMap<>();
@@ -53,46 +93,35 @@
             if (users != null) {
                 for (UserHandle user : users) {
                     long serial = mUserManager.getSerialNumberForUser(user);
-                    UserHandleCompat userCompat = UserHandleCompat.fromUser(user);
-                    mUsers.put(serial, userCompat);
-                    mUserToSerialMap.put(userCompat, serial);
+                    mUsers.put(serial, user);
+                    mUserToSerialMap.put(user, serial);
                 }
             }
         }
     }
 
     @Override
-    public List<UserHandleCompat> getUserProfiles() {
+    public List<UserHandle> getUserProfiles() {
         synchronized (this) {
             if (mUsers != null) {
-                List<UserHandleCompat> users = new ArrayList<>();
-                users.addAll(mUserToSerialMap.keySet());
-                return users;
+                return new ArrayList<>(mUserToSerialMap.keySet());
             }
         }
 
         List<UserHandle> users = mUserManager.getUserProfiles();
-        if (users == null) {
-            return Collections.emptyList();
-        }
-        ArrayList<UserHandleCompat> compatUsers = new ArrayList<UserHandleCompat>(
-                users.size());
-        for (UserHandle user : users) {
-            compatUsers.add(UserHandleCompat.fromUser(user));
-        }
-        return compatUsers;
+        return users == null ? Collections.<UserHandle>emptyList() : users;
     }
 
     @Override
-    public CharSequence getBadgedLabelForUser(CharSequence label, UserHandleCompat user) {
+    public CharSequence getBadgedLabelForUser(CharSequence label, UserHandle user) {
         if (user == null) {
             return label;
         }
-        return mPm.getUserBadgedLabel(label, user.getUser());
+        return mPm.getUserBadgedLabel(label, user);
     }
 
     @Override
-    public long getUserCreationTime(UserHandleCompat user) {
+    public long getUserCreationTime(UserHandle user) {
         SharedPreferences prefs = Utilities.getPrefs(mContext);
         String key = USER_CREATION_TIME_KEY + getSerialNumberForUser(user);
         if (!prefs.contains(key)) {
diff --git a/src/com/android/launcher3/compat/UserManagerCompatVM.java b/src/com/android/launcher3/compat/UserManagerCompatVM.java
index 81d67ea..75c1877 100644
--- a/src/com/android/launcher3/compat/UserManagerCompatVM.java
+++ b/src/com/android/launcher3/compat/UserManagerCompatVM.java
@@ -19,6 +19,7 @@
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.os.Build;
+import android.os.UserHandle;
 
 @TargetApi(Build.VERSION_CODES.M)
 public class UserManagerCompatVM extends UserManagerCompatVL {
@@ -28,7 +29,7 @@
     }
 
     @Override
-    public long getUserCreationTime(UserHandleCompat user) {
-        return mUserManager.getUserCreationTime(user.getUser());
+    public long getUserCreationTime(UserHandle user) {
+        return mUserManager.getUserCreationTime(user);
     }
 }
diff --git a/src/com/android/launcher3/compat/UserManagerCompatVN.java b/src/com/android/launcher3/compat/UserManagerCompatVN.java
index 4edac05..50a0217 100644
--- a/src/com/android/launcher3/compat/UserManagerCompatVN.java
+++ b/src/com/android/launcher3/compat/UserManagerCompatVN.java
@@ -19,8 +19,7 @@
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.os.Build;
-
-import com.android.launcher3.Utilities;
+import android.os.UserHandle;
 
 @TargetApi(Build.VERSION_CODES.N)
 public class UserManagerCompatVN extends UserManagerCompatVM {
@@ -30,13 +29,13 @@
     }
 
     @Override
-    public boolean isQuietModeEnabled(UserHandleCompat user) {
-        return mUserManager.isQuietModeEnabled(user.getUser());
+    public boolean isQuietModeEnabled(UserHandle user) {
+        return mUserManager.isQuietModeEnabled(user);
     }
 
     @Override
-    public boolean isUserUnlocked(UserHandleCompat user) {
-        return mUserManager.isUserUnlocked(user.getUser());
+    public boolean isUserUnlocked(UserHandle user) {
+        return mUserManager.isUserUnlocked(user);
     }
 }
 
diff --git a/src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java b/src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java
new file mode 100644
index 0000000..06493b2
--- /dev/null
+++ b/src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.discovery;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.ShortcutInfo;
+
+public class AppDiscoveryAppInfo extends AppInfo {
+
+    public final boolean showAsDiscoveryItem;
+    public final boolean isInstantApp;
+    public final boolean isRecent;
+    public final float rating;
+    public final long reviewCount;
+    public final @NonNull String publisher;
+    public final @NonNull Intent installIntent;
+    public final @NonNull Intent launchIntent;
+    public final @Nullable String priceFormatted;
+
+    public AppDiscoveryAppInfo(AppDiscoveryItem item) {
+        this.intent = item.isInstantApp ? item.launchIntent : item.installIntent;
+        this.title = item.title;
+        this.iconBitmap = item.bitmap;
+        this.isDisabled = ShortcutInfo.DEFAULT;
+        this.usingLowResIcon = false;
+        this.isInstantApp = item.isInstantApp;
+        this.isRecent = item.isRecent;
+        this.rating = item.starRating;
+        this.showAsDiscoveryItem = true;
+        this.publisher = item.publisher != null ? item.publisher : "";
+        this.priceFormatted = item.price;
+        this.componentName = new ComponentName(item.packageName, "");
+        this.installIntent = item.installIntent;
+        this.launchIntent = item.launchIntent;
+        this.reviewCount = item.reviewCount;
+        this.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
+    }
+
+    @Override
+    public ShortcutInfo makeShortcut() {
+        if (!isDragAndDropSupported()) {
+            throw new RuntimeException("DnD is currently not supported for discovered store apps");
+        }
+        return super.makeShortcut();
+    }
+
+    public boolean isDragAndDropSupported() {
+        return isInstantApp;
+    }
+
+}
diff --git a/src/com/android/launcher3/discovery/AppDiscoveryItem.java b/src/com/android/launcher3/discovery/AppDiscoveryItem.java
new file mode 100644
index 0000000..09c91ac
--- /dev/null
+++ b/src/com/android/launcher3/discovery/AppDiscoveryItem.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.discovery;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+
+/**
+ * This class represents the model for a discovered app via app discovery.
+ * It holds all information for one result retrieved from an app discovery service.
+ */
+public class AppDiscoveryItem {
+
+    public final String packageName;
+    public final boolean isInstantApp;
+    public final boolean isRecent;
+    public final float starRating;
+    public final long reviewCount;
+    public final Intent launchIntent;
+    public final Intent installIntent;
+    public final CharSequence title;
+    public final String publisher;
+    public final String price;
+    public final Bitmap bitmap;
+
+    public AppDiscoveryItem(String packageName,
+                            boolean isInstantApp,
+                            boolean isRecent,
+                            float starRating,
+                            long reviewCount,
+                            CharSequence title,
+                            String publisher,
+                            Bitmap bitmap,
+                            String price,
+                            Intent launchIntent,
+                            Intent installIntent) {
+        this.packageName = packageName;
+        this.isInstantApp = isInstantApp;
+        this.isRecent = isRecent;
+        this.starRating = starRating;
+        this.reviewCount = reviewCount;
+        this.launchIntent = launchIntent;
+        this.installIntent = installIntent;
+        this.title = title;
+        this.publisher = publisher;
+        this.price = price;
+        this.bitmap = bitmap;
+    }
+
+}
diff --git a/src/com/android/launcher3/discovery/AppDiscoveryItemView.java b/src/com/android/launcher3/discovery/AppDiscoveryItemView.java
new file mode 100644
index 0000000..9bb3b10
--- /dev/null
+++ b/src/com/android/launcher3/discovery/AppDiscoveryItemView.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.discovery;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import com.android.launcher3.R;
+
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+
+public class AppDiscoveryItemView extends RelativeLayout {
+
+    private static boolean SHOW_REVIEW_COUNT = false;
+
+    private ImageView mImage;
+    private TextView mTitle;
+    private TextView mRatingText;
+    private RatingView mRatingView;
+    private TextView mReviewCount;
+    private TextView mPrice;
+    private OnLongClickListener mOnLongClickListener;
+
+    public AppDiscoveryItemView(Context context) {
+        this(context, null);
+    }
+
+    public AppDiscoveryItemView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public AppDiscoveryItemView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        this.mImage = (ImageView) findViewById(R.id.image);
+        this.mTitle = (TextView) findViewById(R.id.title);
+        this.mRatingText = (TextView) findViewById(R.id.rating);
+        this.mRatingView = (RatingView) findViewById(R.id.rating_view);
+        this.mPrice = (TextView) findViewById(R.id.price);
+        this.mReviewCount = (TextView) findViewById(R.id.review_count);
+    }
+
+    public void init(OnClickListener clickListener,
+                     AccessibilityDelegate accessibilityDelegate,
+                     OnLongClickListener onLongClickListener) {
+        setOnClickListener(clickListener);
+        mImage.setOnClickListener(clickListener);
+        setAccessibilityDelegate(accessibilityDelegate);
+        mOnLongClickListener = onLongClickListener;
+    }
+
+    public void apply(@NonNull AppDiscoveryAppInfo info) {
+        setTag(info);
+        mImage.setTag(info);
+        mImage.setImageBitmap(info.iconBitmap);
+        mImage.setOnLongClickListener(info.isDragAndDropSupported() ? mOnLongClickListener : null);
+        mTitle.setText(info.title);
+        mPrice.setText(info.priceFormatted != null ? info.priceFormatted : "");
+        mReviewCount.setVisibility(SHOW_REVIEW_COUNT ? View.VISIBLE : View.GONE);
+        if (info.rating >= 0) {
+            mRatingText.setText(new DecimalFormat("#.#").format(info.rating));
+            mRatingView.setRating(info.rating);
+            mRatingView.setVisibility(View.VISIBLE);
+            String reviewCountFormatted = NumberFormat.getInstance().format(info.reviewCount);
+            mReviewCount.setText("(" + reviewCountFormatted + ")");
+        } else {
+            // if we don't have a rating
+            mRatingView.setVisibility(View.GONE);
+            mRatingText.setText("");
+            mReviewCount.setText("");
+        }
+    }
+}
diff --git a/src/com/android/launcher3/discovery/AppDiscoveryUpdateState.java b/src/com/android/launcher3/discovery/AppDiscoveryUpdateState.java
new file mode 100644
index 0000000..0700a10
--- /dev/null
+++ b/src/com/android/launcher3/discovery/AppDiscoveryUpdateState.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.discovery;
+
+public enum AppDiscoveryUpdateState {
+    START, UPDATE, END
+}
diff --git a/src/com/android/launcher3/discovery/RatingView.java b/src/com/android/launcher3/discovery/RatingView.java
new file mode 100644
index 0000000..8fe63d6
--- /dev/null
+++ b/src/com/android/launcher3/discovery/RatingView.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.discovery;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.drawable.ClipDrawable;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+
+import com.android.launcher3.R;
+
+/**
+ * A simple rating view that shows stars with a rating from 0-5.
+ */
+public class RatingView extends View {
+
+    private static final float WIDTH_FACTOR = 0.9f;
+    private static final int MAX_LEVEL = 10000;
+    private static final int MAX_STARS = 5;
+
+    private final Drawable mStarDrawable;
+    private final int mColorGray;
+    private final int mColorHighlight;
+
+    private float rating;
+
+    public RatingView(Context context) {
+        this(context, null);
+    }
+
+    public RatingView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public RatingView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        mStarDrawable = getResources().getDrawable(R.drawable.ic_star_rating, null);
+        mColorGray = 0x1E000000;
+        mColorHighlight = 0x8A000000;
+    }
+
+    public void setRating(float rating) {
+        this.rating = Math.min(Math.max(rating, 0), MAX_STARS);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        drawStars(canvas, MAX_STARS, mColorGray);
+        drawStars(canvas, rating, mColorHighlight);
+    }
+
+    private void drawStars(Canvas canvas, float stars, int color) {
+        int fullWidth = getLayoutParams().width;
+        int cellWidth = fullWidth / MAX_STARS;
+        int starWidth = (int) (cellWidth * WIDTH_FACTOR);
+        int padding = cellWidth - starWidth;
+        int fullStars = (int) stars;
+        float partialStarFactor = stars - fullStars;
+
+        for (int i = 0; i < fullStars; i++) {
+            int x = i * cellWidth + padding;
+            Drawable star = mStarDrawable.getConstantState().newDrawable().mutate();
+            star.setTint(color);
+            star.setBounds(x, padding, x + starWidth, padding + starWidth);
+            star.draw(canvas);
+        }
+        if (partialStarFactor > 0f) {
+            int x = fullStars * cellWidth + padding;
+            ClipDrawable star = new ClipDrawable(mStarDrawable,
+                    Gravity.LEFT, ClipDrawable.HORIZONTAL);
+            star.setTint(color);
+            star.setLevel((int) (MAX_LEVEL * partialStarFactor));
+            star.setBounds(x, padding, x + starWidth, padding + starWidth);
+            star.draw(canvas);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
new file mode 100644
index 0000000..09592a8
--- /dev/null
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.dragndrop;
+
+import static com.android.launcher3.logging.LoggerUtils.newCommandAction;
+import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
+import static com.android.launcher3.logging.LoggerUtils.newItemTarget;
+import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
+
+import android.annotation.TargetApi;
+import android.app.ActivityOptions;
+import android.appwidget.AppWidgetHost;
+import android.appwidget.AppWidgetManager;
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.graphics.Canvas;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.os.Build;
+import android.os.Bundle;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.DragShadowBuilder;
+import android.view.View.OnLongClickListener;
+import android.view.View.OnTouchListener;
+
+import com.android.launcher3.BaseActivity;
+import com.android.launcher3.InstallShortcutReceiver;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.AppWidgetManagerCompat;
+import com.android.launcher3.compat.PinItemRequestCompat;
+import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.launcher3.widget.PendingAddShortcutInfo;
+import com.android.launcher3.widget.PendingAddWidgetInfo;
+import com.android.launcher3.widget.WidgetHostViewLoader;
+import com.android.launcher3.widget.WidgetImageView;
+
+@TargetApi(Build.VERSION_CODES.N_MR1)
+public class AddItemActivity extends BaseActivity implements OnLongClickListener, OnTouchListener {
+
+    private static final int SHADOW_SIZE = 10;
+
+    private static final int REQUEST_BIND_APPWIDGET = 1;
+    private static final String STATE_EXTRA_WIDGET_ID = "state.widget.id";
+
+    private final PointF mLastTouchPos = new PointF();
+
+    private PinItemRequestCompat mRequest;
+    private LauncherAppState mApp;
+    private InvariantDeviceProfile mIdp;
+
+    private LivePreviewWidgetCell mWidgetCell;
+
+    // Widget request specific options.
+    private AppWidgetHost mAppWidgetHost;
+    private AppWidgetManagerCompat mAppWidgetManager;
+    private PendingAddWidgetInfo mPendingWidgetInfo;
+    private int mPendingBindWidgetId;
+    private Bundle mWidgetOptions;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mRequest = PinItemRequestCompat.getPinItemRequest(getIntent());
+        if (mRequest == null) {
+            finish();
+            return;
+        }
+
+        mApp = LauncherAppState.getInstance(this);
+        mIdp = mApp.getInvariantDeviceProfile();
+
+        // Use the application context to get the device profile, as in multiwindow-mode, the
+        // confirmation activity might be rotated.
+        mDeviceProfile = mIdp.getDeviceProfile(getApplicationContext());
+
+        setContentView(R.layout.add_item_confirmation_activity);
+        mWidgetCell = (LivePreviewWidgetCell) findViewById(R.id.widget_cell);
+
+        if (mRequest.getRequestType() == PinItemRequestCompat.REQUEST_TYPE_SHORTCUT) {
+            setupShortcut();
+        } else {
+            if (!setupWidget()) {
+                // TODO: show error toast?
+                finish();
+            }
+        }
+
+        mWidgetCell.setOnTouchListener(this);
+        mWidgetCell.setOnLongClickListener(this);
+
+        // savedInstanceState is null when the activity is created the first time (i.e., avoids
+        // duplicate logging during rotation)
+        if (savedInstanceState == null) {
+            logCommand(Action.Command.ENTRY);
+        }
+    }
+
+    @Override
+    public boolean onTouch(View view, MotionEvent motionEvent) {
+        mLastTouchPos.set(motionEvent.getX(), motionEvent.getY());
+        return false;
+    }
+
+    @Override
+    public boolean onLongClick(View view) {
+        // Find the position of the preview relative to the touch location.
+        WidgetImageView img = mWidgetCell.getWidgetView();
+
+        // If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and
+        // we abort the drag.
+        if (img.getBitmap() == null) {
+            return false;
+        }
+
+        Rect bounds = img.getBitmapBounds();
+        bounds.offset(img.getLeft() - (int) mLastTouchPos.x, img.getTop() - (int) mLastTouchPos.y);
+
+        // Start home and pass the draw request params
+        PinItemDragListener listener = new PinItemDragListener(mRequest, bounds,
+                img.getBitmap().getWidth(), img.getWidth());
+        Intent homeIntent = new Intent(Intent.ACTION_MAIN)
+                .addCategory(Intent.CATEGORY_HOME)
+                .setPackage(getPackageName())
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                .putExtra(PinItemDragListener.EXTRA_PIN_ITEM_DRAG_LISTENER, listener);
+
+        if (!getResources().getBoolean(R.bool.allow_rotation) &&
+                !Utilities.isAllowRotationPrefEnabled(this) &&
+                (getResources().getConfiguration().orientation ==
+                        Configuration.ORIENTATION_LANDSCAPE && !isInMultiWindowMode())) {
+            // If we are starting the drag in landscape even though home is locked in portrait,
+            // restart the home activity to temporarily allow rotation.
+            homeIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        }
+
+        startActivity(homeIntent,
+                ActivityOptions.makeCustomAnimation(this, 0, android.R.anim.fade_out).toBundle());
+
+        // Start a system drag and drop. We use a transparent bitmap as preview for system drag
+        // as the preview is handled internally by launcher.
+        ClipDescription description = new ClipDescription("", new String[]{listener.getMimeType()});
+        ClipData data = new ClipData(description, new ClipData.Item(""));
+        view.startDragAndDrop(data, new DragShadowBuilder(view) {
+
+            @Override
+            public void onDrawShadow(Canvas canvas) { }
+
+            @Override
+            public void onProvideShadowMetrics(Point outShadowSize, Point outShadowTouchPoint) {
+                outShadowSize.set(SHADOW_SIZE, SHADOW_SIZE);
+                outShadowTouchPoint.set(SHADOW_SIZE / 2, SHADOW_SIZE / 2);
+            }
+        }, null, View.DRAG_FLAG_GLOBAL);
+        return false;
+    }
+
+    private void setupShortcut() {
+        PinShortcutRequestActivityInfo shortcutInfo =
+                new PinShortcutRequestActivityInfo(mRequest, this);
+        WidgetItem item = new WidgetItem(shortcutInfo);
+        mWidgetCell.getWidgetView().setTag(new PendingAddShortcutInfo(shortcutInfo));
+        mWidgetCell.applyFromCellItem(item, mApp.getWidgetCache());
+        mWidgetCell.ensurePreview();
+    }
+
+    private boolean setupWidget() {
+        LauncherAppWidgetProviderInfo widgetInfo = LauncherAppWidgetProviderInfo
+                .fromProviderInfo(this, mRequest.getAppWidgetProviderInfo(this));
+        if (widgetInfo.minSpanX > mIdp.numColumns || widgetInfo.minSpanY > mIdp.numRows) {
+            // Cannot add widget
+            return false;
+        }
+        mWidgetCell.setPreview(PinItemDragListener.getPreview(mRequest));
+
+        mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);
+        mAppWidgetHost = new AppWidgetHost(this, Launcher.APPWIDGET_HOST_ID);
+
+        mPendingWidgetInfo = new PendingAddWidgetInfo(widgetInfo);
+        mPendingWidgetInfo.spanX = Math.min(mIdp.numColumns, widgetInfo.spanX);
+        mPendingWidgetInfo.spanY = Math.min(mIdp.numRows, widgetInfo.spanY);
+        mWidgetOptions = WidgetHostViewLoader.getDefaultOptionsForWidget(this, mPendingWidgetInfo);
+
+        WidgetItem item = new WidgetItem(widgetInfo, getPackageManager(), mIdp);
+        mWidgetCell.getWidgetView().setTag(mPendingWidgetInfo);
+        mWidgetCell.applyFromCellItem(item, mApp.getWidgetCache());
+        mWidgetCell.ensurePreview();
+        return true;
+    }
+
+    /**
+     * Called when the cancel button is clicked.
+     */
+    public void onCancelClick(View v) {
+        logCommand(Action.Command.CANCEL);
+        finish();
+    }
+
+    /**
+     * Called when place-automatically button is clicked.
+     */
+    public void onPlaceAutomaticallyClick(View v) {
+        if (mRequest.getRequestType() == PinItemRequestCompat.REQUEST_TYPE_SHORTCUT) {
+            InstallShortcutReceiver.queueShortcut(
+                    new ShortcutInfoCompat(mRequest.getShortcutInfo()), this);
+            logCommand(Action.Command.CONFIRM);
+            mRequest.accept();
+            finish();
+            return;
+        }
+
+        mPendingBindWidgetId = mAppWidgetHost.allocateAppWidgetId();
+        boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
+                mPendingBindWidgetId, mRequest.getAppWidgetProviderInfo(this), mWidgetOptions);
+        if (success) {
+            acceptWidget(mPendingBindWidgetId);
+            return;
+        }
+
+        // request bind widget
+        Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
+        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mPendingBindWidgetId);
+        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER,
+                mPendingWidgetInfo.componentName);
+        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE,
+                mRequest.getAppWidgetProviderInfo(this).getProfile());
+        startActivityForResult(intent, REQUEST_BIND_APPWIDGET);
+    }
+
+    private void acceptWidget(int widgetId) {
+        InstallShortcutReceiver.queueWidget(mRequest.getAppWidgetProviderInfo(this), widgetId, this);
+        mWidgetOptions.putInt(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
+        mRequest.accept(mWidgetOptions);
+        logCommand(Action.Command.CONFIRM);
+        finish();
+    }
+
+    @Override
+    public void onBackPressed() {
+        logCommand(Action.Command.BACK);
+        super.onBackPressed();
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (requestCode == REQUEST_BIND_APPWIDGET) {
+            int widgetId = data != null
+                    ? data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mPendingBindWidgetId)
+                    : mPendingBindWidgetId;
+            if (resultCode == RESULT_OK) {
+                acceptWidget(widgetId);
+            } else {
+                // Simply wait it out.
+                mAppWidgetHost.deleteAppWidgetId(widgetId);
+                mPendingBindWidgetId = -1;
+            }
+            return;
+        }
+        super.onActivityResult(requestCode, resultCode, data);
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putInt(STATE_EXTRA_WIDGET_ID, mPendingBindWidgetId);
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Bundle savedInstanceState) {
+        super.onRestoreInstanceState(savedInstanceState);
+        mPendingBindWidgetId = savedInstanceState
+                .getInt(STATE_EXTRA_WIDGET_ID, mPendingBindWidgetId);
+    }
+
+    private void logCommand(int command) {
+        getUserEventDispatcher().dispatchUserEvent(newLauncherEvent(
+                newCommandAction(command),
+                newItemTarget(mWidgetCell.getWidgetView()),
+                newContainerTarget(ContainerType.PINITEM)), null);
+    }
+}
diff --git a/src/com/android/launcher3/dragndrop/AnotherWindowDragSource.java b/src/com/android/launcher3/dragndrop/AnotherWindowDragSource.java
deleted file mode 100644
index 156941a..0000000
--- a/src/com/android/launcher3/dragndrop/AnotherWindowDragSource.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.dragndrop;
-
-import android.content.Context;
-import android.view.View;
-
-import com.android.launcher3.DragSource;
-import com.android.launcher3.DropTarget.DragObject;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
-
-/**
- * DragSource used when the drag started at another window.
- */
-public class AnotherWindowDragSource implements DragSource {
-
-    private final Context mContext;
-
-    AnotherWindowDragSource(Context context) {
-        mContext = context;
-    }
-
-    @Override
-    public boolean supportsFlingToDelete() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsAppInfoDropTarget() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsDeleteDropTarget() {
-        return false;
-    }
-
-    @Override
-    public float getIntrinsicIconScaleFactor() {
-        return 1;
-    }
-
-    @Override
-    public void onFlingToDeleteCompleted() {
-    }
-
-    @Override
-    public void onDropCompleted(View target, DragObject d,
-            boolean isFlingToDelete, boolean success) {
-        if (!success) {
-            Launcher.getLauncher(mContext).exitSpringLoadedDragModeDelayed(false, 0, null);
-        }
-
-    }
-
-    @Override
-    public void fillInLaunchSourceData(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..7410ae6 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -17,33 +17,25 @@
 package com.android.launcher3.dragndrop;
 
 import android.content.ComponentName;
-import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Point;
-import android.graphics.PointF;
 import android.graphics.Rect;
-import android.os.Handler;
 import android.os.IBinder;
 import android.view.DragEvent;
 import android.view.HapticFeedbackConstants;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
-import android.view.VelocityTracker;
 import android.view.View;
-import android.view.ViewConfiguration;
 import android.view.inputmethod.InputMethodManager;
 
 import com.android.launcher3.DragSource;
 import com.android.launcher3.DropTarget;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.PagedView;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutInfo;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.accessibility.DragViewStateAnnouncer;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.TouchController;
@@ -54,29 +46,14 @@
  * Class for initiating a drag within a view or across multiple views.
  */
 public class DragController implements DragDriver.EventListener, TouchController {
-    private static final String TAG = "Launcher.DragController";
-
-    public static final int SCROLL_DELAY = 500;
-    public static final int RESCROLL_DELAY = PagedView.PAGE_SNAP_ANIMATION_DURATION + 150;
-
     private static final boolean PROFILE_DRAWING_DURING_DRAG = false;
 
-    private static final int SCROLL_OUTSIDE_ZONE = 0;
-    private static final int SCROLL_WAITING_IN_ZONE = 1;
-
-    public static final int SCROLL_NONE = -1;
-    public static final int SCROLL_LEFT = 0;
-    public static final int SCROLL_RIGHT = 1;
-
-    private static final float MAX_FLING_DEGREES = 35f;
-
     @Thunk Launcher mLauncher;
-    private Handler mHandler;
+    private FlingToDeleteHelper mFlingToDeleteHelper;
 
     // temporaries to avoid gc thrash
     private Rect mRectTemp = new Rect();
     private final int[] mCoordinatesTemp = new int[2];
-    private final boolean mIsRtl;
 
     /**
      * Drag driver for the current drag/drop operation, or null if there is no active DND operation.
@@ -93,34 +70,19 @@
     /** Y coordinate of the down event. */
     private int mMotionDownY;
 
-    /** the area at the edge of the screen that makes the workspace go left
-     *   or right while you're dragging.
-     */
-    private final int mScrollZone;
-
     private DropTarget.DragObject mDragObject;
 
     /** Who can receive drop events */
-    private ArrayList<DropTarget> mDropTargets = new ArrayList<DropTarget>();
-    private ArrayList<DragListener> mListeners = new ArrayList<DragListener>();
-    private DropTarget mFlingToDeleteDropTarget;
+    private ArrayList<DropTarget> mDropTargets = new ArrayList<>();
+    private ArrayList<DragListener> mListeners = new ArrayList<>();
 
     /** The window token used as the parent for the DragView. */
     private IBinder mWindowToken;
 
-    /** The view that will be scrolled when dragging to the left and right edges of the screen. */
-    private View mScrollView;
-
     private View mMoveTarget;
 
-    @Thunk DragScroller mDragScroller;
-    @Thunk int mScrollState = SCROLL_OUTSIDE_ZONE;
-    private ScrollRunnable mScrollRunnable = new ScrollRunnable();
-
     private DropTarget mLastDropTarget;
 
-    private InputMethodManager mInputMethodManager;
-
     @Thunk int mLastTouch[] = new int[2];
     @Thunk long mLastTouchUpTime = -1;
     @Thunk int mDistanceSinceScroll = 0;
@@ -128,10 +90,7 @@
     private int mTmpPoint[] = new int[2];
     private Rect mDragLayerRect = new Rect();
 
-    protected final int mFlingToDeleteThresholdVelocity;
-    private VelocityTracker mVelocityTracker;
-
-    private boolean mIsDragDeferred;
+    private boolean mIsInPreDrag;
 
     /**
      * Interface to receive notifications when a drag starts or stops
@@ -155,37 +114,8 @@
      * Used to create a new DragLayer from XML.
      */
     public DragController(Launcher launcher) {
-        Resources r = launcher.getResources();
         mLauncher = launcher;
-        mHandler = new Handler();
-        mScrollZone = r.getDimensionPixelSize(R.dimen.scroll_zone);
-        mVelocityTracker = VelocityTracker.obtain();
-
-        mFlingToDeleteThresholdVelocity =
-                r.getDimensionPixelSize(R.dimen.drag_flingToDeleteMinVelocity);
-        mIsRtl = Utilities.isRtl(r);
-    }
-
-    /**
-     * Starts a drag.
-     *
-     * @param v The view that is being dragged
-     * @param bmp The bitmap that represents the view being dragged
-     * @param source An object representing where the drag originated
-     * @param dragInfo The data associated with the object that is being dragged
-     * @param viewImageBounds the position of the image inside the view
-     */
-    public void startDrag(View v, Bitmap bmp, DragSource source, ItemInfo dragInfo,
-            Rect viewImageBounds, float initialDragViewScale, DragOptions options) {
-        int[] loc = mCoordinatesTemp;
-        mLauncher.getDragLayer().getLocationInDragLayer(v, loc);
-        int dragLayerX = loc[0] + viewImageBounds.left
-                + (int) ((initialDragViewScale * bmp.getWidth() - bmp.getWidth()) / 2);
-        int dragLayerY = loc[1] + viewImageBounds.top
-                + (int) ((initialDragViewScale * bmp.getHeight() - bmp.getHeight()) / 2);
-
-        startDrag(bmp, dragLayerX, dragLayerY, source, dragInfo, null,
-                null, initialDragViewScale, options);
+        mFlingToDeleteHelper = new FlingToDeleteHelper(launcher);
     }
 
     /**
@@ -208,11 +138,8 @@
         }
 
         // Hide soft keyboard, if visible
-        if (mInputMethodManager == null) {
-            mInputMethodManager = (InputMethodManager)
-                    mLauncher.getSystemService(Context.INPUT_METHOD_SERVICE);
-        }
-        mInputMethodManager.hideSoftInputFromWindow(mWindowToken, 0);
+        mLauncher.getSystemService(InputMethodManager.class)
+                .hideSoftInputFromWindow(mWindowToken, 0);
 
         mOptions = options;
         if (mOptions.systemDndStartPoint != null) {
@@ -230,14 +157,12 @@
 
         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)
-                    : 0f;
+        final float scaleDps = 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 +196,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(mDragObject);
         }
 
         mLastTouch[0] = mMotionDownX;
@@ -284,16 +209,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(mDragObject, true /* dragStarted*/);
+        }
+        mIsInPreDrag = false;
     }
 
     /**
@@ -314,10 +237,6 @@
         return mDragDriver != null || (mOptions != null && mOptions.isAccessibleDrag);
     }
 
-    public boolean isExternalDrag() {
-        return (mOptions != null && mOptions.systemDndStartPoint != null);
-    }
-
     /**
      * Stop dragging without dropping.
      */
@@ -329,7 +248,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,26 +271,51 @@
     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();
+        mFlingToDeleteHelper.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(mDragObject, false /* dragStarted*/);
+        }
+        mIsInPreDrag = false;
+        mOptions = null;
+        for (DragListener listener : new ArrayList<>(mListeners)) {
+            listener.onDragEnd();
+        }
     }
 
     /**
@@ -380,16 +326,10 @@
 
         if (mDragObject.deferDragViewCleanupPostAnimation) {
             // If we skipped calling onDragEnd() before, do it now
-            for (DragListener listener : new ArrayList<>(mListeners)) {
-                listener.onDragEnd();
-            }
+            callOnDragEnd();
         }
     }
 
-    public void onDeferredEndFling(DropTarget.DragObject d) {
-        d.dragSource.onFlingToDeleteCompleted();
-    }
-
     /**
      * Clamps the position to the drag layer bounds.
      */
@@ -428,22 +368,16 @@
     }
 
     @Override
-    public void onDriverDragEnd(float x, float y, DropTarget dropTargetOverride) {
+    public void onDriverDragEnd(float x, float y) {
         DropTarget dropTarget;
-        PointF vec = null;
-
-        if (dropTargetOverride != null) {
-            dropTarget = dropTargetOverride;
+        Runnable flingAnimation = mFlingToDeleteHelper.getFlingAnimation(mDragObject);
+        if (flingAnimation != null) {
+            dropTarget = mFlingToDeleteHelper.getDropTarget();
         } else {
-            vec = isFlingingToDelete(mDragObject.dragSource);
-            if (vec != null) {
-                dropTarget = mFlingToDeleteDropTarget;
-            } else {
-                dropTarget = findDropTarget((int) x, (int) y, mCoordinatesTemp);
-            }
+            dropTarget = findDropTarget((int) x, (int) y, mCoordinatesTemp);
         }
 
-        drop(dropTarget, x, y, vec);
+        drop(dropTarget, flingAnimation);
 
         endDrag();
     }
@@ -456,13 +390,13 @@
     /**
      * Call this from a drag source view.
      */
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
+    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
         if (mOptions != null && mOptions.isAccessibleDrag) {
             return false;
         }
 
         // Update the velocity tracker
-        acquireVelocityTrackerAndAddMovement(ev);
+        mFlingToDeleteHelper.recordMotionEvent(ev);
 
         final int action = ev.getAction();
         final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
@@ -486,7 +420,8 @@
     /**
      * Call this from a drag source view.
      */
-    public boolean onDragEvent(DragEvent event) {
+    public boolean onDragEvent(long dragStartTime, DragEvent event) {
+        mFlingToDeleteHelper.recordDragEvent(dragStartTime, event);
         return mDragDriver != null && mDragDriver.onDragEvent(event);
     }
 
@@ -510,16 +445,6 @@
         return mMoveTarget != null && mMoveTarget.dispatchUnhandledMove(focused, direction);
     }
 
-    private void clearScrollRunnable() {
-        mHandler.removeCallbacks(mScrollRunnable);
-        if (mScrollState == SCROLL_WAITING_IN_ZONE) {
-            mScrollState = SCROLL_OUTSIDE_ZONE;
-            mScrollRunnable.setDirection(SCROLL_RIGHT);
-            mDragScroller.onExitScrollArea();
-            mLauncher.getDragLayer().onExitScrollArea();
-        }
-    }
-
     private void handleMoveEvent(int x, int y) {
         mDragObject.dragView.move(x, y);
 
@@ -534,11 +459,10 @@
         mDistanceSinceScroll += Math.hypot(mLastTouch[0] - x, mLastTouch[1] - y);
         mLastTouch[0] = x;
         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();
         }
     }
 
@@ -571,46 +495,16 @@
         mLastDropTarget = dropTarget;
     }
 
-    @Thunk void checkScrollState(int x, int y) {
-        final int slop = ViewConfiguration.get(mLauncher).getScaledWindowTouchSlop();
-        final int delay = mDistanceSinceScroll < slop ? RESCROLL_DELAY : SCROLL_DELAY;
-        final DragLayer dragLayer = mLauncher.getDragLayer();
-        final int forwardDirection = mIsRtl ? SCROLL_RIGHT : SCROLL_LEFT;
-        final int backwardsDirection = mIsRtl ? SCROLL_LEFT : SCROLL_RIGHT;
-
-        if (x < mScrollZone) {
-            if (mScrollState == SCROLL_OUTSIDE_ZONE) {
-                mScrollState = SCROLL_WAITING_IN_ZONE;
-                if (mDragScroller.onEnterScrollArea(x, y, forwardDirection)) {
-                    dragLayer.onEnterScrollArea();
-                    mScrollRunnable.setDirection(forwardDirection);
-                    mHandler.postDelayed(mScrollRunnable, delay);
-                }
-            }
-        } else if (x > mScrollView.getWidth() - mScrollZone) {
-            if (mScrollState == SCROLL_OUTSIDE_ZONE) {
-                mScrollState = SCROLL_WAITING_IN_ZONE;
-                if (mDragScroller.onEnterScrollArea(x, y, backwardsDirection)) {
-                    dragLayer.onEnterScrollArea();
-                    mScrollRunnable.setDirection(backwardsDirection);
-                    mHandler.postDelayed(mScrollRunnable, delay);
-                }
-            }
-        } else {
-            clearScrollRunnable();
-        }
-    }
-
     /**
      * 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;
         }
 
         // Update the velocity tracker
-        acquireVelocityTrackerAndAddMovement(ev);
+        mFlingToDeleteHelper.recordMotionEvent(ev);
 
         final int action = ev.getAction();
         final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
@@ -622,17 +516,6 @@
                 // Remember where the motion event started
                 mMotionDownX = dragLayerX;
                 mMotionDownY = dragLayerY;
-
-                if ((dragLayerX < mScrollZone) || (dragLayerX > mScrollView.getWidth() - mScrollZone)) {
-                    mScrollState = SCROLL_WAITING_IN_ZONE;
-                    mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
-                } else {
-                    mScrollState = SCROLL_OUTSIDE_ZONE;
-                }
-                break;
-            case MotionEvent.ACTION_UP:
-            case MotionEvent.ACTION_CANCEL:
-                mHandler.removeCallbacks(mScrollRunnable);
                 break;
         }
 
@@ -663,47 +546,12 @@
 
         dropTarget.prepareAccessibilityDrop();
         // Perform the drop
-        drop(dropTarget, location[0], location[1], null);
+        drop(dropTarget, null);
         endDrag();
     }
 
-    /**
-     * Determines whether the user flung the current item to delete it.
-     *
-     * @return the vector at which the item was flung, or null if no fling was detected.
-     */
-    private PointF isFlingingToDelete(DragSource source) {
-        if (mFlingToDeleteDropTarget == null) return null;
-        if (!source.supportsFlingToDelete()) return null;
-
-        ViewConfiguration config = ViewConfiguration.get(mLauncher);
-        mVelocityTracker.computeCurrentVelocity(1000, config.getScaledMaximumFlingVelocity());
-        PointF vel = new PointF(mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
-        float theta = MAX_FLING_DEGREES + 1;
-        if (mVelocityTracker.getYVelocity() < mFlingToDeleteThresholdVelocity) {
-            // Do a quick dot product test to ensure that we are flinging upwards
-            PointF upVec = new PointF(0f, -1f);
-            theta = getAngleBetweenVectors(vel, upVec);
-        } else if (mLauncher.getDeviceProfile().isVerticalBarLayout() &&
-                mVelocityTracker.getXVelocity() < mFlingToDeleteThresholdVelocity) {
-            // Remove icon is on left side instead of top, so check if we are flinging to the left.
-            PointF leftVec = new PointF(-1f, 0f);
-            theta = getAngleBetweenVectors(vel, leftVec);
-        }
-        if (theta <= Math.toRadians(MAX_FLING_DEGREES)) {
-            return vel;
-        }
-        return null;
-    }
-
-    private float getAngleBetweenVectors(PointF vec1, PointF vec2) {
-        return (float) Math.acos(((vec1.x * vec2.x) + (vec1.y * vec2.y)) /
-                (vec1.length() * vec2.length()));
-    }
-
-    void drop(DropTarget dropTarget, float x, float y, PointF flingVel) {
+    private void drop(DropTarget dropTarget, Runnable flingAnimation) {
         final int[] coordinates = mCoordinatesTemp;
-
         mDragObject.x = coordinates[0];
         mDragObject.y = coordinates[1];
 
@@ -725,20 +573,19 @@
         if (dropTarget != null) {
             dropTarget.onDragExit(mDragObject);
             if (dropTarget.acceptDrop(mDragObject)) {
-                if (flingVel != null) {
-                    dropTarget.onFlingToDelete(mDragObject, flingVel);
-                } else {
+                if (flingAnimation != null) {
+                    flingAnimation.run();
+                } 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) {
+            mLauncher.getUserEventDispatcher().logDragNDrop(mDragObject, dropTargetAsView);
+            mDragObject.dragSource.onDropCompleted(
+                    dropTargetAsView, mDragObject, flingAnimation != null, accepted);
         }
     }
 
@@ -760,7 +607,7 @@
 
                 dropCoordinates[0] = x;
                 dropCoordinates[1] = y;
-                mLauncher.getDragLayer().mapCoordInSelfToDescendent((View) target, dropCoordinates);
+                mLauncher.getDragLayer().mapCoordInSelfToDescendant((View) target, dropCoordinates);
 
                 return target;
             }
@@ -768,16 +615,12 @@
         return null;
     }
 
-    public void setDragScoller(DragScroller scroller) {
-        mDragScroller = scroller;
-    }
-
     public void setWindowToken(IBinder token) {
         mWindowToken = token;
     }
 
     /**
-     * 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);
@@ -804,61 +647,4 @@
         mDropTargets.remove(target);
     }
 
-    /**
-     * Sets the current fling-to-delete drop target.
-     */
-    public void setFlingToDeleteDropTarget(DropTarget target) {
-        mFlingToDeleteDropTarget = target;
-    }
-
-    private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) {
-        if (mVelocityTracker == null) {
-            mVelocityTracker = VelocityTracker.obtain();
-        }
-        mVelocityTracker.addMovement(ev);
-    }
-
-    private void releaseVelocityTracker() {
-        if (mVelocityTracker != null) {
-            mVelocityTracker.recycle();
-            mVelocityTracker = null;
-        }
-    }
-
-    /**
-     * Set which view scrolls for touch events near the edge of the screen.
-     */
-    public void setScrollView(View v) {
-        mScrollView = v;
-    }
-
-    private class ScrollRunnable implements Runnable {
-        private int mDirection;
-
-        ScrollRunnable() {
-        }
-
-        public void run() {
-            if (mDragScroller != null) {
-                if (mDirection == SCROLL_LEFT) {
-                    mDragScroller.scrollLeft();
-                } else {
-                    mDragScroller.scrollRight();
-                }
-                mScrollState = SCROLL_OUTSIDE_ZONE;
-                mDistanceSinceScroll = 0;
-                mDragScroller.onExitScrollArea();
-                mLauncher.getDragLayer().onExitScrollArea();
-
-                if (isDragging()) {
-                    // Check the scroll again so that we can requeue the scroller if necessary
-                    checkScrollState(mLastTouch[0], mLastTouch[1]);
-                }
-            }
-        }
-
-        void setDirection(int direction) {
-            mDirection = direction;
-        }
-    }
 }
diff --git a/src/com/android/launcher3/dragndrop/DragDriver.java b/src/com/android/launcher3/dragndrop/DragDriver.java
index 4db8c07..65c0f29 100644
--- a/src/com/android/launcher3/dragndrop/DragDriver.java
+++ b/src/com/android/launcher3/dragndrop/DragDriver.java
@@ -16,21 +16,13 @@
 
 package com.android.launcher3.dragndrop;
 
-import android.content.ClipData;
-import android.content.ClipDescription;
 import android.content.Context;
-import android.content.Intent;
 import android.view.DragEvent;
 import android.view.MotionEvent;
 
-import com.android.launcher3.DropTarget;
 import com.android.launcher3.DropTarget.DragObject;
-import com.android.launcher3.InstallShortcutReceiver;
-import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.Utilities;
 
-import java.util.ArrayList;
-
 /**
  * Base class for driving a drag/drop operation.
  */
@@ -40,7 +32,7 @@
     public interface EventListener {
         void onDriverDragMove(float x, float y);
         void onDriverDragExitWindow();
-        void onDriverDragEnd(float x, float y, DropTarget dropTargetOverride);
+        void onDriverDragEnd(float x, float y);
         void onDriverDragCancel();
     }
 
@@ -62,7 +54,7 @@
                 break;
             case MotionEvent.ACTION_UP:
                 mEventListener.onDriverDragMove(ev.getX(), ev.getY());
-                mEventListener.onDriverDragEnd(ev.getX(), ev.getY(), null);
+                mEventListener.onDriverDragEnd(ev.getX(), ev.getY());
                 break;
             case MotionEvent.ACTION_CANCEL:
                 mEventListener.onDriverDragCancel();
@@ -80,7 +72,7 @@
 
         switch (action) {
             case MotionEvent.ACTION_UP:
-                mEventListener.onDriverDragEnd(ev.getX(), ev.getY(), null);
+                mEventListener.onDriverDragEnd(ev.getX(), ev.getY());
                 break;
             case MotionEvent.ACTION_CANCEL:
                 mEventListener.onDriverDragCancel();
@@ -92,7 +84,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);
@@ -108,7 +100,6 @@
     private final DragObject mDragObject;
     private final Context mContext;
 
-    boolean mReceivedDropEvent = false;
     float mLastX = 0;
     float mLastY = 0;
 
@@ -150,65 +141,21 @@
             case DragEvent.ACTION_DROP:
                 mLastX = event.getX();
                 mLastY = event.getY();
-                mReceivedDropEvent =
-                        updateInfoFromClipData(event.getClipData(), event.getClipDescription());
-                return mReceivedDropEvent;
-
+                mEventListener.onDriverDragMove(event.getX(), event.getY());
+                mEventListener.onDriverDragEnd(mLastX, mLastY);
+                return true;
             case DragEvent.ACTION_DRAG_EXITED:
                 mEventListener.onDriverDragExitWindow();
                 return true;
 
             case DragEvent.ACTION_DRAG_ENDED:
-                if (mReceivedDropEvent) {
-                    mEventListener.onDriverDragEnd(mLastX, mLastY, null);
-                } else {
-                    mEventListener.onDriverDragCancel();
-                }
+                mEventListener.onDriverDragCancel();
                 return true;
 
             default:
                 return false;
         }
     }
-
-    private boolean updateInfoFromClipData(ClipData data, ClipDescription desc) {
-        if (data == null) {
-            return false;
-        }
-        ArrayList<Intent> intents = new ArrayList<>();
-        int itemCount = data.getItemCount();
-        for (int i = 0; i < itemCount; i++) {
-            Intent intent = data.getItemAt(i).getIntent();
-            if (intent == null) {
-                continue;
-            }
-
-            // Give preference to shortcut intents.
-            if (!Intent.ACTION_CREATE_SHORTCUT.equals(intent.getAction())) {
-                intents.add(intent);
-                continue;
-            }
-            ShortcutInfo info = InstallShortcutReceiver.fromShortcutIntent(mContext, intent);
-            if (info != null) {
-                mDragObject.dragInfo = info;
-                return true;
-            }
-            return true;
-        }
-
-        // Try creating shortcuts just using the intent and label
-        Intent fullIntent = new Intent().putExtra(Intent.EXTRA_SHORTCUT_NAME, desc.getLabel());
-        for (Intent intent : intents) {
-            fullIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, intent);
-            ShortcutInfo info = InstallShortcutReceiver.fromShortcutIntent(mContext, fullIntent);
-            if (info != null) {
-                mDragObject.dragInfo = info;
-                return true;
-            }
-        }
-
-        return false;
-    }
 }
 
 /**
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 016347b..7178c5e 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -21,24 +21,15 @@
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.annotation.TargetApi;
-import android.content.ClipDescription;
 import android.content.Context;
-import android.content.Intent;
 import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
 import android.graphics.Canvas;
 import android.graphics.Color;
-import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.Region;
-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,30 +40,28 @@
 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;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutAndWidgetContainer;
-import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.Workspace;
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.config.FeatureFlags;
 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.logging.LoggerUtils;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.TouchController;
+import com.android.launcher3.widget.WidgetsBottomSheet;
 
-import java.net.URISyntaxException;
 import java.util.ArrayList;
 
 /**
@@ -90,11 +79,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;
 
@@ -121,13 +108,6 @@
     private final Rect mScrollChildPosition = new Rect();
     private final ViewGroupFocusHelper mFocusIndicatorHelper;
 
-    private boolean mInScrollArea;
-    private boolean mShowPageHints;
-    private Drawable mLeftHoverDrawable;
-    private Drawable mRightHoverDrawable;
-    private Drawable mLeftHoverDrawableActive;
-    private Drawable mRightHoverDrawableActive;
-
     // Related to pinch-to-go-to-overview gesture.
     private PinchToOverviewListener mPinchListener = null;
 
@@ -148,12 +128,7 @@
         setMotionEventSplittingEnabled(false);
         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);
-        mIsRtl = Utilities.isRtl(res);
+        mIsRtl = Utilities.isRtl(getResources());
         mFocusIndicatorHelper = new ViewGroupFocusHelper(this);
     }
 
@@ -183,18 +158,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 +179,29 @@
     }
 
     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();
+                    mLauncher.getUserEventDispatcher().logActionTapOutside(
+                            LoggerUtils.newContainerTarget(topView.getLogContainerType()));
+                    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 +226,33 @@
             }
             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)) {
+        WidgetsBottomSheet widgetsBottomSheet = WidgetsBottomSheet.getOpen(mLauncher);
+        if (widgetsBottomSheet != null && widgetsBottomSheet.onControllerInterceptTouchEvent(ev)) {
+            mActiveController = widgetsBottomSheet;
+            return true;
+        }
+
+        if (mPinchListener != null && mPinchListener.onControllerInterceptTouchEvent(ev)) {
             // Stop listening for scrolling etc. (onTouchEvent() handles the rest of the pinch.)
             mActiveController = mPinchListener;
             return true;
@@ -316,7 +265,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 +315,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 +332,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 +354,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,69 +367,12 @@
             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()) {
-            return;
-        }
-        if (mLauncher.isWorkspaceLocked()) {
-            return;
-        }
-
-        ClipDescription description = event.getClipDescription();
-        if (!description.hasMimeType(ClipDescription.MIMETYPE_TEXT_INTENT)) {
-            return;
-        }
-        ShortcutInfo info = new ShortcutInfo();
-        // Set a dummy intent until we get the final value
-        info.intent = new Intent();
-
-        // Since we are not going through the workspace for starting the drag, set drag related
-        // information on the workspace before starting the drag.
-        ExternalDragPreviewProvider previewProvider =
-                new ExternalDragPreviewProvider(mLauncher, info);
-        mLauncher.getWorkspace().prepareDragWithProvider(previewProvider);
-
-        DragOptions options = new DragOptions();
-        options.systemDndStartPoint = new Point((int) event.getX(), (int) event.getY());
-
-        int halfPadding = previewProvider.previewPadding / 2;
-        mDragController.startDrag(
-                Bitmap.createBitmap(1, 1, Config.ARGB_8888),
-                0, 0,
-                new AnotherWindowDragSource(mLauncher), info,
-                new Point(- halfPadding, halfPadding),
-                previewProvider.getPreviewBounds(), 1f, options);
-    }
-
-    @Override
-    public boolean onDragEvent (DragEvent event) {
-        if (event.getAction() == DragEvent.ACTION_DRAG_STARTED) {
-            handleSystemDragStart(event);
-        }
-        return mDragController.onDragEvent(event);
-    }
-
     /**
      * Determine the rect of the descendant in this DragLayer's coordinates
      *
@@ -534,8 +422,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,11 +444,17 @@
     @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);
     }
 
     @Override
+    public void setInsets(Rect insets) {
+        super.setInsets(insets);
+        setBackgroundResource(insets.top == 0 ? 0 : R.drawable.workspace_bg);
+    }
+
+    @Override
     public LayoutParams generateLayoutParams(AttributeSet attrs) {
         return new LayoutParams(getContext(), attrs);
     }
@@ -645,36 +539,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,
@@ -736,7 +618,7 @@
         } else if (child instanceof FolderIcon) {
             // Account for holographic blur padding on the drag view
             toY += Math.round(scale * (child.getPaddingTop() - dragView.getDragRegionTop()));
-            toY -= scale * Workspace.DRAG_BITMAP_PADDING / 2;
+            toY -= scale * dragView.getBlurSizeOutline() / 2;
             toY -= (1 - scale) * dragView.getMeasuredHeight() / 2;
             // Center in the x coordinate about the target's drawable
             toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
@@ -807,15 +689,11 @@
 
         // If duration < 0, this is a cue to compute the duration based on the distance
         if (duration < 0) {
-            if (mDragController != null && mDragController.isExternalDrag()) {
-                duration = 1;
-            } else {
-                duration = res.getInteger(R.integer.config_dropAnimMaxDuration);
-                if (dist < maxDist) {
-                    duration *= mCubicEaseOutInterpolator.getInterpolation(dist / maxDist);
-                }
-                duration = Math.max(duration, res.getInteger(R.integer.config_dropAnimMinDuration));
+            duration = res.getInteger(R.integer.config_dropAnimMaxDuration);
+            if (dist < maxDist) {
+                duration *= mCubicEaseOutInterpolator.getInterpolation(dist / maxDist);
             }
+            duration = Math.max(duration, res.getInteger(R.integer.config_dropAnimMinDuration));
         }
 
         // Fall back to cubic ease out interpolator for the animation if none is specified
@@ -977,29 +855,6 @@
         }
     }
 
-    void onEnterScrollArea() {
-        mInScrollArea = true;
-        invalidate();
-    }
-
-    void onExitScrollArea() {
-        mInScrollArea = false;
-        invalidate();
-    }
-
-    public void showPageHints() {
-        mShowPageHints = true;
-        Workspace workspace = mLauncher.getWorkspace();
-        getDescendantRectRelativeToSelf(workspace.getChildAt(workspace.numCustomPages()),
-                mScrollChildPosition);
-        invalidate();
-    }
-
-    public void hidePageHints() {
-        mShowPageHints = false;
-        invalidate();
-    }
-
     public void invalidateScrim() {
         if (mBackgroundAlpha > 0.0f) {
             invalidate();
@@ -1029,41 +884,6 @@
         super.dispatchDraw(canvas);
     }
 
-    private void drawPageHints(Canvas canvas) {
-        if (mShowPageHints) {
-            Workspace workspace = mLauncher.getWorkspace();
-            int width = getMeasuredWidth();
-            int page = workspace.getNextPage();
-            CellLayout leftPage = (CellLayout) workspace.getChildAt(mIsRtl ? page + 1 : page - 1);
-            CellLayout rightPage = (CellLayout) workspace.getChildAt(mIsRtl ? page - 1 : page + 1);
-
-            if (leftPage != null && leftPage.isDragTarget()) {
-                Drawable left = mInScrollArea && leftPage.getIsDragOverlapping() ?
-                        mLeftHoverDrawableActive : mLeftHoverDrawable;
-                left.setBounds(0, mScrollChildPosition.top,
-                        left.getIntrinsicWidth(), mScrollChildPosition.bottom);
-                left.draw(canvas);
-            }
-            if (rightPage != null && rightPage.isDragTarget()) {
-                Drawable right = mInScrollArea && rightPage.getIsDragOverlapping() ?
-                        mRightHoverDrawableActive : mRightHoverDrawable;
-                right.setBounds(width - right.getIntrinsicWidth(),
-                        mScrollChildPosition.top, width, mScrollChildPosition.bottom);
-                right.draw(canvas);
-            }
-        }
-    }
-
-    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
-        boolean ret = super.drawChild(canvas, child, drawingTime);
-
-        // We want to draw the page hints above the workspace, but below the drag view.
-        if (child instanceof Workspace) {
-            drawPageHints(canvas);
-        }
-        return ret;
-    }
-
     public void setBackgroundAlpha(float alpha) {
         if (alpha != mBackgroundAlpha) {
             mBackgroundAlpha = alpha;
@@ -1077,7 +897,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 +907,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..230fa2d 100644
--- a/src/com/android/launcher3/dragndrop/DragOptions.java
+++ b/src/com/android/launcher3/dragndrop/DragOptions.java
@@ -17,6 +17,10 @@
 package com.android.launcher3.dragndrop;
 
 import android.graphics.Point;
+import android.support.annotation.CallSuper;
+import android.view.View;
+
+import com.android.launcher3.DropTarget;
 
 /**
  * Set of options to control the drag and drop behavior.
@@ -29,8 +33,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 +42,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(DropTarget.DragObject dragObject);
 
         /**
-         * 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(DropTarget.DragObject dragObject, boolean dragStarted);
     }
 }
diff --git a/src/com/android/launcher3/dragndrop/DragScroller.java b/src/com/android/launcher3/dragndrop/DragScroller.java
deleted file mode 100644
index 165d0b1..0000000
--- a/src/com/android/launcher3/dragndrop/DragScroller.java
+++ /dev/null
@@ -1,40 +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.dragndrop;
-
-/**
- * Handles scrolling while dragging
- *
- */
-public interface DragScroller {
-    void scrollLeft();
-    void scrollRight();
-
-    /**
-     * The touch point has entered the scroll area; a scroll is imminent.
-     * This event will only occur while a drag is active.
-     *
-     * @param direction The scroll direction
-     */
-    boolean onEnterScrollArea(int x, int y, int direction);
-
-    /**
-     * The touch point has left the scroll area.
-     * NOTE: This may not be called, if a drop occurs inside the scroll area.
-     */
-    boolean onExitScrollArea();
-}
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 8a2ae94..7806c98 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -22,27 +22,21 @@
 import android.animation.ValueAnimator;
 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;
 import android.graphics.ColorMatrix;
 import android.graphics.ColorMatrixColorFilter;
 import android.graphics.Paint;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.os.Build;
 import android.view.View;
 import android.view.animation.DecelerateInterpolator;
 
 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.util.Themes;
+import com.android.launcher3.util.Thunk;
 
 import java.util.Arrays;
 
@@ -55,8 +49,11 @@
     private Bitmap mBitmap;
     private Bitmap mCrossFadeBitmap;
     @Thunk Paint mPaint;
+    private final int mBlurSizeOutline;
     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;
@@ -89,7 +86,6 @@
      * @param registrationX The x coordinate of the registration point.
      * @param registrationY The y coordinate of the registration point.
      */
-    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
     public DragView(Launcher launcher, Bitmap bitmap, int registrationX, int registrationY,
                     final float initialScale, final float finalScaleDps) {
         super(launcher);
@@ -103,7 +99,7 @@
         setScaleY(initialScale);
 
         // Animate the view into the correct position
-        mAnim = LauncherAnimUtils.ofFloat(this, 0f, 1f);
+        mAnim = LauncherAnimUtils.ofFloat(0f, 1f);
         mAnim.setDuration(VIEW_ZOOM_DURATION);
         mAnim.addUpdateListener(new AnimatorUpdateListener() {
             @Override
@@ -138,14 +134,16 @@
         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);
         mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
 
-        if (Utilities.ATLEAST_LOLLIPOP) {
-            setElevation(getResources().getDimension(R.dimen.drag_elevation));
-        }
+        mBlurSizeOutline = getResources().getDimensionPixelSize(R.dimen.blur_size_medium_outline);
+
+        setElevation(getResources().getDimension(R.dimen.drag_elevation));
     }
 
     /** Sets the scale of the view over the normal workspace icon size. */
@@ -239,7 +237,7 @@
     }
 
     public void crossFade(int duration) {
-        ValueAnimator va = LauncherAnimUtils.ofFloat(this, 0f, 1f);
+        ValueAnimator va = LauncherAnimUtils.ofFloat(0f, 1f);
         va.setDuration(duration);
         va.setInterpolator(new DecelerateInterpolator(1.5f));
         va.addUpdateListener(new AnimatorUpdateListener() {
@@ -261,17 +259,12 @@
             m1.setSaturation(0);
 
             ColorMatrix m2 = new ColorMatrix();
-            setColorScale(color, m2);
+            Themes.setColorScaleOnMatrix(color, m2);
             m1.postConcat(m2);
 
-            if (Utilities.ATLEAST_LOLLIPOP) {
-                animateFilterTo(m1.getArray());
-            } else {
-                mPaint.setColorFilter(new ColorMatrixColorFilter(m1));
-                invalidate();
-            }
+            animateFilterTo(m1.getArray());
         } else {
-            if (!Utilities.ATLEAST_LOLLIPOP || mCurrentFilter == null) {
+            if (mCurrentFilter == null) {
                 mPaint.setColorFilter(null);
                 invalidate();
             } else {
@@ -280,7 +273,6 @@
         }
     }
 
-    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
     private void animateFilterTo(float[] targetFilter) {
         float[] oldFilter = mCurrentFilter == null ? new ColorMatrix().getArray() : mCurrentFilter;
         mCurrentFilter = Arrays.copyOf(oldFilter, oldFilter.length);
@@ -356,6 +348,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;
@@ -385,8 +384,11 @@
         }
     }
 
-    public static void setColorScale(int color, ColorMatrix target) {
-        target.setScale(Color.red(color) / 255f, Color.green(color) / 255f,
-                Color.blue(color) / 255f, Color.alpha(color) / 255f);
+    public int getBlurSizeOutline() {
+        return mBlurSizeOutline;
+    }
+
+    public float getInitialScale() {
+        return mInitialScale;
     }
 }
diff --git a/src/com/android/launcher3/dragndrop/ExternalDragPreviewProvider.java b/src/com/android/launcher3/dragndrop/ExternalDragPreviewProvider.java
deleted file mode 100644
index 6b14be7..0000000
--- a/src/com/android/launcher3/dragndrop/ExternalDragPreviewProvider.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.dragndrop;
-
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-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;
-
-/**
- * Extension of {@link DragPreviewProvider} which provides a dummy outline when drag starts from
- * a different window.
- * It just draws an empty circle to a placeholder outline.
- */
-public class ExternalDragPreviewProvider extends DragPreviewProvider {
-
-    private final Launcher mLauncher;
-    private final ItemInfo mAddInfo;
-
-    private final int[] mOutlineSize;
-
-    public ExternalDragPreviewProvider(Launcher launcher, ItemInfo addInfo) {
-        super(null);
-        mLauncher = launcher;
-        mAddInfo = addInfo;
-
-        mOutlineSize = mLauncher.getWorkspace().estimateItemSize(mAddInfo, false);
-    }
-
-    public Rect getPreviewBounds() {
-        Rect rect = new Rect();
-        DeviceProfile dp = mLauncher.getDeviceProfile();
-        rect.left = DRAG_BITMAP_PADDING / 2;
-        rect.top = (mOutlineSize[1] - dp.cellHeightPx) / 2;
-        rect.right = rect.left + dp.iconSizePx;
-        rect.bottom = rect.top + dp.iconSizePx;
-        return rect;
-    }
-
-    @Override
-    public Bitmap createDragOutline(Canvas canvas) {
-        final Bitmap b = Bitmap.createBitmap(mOutlineSize[0], mOutlineSize[1], Bitmap.Config.ALPHA_8);
-        canvas.setBitmap(b);
-
-        Paint paint = new Paint();
-        paint.setColor(Color.WHITE);
-        paint.setStyle(Paint.Style.FILL);
-
-        // Use 0.9f times the radius for the actual circle to account for icon normalization.
-        float radius = getPreviewBounds().width() * 0.5f;
-        canvas.drawCircle(DRAG_BITMAP_PADDING / 2 + radius,
-                DRAG_BITMAP_PADDING / 2 + radius, radius * 0.9f, paint);
-
-        HolographicOutlineHelper.obtain(mLauncher).applyExpensiveOutlineWithBlur(b, canvas);
-        canvas.setBitmap(null);
-        return b;
-    }
-}
diff --git a/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java b/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java
new file mode 100644
index 0000000..e794744
--- /dev/null
+++ b/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java
@@ -0,0 +1,136 @@
+/*
+ * 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.dragndrop;
+
+import android.graphics.PointF;
+import android.os.SystemClock;
+import android.view.DragEvent;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.ViewConfiguration;
+
+import com.android.launcher3.ButtonDropTarget;
+import com.android.launcher3.DropTarget;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.util.FlingAnimation;
+
+/**
+ * Utility class to manage fling to delete action during drag and drop.
+ */
+public class FlingToDeleteHelper {
+
+    private static final float MAX_FLING_DEGREES = 35f;
+
+    private final Launcher mLauncher;
+    private final int mFlingToDeleteThresholdVelocity;
+
+    private ButtonDropTarget mDropTarget;
+    private VelocityTracker mVelocityTracker;
+
+    public FlingToDeleteHelper(Launcher launcher) {
+        mLauncher = launcher;
+        mFlingToDeleteThresholdVelocity = launcher.getResources()
+                .getDimensionPixelSize(R.dimen.drag_flingToDeleteMinVelocity);
+    }
+
+    public void recordMotionEvent(MotionEvent ev) {
+        if (mVelocityTracker == null) {
+            mVelocityTracker = VelocityTracker.obtain();
+        }
+        mVelocityTracker.addMovement(ev);
+    }
+
+    /**
+     * Same as {@link #recordMotionEvent}. It creates a temporary {@link MotionEvent} object
+     * using {@param event} for tracking velocity.
+     */
+    public void recordDragEvent(long dragStartTime, DragEvent event) {
+        final int motionAction;
+        switch (event.getAction()) {
+            case DragEvent.ACTION_DRAG_STARTED:
+                motionAction = MotionEvent.ACTION_DOWN;
+                break;
+            case DragEvent.ACTION_DRAG_LOCATION:
+                motionAction = MotionEvent.ACTION_MOVE;
+                break;
+            case DragEvent.ACTION_DRAG_ENDED:
+                motionAction = MotionEvent.ACTION_UP;
+                break;
+            default:
+                return;
+        }
+        MotionEvent emulatedEvent = MotionEvent.obtain(dragStartTime, SystemClock.uptimeMillis(),
+                motionAction, event.getX(), event.getY(), 0);
+        recordMotionEvent(emulatedEvent);
+        emulatedEvent.recycle();
+    }
+
+    public void releaseVelocityTracker() {
+        if (mVelocityTracker != null) {
+            mVelocityTracker.recycle();
+            mVelocityTracker = null;
+        }
+    }
+
+    public DropTarget getDropTarget() {
+        return mDropTarget;
+    }
+
+    public Runnable getFlingAnimation(DropTarget.DragObject dragObject) {
+        PointF vel = isFlingingToDelete();
+        if (vel == null) {
+            return null;
+        }
+        return new FlingAnimation(dragObject, vel, mDropTarget, mLauncher);
+    }
+
+    /**
+     * Determines whether the user flung the current item to delete it.
+     *
+     * @return the vector at which the item was flung, or null if no fling was detected.
+     */
+    private PointF isFlingingToDelete() {
+        if (mDropTarget == null) {
+            mDropTarget = (ButtonDropTarget) mLauncher.findViewById(R.id.delete_target_text);
+        }
+        if (mDropTarget == null || !mDropTarget.isDropEnabled()) return null;
+        ViewConfiguration config = ViewConfiguration.get(mLauncher);
+        mVelocityTracker.computeCurrentVelocity(1000, config.getScaledMaximumFlingVelocity());
+        PointF vel = new PointF(mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
+        float theta = MAX_FLING_DEGREES + 1;
+        if (mVelocityTracker.getYVelocity() < mFlingToDeleteThresholdVelocity) {
+            // Do a quick dot product test to ensure that we are flinging upwards
+            PointF upVec = new PointF(0f, -1f);
+            theta = getAngleBetweenVectors(vel, upVec);
+        } else if (mLauncher.getDeviceProfile().isVerticalBarLayout() &&
+                mVelocityTracker.getXVelocity() < mFlingToDeleteThresholdVelocity) {
+            // Remove icon is on left side instead of top, so check if we are flinging to the left.
+            PointF leftVec = new PointF(-1f, 0f);
+            theta = getAngleBetweenVectors(vel, leftVec);
+        }
+        if (theta <= Math.toRadians(MAX_FLING_DEGREES)) {
+            return vel;
+        }
+        return null;
+    }
+
+    private float getAngleBetweenVectors(PointF vec1, PointF vec2) {
+        return (float) Math.acos(((vec1.x * vec2.x) + (vec1.y * vec2.y)) /
+                (vec1.length() * vec2.length()));
+    }
+}
diff --git a/src/com/android/launcher3/dragndrop/LivePreviewWidgetCell.java b/src/com/android/launcher3/dragndrop/LivePreviewWidgetCell.java
new file mode 100644
index 0000000..36a0292
--- /dev/null
+++ b/src/com/android/launcher3/dragndrop/LivePreviewWidgetCell.java
@@ -0,0 +1,99 @@
+package com.android.launcher3.dragndrop;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.RemoteViews;
+
+import com.android.launcher3.BaseActivity;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.widget.WidgetCell;
+
+/**
+ * Extension of {@link WidgetCell} which supports generating previews from {@link RemoteViews}
+ */
+public class LivePreviewWidgetCell extends WidgetCell {
+
+    private RemoteViews mPreview;
+
+    public LivePreviewWidgetCell(Context context) {
+        this(context, null);
+    }
+
+    public LivePreviewWidgetCell(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public LivePreviewWidgetCell(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    public void setPreview(RemoteViews view) {
+        mPreview = view;
+    }
+
+    @Override
+    public void ensurePreview() {
+        if (mPreview != null && mActiveRequest == null) {
+            Bitmap preview = generateFromRemoteViews(
+                    mActivity, mPreview, mItem.widgetInfo, mPresetPreviewSize, new int[1]);
+            if (preview != null) {
+                applyPreview(preview);
+                return;
+            }
+        }
+        super.ensurePreview();
+    }
+
+    /**
+     * Generates a bitmap by inflating {@param views}.
+     * @see com.android.launcher3.WidgetPreviewLoader#generateWidgetPreview
+     *
+     * TODO: Consider moving this to the background thread.
+     */
+    public static Bitmap generateFromRemoteViews(BaseActivity activity, RemoteViews views,
+            LauncherAppWidgetProviderInfo info, int previewSize, int[] preScaledWidthOut) {
+
+        DeviceProfile dp = activity.getDeviceProfile();
+        int viewWidth = dp.cellWidthPx * info.spanX;
+        int viewHeight = dp.cellHeightPx * info.spanY;
+
+        final View v;
+        try {
+            v = views.apply(activity, new FrameLayout(activity));
+            v.measure(MeasureSpec.makeMeasureSpec(viewWidth, MeasureSpec.EXACTLY),
+                    MeasureSpec.makeMeasureSpec(viewHeight, MeasureSpec.EXACTLY));
+
+            viewWidth = v.getMeasuredWidth();
+            viewHeight = v.getMeasuredHeight();
+            v.layout(0, 0, viewWidth, viewHeight);
+        } catch (Exception e) {
+            return null;
+        }
+
+        preScaledWidthOut[0] = viewWidth;
+        final int bitmapWidth, bitmapHeight;
+        final float scale;
+        if (viewWidth > previewSize) {
+            scale = ((float) previewSize) / viewWidth;
+            bitmapWidth = previewSize;
+            bitmapHeight = (int) (viewHeight * scale);
+        } else {
+            scale = 1;
+            bitmapWidth = viewWidth;
+            bitmapHeight = viewHeight;
+        }
+
+        Bitmap preview = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
+        Canvas c = new Canvas(preview);
+        c.scale(scale, scale);
+        v.draw(c);
+        c.setBitmap(null);
+        return preview;
+    }
+}
diff --git a/src/com/android/launcher3/dragndrop/PinItemDragListener.java b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
new file mode 100644
index 0000000..df0c47c
--- /dev/null
+++ b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.dragndrop;
+
+import android.appwidget.AppWidgetManager;
+import android.content.ClipDescription;
+import android.content.Intent;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.DragEvent;
+import android.view.View;
+import android.widget.RemoteViews;
+
+import com.android.launcher3.DeleteDropTarget;
+import com.android.launcher3.DragSource;
+import com.android.launcher3.DropTarget;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.PendingAddItemInfo;
+import com.android.launcher3.R;
+import com.android.launcher3.compat.PinItemRequestCompat;
+import com.android.launcher3.folder.Folder;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.launcher3.widget.PendingAddShortcutInfo;
+import com.android.launcher3.widget.PendingAddWidgetInfo;
+import com.android.launcher3.widget.PendingItemDragHelper;
+import com.android.launcher3.widget.WidgetAddFlowHandler;
+
+import java.util.UUID;
+
+/**
+ * {@link DragSource} for handling drop from a different window. This object is initialized
+ * in the source window and is passed on to the Launcher activity as an Intent extra.
+ */
+public class PinItemDragListener
+        implements Parcelable, View.OnDragListener, DragSource, DragOptions.PreDragCondition {
+
+    private static final String TAG = "PinItemDragListener";
+
+    private static final String MIME_TYPE_PREFIX = "com.android.launcher3.drag_and_drop/";
+    public static final String EXTRA_PIN_ITEM_DRAG_LISTENER = "pin_item_drag_listener";
+
+    private final PinItemRequestCompat mRequest;
+
+    // Position of preview relative to the touch location
+    private final Rect mPreviewRect;
+
+    private final int mPreviewBitmapWidth;
+    private final int mPreviewViewWidth;
+
+    // Randomly generated id used to verify the drag event.
+    private final String mId;
+
+    private Launcher mLauncher;
+    private DragController mDragController;
+    private long mDragStartTime;
+
+    public PinItemDragListener(PinItemRequestCompat request, Rect previewRect,
+            int previewBitmapWidth, int previewViewWidth) {
+        mRequest = request;
+        mPreviewRect = previewRect;
+        mPreviewBitmapWidth = previewBitmapWidth;
+        mPreviewViewWidth = previewViewWidth;
+        mId = UUID.randomUUID().toString();
+    }
+
+    private PinItemDragListener(Parcel parcel) {
+        mRequest = PinItemRequestCompat.CREATOR.createFromParcel(parcel);
+        mPreviewRect = Rect.CREATOR.createFromParcel(parcel);
+        mPreviewBitmapWidth = parcel.readInt();
+        mPreviewViewWidth = parcel.readInt();
+        mId = parcel.readString();
+    }
+
+    public String getMimeType() {
+        return MIME_TYPE_PREFIX + mId;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int i) {
+        mRequest.writeToParcel(parcel, i);
+        mPreviewRect.writeToParcel(parcel, i);
+        parcel.writeInt(mPreviewBitmapWidth);
+        parcel.writeInt(mPreviewViewWidth);
+        parcel.writeString(mId);
+    }
+
+    public void setLauncher(Launcher launcher) {
+        mLauncher = launcher;
+        mDragController = launcher.getDragController();
+    }
+
+    @Override
+    public boolean onDrag(View view, DragEvent event) {
+        if (mLauncher == null || mDragController == null) {
+            postCleanup();
+            return false;
+        }
+        if (event.getAction() == DragEvent.ACTION_DRAG_STARTED) {
+            if (onDragStart(event)) {
+                return true;
+            } else {
+                postCleanup();
+                return false;
+            }
+        }
+        return mDragController.onDragEvent(mDragStartTime, event);
+    }
+
+    private boolean onDragStart(DragEvent event) {
+        if (!mRequest.isValid()) {
+            return false;
+        }
+        ClipDescription desc =  event.getClipDescription();
+        if (desc == null || !desc.hasMimeType(getMimeType())) {
+            Log.e(TAG, "Someone started a dragAndDrop before us.");
+            return false;
+        }
+
+        final PendingAddItemInfo item;
+        if (mRequest.getRequestType() == PinItemRequestCompat.REQUEST_TYPE_SHORTCUT) {
+            item = new PendingAddShortcutInfo(
+                    new PinShortcutRequestActivityInfo(mRequest, mLauncher));
+        } else {
+            // mRequest.getRequestType() == PinItemRequestCompat.REQUEST_TYPE_APPWIDGET
+            LauncherAppWidgetProviderInfo providerInfo =
+                    LauncherAppWidgetProviderInfo.fromProviderInfo(
+                            mLauncher, mRequest.getAppWidgetProviderInfo(mLauncher));
+            final PinWidgetFlowHandler flowHandler =
+                    new PinWidgetFlowHandler(providerInfo, mRequest);
+            item = new PendingAddWidgetInfo(providerInfo) {
+                @Override
+                public WidgetAddFlowHandler getHandler() {
+                    return flowHandler;
+                }
+            };
+        }
+        View view = new View(mLauncher);
+        view.setTag(item);
+
+        Point downPos = new Point((int) event.getX(), (int) event.getY());
+        DragOptions options = new DragOptions();
+        options.systemDndStartPoint = downPos;
+        options.preDragCondition = this;
+
+        // We use drag event position as the screenPos for the preview image. Since mPreviewRect
+        // already includes the view position relative to the drag event on the source window,
+        // and the absolute position (position relative to the screen) of drag event is same
+        // across windows, using drag position here give a good estimate for relative position
+        // to source window.
+        PendingItemDragHelper dragHelper = new PendingItemDragHelper(view);
+        if (mRequest.getRequestType() == PinItemRequestCompat.REQUEST_TYPE_APPWIDGET) {
+            dragHelper.setPreview(getPreview(mRequest));
+        }
+
+        dragHelper.startDrag(new Rect(mPreviewRect),
+                mPreviewBitmapWidth, mPreviewViewWidth, downPos,  this, options);
+        mDragStartTime = SystemClock.uptimeMillis();
+        return true;
+    }
+
+    @Override
+    public boolean shouldStartDrag(double distanceDragged) {
+        // Stay in pre-drag mode, if workspace is locked.
+        return !mLauncher.isWorkspaceLocked();
+    }
+
+    @Override
+    public void onPreDragStart(DropTarget.DragObject dragObject) {
+        // The predrag starts when the workspace is not yet loaded. In some cases we set
+        // the dragLayer alpha to 0 to have a nice fade-in animation. But that will prevent the
+        // dragView from being visible. Instead just skip the fade-in animation here.
+        mLauncher.getDragLayer().setAlpha(1);
+
+        dragObject.dragView.setColor(
+                mLauncher.getResources().getColor(R.color.delete_target_hover_tint));
+    }
+
+    @Override
+    public void onPreDragEnd(DropTarget.DragObject dragObject, boolean dragStarted) {
+        if (dragStarted) {
+            dragObject.dragView.setColor(0);
+        }
+    }
+
+    @Override
+    public boolean supportsAppInfoDropTarget() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsDeleteDropTarget() {
+        return false;
+    }
+
+    @Override
+    public float getIntrinsicIconScaleFactor() {
+        return 1f;
+    }
+
+    @Override
+    public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete,
+            boolean success) {
+        if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() &&
+                !(target instanceof DeleteDropTarget) && !(target instanceof Folder))) {
+            // Exit spring loaded mode if we have not successfully dropped or have not handled the
+            // drop in Workspace
+            mLauncher.exitSpringLoadedDragModeDelayed(true,
+                    Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
+        }
+
+        if (!success) {
+            d.deferDragViewCleanupPostAnimation = false;
+        }
+        postCleanup();
+    }
+
+    @Override
+    public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target,
+            LauncherLogProto.Target targetParent) {
+        targetParent.containerType = ContainerType.PINITEM;
+    }
+
+    private void postCleanup() {
+        if (mLauncher != null) {
+            // Remove any drag params from the launcher intent since the drag operation is complete.
+            Intent newIntent = new Intent(mLauncher.getIntent());
+            newIntent.removeExtra(EXTRA_PIN_ITEM_DRAG_LISTENER);
+            mLauncher.setIntent(newIntent);
+        }
+
+        new Handler(Looper.getMainLooper()).post(new Runnable() {
+            @Override
+            public void run() {
+                removeListener();
+            }
+        });
+    }
+
+    public void removeListener() {
+        if (mLauncher != null) {
+            mLauncher.getDragLayer().setOnDragListener(null);
+        }
+    }
+
+    public static RemoteViews getPreview(PinItemRequestCompat request) {
+        Bundle extras = request.getExtras();
+        if (extras != null &&
+                extras.get(AppWidgetManager.EXTRA_APPWIDGET_PREVIEW) instanceof RemoteViews) {
+            return (RemoteViews) extras.get(AppWidgetManager.EXTRA_APPWIDGET_PREVIEW);
+        }
+        return null;
+    }
+
+    public static boolean handleDragRequest(Launcher launcher, Intent intent) {
+        if (intent == null || !Intent.ACTION_MAIN.equals(intent.getAction())) {
+            return false;
+        }
+        Parcelable dragExtra = intent.getParcelableExtra(EXTRA_PIN_ITEM_DRAG_LISTENER);
+        if (dragExtra instanceof PinItemDragListener) {
+            PinItemDragListener dragListener = (PinItemDragListener) dragExtra;
+            dragListener.setLauncher(launcher);
+
+            launcher.getDragLayer().setOnDragListener(dragListener);
+            return true;
+        }
+        return false;
+    }
+
+    public static final Parcelable.Creator<PinItemDragListener> CREATOR =
+            new Parcelable.Creator<PinItemDragListener>() {
+                public PinItemDragListener createFromParcel(Parcel source) {
+                    return new PinItemDragListener(source);
+                }
+
+                public PinItemDragListener[] newArray(int size) {
+                    return new PinItemDragListener[size];
+                }
+            };
+}
diff --git a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
new file mode 100644
index 0000000..bb5ac5b
--- /dev/null
+++ b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.dragndrop;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+
+import com.android.launcher3.IconCache;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.R;
+import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.compat.PinItemRequestCompat;
+import com.android.launcher3.compat.ShortcutConfigActivityInfo;
+
+/**
+ * Extension of ShortcutConfigActivityInfo to be used in the confirmation prompt for pin item
+ * request.
+ */
+@TargetApi(Build.VERSION_CODES.N_MR1)
+class PinShortcutRequestActivityInfo extends ShortcutConfigActivityInfo {
+
+    // Class name used in the target component, such that it will never represent an
+    // actual existing class.
+    private static final String DUMMY_COMPONENT_CLASS = "pinned-shortcut";
+
+    private final PinItemRequestCompat mRequest;
+    private final ShortcutInfo mInfo;
+    private final Context mContext;
+
+    public PinShortcutRequestActivityInfo(PinItemRequestCompat request, Context context) {
+        super(new ComponentName(request.getShortcutInfo().getPackage(), DUMMY_COMPONENT_CLASS),
+                request.getShortcutInfo().getUserHandle());
+        mRequest = request;
+        mInfo = request.getShortcutInfo();
+        mContext = context;
+    }
+
+    @Override
+    public int getItemType() {
+        return LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+    }
+
+    @Override
+    public CharSequence getLabel() {
+        return mInfo.getShortLabel();
+    }
+
+    @Override
+    public Drawable getFullResIcon(IconCache cache) {
+        return mContext.getSystemService(LauncherApps.class)
+                .getShortcutIconDrawable(mInfo, LauncherAppState.getIDP(mContext).fillResIconDpi);
+    }
+
+    @Override
+    public com.android.launcher3.ShortcutInfo createShortcutInfo() {
+        // Total duration for the drop animation to complete.
+        long duration = mContext.getResources().getInteger(R.integer.config_dropAnimMaxDuration) +
+                Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT +
+                mContext.getResources().getInteger(R.integer.config_overlayTransitionTime) / 2;
+        // Delay the actual accept() call until the drop animation is complete.
+        return LauncherAppsCompat.createShortcutInfoFromPinItemRequest(
+                mContext, mRequest, duration);
+    }
+
+    @Override
+    public boolean startConfigActivity(Activity activity, int requestCode) {
+        return false;
+    }
+
+    @Override
+    public boolean isPersistable() {
+        return false;
+    }
+}
diff --git a/src/com/android/launcher3/dragndrop/PinWidgetFlowHandler.java b/src/com/android/launcher3/dragndrop/PinWidgetFlowHandler.java
new file mode 100644
index 0000000..b6da6ad
--- /dev/null
+++ b/src/com/android/launcher3/dragndrop/PinWidgetFlowHandler.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.dragndrop;
+
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.compat.PinItemRequestCompat;
+import com.android.launcher3.widget.WidgetAddFlowHandler;
+
+/**
+ * Extension of WidgetAddFlowHandler to handle pin item request behavior.
+ *
+ * No config activity is shown even if it is defined in widget config. And a callback is sent when
+ * the widget is bound.
+ */
+public class PinWidgetFlowHandler extends WidgetAddFlowHandler implements Parcelable {
+
+    private final PinItemRequestCompat mRequest;
+
+    public PinWidgetFlowHandler(AppWidgetProviderInfo providerInfo, PinItemRequestCompat request) {
+        super(providerInfo);
+        mRequest = request;
+    }
+
+    protected PinWidgetFlowHandler(Parcel parcel) {
+        super(parcel);
+        mRequest = PinItemRequestCompat.CREATOR.createFromParcel(parcel);
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int i) {
+        super.writeToParcel(parcel, i);
+        mRequest.writeToParcel(parcel, i);
+    }
+
+    @Override
+    public boolean startConfigActivity(Launcher launcher, int appWidgetId, ItemInfo info,
+            int requestCode) {
+        Bundle extras = new Bundle();
+        extras.putInt(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
+        mRequest.accept(extras);
+        return false;
+    }
+
+    @Override
+    public boolean needsConfigure() {
+        return false;
+    }
+
+    public static final Parcelable.Creator<PinWidgetFlowHandler> CREATOR =
+            new Parcelable.Creator<PinWidgetFlowHandler>() {
+                public PinWidgetFlowHandler createFromParcel(Parcel source) {
+                    return new PinWidgetFlowHandler(source);
+                }
+
+                public PinWidgetFlowHandler[] newArray(int size) {
+                    return new PinWidgetFlowHandler[size];
+                }
+            };
+}
diff --git a/src/com/android/launcher3/dynamicui/ColorExtractionService.java b/src/com/android/launcher3/dynamicui/ColorExtractionService.java
index 1369f60..f94d442 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 | NullPointerException 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 | NullPointerException 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/ClippedFolderIconLayoutRule.java b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
index 6ee02f9..840fcf5 100644
--- a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
+++ b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
@@ -1,16 +1,17 @@
 package com.android.launcher3.folder;
 
-import android.graphics.Path;
-import android.graphics.Point;
+import android.view.View;
 
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
+
+import java.util.ArrayList;
+import java.util.List;
 
 public class ClippedFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule {
 
     static final int MAX_NUM_ITEMS_IN_PREVIEW = 4;
     private static final int MIN_NUM_ITEMS_IN_PREVIEW = 2;
+    private static final int MAX_NUM_ITEMS_PER_ROW = 2;
 
     final float MIN_SCALE = 0.48f;
     final float MAX_SCALE = 0.58f;
@@ -38,7 +39,7 @@
     public FolderIcon.PreviewItemDrawingParams computePreviewItemDrawingParams(int index,
             int curNumItems, FolderIcon.PreviewItemDrawingParams params) {
 
-        float totalScale = scaleForNumItems(curNumItems);
+        float totalScale = scaleForItem(index, curNumItems);
         float transX;
         float transY;
         float overlayAlpha = 0;
@@ -94,7 +95,7 @@
                 MIN_NUM_ITEMS_IN_PREVIEW) / (MAX_NUM_ITEMS_IN_PREVIEW - MIN_NUM_ITEMS_IN_PREVIEW));
         double theta = theta0 + index * (2 * Math.PI / curNumItems) * direction;
 
-        float halfIconSize = (mIconSize * scaleForNumItems(curNumItems)) / 2;
+        float halfIconSize = (mIconSize * scaleForItem(index, curNumItems)) / 2;
 
         // Map the location along the circle, and offset the coordinates to represent the center
         // of the icon, and to be based from the top / left of the preview area. The y component
@@ -104,7 +105,9 @@
 
     }
 
-    private float scaleForNumItems(int numItems) {
+    @Override
+    public float scaleForItem(int index, int numItems) {
+        // Scale is determined by the number of items in the preview.
         float scale = 1f;
         if (numItems <= 2) {
             scale = MAX_SCALE;
@@ -118,7 +121,7 @@
     }
 
     @Override
-    public int numItems() {
+    public int maxNumItems() {
         return MAX_NUM_ITEMS_IN_PREVIEW;
     }
 
@@ -127,4 +130,23 @@
         return true;
     }
 
+    @Override
+    public List<View> getItemsToDisplay(Folder folder) {
+        List<View> items = new ArrayList<>(folder.getItemsInReadingOrder());
+        int numItems = items.size();
+        if (FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION && numItems > MAX_NUM_ITEMS_IN_PREVIEW) {
+            // We match the icons in the preview with the layout of the opened folder (b/27944225),
+            // but we still need to figure out how we want to handle updating the preview when the
+            // upper left quadrant changes.
+            int appsPerRow = folder.mContent.getPageAt(0).getCountX();
+            int appsToDelete = appsPerRow - MAX_NUM_ITEMS_PER_ROW;
+
+            // We only display the upper left quadrant.
+            while (appsToDelete > 0) {
+                items.remove(MAX_NUM_ITEMS_PER_ROW);
+                appsToDelete--;
+            }
+        }
+        return items.subList(0, Math.min(numItems, MAX_NUM_ITEMS_IN_PREVIEW));
+    }
 }
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 698e5aa..93c9ea8 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -22,15 +22,11 @@
 import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
 import android.annotation.SuppressLint;
-import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.Resources;
-import android.graphics.PointF;
 import android.graphics.Rect;
-import android.os.Build;
 import android.text.InputType;
 import android.text.Selection;
-import android.text.Spannable;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.ActionMode;
@@ -45,13 +41,11 @@
 import android.view.animation.AccelerateInterpolator;
 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;
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.DragSource;
@@ -62,24 +56,24 @@
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAnimUtils;
-import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LogDecelerateInterpolator;
 import com.android.launcher3.OnAlarmListener;
+import com.android.launcher3.PagedView;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutInfo;
 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;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.pageindicators.PageIndicatorDots;
-import com.android.launcher3.shortcuts.DeepShortcutsContainer;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.CircleRevealOutlineProvider;
 import com.android.launcher3.util.Thunk;
@@ -91,9 +85,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";
 
     /**
@@ -110,7 +105,12 @@
     /**
      * Time for which the scroll hint is shown before automatically changing page.
      */
-    public static final int SCROLL_HINT_DURATION = DragController.SCROLL_DELAY;
+    public static final int SCROLL_HINT_DURATION = 500;
+    public static final int RESCROLL_DELAY = PagedView.PAGE_SNAP_ANIMATION_DURATION + 150;
+
+    public static final int SCROLL_NONE = -1;
+    public static final int SCROLL_LEFT = 0;
+    public static final int SCROLL_RIGHT = 1;
 
     /**
      * Fraction of icon width which behave as scroll region.
@@ -137,8 +137,6 @@
     private final int mMaterialExpandDuration;
     private final int mMaterialExpandStagger;
 
-    private final InputMethodManager mInputMethodManager;
-
     protected final Launcher mLauncher;
     protected DragController mDragController;
     public FolderInfo mInfo;
@@ -186,8 +184,8 @@
     // Folder scrolling
     private int mScrollAreaOffset;
 
-    @Thunk int mScrollHintDir = DragController.SCROLL_NONE;
-    @Thunk int mCurrentScrollDir = DragController.SCROLL_NONE;
+    @Thunk int mScrollHintDir = SCROLL_NONE;
+    @Thunk int mCurrentScrollDir = SCROLL_NONE;
 
     /**
      * Used to inflate the Workspace from XML.
@@ -198,9 +196,6 @@
     public Folder(Context context, AttributeSet attrs) {
         super(context, attrs);
         setAlwaysDrawnWithCacheEnabled(false);
-        mInputMethodManager = (InputMethodManager)
-                getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
-
         Resources res = getResources();
         mExpandDuration = res.getInteger(R.integer.config_folderExpandDuration);
         mMaterialExpandDuration = res.getInteger(R.integer.config_materialFolderExpandDuration);
@@ -227,14 +222,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) {
@@ -259,8 +247,11 @@
         }
         mFolderName.setOnEditorActionListener(this);
         mFolderName.setSelectAllOnFocus(true);
-        mFolderName.setInputType(mFolderName.getInputType() |
-                InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_FLAG_CAP_WORDS);
+        mFolderName.setInputType(mFolderName.getInputType()
+                & ~InputType.TYPE_TEXT_FLAG_AUTO_CORRECT
+                & ~InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
+                | InputType.TYPE_TEXT_FLAG_CAP_WORDS);
+        mFolderName.forceDisableSuggestions(true);
 
         mFooter = findViewById(R.id.folder_footer);
 
@@ -281,17 +272,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 +288,7 @@
 
             mDragController.addDragListener(this);
             if (options.isAccessibleDrag) {
-                mDragController.addDragListener(new AccessibileDragListenerAdapter(
+                mDragController.addDragListener(new AccessibleDragListenerAdapter(
                         mContent, CellLayout.FOLDER_ACCESSIBILITY_DRAG) {
 
                     @Override
@@ -367,52 +348,44 @@
         });
     }
 
-    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.
         String newTitle = mFolderName.getText().toString();
         mInfo.setTitle(newTitle);
-        LauncherModel.updateItemInDatabase(mLauncher, mInfo);
+        mLauncher.getModelWriter().updateItemInDatabase(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;
     }
 
-    /**
-     * 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;
+    public FolderIcon getFolderIcon() {
+        return mFolderIcon;
     }
 
     public void setDragController(DragController dragController) {
@@ -464,7 +437,7 @@
         // TODO: Remove this, as with multi-page folders, there will never be any overflow
         for (ShortcutInfo item: overflow) {
             mInfo.remove(item, false);
-            LauncherModel.deleteItemFromDatabase(mLauncher, item);
+            mLauncher.getModelWriter().deleteItemFromDatabase(item);
         }
 
         DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
@@ -528,8 +501,32 @@
         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;
 
         mContent.completePendingPageChanges();
         if (!mDragInProgress) {
@@ -542,83 +539,65 @@
         // 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;
+        mFolderIcon.growAndFadeOut();
 
-            setLayerType(LAYER_TYPE_HARDWARE, null);
-            onCompleteRunnable = new Runnable() {
-                @Override
-                public void run() {
-                    setLayerType(LAYER_TYPE_NONE, null);
-                }
-            };
-        } else {
-            prepareReveal();
-            centerAboutIcon();
+        AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
+        int width = getFolderWidth();
+        int height = getFolderHeight();
 
-            AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
-            int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth();
-            int height = getFolderHeight();
+        float transX = - 0.075f * (width / 2 - getPivotX());
+        float transY = - 0.075f * (height / 2 - getPivotY());
+        setTranslationX(transX);
+        setTranslationY(transY);
+        PropertyValuesHolder tx = PropertyValuesHolder.ofFloat(TRANSLATION_X, transX, 0);
+        PropertyValuesHolder ty = PropertyValuesHolder.ofFloat(TRANSLATION_Y, transY, 0);
 
-            float transX = - 0.075f * (width / 2 - getPivotX());
-            float transY = - 0.075f * (height / 2 - getPivotY());
-            setTranslationX(transX);
-            setTranslationY(transY);
-            PropertyValuesHolder tx = PropertyValuesHolder.ofFloat(TRANSLATION_X, transX, 0);
-            PropertyValuesHolder ty = PropertyValuesHolder.ofFloat(TRANSLATION_Y, transY, 0);
+        Animator drift = ObjectAnimator.ofPropertyValuesHolder(this, tx, ty);
+        drift.setDuration(mMaterialExpandDuration);
+        drift.setStartDelay(mMaterialExpandStagger);
+        drift.setInterpolator(new LogDecelerateInterpolator(100, 0));
 
-            Animator drift = ObjectAnimator.ofPropertyValuesHolder(this, tx, ty);
-            drift.setDuration(mMaterialExpandDuration);
-            drift.setStartDelay(mMaterialExpandStagger);
-            drift.setInterpolator(new LogDecelerateInterpolator(100, 0));
+        int rx = (int) Math.max(Math.max(width - getPivotX(), 0), getPivotX());
+        int ry = (int) Math.max(Math.max(height - getPivotY(), 0), getPivotY());
+        float radius = (float) Math.hypot(rx, ry);
 
-            int rx = (int) Math.max(Math.max(width - getPivotX(), 0), getPivotX());
-            int ry = (int) Math.max(Math.max(height - getPivotY(), 0), getPivotY());
-            float radius = (float) Math.hypot(rx, ry);
+        Animator reveal = new CircleRevealOutlineProvider((int) getPivotX(),
+                (int) getPivotY(), 0, radius).createRevealAnimator(this);
+        reveal.setDuration(mMaterialExpandDuration);
+        reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
 
-            Animator reveal = new CircleRevealOutlineProvider((int) getPivotX(),
-                    (int) getPivotY(), 0, radius).createRevealAnimator(this);
-            reveal.setDuration(mMaterialExpandDuration);
-            reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
+        mContent.setAlpha(0f);
+        Animator iconsAlpha = ObjectAnimator.ofFloat(mContent, "alpha", 0f, 1f);
+        iconsAlpha.setDuration(mMaterialExpandDuration);
+        iconsAlpha.setStartDelay(mMaterialExpandStagger);
+        iconsAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
 
-            mContent.setAlpha(0f);
-            Animator iconsAlpha = ObjectAnimator.ofFloat(mContent, "alpha", 0f, 1f);
-            iconsAlpha.setDuration(mMaterialExpandDuration);
-            iconsAlpha.setStartDelay(mMaterialExpandStagger);
-            iconsAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
+        mFooter.setAlpha(0f);
+        Animator textAlpha = ObjectAnimator.ofFloat(mFooter, "alpha", 0f, 1f);
+        textAlpha.setDuration(mMaterialExpandDuration);
+        textAlpha.setStartDelay(mMaterialExpandStagger);
+        textAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
 
-            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);
 
-            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,21 +628,20 @@
             // 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
                 public void onAnimationEnd(Animator animation) {
                     mFolderName.animate().setDuration(FOLDER_NAME_ANIMATION_DURATION)
                         .translationX(0)
-                        .setInterpolator(Utilities.ATLEAST_LOLLIPOP ?
-                                AnimationUtils.loadInterpolator(mLauncher,
-                                        android.R.interpolator.fast_out_slow_in)
-                                : new LogDecelerateInterpolator(100, 0));
+                        .setInterpolator(AnimationUtils.loadInterpolator(
+                                mLauncher, android.R.interpolator.fast_out_slow_in));
                     mPageIndicator.playEntryAnimation();
 
                     if (updateAnimationFlag) {
-                        mInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, true, mLauncher);
+                        mInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, true,
+                                mLauncher.getModelWriter());
                     }
                 }
             });
@@ -672,7 +650,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 +658,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 +675,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 +728,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) {
@@ -767,7 +780,6 @@
         }
     };
 
-    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
     public boolean isLayoutRtl() {
         return (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
     }
@@ -811,15 +823,15 @@
         boolean isOutsideRightEdge = x > (getWidth() - cellOverlap);
 
         if (currentPage > 0 && (mContent.mIsRtl ? isOutsideRightEdge : isOutsideLeftEdge)) {
-            showScrollHint(DragController.SCROLL_LEFT, d);
+            showScrollHint(SCROLL_LEFT, d);
         } else if (currentPage < (mContent.getPageCount() - 1)
                 && (mContent.mIsRtl ? isOutsideLeftEdge : isOutsideRightEdge)) {
-            showScrollHint(DragController.SCROLL_RIGHT, d);
+            showScrollHint(SCROLL_RIGHT, d);
         } else {
             mOnScrollHintAlarm.cancelAlarm();
-            if (mScrollHintDir != DragController.SCROLL_NONE) {
+            if (mScrollHintDir != SCROLL_NONE) {
                 mContent.clearScrollHint();
-                mScrollHintDir = DragController.SCROLL_NONE;
+                mScrollHintDir = SCROLL_NONE;
             }
         }
     }
@@ -850,8 +862,8 @@
     };
 
     public void completeDragExit() {
-        if (mInfo.opened) {
-            mLauncher.closeFolder();
+        if (mIsOpen) {
+            close(true);
             mRearrangeOnClose = true;
         } else if (mState == STATE_ANIMATING) {
             mRearrangeOnClose = true;
@@ -877,9 +889,9 @@
 
         mOnScrollHintAlarm.cancelAlarm();
         mScrollPauseAlarm.cancelAlarm();
-        if (mScrollHintDir != DragController.SCROLL_NONE) {
+        if (mScrollHintDir != SCROLL_NONE) {
             mContent.clearScrollHint();
-            mScrollHintDir = DragController.SCROLL_NONE;
+            mScrollHintDir = SCROLL_NONE;
         }
     }
 
@@ -916,7 +928,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)
@@ -955,7 +967,8 @@
         // been refreshed yet.
         if (getItemCount() <= mContent.itemsPerPage()) {
             // Show the animation, next time something is added to the folder.
-            mInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, false, mLauncher);
+            mInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, false,
+                    mLauncher.getModelWriter());
         }
 
         if (!isFlingToDelete) {
@@ -985,13 +998,8 @@
     }
 
     @Override
-    public boolean supportsFlingToDelete() {
-        return true;
-    }
-
-    @Override
     public boolean supportsAppInfoDropTarget() {
-        return !FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND;
+        return true;
     }
 
     @Override
@@ -999,16 +1007,6 @@
         return true;
     }
 
-    @Override
-    public void onFlingToDelete(DragObject d, PointF vec) {
-        // Do nothing
-    }
-
-    @Override
-    public void onFlingToDeleteCompleted() {
-        // Do nothing
-    }
-
     private void updateItemLocationsInDatabaseBatch() {
         ArrayList<View> list = getItemsInReadingOrder();
         ArrayList<ItemInfo> items = new ArrayList<ItemInfo>();
@@ -1019,7 +1017,7 @@
             items.add(info);
         }
 
-        LauncherModel.moveItemsInDatabase(mLauncher, items, mInfo.id, 0);
+        mLauncher.getModelWriter().moveItemsInDatabase(items, mInfo.id, 0);
     }
 
     public void notifyDrop() {
@@ -1041,7 +1039,7 @@
 
         DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
         DragLayer parent = (DragLayer) mLauncher.findViewById(R.id.drag_layer);
-        int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth();
+        int width = getFolderWidth();
         int height = getFolderHeight();
 
         float scale = parent.getDescendantRectRelativeToSelf(mFolderIcon, sTempRect);
@@ -1083,6 +1081,7 @@
         int folderPivotY = height / 2 + (centeredTop - top);
         setPivotX(folderPivotX);
         setPivotY(folderPivotY);
+
         mFolderIconPivotX = (int) (mFolderIcon.getMeasuredWidth() *
                 (1.0f * folderPivotX / width));
         mFolderIconPivotY = (int) (mFolderIcon.getMeasuredHeight() *
@@ -1114,6 +1113,10 @@
         return Math.max(mContent.getDesiredWidth(), MIN_CONTENT_DIMEN);
     }
 
+    private int getFolderWidth() {
+        return getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth();
+    }
+
     private int getFolderHeight() {
         return getFolderHeight(getContentAreaHeight());
     }
@@ -1186,8 +1189,8 @@
                                 mInfo.screenId);
                         ShortcutInfo finalItem = mInfo.contents.remove(0);
                         newIcon = mLauncher.createShortcut(cellLayout, finalItem);
-                        LauncherModel.addOrMoveItemInDatabase(mLauncher, finalItem, mInfo.container,
-                                mInfo.screenId, mInfo.cellX, mInfo.cellY);
+                        mLauncher.getModelWriter().addOrMoveItemInDatabase(finalItem,
+                                mInfo.container, mInfo.screenId, mInfo.cellX, mInfo.cellY);
                     }
 
                     // Remove the folder
@@ -1200,8 +1203,7 @@
                         // We add the child after removing the folder to prevent both from existing
                         // at the same time in the CellLayout.  We need to add the new item with
                         // addInScreenFromBind() to ensure that hotseat items are placed correctly.
-                        mLauncher.getWorkspace().addInScreenFromBind(newIcon, mInfo.container,
-                                mInfo.screenId, mInfo.cellX, mInfo.cellY, mInfo.spanX, mInfo.spanY);
+                        mLauncher.getWorkspace().addInScreenFromBind(newIcon, mInfo);
 
                         // Focus the newly created child
                         newIcon.requestFocus();
@@ -1299,8 +1301,8 @@
             currentDragView = mContent.createAndAddViewForRank(si, mEmptyCellRank);
             // Actually move the item in the database if it was an external drag. Call this
             // before creating the view, so that ShortcutInfo is updated appropriately.
-            LauncherModel.addOrMoveItemInDatabase(
-                    mLauncher, si, mInfo.id, 0, si.cellX, si.cellY);
+            mLauncher.getModelWriter().addOrMoveItemInDatabase(
+                    si, mInfo.id, 0, si.cellX, si.cellY);
 
             // We only need to update the locations if it doesn't get handled in #onDropCompleted.
             if (d.dragSource != this) {
@@ -1309,9 +1311,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 +1332,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.
@@ -1344,7 +1342,7 @@
 
         if (mContent.getPageCount() > 1) {
             // The animation has already been shown while opening the folder.
-            mInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, true, mLauncher);
+            mInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, true, mLauncher.getModelWriter());
         }
 
         if (d.stateAnnouncer != null) {
@@ -1368,8 +1366,8 @@
     public void onAdd(ShortcutInfo item) {
         mContent.createAndAddViewForRank(item, mContent.allocateRankForNewItem());
         mItemsInvalidated = true;
-        LauncherModel.addOrMoveItemInDatabase(
-                mLauncher, item, mInfo.id, 0, item.cellX, item.cellY);
+        mLauncher.getModelWriter().addOrMoveItemInDatabase(
+                item, mInfo.id, 0, item.cellX, item.cellY);
     }
 
     public void onRemove(ShortcutInfo item) {
@@ -1382,8 +1380,8 @@
             rearrangeChildren();
         }
         if (getItemCount() <= 1) {
-            if (mInfo.opened) {
-                mLauncher.closeFolder(this, true);
+            if (mIsOpen) {
+                close(true);
             } else {
                 replaceFolderWithFinalItem();
             }
@@ -1405,6 +1403,11 @@
         updateTextViewFocus();
     }
 
+    @Override
+    public void prepareAutoUpdate() {
+        close(false);
+    }
+
     public void onTitleChanged(CharSequence title) {
     }
 
@@ -1429,7 +1432,7 @@
             if (hasFocus) {
                 startEditingFolderName();
             } else {
-                dismissEditingName();
+                mFolderName.dispatchBackKey();
             }
         }
     }
@@ -1442,11 +1445,11 @@
     }
 
     @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();
-        targetParent.containerType = LauncherLogProto.FOLDER;
+        targetParent.containerType = ContainerType.FOLDER;
     }
 
     private class OnScrollHintListener implements OnAlarmListener {
@@ -1462,21 +1465,21 @@
          */
         @Override
         public void onAlarm(Alarm alarm) {
-            if (mCurrentScrollDir == DragController.SCROLL_LEFT) {
+            if (mCurrentScrollDir == SCROLL_LEFT) {
                 mContent.scrollLeft();
-                mScrollHintDir = DragController.SCROLL_NONE;
-            } else if (mCurrentScrollDir == DragController.SCROLL_RIGHT) {
+                mScrollHintDir = SCROLL_NONE;
+            } else if (mCurrentScrollDir == SCROLL_RIGHT) {
                 mContent.scrollRight();
-                mScrollHintDir = DragController.SCROLL_NONE;
+                mScrollHintDir = SCROLL_NONE;
             } else {
                 // This should not happen
                 return;
             }
-            mCurrentScrollDir = DragController.SCROLL_NONE;
+            mCurrentScrollDir = SCROLL_NONE;
 
             // Pause drag event until the scrolling is finished
             mScrollPauseAlarm.setOnAlarmListener(new OnScrollFinishedListener(mDragObject));
-            mScrollPauseAlarm.setAlarm(DragController.RESCROLL_DELAY);
+            mScrollPauseAlarm.setAlarm(RESCROLL_DELAY);
         }
     }
 
@@ -1528,4 +1531,16 @@
             updateTextViewFocus();
         }
     }
+
+    /**
+     * Returns a folder which is already open or null
+     */
+    public static Folder getOpen(Launcher launcher) {
+        return getOpenView(launcher, TYPE_FOLDER);
+    }
+
+    @Override
+    public int getLogContainerType() {
+        return ContainerType.FOLDER;
+    }
 }
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 69c2b0f..25123fb 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -18,20 +18,27 @@
 
 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;
 import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.Path;
+import android.graphics.Point;
 import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.RadialGradient;
 import android.graphics.Rect;
 import android.graphics.Region;
+import android.graphics.Shader;
 import android.graphics.drawable.Drawable;
 import android.os.Parcelable;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
+import android.util.Property;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
@@ -52,26 +59,27 @@
 import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.FolderInfo;
 import com.android.launcher3.FolderInfo.FolderListener;
-import com.android.launcher3.IconCache;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAnimUtils;
-import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.OnAlarmListener;
-import com.android.launcher3.PreloadIconDrawable;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.SimpleOnStylusPressListener;
 import com.android.launcher3.StylusEventHelper;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
+import com.android.launcher3.badge.BadgeRenderer;
+import com.android.launcher3.badge.FolderBadgeInfo;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.graphics.IconPalette;
 import com.android.launcher3.util.Thunk;
 
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * An icon that can appear on in the workspace representing an {@link Folder}.
@@ -114,7 +122,7 @@
     private PreviewLayoutRule mPreviewLayoutRule;
 
     boolean mAnimating = false;
-    private Rect mOldBounds = new Rect();
+    private Rect mTempBounds = new Rect();
 
     private float mSlop;
 
@@ -122,10 +130,27 @@
     private ArrayList<PreviewItemDrawingParams> mDrawingParams = new ArrayList<PreviewItemDrawingParams>();
     private Drawable mReferenceDrawable = null;
 
-    Paint mBgPaint = new Paint();
-
     private Alarm mOpenAlarm = new Alarm();
 
+    private FolderBadgeInfo mBadgeInfo = new FolderBadgeInfo();
+    private BadgeRenderer mBadgeRenderer;
+    private float mBadgeScale;
+    private Point mTempSpaceForBadgeOffset = new Point();
+
+    private static final Property<FolderIcon, Float> BADGE_SCALE_PROPERTY
+            = new Property<FolderIcon, Float>(Float.TYPE, "badgeScale") {
+        @Override
+        public Float get(FolderIcon folderIcon) {
+            return folderIcon.mBadgeScale;
+        }
+
+        @Override
+        public void set(FolderIcon folderIcon, Float value) {
+            folderIcon.mBadgeScale = value;
+            folderIcon.invalidate();
+        }
+    };
+
     public FolderIcon(Context context, AttributeSet attrs) {
         super(context, attrs);
         init();
@@ -142,10 +167,11 @@
         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,
-            FolderInfo folderInfo, IconCache iconCache) {
+            FolderInfo folderInfo) {
         @SuppressWarnings("all") // suppress dead code warning
         final boolean error = INITIAL_ITEM_ANIMATION_DURATION >= DROP_IN_ANIMATION_DURATION;
         if (error) {
@@ -157,11 +183,6 @@
         DeviceProfile grid = launcher.getDeviceProfile();
         FolderIcon icon = (FolderIcon) LayoutInflater.from(launcher).inflate(resId, group, false);
 
-        // For performance and compatibility reasons we render the preview using a software layer.
-        // In particular, hardware path clipping has spotty ecosystem support and bad performance.
-        // Software rendering also allows us to use shadow layers.
-        icon.setLayerType(LAYER_TYPE_SOFTWARE, new Paint(Paint.FILTER_BITMAP_FLAG));
-
         icon.setClipToPadding(false);
         icon.mFolderName = (BubbleTextView) icon.findViewById(R.id.folder_icon_name);
         icon.mFolderName.setText(folderInfo.title);
@@ -173,6 +194,7 @@
         icon.setOnClickListener(launcher);
         icon.mInfo = folderInfo;
         icon.mLauncher = launcher;
+        icon.mBadgeRenderer = launcher.getDeviceProfile().mBadgeRenderer;
         icon.setContentDescription(launcher.getString(R.string.folder_name_format, folderInfo.title));
         Folder folder = Folder.fromXml(launcher);
         folder.setDragController(launcher.getDragController());
@@ -202,16 +224,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,12 +261,12 @@
     OnAlarmListener mOnOpenListener = new OnAlarmListener() {
         public void onAlarm(Alarm alarm) {
             mFolder.beginExternalDrag();
-            mLauncher.openFolder(FolderIcon.this);
+            mFolder.animateOpen();
         }
     };
 
     public Drawable prepareCreate(final View destView) {
-        Drawable animateDrawable = getTopDrawable((TextView) destView);
+        Drawable animateDrawable = ((TextView) destView).getCompoundDrawables()[1];
         computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(),
                 destView.getMeasuredWidth());
         return animateDrawable;
@@ -273,7 +291,7 @@
     }
 
     public void performDestroyAnimation(final View finalView, Runnable onCompleteRunnable) {
-        Drawable animateDrawable = getTopDrawable((TextView) finalView);
+        Drawable animateDrawable = ((TextView) finalView).getCompoundDrawables()[1];
         computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(),
                 finalView.getMeasuredWidth());
 
@@ -325,7 +343,7 @@
             to.offset(center[0] - animateView.getMeasuredWidth() / 2,
                       center[1] - animateView.getMeasuredHeight() / 2);
 
-            float finalAlpha = index < mPreviewLayoutRule.numItems() ? 0.5f : 0f;
+            float finalAlpha = index < mPreviewLayoutRule.maxNumItems() ? 0.5f : 0f;
 
             float finalScale = scale * scaleRelativeToDragLayer;
             dragLayer.animateView(animateView, from, to, finalAlpha,
@@ -384,6 +402,26 @@
         computePreviewDrawingParams(d.getIntrinsicWidth(), getMeasuredWidth());
     }
 
+    public void setBadgeInfo(FolderBadgeInfo badgeInfo) {
+        updateBadgeScale(mBadgeInfo.hasBadge(), badgeInfo.hasBadge());
+        mBadgeInfo = badgeInfo;
+    }
+
+    /**
+     * Sets mBadgeScale to 1 or 0, animating if wasBadged or isBadged is false
+     * (the badge is being added or removed).
+     */
+    private void updateBadgeScale(boolean wasBadged, boolean isBadged) {
+        float newBadgeScale = isBadged ? 1f : 0f;
+        // Animate when a badge is first added or when it is removed.
+        if ((wasBadged ^ isBadged) && isShown()) {
+            ObjectAnimator.ofFloat(this, BADGE_SCALE_PROPERTY, newBadgeScale).start();
+        } else {
+            mBadgeScale = newBadgeScale;
+            invalidate();
+        }
+    }
+
     static class PreviewItemDrawingParams {
         PreviewItemDrawingParams(float transX, float transY, float scale, float overlayAlpha) {
             this.transX = transX;
@@ -418,8 +456,8 @@
     }
 
     private float getLocalCenterForIndex(int index, int curNumItems, int[] center) {
-        mTmpParams = computePreviewItemDrawingParams(Math.min(mPreviewLayoutRule.numItems(), index),
-                curNumItems, mTmpParams);
+        mTmpParams = computePreviewItemDrawingParams(
+                Math.min(mPreviewLayoutRule.maxNumItems(), index), curNumItems, mTmpParams);
 
         mTmpParams.transX += mBackground.basePreviewOffsetX;
         mTmpParams.transY += mBackground.basePreviewOffsetY;
@@ -452,27 +490,24 @@
     }
 
     private void drawPreviewItem(Canvas canvas, PreviewItemDrawingParams params) {
-        canvas.save();
+        canvas.save(Canvas.MATRIX_SAVE_FLAG);
         canvas.translate(params.transX, params.transY);
         canvas.scale(params.scale, params.scale);
         Drawable d = params.drawable;
 
         if (d != null) {
-            mOldBounds.set(d.getBounds());
+            mTempBounds.set(d.getBounds());
             d.setBounds(0, 0, mIntrinsicIconSize, mIntrinsicIconSize);
             if (d instanceof FastBitmapDrawable) {
                 FastBitmapDrawable fd = (FastBitmapDrawable) d;
-                float oldBrightness = fd.getBrightness();
-                fd.setBrightness(params.overlayAlpha);
-                d.draw(canvas);
-                fd.setBrightness(oldBrightness);
+                fd.drawWithBrightness(canvas, params.overlayAlpha);
             } else {
                 d.setColorFilter(Color.argb((int) (params.overlayAlpha * 255), 255, 255, 255),
                         PorterDuff.Mode.SRC_ATOP);
                 d.draw(canvas);
                 d.clearColorFilter();
             }
-            d.setBounds(mOldBounds);
+            d.setBounds(mTempBounds);
         }
         canvas.restore();
     }
@@ -482,10 +517,29 @@
      * information, handles drawing, and animation (accept state <--> rest state).
      */
     public static class PreviewBackground {
+
+        private final PorterDuffXfermode mClipPorterDuffXfermode
+                = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
+        // Create a RadialGradient such that it draws a black circle and then extends with
+        // transparent. To achieve this, we keep the gradient to black for the range [0, 1) and
+        // just at the edge quickly change it to transparent.
+        private final RadialGradient mClipShader = new RadialGradient(0, 0, 1,
+                new int[] {Color.BLACK, Color.BLACK, Color.TRANSPARENT },
+                new float[] {0, 0.999f, 1},
+                Shader.TileMode.CLAMP);
+
+        private final PorterDuffXfermode mShadowPorterDuffXfermode
+                = new PorterDuffXfermode(PorterDuff.Mode.DST_OUT);
+        private RadialGradient mShadowShader = null;
+
+        private final Matrix mShaderMatrix = new Matrix();
+        private final Path mPath = new Path();
+
+        private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+
         private float mScale = 1f;
         private float mColorMultiplier = 1f;
-        private Path mClipPath = new Path();
-        private int mStrokeWidth;
+        private float mStrokeWidth;
         private View mInvalidateDelegate;
 
         public int previewSize;
@@ -508,7 +562,7 @@
         private static final int BG_OPACITY = 160;
         private static final int MAX_BG_OPACITY = 225;
         private static final int BG_INTENSITY = 245;
-        private static final int SHADOW_OPACITY = 80;
+        private static final int SHADOW_OPACITY = 40;
 
         ValueAnimator mScaleAnimator;
 
@@ -524,7 +578,16 @@
             basePreviewOffsetX = (availableSpace - this.previewSize) / 2;
             basePreviewOffsetY = previewPadding + grid.folderBackgroundOffset + topPadding;
 
-            mStrokeWidth = Utilities.pxFromDp(1, dm);
+            // Stroke width is 1dp
+            mStrokeWidth = dm.density;
+
+            float radius = getScaledRadius();
+            float shadowRadius = radius + mStrokeWidth;
+            int shadowColor = Color.argb(SHADOW_OPACITY, 0, 0, 0);
+            mShadowShader = new RadialGradient(0, 0, 1,
+                    new int[] {shadowColor, Color.TRANSPARENT},
+                    new float[] {radius / shadowRadius, 1},
+                    Shader.TileMode.CLAMP);
 
             invalidate();
         }
@@ -545,11 +608,15 @@
             return basePreviewOffsetY - (getScaledRadius() - getRadius());
         }
 
-        void invalidate() {
-            int radius = getScaledRadius();
-            mClipPath.reset();
-            mClipPath.addCircle(radius, radius, radius, Path.Direction.CW);
+        /**
+         * Returns the progress of the scale animation, where 0 means the scale is at 1f
+         * and 1 means the scale is at ACCEPT_SCALE_FACTOR.
+         */
+        float getScaleProgress() {
+            return (mScale - 1f) / (ACCEPT_SCALE_FACTOR - 1f);
+        }
 
+        void invalidate() {
             if (mInvalidateDelegate != null) {
                 mInvalidateDelegate.invalidate();
             }
@@ -564,70 +631,94 @@
             invalidate();
         }
 
-        public void drawBackground(Canvas canvas, Paint paint) {
-            canvas.save();
-            canvas.translate(getOffsetX(), getOffsetY());
-
-            paint.reset();
-            paint.setStyle(Paint.Style.FILL);
-            paint.setXfermode(null);
-            paint.setAntiAlias(true);
-
+        public void drawBackground(Canvas canvas) {
+            mPaint.setStyle(Paint.Style.FILL);
             int alpha = (int) Math.min(MAX_BG_OPACITY, BG_OPACITY * mColorMultiplier);
-            paint.setColor(Color.argb(alpha, BG_INTENSITY, BG_INTENSITY, BG_INTENSITY));
+            mPaint.setColor(Color.argb(alpha, BG_INTENSITY, BG_INTENSITY, BG_INTENSITY));
 
+            drawCircle(canvas, 0 /* deltaRadius */);
+
+            // Draw shadow.
+            if (mShadowShader == null) {
+                return;
+            }
             float radius = getScaledRadius();
+            float shadowRadius = radius + mStrokeWidth;
+            mPaint.setColor(Color.BLACK);
+            int offsetX = getOffsetX();
+            int offsetY = getOffsetY();
+            final int saveCount;
+            if (canvas.isHardwareAccelerated()) {
+                saveCount = canvas.saveLayer(offsetX - mStrokeWidth, offsetY,
+                        offsetX + radius + shadowRadius, offsetY + shadowRadius + shadowRadius,
+                        null, Canvas.CLIP_TO_LAYER_SAVE_FLAG | Canvas.HAS_ALPHA_LAYER_SAVE_FLAG);
 
-            canvas.drawCircle(radius, radius, radius, paint);
-            canvas.clipPath(mClipPath, Region.Op.DIFFERENCE);
+            } else {
+                saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
+                clipCanvasSoftware(canvas, Region.Op.DIFFERENCE);
+            }
 
-            paint.setStyle(Paint.Style.STROKE);
-            paint.setColor(Color.TRANSPARENT);
-            paint.setShadowLayer(mStrokeWidth, 0, mStrokeWidth, Color.argb(SHADOW_OPACITY, 0, 0, 0));
-            canvas.drawCircle(radius, radius, radius, paint);
+            mShaderMatrix.setScale(shadowRadius, shadowRadius);
+            mShaderMatrix.postTranslate(radius + offsetX, shadowRadius + offsetY);
+            mShadowShader.setLocalMatrix(mShaderMatrix);
+            mPaint.setShader(mShadowShader);
+            canvas.drawPaint(mPaint);
+            mPaint.setShader(null);
 
-            canvas.restore();
+            if (canvas.isHardwareAccelerated()) {
+                mPaint.setXfermode(mShadowPorterDuffXfermode);
+                canvas.drawCircle(radius + offsetX, radius + offsetY, radius, mPaint);
+                mPaint.setXfermode(null);
+            }
+
+            canvas.restoreToCount(saveCount);
         }
 
-        public void drawBackgroundStroke(Canvas canvas, Paint paint) {
-            canvas.save();
-            canvas.translate(getOffsetX(), getOffsetY());
-
-            paint.reset();
-            paint.setAntiAlias(true);
-            paint.setColor(Color.argb(255, BG_INTENSITY, BG_INTENSITY, BG_INTENSITY));
-            paint.setStyle(Paint.Style.STROKE);
-            paint.setStrokeWidth(mStrokeWidth);
-
-            float radius = getScaledRadius();
-            canvas.drawCircle(radius, radius, radius - 1, paint);
-
-            canvas.restore();
+        public void drawBackgroundStroke(Canvas canvas) {
+            mPaint.setColor(Color.argb(255, BG_INTENSITY, BG_INTENSITY, BG_INTENSITY));
+            mPaint.setStyle(Paint.Style.STROKE);
+            mPaint.setStrokeWidth(mStrokeWidth);
+            drawCircle(canvas, 1 /* deltaRadius */);
         }
 
-        public void drawLeaveBehind(Canvas canvas, Paint paint) {
+        public void drawLeaveBehind(Canvas canvas) {
             float originalScale = mScale;
             mScale = 0.5f;
 
-            canvas.save();
-            canvas.translate(getOffsetX(), getOffsetY());
+            mPaint.setStyle(Paint.Style.FILL);
+            mPaint.setColor(Color.argb(160, 245, 245, 245));
+            drawCircle(canvas, 0 /* deltaRadius */);
 
-            paint.reset();
-            paint.setAntiAlias(true);
-            paint.setColor(Color.argb(160, 245, 245, 245));
-
-            float radius = getScaledRadius();
-            canvas.drawCircle(radius, radius, radius, paint);
-
-            canvas.restore();
             mScale = originalScale;
         }
 
-        // It is the callers responsibility to save and restore the canvas.
-        private void clipCanvas(Canvas canvas) {
-            canvas.translate(getOffsetX(), getOffsetY());
-            canvas.clipPath(mClipPath);
-            canvas.translate(-getOffsetX(), -getOffsetY());
+        private void drawCircle(Canvas canvas,float deltaRadius) {
+            float radius = getScaledRadius();
+            canvas.drawCircle(radius + getOffsetX(), radius + getOffsetY(),
+                    radius - deltaRadius, mPaint);
+        }
+
+        // It is the callers responsibility to save and restore the canvas layers.
+        private void clipCanvasSoftware(Canvas canvas, Region.Op op) {
+            mPath.reset();
+            float r = getScaledRadius();
+            mPath.addCircle(r + getOffsetX(), r + getOffsetY(), r, Path.Direction.CW);
+            canvas.clipPath(mPath, op);
+        }
+
+        // It is the callers responsibility to save and restore the canvas layers.
+        private void clipCanvasHardware(Canvas canvas) {
+            mPaint.setColor(Color.BLACK);
+            mPaint.setXfermode(mClipPorterDuffXfermode);
+
+            float radius = getScaledRadius();
+            mShaderMatrix.setScale(radius, radius);
+            mShaderMatrix.postTranslate(radius + getOffsetX(), radius + getOffsetY());
+            mClipShader.setLocalMatrix(mShaderMatrix);
+            mPaint.setShader(mClipShader);
+            canvas.drawPaint(mPaint);
+            mPaint.setXfermode(null);
+            mPaint.setShader(null);
         }
 
         private void delegateDrawing(CellLayout delegate, int cellX, int cellY) {
@@ -667,7 +758,7 @@
                 mScaleAnimator.cancel();
             }
 
-            mScaleAnimator = LauncherAnimUtils.ofFloat(null, 0f, 1.0f);
+            mScaleAnimator = LauncherAnimUtils.ofFloat(0f, 1.0f);
 
             mScaleAnimator.addUpdateListener(new AnimatorUpdateListener() {
                 @Override
@@ -747,17 +838,22 @@
         }
 
         if (!mBackground.drawingDelegated()) {
-            mBackground.drawBackground(canvas, mBgPaint);
+            mBackground.drawBackground(canvas);
         }
 
         if (mFolder == null) return;
         if (mFolder.getItemCount() == 0 && !mAnimating) return;
 
-        canvas.save();
+        final int saveCount;
 
-
-        if (mPreviewLayoutRule.clipToBackground()) {
-            mBackground.clipCanvas(canvas);
+        if (canvas.isHardwareAccelerated()) {
+            saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), null,
+                    Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
+        } else {
+            saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
+            if (mPreviewLayoutRule.clipToBackground()) {
+                mBackground.clipCanvasSoftware(canvas, Region.Op.INTERSECT);
+            }
         }
 
         // The items are drawn in coordinates relative to the preview offset
@@ -770,16 +866,29 @@
                 drawPreviewItem(canvas, p);
             }
         }
-        canvas.restore();
+        canvas.translate(-mBackground.basePreviewOffsetX, -mBackground.basePreviewOffsetY);
+
+        if (mPreviewLayoutRule.clipToBackground() && canvas.isHardwareAccelerated()) {
+            mBackground.clipCanvasHardware(canvas);
+        }
+        canvas.restoreToCount(saveCount);
 
         if (mPreviewLayoutRule.clipToBackground() && !mBackground.drawingDelegated()) {
-            mBackground.drawBackgroundStroke(canvas, mBgPaint);
+            mBackground.drawBackgroundStroke(canvas);
         }
-    }
 
-    private Drawable getTopDrawable(TextView v) {
-        Drawable d = v.getCompoundDrawables()[1];
-        return (d instanceof PreloadIconDrawable) ? ((PreloadIconDrawable) d).mIcon : d;
+        if ((mBadgeInfo != null && mBadgeInfo.hasBadge()) || mBadgeScale > 0) {
+            int offsetX = mBackground.getOffsetX();
+            int offsetY = mBackground.getOffsetY();
+            int previewSize = (int) (mBackground.previewSize * mBackground.mScale);
+            mTempBounds.set(offsetX, offsetY, offsetX + previewSize, offsetY + previewSize);
+
+            // If we are animating to the accepting state, animate the badge out.
+            float badgeScale = Math.max(0, mBadgeScale - mBackground.getScaleProgress());
+            mTempSpaceForBadgeOffset.set(getWidth() - mTempBounds.right, mTempBounds.top);
+            mBadgeRenderer.draw(canvas, IconPalette.FOLDER_ICON_PALETTE, mBadgeInfo, mTempBounds,
+                    badgeScale, mTempSpaceForBadgeOffset);
+        }
     }
 
     class FolderPreviewItemAnim {
@@ -813,7 +922,7 @@
             final float transX0 = mTmpParams.transX;
             final float transY0 = mTmpParams.transY;
 
-            mValueAnimator = LauncherAnimUtils.ofFloat(FolderIcon.this, 0f, 1.0f);
+            mValueAnimator = LauncherAnimUtils.ofFloat(0f, 1.0f);
             mValueAnimator.addUpdateListener(new AnimatorUpdateListener(){
                 public void onAnimationUpdate(ValueAnimator animation) {
                     float progress = animation.getAnimatedFraction();
@@ -883,8 +992,8 @@
     }
 
     private void updateItemDrawingParams(boolean animate) {
-        ArrayList<View> items = mFolder.getItemsInReadingOrder();
-        int nItemsInPreview = Math.min(items.size(), mPreviewLayoutRule.numItems());
+        List<View> items = mPreviewLayoutRule.getItemsToDisplay(mFolder);
+        int nItemsInPreview = items.size();
 
         int prevNumItems = mDrawingParams.size();
 
@@ -898,7 +1007,7 @@
 
         for (int i = 0; i < mDrawingParams.size(); i++) {
             PreviewItemDrawingParams p = mDrawingParams.get(i);
-            p.drawable = getTopDrawable((TextView) items.get(i));
+            p.drawable = ((TextView) items.get(i)).getCompoundDrawables()[1];
 
             if (!animate || FeatureFlags.LAUNCHER3_LEGACY_FOLDER_ICON) {
                 computePreviewItemDrawingParams(i, nItemsInPreview, p);
@@ -929,16 +1038,31 @@
         requestLayout();
     }
 
+    @Override
+    public void prepareAutoUpdate() {
+    }
+
+    @Override
     public void onAdd(ShortcutInfo item) {
+        boolean wasBadged = mBadgeInfo.hasBadge();
+        mBadgeInfo.addBadgeInfo(mLauncher.getPopupDataProvider().getBadgeInfoForItem(item));
+        boolean isBadged = mBadgeInfo.hasBadge();
+        updateBadgeScale(wasBadged, isBadged);
         invalidate();
         requestLayout();
     }
 
+    @Override
     public void onRemove(ShortcutInfo item) {
+        boolean wasBadged = mBadgeInfo.hasBadge();
+        mBadgeInfo.subtractBadgeInfo(mLauncher.getPopupDataProvider().getBadgeInfoForItem(item));
+        boolean isBadged = mBadgeInfo.hasBadge();
+        updateBadgeScale(wasBadged, isBadged);
         invalidate();
         requestLayout();
     }
 
+    @Override
     public void onTitleChanged(CharSequence title) {
         mFolderName.setText(title);
         setContentDescription(getContext().getString(R.string.folder_name_format, title));
@@ -974,12 +1098,6 @@
     }
 
     @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
-    }
-
-    @Override
     public void cancelLongPress() {
         super.cancelLongPress();
         mLongPressHelper.cancelLongPress();
@@ -990,13 +1108,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);
-
-        public int numItems();
-        public boolean clipToBackground();
+        void init(int availableSpace, int intrinsicIconSize, boolean rtl);
+        float scaleForItem(int index, int totalNumItems);
+        int maxNumItems();
+        boolean clipToBackground();
+        List<View> getItemsToDisplay(Folder folder);
     }
 }
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index 7e7ee34..eecce18 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -31,7 +31,6 @@
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.FocusHelper.PagedFolderKeyEventListener;
-import com.android.launcher3.IconCache;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
@@ -46,6 +45,7 @@
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
 import com.android.launcher3.pageindicators.PageIndicator;
+import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.Thunk;
 
 import java.util.ArrayList;
@@ -73,7 +73,6 @@
     public final boolean mIsRtl;
 
     private final LayoutInflater mInflater;
-    private final IconCache mIconCache;
     private final ViewGroupFocusHelper mFocusIndicatorHelper;
 
     @Thunk final HashMap<View, Runnable> mPendingAnimations = new HashMap<>();
@@ -98,21 +97,18 @@
 
     public FolderPagedView(Context context, AttributeSet attrs) {
         super(context, attrs);
-        LauncherAppState app = LauncherAppState.getInstance();
-
-        InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
+        InvariantDeviceProfile profile = LauncherAppState.getIDP(context);
         mMaxCountX = profile.numFolderColumns;
         mMaxCountY = profile.numFolderRows;
 
         mMaxItemsPerPage = mMaxCountX * mMaxCountY;
 
         mInflater = LayoutInflater.from(context);
-        mIconCache = app.getIconCache();
 
         mIsRtl = Utilities.isRtl(getResources());
         setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
 
-        setEdgeGlowColor(getResources().getColor(R.color.folder_edge_effect_color));
+        setEdgeGlowColor(Themes.getAttrColor(context, android.R.attr.colorEdgeEffect));
         mFocusIndicatorHelper = new ViewGroupFocusHelper(this);
     }
 
@@ -231,7 +227,7 @@
     public View createNewView(ShortcutInfo item) {
         final BubbleTextView textView = (BubbleTextView) mInflater.inflate(
                 R.layout.folder_application, null, false);
-        textView.applyFromShortcutInfo(item, mIconCache);
+        textView.applyFromShortcutInfo(item);
         textView.setOnClickListener(mFolder);
         textView.setOnLongClickListener(mFolder);
         textView.setOnFocusChangeListener(mFocusIndicatorHelper);
@@ -253,10 +249,9 @@
 
     private CellLayout createAndAddNewPage() {
         DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile();
-        CellLayout page = new CellLayout(getContext());
+        CellLayout page = (CellLayout) mInflater.inflate(R.layout.folder_page, this, false);
         page.setCellDimensions(grid.folderCellWidthPx, grid.folderCellHeightPx);
         page.getShortcutsAndWidgets().setMotionEventSplittingEnabled(false);
-        page.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
         page.setInvertIfRtl(true);
         page.setGridSize(mGridCountX, mGridCountY);
 
@@ -342,7 +337,7 @@
                     info.cellY = newY;
                     info.rank = rank;
                     if (saveChanges) {
-                        LauncherModel.addOrMoveItemInDatabase(getContext(), info,
+                        mFolder.mLauncher.getModelWriter().addOrMoveItemInDatabase(info,
                                 mFolder.mInfo.id, 0, info.cellX, info.cellY);
                     }
                 }
@@ -487,7 +482,7 @@
      * Scrolls the current view by a fraction
      */
     public void showScrollHint(int direction) {
-        float fraction = (direction == DragController.SCROLL_LEFT) ^ mIsRtl
+        float fraction = (direction == Folder.SCROLL_LEFT) ^ mIsRtl
                 ? -SCROLL_HINT_FRACTION : SCROLL_HINT_FRACTION;
         int hint = (int) (fraction * getWidth());
         int scroll = getScrollForPage(getNextPage()) + hint;
@@ -524,12 +519,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 +673,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/folder/StackFolderIconLayoutRule.java b/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java
index 7fb02e3..297203a 100644
--- a/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java
+++ b/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java
@@ -16,10 +16,12 @@
 
 package com.android.launcher3.folder;
 
-import android.graphics.Path;
+import android.view.View;
 
 import com.android.launcher3.folder.FolderIcon.PreviewItemDrawingParams;
 
+import java.util.List;
+
 public class StackFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule {
 
     static final int MAX_NUM_ITEMS_IN_PREVIEW = 3;
@@ -54,10 +56,10 @@
     @Override
     public PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems,
             PreviewItemDrawingParams params) {
+        float scale = scaleForItem(index, curNumItems);
 
         index = MAX_NUM_ITEMS_IN_PREVIEW - index - 1;
         float r = (index * 1.0f) / (MAX_NUM_ITEMS_IN_PREVIEW - 1);
-        float scale = (1 - PERSPECTIVE_SCALE_FACTOR * (1 - r));
 
         float offset = (1 - r) * mMaxPerspectiveShift;
         float scaledSize = scale * mBaselineIconSize;
@@ -80,12 +82,26 @@
     }
 
     @Override
-    public int numItems() {
+    public int maxNumItems() {
         return MAX_NUM_ITEMS_IN_PREVIEW;
     }
 
     @Override
+    public float scaleForItem(int index, int numItems) {
+        // Scale is determined by the position of the icon in the preview.
+        index = MAX_NUM_ITEMS_IN_PREVIEW - index - 1;
+        float r = (index * 1.0f) / (MAX_NUM_ITEMS_IN_PREVIEW - 1);
+        return (1 - PERSPECTIVE_SCALE_FACTOR * (1 - r));
+    }
+
+    @Override
     public boolean clipToBackground() {
         return false;
     }
+
+    @Override
+    public List<View> getItemsToDisplay(Folder folder) {
+        List<View> items = folder.getItemsInReadingOrder();
+        return items.subList(0, Math.min(items.size(), MAX_NUM_ITEMS_IN_PREVIEW));
+    }
 }
diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java
index bc91c15..bb136f7 100644
--- a/src/com/android/launcher3/graphics/DragPreviewProvider.java
+++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.graphics;
 
+import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Rect;
@@ -24,9 +25,9 @@
 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.LauncherAppWidgetHostView;
+import com.android.launcher3.R;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.config.ProviderConfig;
 import com.android.launcher3.folder.FolderIcon;
@@ -36,8 +37,6 @@
  */
 public class DragPreviewProvider {
 
-    public static final int DRAG_BITMAP_PADDING = 2;
-
     private final Rect mTempRect = new Rect();
 
     protected final View mView;
@@ -45,17 +44,25 @@
     // The padding added to the drag view during the preview generation.
     public final int previewPadding;
 
-    public Bitmap gerenatedDragOutline;
+    protected final int blurSizeOutline;
+
+    public Bitmap generatedDragOutline;
 
     public DragPreviewProvider(View view) {
+        this(view, view.getContext());
+    }
+
+    public DragPreviewProvider(View view, Context context) {
         mView = view;
+        blurSizeOutline =
+                context.getResources().getDimensionPixelSize(R.dimen.blur_size_medium_outline);
 
         if (mView instanceof TextView) {
             Drawable d = Workspace.getTextViewIcon((TextView) mView);
             Rect bounds = getDrawableBounds(d);
-            previewPadding = DRAG_BITMAP_PADDING - bounds.left - bounds.top;
+            previewPadding = blurSizeOutline - bounds.left - bounds.top;
         } else {
-            previewPadding = DRAG_BITMAP_PADDING;
+            previewPadding = blurSizeOutline;
         }
     }
 
@@ -67,8 +74,8 @@
         if (mView instanceof TextView) {
             Drawable d = Workspace.getTextViewIcon((TextView) mView);
             Rect bounds = getDrawableBounds(d);
-            destCanvas.translate(DRAG_BITMAP_PADDING / 2 - bounds.left,
-                    DRAG_BITMAP_PADDING / 2 - bounds.top);
+            destCanvas.translate(blurSizeOutline / 2 - bounds.left,
+                    blurSizeOutline / 2 - bounds.top);
             d.draw(destCanvas);
         } else {
             final Rect clipRect = mTempRect;
@@ -83,8 +90,8 @@
                     textVisible = true;
                 }
             }
-            destCanvas.translate(-mView.getScrollX() + DRAG_BITMAP_PADDING / 2,
-                    -mView.getScrollY() + DRAG_BITMAP_PADDING / 2);
+            destCanvas.translate(-mView.getScrollX() + blurSizeOutline / 2,
+                    -mView.getScrollY() + blurSizeOutline / 2);
             destCanvas.clipRect(clipRect, Op.REPLACE);
             mView.draw(destCanvas);
 
@@ -101,31 +108,41 @@
      * Responsibility for the bitmap is transferred to the caller.
      */
     public Bitmap createDragBitmap(Canvas canvas) {
-        Bitmap b;
+        float scale = 1f;
+        int width = mView.getWidth();
+        int height = mView.getHeight();
 
         if (mView instanceof TextView) {
             Drawable d = Workspace.getTextViewIcon((TextView) mView);
             Rect bounds = getDrawableBounds(d);
-            b = Bitmap.createBitmap(bounds.width() + DRAG_BITMAP_PADDING,
-                    bounds.height() + DRAG_BITMAP_PADDING, Bitmap.Config.ARGB_8888);
-        } else {
-            b = Bitmap.createBitmap(mView.getWidth() + DRAG_BITMAP_PADDING,
-                    mView.getHeight() + DRAG_BITMAP_PADDING, Bitmap.Config.ARGB_8888);
+            width = bounds.width();
+            height = bounds.height();
+        } else if (mView instanceof LauncherAppWidgetHostView) {
+            scale = ((LauncherAppWidgetHostView) mView).getScaleToFit();
+            width = (int) (mView.getWidth() * scale);
+            height = (int) (mView.getHeight() * scale);
         }
 
+        Bitmap b = Bitmap.createBitmap(width + blurSizeOutline, height + blurSizeOutline,
+                Bitmap.Config.ARGB_8888);
         canvas.setBitmap(b);
+
+        canvas.save();
+        canvas.scale(scale, scale);
         drawDragView(canvas);
+        canvas.restore();
+
         canvas.setBitmap(null);
 
         return b;
     }
 
     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);
     }
 
     /**
@@ -133,12 +150,28 @@
      * Responsibility for the bitmap is transferred to the caller.
      */
     public Bitmap createDragOutline(Canvas canvas) {
-        final Bitmap b = Bitmap.createBitmap(mView.getWidth() + DRAG_BITMAP_PADDING,
-                mView.getHeight() + DRAG_BITMAP_PADDING, Bitmap.Config.ALPHA_8);
+        float scale = 1f;
+        int width = mView.getWidth();
+        int height = mView.getHeight();
+
+        if (mView instanceof LauncherAppWidgetHostView) {
+            scale = ((LauncherAppWidgetHostView) mView).getScaleToFit();
+            width = (int) Math.floor(mView.getWidth() * scale);
+            height = (int) Math.floor(mView.getHeight() * scale);
+        }
+
+        Bitmap b = Bitmap.createBitmap(width + blurSizeOutline, height + blurSizeOutline,
+                Bitmap.Config.ALPHA_8);
         canvas.setBitmap(b);
+
+        canvas.save();
+        canvas.scale(scale, scale);
         drawDragView(canvas);
-        HolographicOutlineHelper.obtain(mView.getContext())
+        canvas.restore();
+
+        HolographicOutlineHelper.getInstance(mView.getContext())
                 .applyExpensiveOutlineWithBlur(b, canvas);
+
         canvas.setBitmap(null);
         return b;
     }
@@ -151,18 +184,22 @@
         } else {
             bounds.offsetTo(0, 0);
         }
-        if (d instanceof PreloadIconDrawable) {
-            int inset = -((PreloadIconDrawable) d).getOutset();
-            bounds.inset(inset, inset);
-        }
         return bounds;
     }
 
     public float getScaleAndPosition(Bitmap preview, int[] outPos) {
         float scale = Launcher.getLauncher(mView.getContext())
                 .getDragLayer().getLocationInDragLayer(mView, outPos);
-        outPos[0] = Math.round(outPos[0] - (preview.getWidth() - scale * mView.getWidth()) / 2);
-        outPos[1] = Math.round(outPos[1] - (1 - scale) * preview.getHeight() / 2 - previewPadding / 2);
+        if (mView instanceof LauncherAppWidgetHostView) {
+            // App widgets are technically scaled, but are drawn at their expected size -- so the
+            // app widget scale should not affect the scale of the preview.
+            scale /= ((LauncherAppWidgetHostView) mView).getScaleToFit();
+        }
+
+        outPos[0] = Math.round(outPos[0] -
+                (preview.getWidth() - scale * mView.getWidth() * mView.getScaleX()) / 2);
+        outPos[1] = Math.round(outPos[1] - (1 - scale) * preview.getHeight() / 2
+                - previewPadding / 2);
         return scale;
     }
 }
diff --git a/src/com/android/launcher3/graphics/DrawableFactory.java b/src/com/android/launcher3/graphics/DrawableFactory.java
new file mode 100644
index 0000000..60bbce4
--- /dev/null
+++ b/src/com/android/launcher3/graphics/DrawableFactory.java
@@ -0,0 +1,150 @@
+/*
+ * 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.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Process;
+import android.os.UserHandle;
+import android.support.annotation.UiThread;
+import android.util.Log;
+
+import com.android.launcher3.FastBitmapDrawable;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.allapps.AllAppsBackgroundDrawable;
+
+import java.util.HashMap;
+
+/**
+ * Factory for creating new drawables.
+ */
+public class DrawableFactory {
+
+    private static final String TAG = "DrawableFactory";
+
+    private static DrawableFactory sInstance;
+    private static final Object LOCK = new Object();
+
+    private Path mPreloadProgressPath;
+
+    public static DrawableFactory get(Context context) {
+        synchronized (LOCK) {
+            if (sInstance == null) {
+                sInstance = Utilities.getOverrideObject(DrawableFactory.class,
+                        context.getApplicationContext(), R.string.drawable_factory_class);
+            }
+            return sInstance;
+        }
+    }
+
+    protected final UserHandle mMyUser = Process.myUserHandle();
+    protected final HashMap<UserHandle, Bitmap> mUserBadges = new HashMap<>();
+
+    /**
+     * Returns a FastBitmapDrawable with the icon.
+     */
+    public FastBitmapDrawable newIcon(Bitmap icon, ItemInfo info) {
+        return new FastBitmapDrawable(icon);
+    }
+
+    /**
+     * Returns a FastBitmapDrawable with the icon.
+     */
+    public PreloadIconDrawable newPendingIcon(Bitmap icon, Context context) {
+        if (mPreloadProgressPath == null) {
+            mPreloadProgressPath = getPreloadProgressPath(context);
+        }
+        return new PreloadIconDrawable(icon, mPreloadProgressPath, context);
+    }
+
+
+    protected Path getPreloadProgressPath(Context context) {
+        if (Utilities.isAtLeastO()) {
+            try {
+                // Try to load the path from Mask Icon
+                Drawable icon = context.getDrawable(R.drawable.adaptive_icon_drawable_wrapper);
+                icon.setBounds(0, 0,
+                        PreloadIconDrawable.PATH_SIZE, PreloadIconDrawable.PATH_SIZE);
+                return (Path) icon.getClass().getMethod("getIconMask").invoke(icon);
+            } catch (Exception e) {
+                Log.e(TAG, "Error loading mask icon", e);
+            }
+        }
+
+        // Create a circle static from top center and going clockwise.
+        Path p = new Path();
+        p.moveTo(PreloadIconDrawable.PATH_SIZE / 2, 0);
+        p.addArc(0, 0, PreloadIconDrawable.PATH_SIZE, PreloadIconDrawable.PATH_SIZE, -90, 360);
+        return p;
+    }
+
+    public AllAppsBackgroundDrawable getAllAppsBackground(Context context) {
+        return new AllAppsBackgroundDrawable(context);
+    }
+
+    /**
+     * Returns a drawable that can be used as a badge for the user or null.
+     */
+    @UiThread
+    public Drawable getBadgeForUser(UserHandle user, Context context) {
+        if (mMyUser.equals(user)) {
+            return null;
+        }
+
+        Bitmap badgeBitmap = getUserBadge(user, context);
+        FastBitmapDrawable d = new FastBitmapDrawable(badgeBitmap);
+        d.setFilterBitmap(true);
+        d.setBounds(0, 0, badgeBitmap.getWidth(), badgeBitmap.getHeight());
+        return d;
+    }
+
+    protected synchronized Bitmap getUserBadge(UserHandle user, Context context) {
+        Bitmap badgeBitmap = mUserBadges.get(user);
+        if (badgeBitmap != null) {
+            return badgeBitmap;
+        }
+
+        final Resources res = context.getApplicationContext().getResources();
+        int badgeSize = res.getDimensionPixelSize(R.dimen.profile_badge_size);
+        badgeBitmap = Bitmap.createBitmap(badgeSize, badgeSize, Bitmap.Config.ARGB_8888);
+
+        Drawable drawable = context.getPackageManager().getUserBadgedDrawableForDensity(
+                new BitmapDrawable(res, badgeBitmap), user, new Rect(0, 0, badgeSize, badgeSize),
+                0);
+        if (drawable instanceof BitmapDrawable) {
+            badgeBitmap = ((BitmapDrawable) drawable).getBitmap();
+        } else {
+            badgeBitmap.eraseColor(Color.TRANSPARENT);
+            Canvas c = new Canvas(badgeBitmap);
+            drawable.setBounds(0, 0, badgeSize, badgeSize);
+            drawable.draw(c);
+            c.setBitmap(null);
+        }
+
+        mUserBadges.put(user, badgeBitmap);
+        return badgeBitmap;
+    }
+}
diff --git a/src/com/android/launcher3/graphics/FixedScaleDrawable.java b/src/com/android/launcher3/graphics/FixedScaleDrawable.java
new file mode 100644
index 0000000..262a95e
--- /dev/null
+++ b/src/com/android/launcher3/graphics/FixedScaleDrawable.java
@@ -0,0 +1,56 @@
+package com.android.launcher3.graphics;
+
+import android.annotation.TargetApi;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.graphics.Canvas;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.DrawableWrapper;
+import android.os.Build;
+import android.util.AttributeSet;
+
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * Extension of {@link DrawableWrapper} which scales the child drawables by a fixed amount.
+ */
+@TargetApi(Build.VERSION_CODES.N)
+public class FixedScaleDrawable extends DrawableWrapper {
+
+    // TODO b/33553066 use the constant defined in MaskableIconDrawable
+    private static final float LEGACY_ICON_SCALE = .7f * .6667f;
+    private float mScaleX, mScaleY;
+
+    public FixedScaleDrawable() {
+        super(new ColorDrawable());
+        mScaleX = LEGACY_ICON_SCALE;
+        mScaleY = LEGACY_ICON_SCALE;
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
+        canvas.scale(mScaleX, mScaleY,
+                getBounds().exactCenterX(), getBounds().exactCenterY());
+        super.draw(canvas);
+        canvas.restoreToCount(saveCount);
+    }
+
+    @Override
+    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) { }
+
+    @Override
+    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) { }
+
+    public void setScale(float scale) {
+        float h = getIntrinsicHeight();
+        float w = getIntrinsicWidth();
+        mScaleX = scale * LEGACY_ICON_SCALE;
+        mScaleY = scale * LEGACY_ICON_SCALE;
+        if (h > w && w > 0) {
+            mScaleX *= w / h;
+        } else if (w > h && h > 0) {
+            mScaleY *= h / w;
+        }
+    }
+}
diff --git a/src/com/android/launcher3/HolographicOutlineHelper.java b/src/com/android/launcher3/graphics/HolographicOutlineHelper.java
similarity index 83%
rename from src/com/android/launcher3/HolographicOutlineHelper.java
rename to src/com/android/launcher3/graphics/HolographicOutlineHelper.java
index 9dec7d9..c9873d9 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;
     }
@@ -84,33 +86,26 @@
      * bitmap.
      */
     public void applyExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas) {
-        applyExpensiveOutlineWithBlur(srcDst, srcDstCanvas, true);
-    }
-
-    public void applyExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas,
-            boolean clipAlpha) {
         if (ProviderConfig.IS_DOGFOOD_BUILD && srcDst.getConfig() != Bitmap.Config.ALPHA_8) {
             throw new RuntimeException("Outline blue is only supported on alpha bitmaps");
         }
 
         // We start by removing most of the alpha channel so as to ignore shadows, and
         // other types of partial transparency when defining the shape of the object
-        if (clipAlpha) {
-            byte[] pixels = new byte[srcDst.getWidth() * srcDst.getHeight()];
-            ByteBuffer buffer = ByteBuffer.wrap(pixels);
-            buffer.rewind();
-            srcDst.copyPixelsToBuffer(buffer);
+        byte[] pixels = new byte[srcDst.getWidth() * srcDst.getHeight()];
+        ByteBuffer buffer = ByteBuffer.wrap(pixels);
+        buffer.rewind();
+        srcDst.copyPixelsToBuffer(buffer);
 
-            for (int i = 0; i < pixels.length; i++) {
-                if ((pixels[i] & 0xFF) < 188) {
-                    pixels[i] = 0;
-                }
+        for (int i = 0; i < pixels.length; i++) {
+            if ((pixels[i] & 0xFF) < 188) {
+                pixels[i] = 0;
             }
-
-            buffer.rewind();
-            srcDst.copyPixelsFromBuffer(buffer);
         }
 
+        buffer.rewind();
+        srcDst.copyPixelsFromBuffer(buffer);
+
         // calculate the outer blur first
         mBlurPaint.setMaskFilter(mMediumOuterBlurMaskFilter);
         int[] outerBlurOffset = new int[2];
@@ -155,19 +150,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 +167,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 +191,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/graphics/IconNormalizer.java b/src/com/android/launcher3/graphics/IconNormalizer.java
new file mode 100644
index 0000000..34d0b72
--- /dev/null
+++ b/src/com/android/launcher3/graphics/IconNormalizer.java
@@ -0,0 +1,420 @@
+/*
+ * 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.graphics;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.Log;
+
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.Utilities;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.nio.ByteBuffer;
+import java.util.Random;
+
+public class IconNormalizer {
+
+    private static final String TAG = "IconNormalizer";
+    private static final boolean DEBUG = false;
+    // Ratio of icon visible area to full icon size for a square shaped icon
+    private static final float MAX_SQUARE_AREA_FACTOR = 375.0f / 576;
+    // Ratio of icon visible area to full icon size for a circular shaped icon
+    private static final float MAX_CIRCLE_AREA_FACTOR = 380.0f / 576;
+
+    private static final float CIRCLE_AREA_BY_RECT = (float) Math.PI / 4;
+
+    // Slope used to calculate icon visible area to full icon size for any generic shaped icon.
+    private static final float LINEAR_SCALE_SLOPE =
+            (MAX_CIRCLE_AREA_FACTOR - MAX_SQUARE_AREA_FACTOR) / (1 - CIRCLE_AREA_BY_RECT);
+
+    private static final int MIN_VISIBLE_ALPHA = 40;
+
+    // Shape detection related constants
+    private static final float BOUND_RATIO_MARGIN = .05f;
+    private static final float PIXEL_DIFF_PERCENTAGE_THRESHOLD = 0.005f;
+    private static final float SCALE_NOT_INITIALIZED = 0;
+
+    private static final Object LOCK = new Object();
+    private static IconNormalizer sIconNormalizer;
+
+    private final int mMaxSize;
+    private final Bitmap mBitmap;
+    private final Bitmap mBitmapARGB;
+    private final Canvas mCanvas;
+    private final Paint mPaintMaskShape;
+    private final Paint mPaintMaskShapeOutline;
+    private final byte[] mPixels;
+    private final int[] mPixelsARGB;
+
+    private final Rect mAdaptiveIconBounds;
+    private float mAdaptiveIconScale;
+
+    // for each y, stores the position of the leftmost x and the rightmost x
+    private final float[] mLeftBorder;
+    private final float[] mRightBorder;
+    private final Rect mBounds;
+    private final Matrix mMatrix;
+
+    private Paint mPaintIcon;
+    private Canvas mCanvasARGB;
+
+    private File mDir;
+    private int mFileId;
+    private Random mRandom;
+
+    private IconNormalizer(Context context) {
+        // Use twice the icon size as maximum size to avoid scaling down twice.
+        mMaxSize = LauncherAppState.getIDP(context).iconBitmapSize * 2;
+        mBitmap = Bitmap.createBitmap(mMaxSize, mMaxSize, Bitmap.Config.ALPHA_8);
+        mCanvas = new Canvas(mBitmap);
+        mPixels = new byte[mMaxSize * mMaxSize];
+        mPixelsARGB = new int[mMaxSize * mMaxSize];
+        mLeftBorder = new float[mMaxSize];
+        mRightBorder = new float[mMaxSize];
+        mBounds = new Rect();
+        mAdaptiveIconBounds = new Rect();
+
+        // Needed for isShape() method
+        mBitmapARGB = Bitmap.createBitmap(mMaxSize, mMaxSize, Bitmap.Config.ARGB_8888);
+        mCanvasARGB = new Canvas(mBitmapARGB);
+
+        mPaintIcon = new Paint();
+        mPaintIcon.setColor(Color.WHITE);
+
+        mPaintMaskShape = new Paint();
+        mPaintMaskShape.setColor(Color.RED);
+        mPaintMaskShape.setStyle(Paint.Style.FILL);
+        mPaintMaskShape.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR));
+
+        mPaintMaskShapeOutline = new Paint();
+        mPaintMaskShapeOutline.setStrokeWidth(2 * context.getResources().getDisplayMetrics().density);
+        mPaintMaskShapeOutline.setStyle(Paint.Style.STROKE);
+        mPaintMaskShapeOutline.setColor(Color.BLACK);
+        mPaintMaskShapeOutline.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
+
+        mMatrix = new Matrix();
+        int[] mPixels = new int[mMaxSize * mMaxSize];
+        mAdaptiveIconScale = SCALE_NOT_INITIALIZED;
+
+        mDir = context.getExternalFilesDir(null);
+        mRandom = new Random();
+    }
+
+    /**
+     * Returns if the shape of the icon is same as the path.
+     * For this method to work, the shape path bounds should be in [0,1]x[0,1] bounds.
+     */
+    private boolean isShape(Path maskPath) {
+        // Condition1:
+        // If width and height of the path not close to a square, then the icon shape is
+        // not same as the mask shape.
+        float iconRatio = ((float) mBounds.width()) / mBounds.height();
+        if (Math.abs(iconRatio - 1) > BOUND_RATIO_MARGIN) {
+            if (DEBUG) {
+                Log.d(TAG, "Not same as mask shape because width != height. " + iconRatio);
+            }
+            return false;
+        }
+
+        // Condition 2:
+        // Actual icon (white) and the fitted shape (e.g., circle)(red) XOR operation
+        // should generate transparent image, if the actual icon is equivalent to the shape.
+        mFileId = mRandom.nextInt();
+        mBitmapARGB.eraseColor(Color.TRANSPARENT);
+        mCanvasARGB.drawBitmap(mBitmap, 0, 0, mPaintIcon);
+
+        if (DEBUG) {
+            final File beforeFile = new File(mDir, "isShape" + mFileId + "_before.png");
+            try {
+                mBitmapARGB.compress(Bitmap.CompressFormat.PNG, 100,
+                        new FileOutputStream(beforeFile));
+            } catch (Exception e) {}
+        }
+
+        // Fit the shape within the icon's bounding box
+        mMatrix.reset();
+        mMatrix.setScale(mBounds.width(), mBounds.height());
+        mMatrix.postTranslate(mBounds.left, mBounds.top);
+        maskPath.transform(mMatrix);
+
+        // XOR operation
+        mCanvasARGB.drawPath(maskPath, mPaintMaskShape);
+
+        // DST_OUT operation around the mask path outline
+        mCanvasARGB.drawPath(maskPath, mPaintMaskShapeOutline);
+
+        boolean isTrans = isTransparentBitmap(mBitmapARGB);
+        if (DEBUG) {
+            final File afterFile = new File(mDir, "isShape" + mFileId + "_after_" + isTrans + ".png");
+            try {
+                mBitmapARGB.compress(Bitmap.CompressFormat.PNG, 100,
+                        new FileOutputStream(afterFile));
+            } catch (Exception e) {}
+        }
+
+        // Check if the result is almost transparent
+        if (!isTrans) {
+            if (DEBUG) {
+                Log.d(TAG, "Not same as mask shape");
+            }
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Used to determine if certain the bitmap is transparent.
+     */
+    private boolean isTransparentBitmap(Bitmap bitmap) {
+        int w = mBounds.width();
+        int h = mBounds.height();
+        bitmap.getPixels(mPixelsARGB, 0 /* the first index to write into the array */,
+                w /* stride */,
+                mBounds.left, mBounds.top,
+                w, h);
+        int sum = 0;
+        for (int i = 0; i < w * h; i++) {
+            if(Color.alpha(mPixelsARGB[i]) > MIN_VISIBLE_ALPHA) {
+                    sum++;
+            }
+        }
+        float percentageDiffPixels = ((float) sum) / (mBounds.width() * mBounds.height());
+        boolean transparentImage = percentageDiffPixels < PIXEL_DIFF_PERCENTAGE_THRESHOLD;
+        if (DEBUG) {
+            Log.d(TAG, "Total # pixel that is different (id="+ mFileId + "):" + percentageDiffPixels + "="+ sum + "/" + mBounds.width() * mBounds.height());
+        }
+        return transparentImage;
+    }
+
+    /**
+     * Returns the amount by which the {@param d} should be scaled (in both dimensions) so that it
+     * matches the design guidelines for a launcher icon.
+     *
+     * We first calculate the convex hull of the visible portion of the icon.
+     * This hull then compared with the bounding rectangle of the hull to find how closely it
+     * resembles a circle and a square, by comparing the ratio of the areas. Note that this is not an
+     * ideal solution but it gives satisfactory result without affecting the performance.
+     *
+     * This closeness is used to determine the ratio of hull area to the full icon size.
+     * Refer {@link #MAX_CIRCLE_AREA_FACTOR} and {@link #MAX_SQUARE_AREA_FACTOR}
+     *
+     * @param outBounds optional rect to receive the fraction distance from each edge.
+     */
+    public synchronized float getScale(@NonNull Drawable d, @Nullable RectF outBounds,
+            @Nullable Path path, @Nullable boolean[] outMaskShape) {
+        if (Utilities.isAtLeastO() && d instanceof AdaptiveIconDrawable &&
+                mAdaptiveIconScale != SCALE_NOT_INITIALIZED) {
+            if (outBounds != null) {
+                outBounds.set(mAdaptiveIconBounds);
+            }
+            return mAdaptiveIconScale;
+        }
+        int width = d.getIntrinsicWidth();
+        int height = d.getIntrinsicHeight();
+        if (width <= 0 || height <= 0) {
+            width = width <= 0 || width > mMaxSize ? mMaxSize : width;
+            height = height <= 0 || height > mMaxSize ? mMaxSize : height;
+        } else if (width > mMaxSize || height > mMaxSize) {
+            int max = Math.max(width, height);
+            width = mMaxSize * width / max;
+            height = mMaxSize * height / max;
+        }
+
+        mBitmap.eraseColor(Color.TRANSPARENT);
+        d.setBounds(0, 0, width, height);
+        d.draw(mCanvas);
+
+        ByteBuffer buffer = ByteBuffer.wrap(mPixels);
+        buffer.rewind();
+        mBitmap.copyPixelsToBuffer(buffer);
+
+        // Overall bounds of the visible icon.
+        int topY = -1;
+        int bottomY = -1;
+        int leftX = mMaxSize + 1;
+        int rightX = -1;
+
+        // Create border by going through all pixels one row at a time and for each row find
+        // the first and the last non-transparent pixel. Set those values to mLeftBorder and
+        // mRightBorder and use -1 if there are no visible pixel in the row.
+
+        // buffer position
+        int index = 0;
+        // buffer shift after every row, width of buffer = mMaxSize
+        int rowSizeDiff = mMaxSize - width;
+        // first and last position for any row.
+        int firstX, lastX;
+
+        for (int y = 0; y < height; y++) {
+            firstX = lastX = -1;
+            for (int x = 0; x < width; x++) {
+                if ((mPixels[index] & 0xFF) > MIN_VISIBLE_ALPHA) {
+                    if (firstX == -1) {
+                        firstX = x;
+                    }
+                    lastX = x;
+                }
+                index++;
+            }
+            index += rowSizeDiff;
+
+            mLeftBorder[y] = firstX;
+            mRightBorder[y] = lastX;
+
+            // If there is at least one visible pixel, update the overall bounds.
+            if (firstX != -1) {
+                bottomY = y;
+                if (topY == -1) {
+                    topY = y;
+                }
+
+                leftX = Math.min(leftX, firstX);
+                rightX = Math.max(rightX, lastX);
+            }
+        }
+
+        if (topY == -1 || rightX == -1) {
+            // No valid pixels found. Do not scale.
+            return 1;
+        }
+
+        convertToConvexArray(mLeftBorder, 1, topY, bottomY);
+        convertToConvexArray(mRightBorder, -1, topY, bottomY);
+
+        // Area of the convex hull
+        float area = 0;
+        for (int y = 0; y < height; y++) {
+            if (mLeftBorder[y] <= -1) {
+                continue;
+            }
+            area += mRightBorder[y] - mLeftBorder[y] + 1;
+        }
+
+        // Area of the rectangle required to fit the convex hull
+        float rectArea = (bottomY + 1 - topY) * (rightX + 1 - leftX);
+        float hullByRect = area / rectArea;
+
+        float scaleRequired;
+        if (hullByRect < CIRCLE_AREA_BY_RECT) {
+            scaleRequired = MAX_CIRCLE_AREA_FACTOR;
+        } else {
+            scaleRequired = MAX_SQUARE_AREA_FACTOR + LINEAR_SCALE_SLOPE * (1 - hullByRect);
+        }
+        mBounds.left = leftX;
+        mBounds.right = rightX;
+
+        mBounds.top = topY;
+        mBounds.bottom = bottomY;
+
+        if (outBounds != null) {
+            outBounds.set(((float) mBounds.left) / width, ((float) mBounds.top),
+                    1 - ((float) mBounds.right) / width,
+                    1 - ((float) mBounds.bottom) / height);
+        }
+
+        if (outMaskShape != null && outMaskShape.length > 0) {
+            outMaskShape[0] = isShape(path);
+        }
+        float areaScale = area / (width * height);
+        // Use sqrt of the final ratio as the images is scaled across both width and height.
+        float scale = areaScale > scaleRequired ? (float) Math.sqrt(scaleRequired / areaScale) : 1;
+        if (Utilities.isAtLeastO() && d instanceof AdaptiveIconDrawable &&
+                mAdaptiveIconScale == SCALE_NOT_INITIALIZED) {
+            mAdaptiveIconScale = scale;
+            mAdaptiveIconBounds.set(mBounds);
+        }
+        return scale;
+    }
+
+    /**
+     * Modifies {@param xCoordinates} to represent a convex border. Fills in all missing values
+     * (except on either ends) with appropriate values.
+     * @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[] xCoordinates, int direction, int topY, int bottomY) {
+        int total = xCoordinates.length;
+        // The tangent at each pixel.
+        float[] angles = new float[total - 1];
+
+        int first = topY; // First valid y coordinate
+        int last = -1;    // Last valid y coordinate which didn't have a missing value
+
+        float lastAngle = Float.MAX_VALUE;
+
+        for (int i = topY + 1; i <= bottomY; i++) {
+            if (xCoordinates[i] <= -1) {
+                continue;
+            }
+            int start;
+
+            if (lastAngle == Float.MAX_VALUE) {
+                start = first;
+            } else {
+                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 = (xCoordinates[i] - xCoordinates[start]) / (i - start);
+                        if ((currentAngle - angles[start]) * direction >= 0) {
+                            break;
+                        }
+                    }
+                }
+            }
+
+            // Reset from last check
+            lastAngle = (xCoordinates[i] - xCoordinates[start]) / (i - start);
+            // Update all the points from start.
+            for (int j = start; j < i; j++) {
+                angles[j] = lastAngle;
+                xCoordinates[j] = xCoordinates[start] + lastAngle * (j - start);
+            }
+            last = i;
+        }
+    }
+
+    public static IconNormalizer getInstance(Context context) {
+        synchronized (LOCK) {
+            if (sIconNormalizer == null) {
+                sIconNormalizer = new IconNormalizer(context);
+            }
+        }
+        return sIconNormalizer;
+    }
+}
diff --git a/src/com/android/launcher3/graphics/IconPalette.java b/src/com/android/launcher3/graphics/IconPalette.java
new file mode 100644
index 0000000..0182e53
--- /dev/null
+++ b/src/com/android/launcher3/graphics/IconPalette.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.graphics;
+
+import android.app.Notification;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.support.v4.graphics.ColorUtils;
+import android.util.Log;
+
+import com.android.launcher3.R;
+import com.android.launcher3.util.Themes;
+
+/**
+ * Contains colors based on the dominant color of an icon.
+ */
+public class IconPalette {
+
+    private static final boolean DEBUG = false;
+    private static final String TAG = "IconPalette";
+
+    public static final IconPalette FOLDER_ICON_PALETTE = new IconPalette(Color.parseColor("#BDC1C6"));
+
+    private static final float MIN_PRELOAD_COLOR_SATURATION = 0.2f;
+    private static final float MIN_PRELOAD_COLOR_LIGHTNESS = 0.6f;
+
+    public final int dominantColor;
+    public final int backgroundColor;
+    public final ColorMatrixColorFilter backgroundColorMatrixFilter;
+    public final ColorMatrixColorFilter saturatedBackgroundColorMatrixFilter;
+    public final int textColor;
+    public final int secondaryColor;
+
+    private IconPalette(int color) {
+        dominantColor = color;
+        backgroundColor = getMutedColor(dominantColor);
+        ColorMatrix backgroundColorMatrix = new ColorMatrix();
+        Themes.setColorScaleOnMatrix(backgroundColor, backgroundColorMatrix);
+        backgroundColorMatrixFilter = new ColorMatrixColorFilter(backgroundColorMatrix);
+        // Get slightly more saturated background color.
+        Themes.setColorScaleOnMatrix(getMutedColor(dominantColor, 0.54f), backgroundColorMatrix);
+        saturatedBackgroundColorMatrixFilter = new ColorMatrixColorFilter(backgroundColorMatrix);
+        textColor = getTextColorForBackground(backgroundColor);
+        secondaryColor = getLowContrastColor(backgroundColor);
+    }
+
+    /**
+     * Returns a color suitable for the progress bar color of preload icon.
+     */
+    public int getPreloadProgressColor(Context context) {
+        int result = dominantColor;
+
+        // Make sure that the dominant color has enough saturation to be visible properly.
+        float[] hsv = new float[3];
+        Color.colorToHSV(result, hsv);
+        if (hsv[1] < MIN_PRELOAD_COLOR_SATURATION) {
+            result = Themes.getColorAccent(context);
+        } else {
+            hsv[2] = Math.max(MIN_PRELOAD_COLOR_LIGHTNESS, hsv[2]);
+            result = Color.HSVToColor(hsv);
+        }
+        return result;
+    }
+
+    public static IconPalette fromDominantColor(int dominantColor) {
+        return new IconPalette(dominantColor);
+    }
+
+    /**
+     * Resolves a color such that it has enough contrast to be used as the
+     * color of an icon or text on the given background color.
+     *
+     * @return a color of the same hue with enough contrast against the background.
+     *
+     * This was copied from com.android.internal.util.NotificationColorUtil.
+     */
+    public static int resolveContrastColor(Context context, int color, int background) {
+        final int resolvedColor = resolveColor(context, color);
+
+        int contrastingColor = ensureTextContrast(resolvedColor, background);
+
+        if (contrastingColor != resolvedColor) {
+            if (DEBUG){
+                Log.w(TAG, String.format(
+                        "Enhanced contrast of notification for %s " +
+                                "%s (over background) by changing #%s to %s",
+                        context.getPackageName(),
+                        contrastChange(resolvedColor, contrastingColor, background),
+                        Integer.toHexString(resolvedColor), Integer.toHexString(contrastingColor)));
+            }
+        }
+        return contrastingColor;
+    }
+
+    /**
+     * Resolves {@param color} to an actual color if it is {@link Notification#COLOR_DEFAULT}
+     *
+     * This was copied from com.android.internal.util.NotificationColorUtil.
+     */
+    private static int resolveColor(Context context, int color) {
+        if (color == Notification.COLOR_DEFAULT) {
+            return context.getColor(R.color.notification_icon_default_color);
+        }
+        return color;
+    }
+
+    /** For debugging. This was copied from com.android.internal.util.NotificationColorUtil. */
+    private static String contrastChange(int colorOld, int colorNew, int bg) {
+        return String.format("from %.2f:1 to %.2f:1",
+                ColorUtils.calculateContrast(colorOld, bg),
+                ColorUtils.calculateContrast(colorNew, bg));
+    }
+
+    /**
+     * Finds a text color with sufficient contrast over bg that has the same hue as the original
+     * color.
+     *
+     * This was copied from com.android.internal.util.NotificationColorUtil.
+     */
+    private static int ensureTextContrast(int color, int bg) {
+        return findContrastColor(color, bg, true, 4.5);
+    }
+    /**
+     * Finds a suitable color such that there's enough contrast.
+     *
+     * @param color the color to start searching from.
+     * @param other the color to ensure contrast against. Assumed to be lighter than {@param color}
+     * @param findFg if true, we assume {@param color} is a foreground, otherwise a background.
+     * @param minRatio the minimum contrast ratio required.
+     * @return a color with the same hue as {@param color}, potentially darkened to meet the
+     *          contrast ratio.
+     *
+     * This was copied from com.android.internal.util.NotificationColorUtil.
+     */
+    private static int findContrastColor(int color, int other, boolean findFg, double minRatio) {
+        int fg = findFg ? color : other;
+        int bg = findFg ? other : color;
+        if (ColorUtils.calculateContrast(fg, bg) >= minRatio) {
+            return color;
+        }
+
+        double[] lab = new double[3];
+        ColorUtils.colorToLAB(findFg ? fg : bg, lab);
+
+        double low = 0, high = lab[0];
+        final double a = lab[1], b = lab[2];
+        for (int i = 0; i < 15 && high - low > 0.00001; i++) {
+            final double l = (low + high) / 2;
+            if (findFg) {
+                fg = ColorUtils.LABToColor(l, a, b);
+            } else {
+                bg = ColorUtils.LABToColor(l, a, b);
+            }
+            if (ColorUtils.calculateContrast(fg, bg) > minRatio) {
+                low = l;
+            } else {
+                high = l;
+            }
+        }
+        return ColorUtils.LABToColor(low, a, b);
+    }
+
+    private static int getMutedColor(int color) {
+        return getMutedColor(color, 0.87f);
+    }
+
+    private static int getMutedColor(int color, float whiteScrimAlpha) {
+        int whiteScrim = ColorUtils.setAlphaComponent(Color.WHITE, (int) (255 * whiteScrimAlpha));
+        return ColorUtils.compositeColors(whiteScrim, color);
+    }
+
+    private static int getTextColorForBackground(int backgroundColor) {
+        return getLighterOrDarkerVersionOfColor(backgroundColor, 4.5f);
+    }
+
+    private static int getLowContrastColor(int color) {
+        return getLighterOrDarkerVersionOfColor(color, 1.5f);
+    }
+
+    private static int getLighterOrDarkerVersionOfColor(int color, float contrastRatio) {
+        int whiteMinAlpha = ColorUtils.calculateMinimumAlpha(Color.WHITE, color, contrastRatio);
+        int blackMinAlpha = ColorUtils.calculateMinimumAlpha(Color.BLACK, color, contrastRatio);
+        int translucentWhiteOrBlack;
+        if (whiteMinAlpha >= 0) {
+            translucentWhiteOrBlack = ColorUtils.setAlphaComponent(Color.WHITE, whiteMinAlpha);
+        } else if (blackMinAlpha >= 0) {
+            translucentWhiteOrBlack = ColorUtils.setAlphaComponent(Color.BLACK, blackMinAlpha);
+        } else {
+            translucentWhiteOrBlack = Color.WHITE;
+        }
+        return ColorUtils.compositeColors(translucentWhiteOrBlack, color);
+    }
+}
diff --git a/src/com/android/launcher3/graphics/IconShapeOverride.java b/src/com/android/launcher3/graphics/IconShapeOverride.java
new file mode 100644
index 0000000..f78f8a5
--- /dev/null
+++ b/src/com/android/launcher3/graphics/IconShapeOverride.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.graphics;
+
+import android.annotation.TargetApi;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.os.Build;
+import android.os.SystemClock;
+import android.preference.ListPreference;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceChangeListener;
+import android.provider.Settings;
+import android.support.annotation.NonNull;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherFiles;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.util.LooperExecuter;
+
+import java.lang.reflect.Field;
+
+/**
+ * Utility class to override shape of {@link android.graphics.drawable.AdaptiveIconDrawable}.
+ */
+@TargetApi(Build.VERSION_CODES.O)
+public class IconShapeOverride {
+
+    private static final String TAG = "IconShapeOverride";
+
+    public static final String KEY_PREFERENCE = "pref_override_icon_shape";
+
+    // Time to wait before killing the process this ensures that the progress bar is visible for
+    // sufficient time so that there is no flicker.
+    private static final long PROCESS_KILL_DELAY_MS = 1000;
+
+    private static final int RESTART_REQUEST_CODE = 42; // the answer to everything
+
+    public static boolean isSupported(Context context) {
+        if (!Utilities.isAtLeastO()) {
+            return false;
+        }
+        // Only supported when developer settings is enabled
+        if (Settings.Global.getInt(context.getContentResolver(),
+                Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 1) {
+            return false;
+        }
+
+        try {
+            if (getSystemResField().get(null) != Resources.getSystem()) {
+                // Our assumption that mSystem is the system resource is not true.
+                return false;
+            }
+        } catch (Exception e) {
+            // Ignore, not supported
+            return false;
+        }
+
+        return getConfigResId() != 0;
+    }
+
+    public static void apply(Context context) {
+        if (!Utilities.isAtLeastO()) {
+            return;
+        }
+        String path = getAppliedValue(context);
+        if (TextUtils.isEmpty(path)) {
+            return;
+        }
+        if (!isSupported(context)) {
+            return;
+        }
+
+        // magic
+        try {
+            Resources override =
+                    new ResourcesOverride(Resources.getSystem(), getConfigResId(), path);
+            getSystemResField().set(null, override);
+        } catch (Exception e) {
+            Log.e(TAG, "Unable to override icon shape", e);
+            // revert value.
+            prefs(context).edit().remove(KEY_PREFERENCE).apply();
+        }
+    }
+
+    private static Field getSystemResField() throws Exception {
+        Field staticField = Resources.class.getDeclaredField("mSystem");
+        staticField.setAccessible(true);
+        return staticField;
+    }
+
+    private static int getConfigResId() {
+        return Resources.getSystem().getIdentifier("config_icon_mask", "string", "android");
+    }
+
+    private static String getAppliedValue(Context context) {
+        return prefs(context).getString(KEY_PREFERENCE, "");
+    }
+
+    private static SharedPreferences prefs(Context context) {
+        return context.getSharedPreferences(LauncherFiles.DEVICE_PREFERENCES_KEY, 0);
+    }
+
+    public static void handlePreferenceUi(ListPreference preference) {
+        Context context = preference.getContext();
+        preference.setValue(getAppliedValue(context));
+        preference.setOnPreferenceChangeListener(new PreferenceChangeHandler(context));
+    }
+
+    private static class ResourcesOverride extends Resources {
+
+        private final int mOverrideId;
+        private final String mOverrideValue;
+
+        @SuppressWarnings("deprecation")
+        public ResourcesOverride(Resources parent, int overrideId, String overrideValue) {
+            super(parent.getAssets(), parent.getDisplayMetrics(), parent.getConfiguration());
+            mOverrideId = overrideId;
+            mOverrideValue = overrideValue;
+        }
+
+        @NonNull
+        @Override
+        public String getString(int id) throws NotFoundException {
+            if (id == mOverrideId) {
+                return mOverrideValue;
+            }
+            return super.getString(id);
+        }
+    }
+
+    private static class PreferenceChangeHandler implements OnPreferenceChangeListener {
+
+        private final Context mContext;
+
+        private PreferenceChangeHandler(Context context) {
+            mContext = context;
+        }
+
+        @Override
+        public boolean onPreferenceChange(Preference preference, Object o) {
+            String newValue = (String) o;
+            if (!getAppliedValue(mContext).equals(newValue)) {
+                // Value has changed
+                ProgressDialog.show(mContext,
+                        null /* title */,
+                        mContext.getString(R.string.icon_shape_override_progress),
+                        true /* indeterminate */,
+                        false /* cancelable */);
+                new LooperExecuter(LauncherModel.getWorkerLooper()).execute(
+                        new OverrideApplyHandler(mContext, newValue));
+            }
+            return false;
+        }
+    }
+
+    private static class OverrideApplyHandler implements Runnable {
+
+        private final Context mContext;
+        private final String mValue;
+
+        private OverrideApplyHandler(Context context, String value) {
+            mContext = context;
+            mValue = value;
+        }
+
+        @Override
+        public void run() {
+            // Synchronously write the preference.
+            prefs(mContext).edit().putString(KEY_PREFERENCE, mValue).commit();
+            // Clear the icon cache.
+            LauncherAppState.getInstance(mContext).getIconCache().clear();
+
+            // Wait for it
+            try {
+                Thread.sleep(PROCESS_KILL_DELAY_MS);
+            } catch (Exception e) {
+                Log.e(TAG, "Error waiting", e);
+            }
+
+            // Schedule an alarm before we kill ourself.
+            Intent homeIntent = new Intent(Intent.ACTION_MAIN)
+                    .addCategory(Intent.CATEGORY_HOME)
+                    .setPackage(mContext.getPackageName())
+                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            PendingIntent pi = PendingIntent.getActivity(mContext, RESTART_REQUEST_CODE,
+                    homeIntent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
+            mContext.getSystemService(AlarmManager.class).setExact(
+                    AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + 50, pi);
+
+            // Kill process
+            android.os.Process.killProcess(android.os.Process.myPid());
+        }
+    }
+}
diff --git a/src/com/android/launcher3/graphics/LauncherIcons.java b/src/com/android/launcher3/graphics/LauncherIcons.java
new file mode 100644
index 0000000..53521f2
--- /dev/null
+++ b/src/com/android/launcher3/graphics/LauncherIcons.java
@@ -0,0 +1,373 @@
+/*
+ * 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.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.Intent.ShortcutIconResource;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PaintFlagsDrawFilter;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.PaintDrawable;
+import android.os.Build;
+import android.os.Process;
+import android.os.UserHandle;
+
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.IconCache;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.model.PackageItemInfo;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+
+/**
+ * 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));
+    }
+
+    /**
+     * 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(ShortcutIconResource iconRes, Context context) {
+        PackageManager packageManager = context.getPackageManager();
+        // the resource
+        try {
+            Resources resources = packageManager.getResourcesForApplication(iconRes.packageName);
+            if (resources != null) {
+                final int id = resources.getIdentifier(iconRes.resourceName, null, null);
+                return createIconBitmap(resources.getDrawableForDensity(
+                        id, LauncherAppState.getIDP(context).fillResIconDpi), context);
+            }
+        } catch (Exception e) {
+            // Icon not found.
+        }
+        return null;
+    }
+
+    /**
+     * 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 = LauncherAppState.getIDP(context).iconBitmapSize;
+        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.
+     */
+    public static Bitmap createBadgedIconBitmap(
+            Drawable icon, UserHandle user, Context context, int iconAppTargetSdk) {
+
+        IconNormalizer normalizer;
+        float scale = 1f;
+        if (!FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION) {
+            normalizer = IconNormalizer.getInstance(context);
+            if (Utilities.isAtLeastO() && iconAppTargetSdk >= Build.VERSION_CODES.O) {
+                boolean[] outShape = new boolean[1];
+                AdaptiveIconDrawable dr = (AdaptiveIconDrawable)
+                        context.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate();
+                dr.setBounds(0, 0, 1, 1);
+                scale = normalizer.getScale(icon, null, dr.getIconMask(), outShape);
+                if (FeatureFlags.LEGACY_ICON_TREATMENT &&
+                        !outShape[0]){
+                    Drawable wrappedIcon = wrapToAdaptiveIconDrawable(context, icon, scale);
+                    if (wrappedIcon != icon) {
+                        icon = wrappedIcon;
+                        scale = normalizer.getScale(icon, null, null, null);
+                    }
+                }
+            } else {
+                scale = normalizer.getScale(icon, null, null, null);
+            }
+        }
+        Bitmap bitmap = createIconBitmap(icon, context, scale);
+        if (FeatureFlags.ADAPTIVE_ICON_SHADOW && Utilities.isAtLeastO() &&
+                icon instanceof AdaptiveIconDrawable) {
+            bitmap = ShadowGenerator.getInstance(context).recreateIcon(bitmap);
+        }
+        return badgeIconForUser(bitmap, user, context);
+    }
+
+    /**
+     * Badges the provided icon with the user badge if required.
+     */
+    public static Bitmap badgeIconForUser(Bitmap icon, UserHandle user, Context context) {
+        if (user != null && !Process.myUserHandle().equals(user)) {
+            BitmapDrawable drawable = new FixedSizeBitmapDrawable(icon);
+            Drawable badged = context.getPackageManager().getUserBadgedIcon(
+                    drawable, user);
+            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, int iconAppTargetSdk) {
+        RectF iconBounds = new RectF();
+        IconNormalizer normalizer;
+        float scale = 1f;
+        if (!FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION) {
+            normalizer = IconNormalizer.getInstance(context);
+            if (Utilities.isAtLeastO() && iconAppTargetSdk >= Build.VERSION_CODES.O) {
+                boolean[] outShape = new boolean[1];
+                AdaptiveIconDrawable dr = (AdaptiveIconDrawable)
+                        context.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate();
+                dr.setBounds(0, 0, 1, 1);
+                scale = normalizer.getScale(icon, iconBounds, dr.getIconMask(), outShape);
+                if (Utilities.isAtLeastO() && FeatureFlags.LEGACY_ICON_TREATMENT &&
+                        !outShape[0]) {
+                    Drawable wrappedIcon = wrapToAdaptiveIconDrawable(context, icon, scale);
+                    if (wrappedIcon != icon) {
+                        icon = wrappedIcon;
+                        scale = normalizer.getScale(icon, iconBounds, null, null);
+                    }
+                }
+            } else {
+                scale = normalizer.getScale(icon, iconBounds, null, null);
+            }
+
+        }
+        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, int)}
+     */
+    public static Bitmap addShadowToIcon(Bitmap icon, Context context) {
+        return ShadowGenerator.getInstance(context).recreateIcon(icon);
+    }
+
+    /**
+     * Adds the {@param badge} on top of {@param srcTgt} using the badge dimensions.
+     */
+    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) {
+        float scale = 1f;
+        if (FeatureFlags.ADAPTIVE_ICON_SHADOW && Utilities.isAtLeastO() &&
+                icon instanceof AdaptiveIconDrawable) {
+            scale = ShadowGenerator.getScaleForBounds(new RectF(0, 0, 0, 0));
+        }
+        Bitmap bitmap =  createIconBitmap(icon, context, scale);
+        if (FeatureFlags.ADAPTIVE_ICON_SHADOW && Utilities.isAtLeastO() &&
+                icon instanceof AdaptiveIconDrawable) {
+            bitmap = ShadowGenerator.getInstance(context).recreateIcon(bitmap);
+        }
+        return bitmap;
+    }
+
+    /**
+     * @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 = LauncherAppState.getIDP(context).iconBitmapSize;
+            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;
+
+            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());
+            if (Utilities.isAtLeastO() && icon instanceof AdaptiveIconDrawable) {
+                int offset = Math.min(left, top);
+                int size = Math.max(width, height);
+                icon.setBounds(offset, offset, offset + size, offset + size);
+            } else {
+                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;
+        }
+    }
+
+    /**
+     * If the platform is running O but the app is not providing AdaptiveIconDrawable, then
+     * shrink the legacy icon and set it as foreground. Use color drawable as background to
+     * create AdaptiveIconDrawable.
+     */
+    static Drawable wrapToAdaptiveIconDrawable(Context context, Drawable drawable, float scale) {
+        if (!(FeatureFlags.LEGACY_ICON_TREATMENT && Utilities.isAtLeastO())) {
+            return drawable;
+        }
+
+        try {
+            if (!(drawable instanceof AdaptiveIconDrawable)) {
+                AdaptiveIconDrawable iconWrapper = (AdaptiveIconDrawable)
+                        context.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate();
+                FixedScaleDrawable fsd = ((FixedScaleDrawable) iconWrapper.getForeground());
+                fsd.setDrawable(drawable);
+                fsd.setScale(scale);
+                return (Drawable) iconWrapper;
+            }
+        } catch (Exception e) {
+            return drawable;
+        }
+        return drawable;
+    }
+
+    public static Bitmap createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context) {
+        return createShortcutIcon(shortcutInfo, context, true /* badged */);
+    }
+
+    public static Bitmap createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context,
+            boolean badged) {
+        LauncherAppState app = LauncherAppState.getInstance(context);
+        Drawable unbadgedDrawable = DeepShortcutManager.getInstance(context)
+                .getShortcutIconDrawable(shortcutInfo,
+                        app.getInvariantDeviceProfile().fillResIconDpi);
+        IconCache cache = app.getIconCache();
+        Bitmap unbadgedBitmap = unbadgedDrawable == null
+                ? cache.getDefaultIcon(Process.myUserHandle())
+                : LauncherIcons.createScaledBitmapWithoutShadow(unbadgedDrawable, context,
+                Build.VERSION_CODES.O);
+
+        if (!badged) {
+            return unbadgedBitmap;
+        }
+        unbadgedBitmap = LauncherIcons.addShadowToIcon(unbadgedBitmap, context);
+
+        final Bitmap badgeBitmap;
+        ComponentName cn = shortcutInfo.getActivity();
+        if (cn != null) {
+            // Get the app info for the source activity.
+            AppInfo appInfo = new AppInfo();
+            appInfo.user = shortcutInfo.getUserHandle();
+            appInfo.componentName = cn;
+            appInfo.intent = new Intent(Intent.ACTION_MAIN)
+                    .addCategory(Intent.CATEGORY_LAUNCHER)
+                    .setComponent(cn);
+            cache.getTitleAndIcon(appInfo, false);
+            badgeBitmap = appInfo.iconBitmap;
+        } else {
+            PackageItemInfo pkgInfo = new PackageItemInfo(shortcutInfo.getPackage());
+            cache.getTitleAndIconForApp(pkgInfo, false);
+            badgeBitmap = pkgInfo.iconBitmap;
+        }
+        return badgeWithBitmap(unbadgedBitmap, badgeBitmap, context);
+    }
+
+    /**
+     * 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/graphics/PreloadIconDrawable.java b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
new file mode 100644
index 0000000..22ce098
--- /dev/null
+++ b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
@@ -0,0 +1,296 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.android.launcher3.graphics;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PathMeasure;
+import android.graphics.Rect;
+import android.util.Property;
+import android.util.SparseArray;
+import android.view.animation.LinearInterpolator;
+
+import com.android.launcher3.FastBitmapDrawable;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Extension of {@link FastBitmapDrawable} which shows a progress bar around the icon.
+ */
+public class PreloadIconDrawable extends FastBitmapDrawable {
+
+    private static final Property<PreloadIconDrawable, Float> INTERNAL_STATE =
+            new Property<PreloadIconDrawable, Float>(Float.TYPE, "internalStateProgress") {
+                @Override
+                public Float get(PreloadIconDrawable object) {
+                    return object.mInternalStateProgress;
+                }
+
+                @Override
+                public void set(PreloadIconDrawable object, Float value) {
+                    object.setInternalProgress(value);
+                }
+            };
+
+    public static final int PATH_SIZE = 100;
+
+    private static final float PROGRESS_WIDTH = 7;
+    private static final float PROGRESS_GAP = 2;
+    private static final int MAX_PAINT_ALPHA = 255;
+
+    private static final long DURATION_SCALE = 500;
+
+    // The smaller the number, the faster the animation would be.
+    // Duration = COMPLETE_ANIM_FRACTION * DURATION_SCALE
+    private static final float COMPLETE_ANIM_FRACTION = 0.3f;
+
+    private static final int COLOR_TRACK = 0x77EEEEEE;
+    private static final int COLOR_SHADOW = 0x55000000;
+
+    private static final float SMALL_SCALE = 0.75f;
+
+    private static final SparseArray<WeakReference<Bitmap>> sShadowCache = new SparseArray<>();
+
+    private final Matrix mTmpMatrix = new Matrix();
+    private final PathMeasure mPathMeasure = new PathMeasure();
+
+    private final Context mContext;
+
+    // Path in [0, 100] bounds.
+    private final Path mProgressPath;
+
+    private final Path mScaledTrackPath;
+    private final Path mScaledProgressPath;
+    private final Paint mProgressPaint;
+
+    private Bitmap mShadowBitmap;
+    private int mIndicatorColor = 0;
+
+    private int mTrackAlpha;
+    private float mTrackLength;
+    private float mIconScale;
+
+    private boolean mRanFinishAnimation;
+
+    // Progress of the internal state. [0, 1] indicates the fraction of completed progress,
+    // [1, (1 + COMPLETE_ANIM_FRACTION)] indicates the progress of zoom animation.
+    private float mInternalStateProgress;
+
+    private ObjectAnimator mCurrentAnim;
+
+    /**
+     * @param progressPath fixed path in the bounds [0, 0, 100, 100] representing a progress bar.
+     */
+    public PreloadIconDrawable(Bitmap b, Path progressPath, Context context) {
+        super(b);
+        mContext = context;
+        mProgressPath = progressPath;
+        mScaledTrackPath = new Path();
+        mScaledProgressPath = new Path();
+
+        mProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+        mProgressPaint.setStyle(Paint.Style.STROKE);
+        mProgressPaint.setStrokeCap(Paint.Cap.ROUND);
+
+        setInternalProgress(0);
+    }
+
+    @Override
+    protected void onBoundsChange(Rect bounds) {
+        super.onBoundsChange(bounds);
+        mTmpMatrix.setScale(
+                (bounds.width() - PROGRESS_WIDTH - 2 * PROGRESS_GAP) / PATH_SIZE,
+                (bounds.height() - PROGRESS_WIDTH - 2 * PROGRESS_GAP) / PATH_SIZE);
+        mTmpMatrix.postTranslate(
+                bounds.left + PROGRESS_WIDTH / 2 + PROGRESS_GAP,
+                bounds.top + PROGRESS_WIDTH / 2 + PROGRESS_GAP);
+
+        mProgressPath.transform(mTmpMatrix, mScaledTrackPath);
+        float scale = bounds.width() / PATH_SIZE;
+        mProgressPaint.setStrokeWidth(PROGRESS_WIDTH * scale);
+
+        mShadowBitmap = getShadowBitmap(bounds.width(), bounds.height(),
+                (PROGRESS_GAP ) * scale);
+        mPathMeasure.setPath(mScaledTrackPath, true);
+        mTrackLength = mPathMeasure.getLength();
+
+        setInternalProgress(mInternalStateProgress);
+    }
+
+    private Bitmap getShadowBitmap(int width, int height, float shadowRadius) {
+        int key = (width << 16) | height;
+        WeakReference<Bitmap> shadowRef = sShadowCache.get(key);
+        Bitmap shadow = shadowRef != null ? shadowRef.get() : null;
+        if (shadow != null) {
+            return shadow;
+        }
+        shadow = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        Canvas c = new Canvas(shadow);
+        mProgressPaint.setShadowLayer(shadowRadius, 0, 0, COLOR_SHADOW);
+        mProgressPaint.setColor(COLOR_TRACK);
+        mProgressPaint.setAlpha(MAX_PAINT_ALPHA);
+        c.drawPath(mScaledTrackPath, mProgressPaint);
+        mProgressPaint.clearShadowLayer();
+        c.setBitmap(null);
+
+        sShadowCache.put(key, new WeakReference<>(shadow));
+        return shadow;
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        if (mRanFinishAnimation) {
+            super.draw(canvas);
+            return;
+        }
+
+        // Draw track.
+        mProgressPaint.setColor(mIndicatorColor);
+        mProgressPaint.setAlpha(mTrackAlpha);
+        if (mShadowBitmap != null) {
+            canvas.drawBitmap(mShadowBitmap, getBounds().left, getBounds().top, mProgressPaint);
+        }
+        canvas.drawPath(mScaledProgressPath, mProgressPaint);
+
+        int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
+        Rect bounds = getBounds();
+
+        canvas.scale(mIconScale, mIconScale, bounds.exactCenterX(), bounds.exactCenterY());
+        drawInternal(canvas);
+        canvas.restoreToCount(saveCount);
+    }
+
+    /**
+     * Updates the install progress based on the level
+     */
+    @Override
+    protected boolean onLevelChange(int level) {
+        // Run the animation if we have already been bound.
+        updateInternalState(level * 0.01f,  getBounds().width() > 0, false);
+        return true;
+    }
+
+    /**
+     * Runs the finish animation if it is has not been run after last call to
+     * {@link #onLevelChange}
+     */
+    public void maybePerformFinishedAnimation() {
+        // If the drawable was recently initialized, skip the progress animation.
+        if (mInternalStateProgress == 0) {
+            mInternalStateProgress = 1;
+        }
+        updateInternalState(1 + COMPLETE_ANIM_FRACTION, true, true);
+    }
+
+    public boolean hasNotCompleted() {
+        return !mRanFinishAnimation;
+    }
+
+    private void updateInternalState(float finalProgress, boolean shouldAnimate, boolean isFinish) {
+        if (mCurrentAnim != null) {
+            mCurrentAnim.cancel();
+            mCurrentAnim = null;
+        }
+
+        if (Float.compare(finalProgress, mInternalStateProgress) == 0) {
+            return;
+        }
+        if (finalProgress < mInternalStateProgress) {
+            shouldAnimate = false;
+        }
+        if (!shouldAnimate || mRanFinishAnimation) {
+            setInternalProgress(finalProgress);
+        } else {
+            mCurrentAnim = ObjectAnimator.ofFloat(this, INTERNAL_STATE, finalProgress);
+            mCurrentAnim.setDuration(
+                    (long) ((finalProgress - mInternalStateProgress) * DURATION_SCALE));
+            mCurrentAnim.setInterpolator(new LinearInterpolator());
+            if (isFinish) {
+                mCurrentAnim.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        mRanFinishAnimation = true;
+                    }
+                });
+            }
+            mCurrentAnim.start();
+        }
+
+    }
+
+    /**
+     * Sets the internal progress and updates the UI accordingly
+     *   for progress <= 0:
+     *     - icon in the small scale and disabled state
+     *     - progress track is visible
+     *     - progress bar is not visible
+     *   for 0 < progress < 1
+     *     - icon in the small scale and disabled state
+     *     - progress track is visible
+     *     - progress bar is visible with dominant color. Progress bar is drawn as a fraction of
+     *       {@link #mScaledTrackPath}.
+     *       @see PathMeasure#getSegment(float, float, Path, boolean)
+     *   for 1 <= progress < (1 + COMPLETE_ANIM_FRACTION)
+     *     - we calculate fraction of progress in the above range
+     *     - progress track is drawn with alpha based on fraction
+     *     - progress bar is drawn at 100% with alpha based on fraction
+     *     - icon is scaled up based on fraction and is drawn in enabled state
+     *   for progress >= (1 + COMPLETE_ANIM_FRACTION)
+     *     - only icon is drawn in normal state
+     */
+    private void setInternalProgress(float progress) {
+        mInternalStateProgress = progress;
+        if (progress <= 0) {
+            mIconScale = SMALL_SCALE;
+            mScaledTrackPath.reset();
+            mTrackAlpha = MAX_PAINT_ALPHA;
+            setIsDisabled(true);
+        } else if (mIndicatorColor == 0) {
+            // Update the indicator color
+            mIndicatorColor = getIconPalette().getPreloadProgressColor(mContext);
+        }
+
+        if (progress < 1 && progress > 0) {
+            mPathMeasure.getSegment(0, progress * mTrackLength, mScaledProgressPath, true);
+            mIconScale = SMALL_SCALE;
+            mTrackAlpha = MAX_PAINT_ALPHA;
+            setIsDisabled(true);
+        } else if (progress >= 1) {
+            setIsDisabled(false);
+            mScaledTrackPath.set(mScaledProgressPath);
+            float fraction = (progress - 1) / COMPLETE_ANIM_FRACTION;
+
+            if (fraction >= 1) {
+                // Animation has completed
+                mIconScale = 1;
+                mTrackAlpha = 0;
+            } else {
+                mTrackAlpha = Math.round((1 - fraction) * MAX_PAINT_ALPHA);
+                mIconScale = SMALL_SCALE + (1 - SMALL_SCALE) * fraction;
+            }
+        }
+        invalidateSelf();
+    }
+}
diff --git a/src/com/android/launcher3/graphics/ShadowGenerator.java b/src/com/android/launcher3/graphics/ShadowGenerator.java
index 2b24ec9..469fe34 100644
--- a/src/com/android/launcher3/graphics/ShadowGenerator.java
+++ b/src/com/android/launcher3/graphics/ShadowGenerator.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.graphics;
 
+import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.Config;
 import android.graphics.BlurMaskFilter;
@@ -38,9 +39,9 @@
 
     // Percent of actual icon size
     private static final float KEY_SHADOW_DISTANCE = 1f/48;
-    private static final int KEY_SHADOW_ALPHA = 61;
+    public static final int KEY_SHADOW_ALPHA = 61;
 
-    private static final int AMBIENT_SHADOW_ALPHA = 30;
+    public static final int AMBIENT_SHADOW_ALPHA = 30;
 
     private static final Object LOCK = new Object();
     // Singleton object guarded by {@link #LOCK}
@@ -52,8 +53,8 @@
     private final Paint mBlurPaint;
     private final Paint mDrawPaint;
 
-    private ShadowGenerator() {
-        mIconSize = LauncherAppState.getInstance().getInvariantDeviceProfile().iconBitmapSize;
+    private ShadowGenerator(Context context) {
+        mIconSize = LauncherAppState.getIDP(context).iconBitmapSize;
         mCanvas = new Canvas();
         mBlurPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
         mBlurPaint.setMaskFilter(new BlurMaskFilter(mIconSize * BLUR_FACTOR, Blur.NORMAL));
@@ -82,11 +83,51 @@
         return result;
     }
 
-    public static ShadowGenerator getInstance() {
+    public static Bitmap createPillWithShadow(int rectColor, int width, int height) {
+
+        float shadowRadius = height * 1f / 32;
+        float shadowYOffset = height * 1f / 16;
+
+        int radius = height / 2;
+
+        Canvas canvas = new Canvas();
+        Paint blurPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+        blurPaint.setMaskFilter(new BlurMaskFilter(shadowRadius, Blur.NORMAL));
+
+        int centerX = Math.round(width / 2 + shadowRadius);
+        int centerY = Math.round(radius + shadowRadius + shadowYOffset);
+        int center = Math.max(centerX, centerY);
+        int size = center * 2;
+        Bitmap result = Bitmap.createBitmap(size, size, Config.ARGB_8888);
+        canvas.setBitmap(result);
+
+        int left = center - width / 2;
+        int top = center - height / 2;
+        int right = center + width / 2;
+        int bottom = center + height / 2;
+
+        // Draw ambient shadow, center aligned within size
+        blurPaint.setAlpha(AMBIENT_SHADOW_ALPHA);
+        canvas.drawRoundRect(left, top, right, bottom, radius, radius, blurPaint);
+
+        // Draw key shadow, bottom aligned within size
+        blurPaint.setAlpha(KEY_SHADOW_ALPHA);
+        canvas.drawRoundRect(left, top + shadowYOffset, right, bottom + shadowYOffset,
+                radius, radius, blurPaint);
+
+        // Draw the circle
+        Paint drawPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+        drawPaint.setColor(rectColor);
+        canvas.drawRoundRect(left, top, right, bottom, radius, radius, drawPaint);
+
+        return result;
+    }
+
+    public static ShadowGenerator getInstance(Context context) {
         Preconditions.assertNonUiThread();
         synchronized (LOCK) {
             if (sShadowGenerator == null) {
-                sShadowGenerator = new ShadowGenerator();
+                sShadowGenerator = new ShadowGenerator(context);
             }
         }
         return sShadowGenerator;
diff --git a/src/com/android/launcher3/keyboard/CustomActionsPopup.java b/src/com/android/launcher3/keyboard/CustomActionsPopup.java
new file mode 100644
index 0000000..bb0b58a
--- /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.popup.PopupContainerWithArrow;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
+
+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;
+        PopupContainerWithArrow container = PopupContainerWithArrow.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..c07ab08 100644
--- a/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java
+++ b/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java
@@ -23,12 +23,10 @@
 import android.animation.RectEvaluator;
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.annotation.TargetApi;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.Rect;
-import android.os.Build.VERSION_CODES;
 import android.util.Property;
 import android.view.View;
 import android.view.View.OnFocusChangeListener;
@@ -38,7 +36,6 @@
 /**
  * A helper class to draw background of a focused view.
  */
-@TargetApi(VERSION_CODES.LOLLIPOP)
 public abstract class FocusIndicatorHelper implements
         OnFocusChangeListener, AnimatorUpdateListener {
 
@@ -143,7 +140,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/DumpTargetWrapper.java b/src/com/android/launcher3/logging/DumpTargetWrapper.java
new file mode 100644
index 0000000..365e8f2
--- /dev/null
+++ b/src/com/android/launcher3/logging/DumpTargetWrapper.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.logging;
+
+import android.os.Process;
+import android.text.TextUtils;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.model.nano.LauncherDumpProto;
+import com.android.launcher3.model.nano.LauncherDumpProto.ContainerType;
+import com.android.launcher3.model.nano.LauncherDumpProto.DumpTarget;
+import com.android.launcher3.model.nano.LauncherDumpProto.ItemType;
+import com.android.launcher3.model.nano.LauncherDumpProto.UserType;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class can be used when proto definition doesn't support nesting.
+ */
+public class DumpTargetWrapper {
+    DumpTarget node;
+    ArrayList<DumpTargetWrapper> children;
+
+    public DumpTargetWrapper() {
+        children = new ArrayList<>();
+    }
+
+    public DumpTargetWrapper(int containerType, int id) {
+        this();
+        node = newContainerTarget(containerType, id);
+    }
+
+    public DumpTargetWrapper(ItemInfo info) {
+        this();
+        node = newItemTarget(info);
+    }
+
+    public DumpTarget getDumpTarget() {
+        return node;
+    }
+
+    public void add(DumpTargetWrapper child) {
+        children.add(child);
+    }
+
+    public List<DumpTarget> getFlattenedList() {
+        ArrayList<DumpTarget> list = new ArrayList<>();
+        list.add(node);
+        if (!children.isEmpty()) {
+            for(DumpTargetWrapper t: children) {
+                list.addAll(t.getFlattenedList());
+            }
+            list.add(node); // add a delimiter empty object
+        }
+        return list;
+    }
+    public DumpTarget newItemTarget(ItemInfo info) {
+        DumpTarget dt = new DumpTarget();
+        dt.type = DumpTarget.Type.ITEM;
+
+        switch (info.itemType) {
+            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
+                dt.itemType = ItemType.APP_ICON;
+                break;
+            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+                dt.itemType = ItemType.UNKNOWN_ITEMTYPE;
+                break;
+            case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
+                dt.itemType = ItemType.WIDGET;
+                break;
+            case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
+                dt.itemType = ItemType.SHORTCUT;
+                break;
+        }
+        return dt;
+    }
+
+    public DumpTarget newContainerTarget(int type, int id) {
+        DumpTarget dt = new DumpTarget();
+        dt.type = DumpTarget.Type.CONTAINER;
+        dt.containerType = type;
+        dt.pageId = id;
+        return dt;
+    }
+
+    public static String getDumpTargetStr(DumpTarget t) {
+        if (t == null){
+            return "";
+        }
+        switch (t.type) {
+            case LauncherDumpProto.DumpTarget.Type.ITEM:
+                return getItemStr(t);
+            case LauncherDumpProto.DumpTarget.Type.CONTAINER:
+                String str = LoggerUtils.getFieldName(t.containerType, ContainerType.class);
+                if (t.containerType == ContainerType.WORKSPACE) {
+                    str += " id=" + t.pageId;
+                } else if (t.containerType == ContainerType.FOLDER) {
+                    str += " grid(" + t.gridX + "," + t.gridY+ ")";
+                }
+                return str;
+            default:
+                return "UNKNOWN TARGET TYPE";
+        }
+    }
+
+    private static String getItemStr(DumpTarget t) {
+        String typeStr = LoggerUtils.getFieldName(t.itemType, ItemType.class);
+        if (!TextUtils.isEmpty(t.packageName)) {
+            typeStr += ", package=" + t.packageName;
+        }
+        if (!TextUtils.isEmpty(t.component)) {
+            typeStr += ", component=" + t.component;
+        }
+        return typeStr + ", grid(" + t.gridX + "," + t.gridY + "), span(" + t.spanX + "," + t.spanY
+                + "), pageIdx=" + t.pageId + " user=" + t.userType;
+    }
+
+    public DumpTarget writeToDumpTarget(ItemInfo info) {
+        node.component = info.getTargetComponent() == null? "":
+                info.getTargetComponent().flattenToString();
+        node.packageName = info.getTargetComponent() == null? "":
+                info.getTargetComponent().getPackageName();
+        if (info instanceof LauncherAppWidgetInfo) {
+            node.component = ((LauncherAppWidgetInfo) info).providerName.flattenToString();
+            node.packageName = ((LauncherAppWidgetInfo) info).providerName.getPackageName();
+        }
+
+        node.gridX = info.cellX;
+        node.gridY = info.cellY;
+        node.spanX = info.spanX;
+        node.spanY = info.spanY;
+        node.userType = (info.user.equals(Process.myUserHandle()))? UserType.DEFAULT : UserType.WORK;
+        return node;
+    }
+}
diff --git a/src/com/android/launcher3/logging/FileLog.java b/src/com/android/launcher3/logging/FileLog.java
index 8629e92..ffb41b7 100644
--- a/src/com/android/launcher3/logging/FileLog.java
+++ b/src/com/android/launcher3/logging/FileLog.java
@@ -40,7 +40,7 @@
     private static File sLogsDirectory = null;
 
     public static void setDir(File logsDir) {
-        if (ProviderConfig.IS_DOGFOOD_BUILD) {
+        if (ProviderConfig.IS_DOGFOOD_BUILD || Utilities.IS_DEBUG_DEVICE) {
             synchronized (DATE_FORMAT) {
                 // If the target directory changes, stop any active thread.
                 if (sHandler != null && !logsDir.equals(sLogsDirectory)) {
diff --git a/src/com/android/launcher3/logging/LoggerUtils.java b/src/com/android/launcher3/logging/LoggerUtils.java
index c2b97eb..499fdc7 100644
--- a/src/com/android/launcher3/logging/LoggerUtils.java
+++ b/src/com/android/launcher3/logging/LoggerUtils.java
@@ -1,5 +1,23 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 package com.android.launcher3.logging;
 
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.SparseArray;
 import android.view.View;
 
 import com.android.launcher3.ButtonDropTarget;
@@ -8,63 +26,78 @@
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.UninstallDropTarget;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
+import com.android.launcher3.userevent.nano.LauncherLogProto.LauncherEvent;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+
 /**
- * Debugging helper methods.
- * toString() cannot be overriden inside auto generated {@link LauncherLogProto}.
- * Note: switch statement cannot be replaced with reflection as proguard strips the constants
+ * Helper methods for logging.
  */
 public class LoggerUtils {
-    private static final String TAG = "LoggerUtils";
+    private static final ArrayMap<Class, SparseArray<String>> sNameCache = new ArrayMap<>();
+    private static final String UNKNOWN = "UNKNOWN";
 
-    public static String getActionStr(LauncherLogProto.Action action) {
-        switch(action.touch) {
-            case Action.TAP: return "TAP";
-            case Action.LONGPRESS: return "LONGPRESS";
-            case Action.DRAGDROP: return "DRAGDROP";
-            case Action.PINCH: return "PINCH";
-            case Action.SWIPE: return "SWIPE";
-            case Action.FLING: return "FLING";
-            default: return "UNKNOWN";
+    public static String getFieldName(int value, Class c) {
+        SparseArray<String> cache;
+        synchronized (sNameCache) {
+            cache = sNameCache.get(c);
+            if (cache == null) {
+                cache = new SparseArray<>();
+                for (Field f : c.getDeclaredFields()) {
+                    if (f.getType() == int.class && Modifier.isStatic(f.getModifiers())) {
+                        try {
+                            f.setAccessible(true);
+                            cache.put(f.getInt(null), f.getName());
+                        } catch (IllegalAccessException e) {
+                            // Ignore
+                        }
+                    }
+                }
+                sNameCache.put(c, cache);
+            }
+        }
+        String result = cache.get(value);
+        return result != null ? result : UNKNOWN;
+    }
+
+    public static String getActionStr(Action action) {
+        switch (action.type) {
+            case Action.Type.TOUCH: return getFieldName(action.touch, Action.Touch.class);
+            case Action.Type.COMMAND: return getFieldName(action.command, Action.Command.class);
+            default: return UNKNOWN;
         }
     }
 
     public static String getTargetStr(Target t) {
-        String typeStr = "";
         if (t == null){
-            return typeStr;
+            return "";
         }
         switch (t.type) {
-            case Target.ITEM:
+            case Target.Type.ITEM:
                 return getItemStr(t);
-            case Target.CONTROL:
-                return getControlStr(t);
-            case Target.CONTAINER:
-                return getContainerStr(t);
+            case Target.Type.CONTROL:
+                return getFieldName(t.controlType, ControlType.class);
+            case Target.Type.CONTAINER:
+                String str = getFieldName(t.containerType, ContainerType.class);
+                if (t.containerType == ContainerType.WORKSPACE) {
+                    str += " id=" + t.pageIndex;
+                } else if (t.containerType == ContainerType.FOLDER) {
+                    str += " grid(" + t.gridX + "," + t.gridY+ ")";
+                }
+                return str;
             default:
                 return "UNKNOWN TARGET TYPE";
         }
     }
 
     private static String getItemStr(Target t) {
-        String typeStr = "";
-        if (t == null){
-            return typeStr;
-        }
-        switch(t.itemType){
-            case LauncherLogProto.APP_ICON: typeStr = "APPICON"; break;
-            case LauncherLogProto.SHORTCUT: typeStr = "SHORTCUT"; break;
-            case LauncherLogProto.WIDGET: typeStr = "WIDGET"; break;
-            case LauncherLogProto.DEEPSHORTCUT: typeStr = "DEEPSHORTCUT"; break;
-            case LauncherLogProto.FOLDER_ICON: typeStr = "FOLDERICON"; break;
-            case LauncherLogProto.SEARCHBOX: typeStr = "SEARCHBOX"; break;
-
-            default: typeStr = "UNKNOWN";
-        }
-
+        String typeStr = getFieldName(t.itemType, ItemType.class);
         if (t.packageNameHash != 0) {
             typeStr += ", packageHash=" + t.packageNameHash;
         }
@@ -74,175 +107,84 @@
         if (t.intentHash != 0) {
             typeStr += ", intentHash=" + t.intentHash;
         }
-        if (t.spanX != 0) {
-            typeStr += ", spanX=" + t.spanX;
-        }
-        return typeStr += ", grid=(" + t.gridX + "," + t.gridY + "), id=" + t.pageIndex;
+        return typeStr + ", grid(" + t.gridX + "," + t.gridY + "), span(" + t.spanX + "," + t.spanY
+                + "), pageIdx=" + t.pageIndex;
     }
 
-    private static String getControlStr(Target t) {
-        if (t == null){
-            return "";
-        }
-        switch(t.controlType) {
-            case LauncherLogProto.ALL_APPS_BUTTON: return "ALL_APPS_BUTTON";
-            case LauncherLogProto.WIDGETS_BUTTON: return "WIDGETS_BUTTON";
-            case LauncherLogProto.WALLPAPER_BUTTON: return "WALLPAPER_BUTTON";
-            case LauncherLogProto.SETTINGS_BUTTON: return "SETTINGS_BUTTON";
-            case LauncherLogProto.REMOVE_TARGET: return "REMOVE_TARGET";
-            case LauncherLogProto.UNINSTALL_TARGET: return "UNINSTALL_TARGET";
-            case LauncherLogProto.APPINFO_TARGET: return "APPINFO_TARGET";
-            case LauncherLogProto.RESIZE_HANDLE: return "RESIZE_HANDLE";
-            default: return "UNKNOWN";
-        }
+    public static Target newItemTarget(View v) {
+        return (v.getTag() instanceof ItemInfo)
+                ? newItemTarget((ItemInfo) v.getTag())
+                : newTarget(Target.Type.ITEM);
     }
 
-    private static String getContainerStr(LauncherLogProto.Target t) {
-        String str = "";
-        if (t == null) {
-            return str;
-        }
-        switch (t.containerType) {
-            case LauncherLogProto.WORKSPACE:
-                str = "WORKSPACE";
-                break;
-            case LauncherLogProto.HOTSEAT:
-                str = "HOTSEAT";
-                break;
-            case LauncherLogProto.FOLDER:
-                str = "FOLDER";
-                break;
-            case LauncherLogProto.ALLAPPS:
-                str = "ALLAPPS";
-                break;
-            case LauncherLogProto.WIDGETS:
-                str = "WIDGETS";
-                break;
-            case LauncherLogProto.OVERVIEW:
-                str = "OVERVIEW";
-                break;
-            case LauncherLogProto.PREDICTION:
-                str = "PREDICTION";
-                break;
-            case LauncherLogProto.SEARCHRESULT:
-                str = "SEARCHRESULT";
-                break;
-            case LauncherLogProto.DEEPSHORTCUTS:
-                str = "DEEPSHORTCUTS";
-                break;
-            default:
-                str = "UNKNOWN";
-        }
-        return str + " id=" + t.pageIndex;
-    }
-
-    /**
-     * Used for launching an event by tapping on an icon.
-     */
-    public static LauncherLogProto.LauncherEvent initLauncherEvent(
-            int actionType,
-            View v,
-            int parentTargetType){
-        LauncherLogProto.LauncherEvent event = new LauncherLogProto.LauncherEvent();
-
-        event.srcTarget = new LauncherLogProto.Target[2];
-        event.srcTarget[0] = initTarget(v);
-        event.srcTarget[1] = new LauncherLogProto.Target();
-        event.srcTarget[1].type = parentTargetType;
-
-        event.action = new LauncherLogProto.Action();
-        event.action.type = actionType;
-        return event;
-    }
-
-    /**
-     * Used for clicking on controls and buttons.
-     */
-    public static LauncherLogProto.LauncherEvent initLauncherEvent(
-            int actionType,
-            int childTargetType){
-        LauncherLogProto.LauncherEvent event = new LauncherLogProto.LauncherEvent();
-
-        event.srcTarget = new LauncherLogProto.Target[1];
-        event.srcTarget[0] = new LauncherLogProto.Target();
-        event.srcTarget[0].type = childTargetType;
-
-        event.action = new LauncherLogProto.Action();
-        event.action.type = actionType;
-        return event;
-    }
-
-    /**
-     * Used for drag and drop interaction.
-     */
-    public static LauncherLogProto.LauncherEvent initLauncherEvent(
-            int actionType,
-            View v,
-            ItemInfo info,
-            int parentSrcTargetType,
-            View parentDestTargetType){
-        LauncherLogProto.LauncherEvent event = new LauncherLogProto.LauncherEvent();
-
-        event.srcTarget = new LauncherLogProto.Target[2];
-        event.srcTarget[0] = initTarget(v, info);
-        event.srcTarget[1] = new LauncherLogProto.Target();
-        event.srcTarget[1].type = parentSrcTargetType;
-
-        event.destTarget = new LauncherLogProto.Target[2];
-        event.destTarget[0] = initTarget(v, info);
-        event.destTarget[1] = initDropTarget(parentDestTargetType);
-
-        event.action = new LauncherLogProto.Action();
-        event.action.type = actionType;
-        return event;
-    }
-
-    private static Target initTarget(View v, ItemInfo info) {
-        Target t = new LauncherLogProto.Target();
-        t.type = Target.ITEM;
+    public static Target newItemTarget(ItemInfo info) {
+        Target t = newTarget(Target.Type.ITEM);
         switch (info.itemType) {
             case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
-                t.itemType = LauncherLogProto.APP_ICON;
+                t.itemType = ItemType.APP_ICON;
                 break;
             case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
-                t.itemType = LauncherLogProto.SHORTCUT;
+                t.itemType = ItemType.SHORTCUT;
                 break;
             case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
-                t.itemType = LauncherLogProto.FOLDER_ICON;
+                t.itemType = ItemType.FOLDER_ICON;
                 break;
             case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
-                t.itemType = LauncherLogProto.WIDGET;
+                t.itemType = ItemType.WIDGET;
                 break;
             case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
-                t.itemType = LauncherLogProto.DEEPSHORTCUT;
+                t.itemType = ItemType.DEEPSHORTCUT;
                 break;
         }
         return t;
     }
 
-    private static Target initDropTarget(View v) {
-        Target t = new LauncherLogProto.Target();
-        t.type = (v instanceof ButtonDropTarget)? Target.CONTROL : Target.CONTAINER;
-        if (t.type == Target.CONTAINER) {
-            return t;
+    public static Target newDropTarget(View v) {
+        if (!(v instanceof ButtonDropTarget)) {
+            return newTarget(Target.Type.CONTAINER);
         }
-
+        Target t = newTarget(Target.Type.CONTROL);
         if (v instanceof InfoDropTarget) {
-            t.controlType = LauncherLogProto.APPINFO_TARGET;
+            t.controlType = ControlType.APPINFO_TARGET;
         } else if (v instanceof UninstallDropTarget) {
-            t.controlType = LauncherLogProto.UNINSTALL_TARGET;
+            t.controlType = ControlType.UNINSTALL_TARGET;
         } else if (v instanceof DeleteDropTarget) {
-            t.controlType = LauncherLogProto.REMOVE_TARGET;
+            t.controlType = ControlType.REMOVE_TARGET;
         }
         return t;
     }
 
-    private static Target initTarget(View v) {
-        Target t = new LauncherLogProto.Target();
-        t.type = Target.ITEM;
-        if (!(v.getTag() instanceof ItemInfo)) {
-            return t;
-        }
-        return initTarget(v, (ItemInfo) v.getTag());
+    public static Target newTarget(int targetType) {
+        Target t = new Target();
+        t.type = targetType;
+        return t;
+    }
+    public static Target newContainerTarget(int containerType) {
+        Target t = newTarget(Target.Type.CONTAINER);
+        t.containerType = containerType;
+        return t;
+    }
+
+    public static Action newAction(int type) {
+        Action a = new Action();
+        a.type = type;
+        return a;
+    }
+    public static Action newCommandAction(int command) {
+        Action a = newAction(Action.Type.COMMAND);
+        a.command = command;
+        return a;
+    }
+    public static Action newTouchAction(int touch) {
+        Action a = newAction(Action.Type.TOUCH);
+        a.touch = touch;
+        return a;
+    }
+
+    public static LauncherEvent newLauncherEvent(Action action, Target... srcTargets) {
+        LauncherEvent event = new LauncherEvent();
+        event.srcTarget = srcTargets;
+        event.action = action;
+        return event;
     }
 }
diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java
index 56fdce8..07e99c6 100644
--- a/src/com/android/launcher3/logging/UserEventDispatcher.java
+++ b/src/com/android/launcher3/logging/UserEventDispatcher.java
@@ -16,7 +16,9 @@
 
 package com.android.launcher3.logging;
 
+import android.app.PendingIntent;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
 import android.os.SystemClock;
 import android.util.Log;
@@ -25,19 +27,30 @@
 
 import com.android.launcher3.DropTarget;
 import com.android.launcher3.ItemInfo;
+import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.ProviderConfig;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.LauncherEvent;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.LogConfig;
 
 import java.util.List;
 import java.util.Locale;
 
+import static com.android.launcher3.logging.LoggerUtils.newCommandAction;
+import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
+import static com.android.launcher3.logging.LoggerUtils.newDropTarget;
+import static com.android.launcher3.logging.LoggerUtils.newItemTarget;
+import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
+import static com.android.launcher3.logging.LoggerUtils.newTarget;
+import static com.android.launcher3.logging.LoggerUtils.newTouchAction;
+
 /**
  * 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
  */
@@ -45,16 +58,21 @@
 
     private final static int MAXIMUM_VIEW_HIERARCHY_LEVEL = 5;
 
-    private final boolean mIsVerbose;
+    private static final String TAG = "UserEvent";
+    private static final boolean IS_VERBOSE =
+            ProviderConfig.IS_DOGFOOD_BUILD && Utilities.isPropertyEnabled(LogConfig.USEREVENT);
+
+    public static UserEventDispatcher newInstance(Context context, boolean isInMultiWindowMode) {
+        UserEventDispatcher ued = Utilities.getOverrideObject(UserEventDispatcher.class,
+                context.getApplicationContext(), R.string.user_event_dispatcher_class);
+        ued.mIsInMultiWindowMode = isInMultiWindowMode;
+        return ued;
+    }
 
     /**
-     * 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.
@@ -64,15 +82,14 @@
          * @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) {
-        ViewParent parent = null;
-
+    public static LogContainerProvider getLaunchProviderRecursive(View v) {
+        ViewParent parent;
         if (v != null) {
             parent = v.getParent();
         } else {
@@ -82,8 +99,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();
             }
@@ -91,23 +108,14 @@
         return null;
     }
 
-    private String TAG = "UserEvent";
-
     private long mElapsedContainerMillis;
     private long mElapsedSessionMillis;
     private long mActionDurationMillis;
+    private boolean mIsInMultiWindowMode;
 
     // Used for filling in predictedRank on {@link Target}s.
     private List<ComponentKey> mPredictedApps;
 
-    public UserEventDispatcher() {
-        if (ProviderConfig.IS_DOGFOOD_BUILD) {
-            mIsVerbose = Utilities.isPropertyEnabled(TAG);
-        } else {
-            mIsVerbose = false;
-        }
-    }
-
     //                      APP_ICON    SHORTCUT    WIDGET
     // --------------------------------------------------------------
     // packageNameHash      required    optional    required
@@ -115,94 +123,155 @@
     // intentHash                       required
     // --------------------------------------------------------------
 
-    protected LauncherEvent createLauncherEvent(View v, Intent intent) {
-        LauncherEvent event = LoggerUtils.initLauncherEvent(
-                Action.TOUCH, v, Target.CONTAINER);
-        event.action.touch = Action.TAP;
+    protected LauncherEvent createLauncherEvent(View v, int intentHashCode, ComponentName cn) {
+        LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.TAP),
+                newItemTarget(v), newTarget(Target.Type.CONTAINER));
 
-        // 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.
+        // TODO: make idx percolate up the view hierarchy if needed.
         int idx = 0;
-        LaunchSourceProvider 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]);
-
-        event.srcTarget[idx].intentHash = intent.hashCode();
-        ComponentName cn = intent.getComponent();
-        if (cn != null) {
-            event.srcTarget[idx].packageNameHash = cn.getPackageName().hashCode();
-            event.srcTarget[idx].componentHash = cn.hashCode();
-            if (mPredictedApps != null) {
-                event.srcTarget[idx].predictedRank = mPredictedApps.indexOf(
-                        new ComponentKey(cn, itemInfo.user));
+        if (fillInLogContainerData(event, v)) {
+            ItemInfo itemInfo = (ItemInfo) v.getTag();
+            event.srcTarget[idx].intentHash = intentHashCode;
+            if (cn != null) {
+                event.srcTarget[idx].packageNameHash = cn.getPackageName().hashCode();
+                event.srcTarget[idx].componentHash = cn.hashCode();
+                if (mPredictedApps != null) {
+                    event.srcTarget[idx].predictedRank = mPredictedApps.indexOf(
+                            new ComponentKey(cn, itemInfo.user));
+                }
             }
         }
         return event;
     }
 
+    public boolean fillInLogContainerData(LauncherEvent event, View v) {
+        // Fill in grid(x,y), pageIndex of the child and container type of the parent
+        LogContainerProvider provider = getLaunchProviderRecursive(v);
+        if (v == null || !(v.getTag() instanceof ItemInfo) || provider == null) {
+            return false;
+        }
+        ItemInfo itemInfo = (ItemInfo) v.getTag();
+        provider.fillInLogContainerData(v, itemInfo, event.srcTarget[0], event.srcTarget[1]);
+        return true;
+    }
+
     public void logAppLaunch(View v, Intent intent) {
-        LauncherEvent ev = createLauncherEvent(v, intent);
+        LauncherEvent ev = createLauncherEvent(v, intent.hashCode(), intent.getComponent());
         if (ev == null) {
             return;
         }
         dispatchUserEvent(ev, intent);
     }
 
-    public void logActionOnItem(int action, int itemType) {
-        LauncherEvent event = LoggerUtils.initLauncherEvent(Action.TOUCH, Target.ITEM);
-        event.action.touch = action;
-        event.srcTarget[0].itemType = itemType;
+    public void logNotificationLaunch(View v, PendingIntent intent) {
+        ComponentName dummyComponent = new ComponentName(intent.getCreatorPackage(), "--dummy--");
+        LauncherEvent ev = createLauncherEvent(v, intent.hashCode(), dummyComponent);
+        if (ev == null) {
+            return;
+        }
+        dispatchUserEvent(ev, null);
+    }
+
+    public void logActionCommand(int command, int containerType) {
+        logActionCommand(command, containerType, 0);
+    }
+
+    public void logActionCommand(int command, int containerType, int pageIndex) {
+        LauncherEvent event = newLauncherEvent(
+                newCommandAction(command), newContainerTarget(containerType));
+        event.srcTarget[0].pageIndex = pageIndex;
+        dispatchUserEvent(event, null);
+    }
+
+    /**
+     * TODO: Make this function work when a container view is passed as the 2nd param.
+     */
+    public void logActionCommand(int command, View itemView, int containerType) {
+        LauncherEvent event = newLauncherEvent(newCommandAction(command),
+                newItemTarget(itemView), newTarget(Target.Type.CONTAINER));
+
+        if (fillInLogContainerData(event, itemView)) {
+            // TODO: Remove the following two lines once fillInLogContainerData can take in a
+            // container view.
+            event.srcTarget[0].type = Target.Type.CONTAINER;
+            event.srcTarget[0].containerType = containerType;
+        }
         dispatchUserEvent(event, null);
     }
 
     public void logActionOnControl(int action, int controlType) {
-        LauncherEvent event = LoggerUtils.initLauncherEvent(Action.TOUCH, Target.CONTROL);
-        event.action.touch = action;
+        LauncherEvent event = newLauncherEvent(
+                newTouchAction(action), newTarget(Target.Type.CONTROL));
         event.srcTarget[0].controlType = controlType;
         dispatchUserEvent(event, null);
     }
 
+    public void logActionTapOutside(Target target) {
+        LauncherEvent event = newLauncherEvent(newTouchAction(Action.Type.TOUCH),
+                target);
+        event.action.isOutside = true;
+        dispatchUserEvent(event, null);
+    }
+
     public void logActionOnContainer(int action, int dir, int containerType) {
-        LauncherEvent event = LoggerUtils.initLauncherEvent(Action.TOUCH, Target.CONTAINER);
-        event.action.touch = action;
+        logActionOnContainer(action, dir, containerType, 0);
+    }
+
+    public void logActionOnContainer(int action, int dir, int containerType, int pageIndex) {
+        LauncherEvent event = newLauncherEvent(newTouchAction(action),
+                newContainerTarget(containerType));
         event.action.dir = dir;
-        event.srcTarget[0].containerType = containerType;
+        event.srcTarget[0].pageIndex = pageIndex;
+        dispatchUserEvent(event, null);
+    }
+
+    public void logActionOnItem(int action, int dir, int itemType) {
+        Target itemTarget = newTarget(Target.Type.ITEM);
+        itemTarget.itemType = itemType;
+        LauncherEvent event = newLauncherEvent(newTouchAction(action), itemTarget);
+        event.action.dir = dir;
         dispatchUserEvent(event, null);
     }
 
     public void logDeepShortcutsOpen(View icon) {
-        LauncherEvent event = LoggerUtils.initLauncherEvent(
-                Action.TOUCH, icon, Target.CONTAINER);
-        LaunchSourceProvider provider = getLaunchProviderRecursive(icon);
-        if (icon == null && !(icon.getTag() instanceof ItemInfo)) {
+        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]);
-        event.action.touch = Action.LONGPRESS;
+        LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.LONGPRESS),
+                newItemTarget(info), newTarget(Target.Type.CONTAINER));
+        provider.fillInLogContainerData(icon, info, event.srcTarget[0], event.srcTarget[1]);
         dispatchUserEvent(event, null);
+
+        resetElapsedContainerMillis();
     }
 
     public void setPredictedApps(List<ComponentKey> predictedApps) {
         mPredictedApps = predictedApps;
     }
 
-    public void logDragNDrop(DropTarget.DragObject dragObj, View dropTargetAsView) {
-        LauncherEvent event = LoggerUtils.initLauncherEvent(Action.TOUCH,
-                dragObj.dragView,
-                dragObj.originalDragInfo,
-                Target.CONTAINER,
-                dropTargetAsView);
-        event.action.touch = Action.DRAGDROP;
+    /* Currently we are only interested in whether this event happens or not and don't
+    * care about which screen moves to where. */
+    public void logOverviewReorder() {
+        LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.DRAGDROP),
+                newContainerTarget(ContainerType.WORKSPACE),
+                newContainerTarget(ContainerType.OVERVIEW));
+        dispatchUserEvent(event, null);
+    }
 
-        dragObj.dragSource.fillInLaunchSourceData(null, dragObj.originalDragInfo,
+    public void logDragNDrop(DropTarget.DragObject dragObj, View dropTargetAsView) {
+        LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.DRAGDROP),
+                newItemTarget(dragObj.originalDragInfo), newTarget(Target.Type.CONTAINER));
+        event.destTarget = new Target[] {
+                newItemTarget(dragObj.originalDragInfo), newDropTarget(dropTargetAsView)
+        };
+
+        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]);
 
         }
@@ -227,29 +296,31 @@
     }
 
     public void dispatchUserEvent(LauncherEvent ev, Intent intent) {
+        ev.isInMultiWindowMode = mIsInMultiWindowMode;
         ev.elapsedContainerMillis = SystemClock.uptimeMillis() - mElapsedContainerMillis;
         ev.elapsedSessionMillis = SystemClock.uptimeMillis() - mElapsedSessionMillis;
 
-        if (!mIsVerbose) {
+        if (!IS_VERBOSE) {
             return;
         }
-        Log.d(TAG, String.format(Locale.US,
-                "\naction:%s\n Source child:%s\tparent:%s",
-                LoggerUtils.getActionStr(ev.action),
-                LoggerUtils.getTargetStr(ev.srcTarget != null ? ev.srcTarget[0] : null),
-                LoggerUtils.getTargetStr(ev.srcTarget != null && ev.srcTarget.length > 1 ?
-                        ev.srcTarget[1] : null)));
-        if (ev.destTarget != null && ev.destTarget.length > 0) {
-            Log.d(TAG, String.format(Locale.US,
-                    " Destination child:%s\tparent:%s",
-                    LoggerUtils.getTargetStr(ev.destTarget != null ? ev.destTarget[0] : null),
-                    LoggerUtils.getTargetStr(ev.destTarget != null && ev.destTarget.length > 1 ?
-                            ev.destTarget[1] : null)));
+        String log = "action:" + LoggerUtils.getActionStr(ev.action);
+        if (ev.srcTarget != null && ev.srcTarget.length > 0) {
+            log += "\n Source " + getTargetsStr(ev.srcTarget);
         }
-        Log.d(TAG, String.format(Locale.US,
-                " Elapsed container %d ms session %d ms action %d ms",
+        if (ev.destTarget != null && ev.destTarget.length > 0) {
+            log += "\n Destination " + getTargetsStr(ev.destTarget);
+        }
+        log += String.format(Locale.US,
+                "\n Elapsed container %d ms session %d ms action %d ms",
                 ev.elapsedContainerMillis,
                 ev.elapsedSessionMillis,
-                ev.actionDurationMillis));
+                ev.actionDurationMillis);
+        log += "\n isInMultiWindowMode " + ev.isInMultiWindowMode;
+        Log.d(TAG, log);
+    }
+
+    private static String getTargetsStr(Target[] targets) {
+        return "child:" + LoggerUtils.getTargetStr(targets[0]) +
+                (targets.length > 1 ? "\tparent:" + LoggerUtils.getTargetStr(targets[1]) : "");
     }
 }
diff --git a/src/com/android/launcher3/model/AbstractUserComparator.java b/src/com/android/launcher3/model/AbstractUserComparator.java
deleted file mode 100644
index bd28560..0000000
--- a/src/com/android/launcher3/model/AbstractUserComparator.java
+++ /dev/null
@@ -1,49 +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.ItemInfo;
-import com.android.launcher3.compat.UserHandleCompat;
-import com.android.launcher3.compat.UserManagerCompat;
-
-import java.util.Comparator;
-
-/**
- * A comparator to arrange items based on user profiles.
- */
-public abstract class AbstractUserComparator<T extends ItemInfo> implements Comparator<T> {
-
-    private final UserManagerCompat mUserManager;
-    private final UserHandleCompat mMyUser;
-
-    public AbstractUserComparator(Context context) {
-        mUserManager = UserManagerCompat.getInstance(context);
-        mMyUser = UserHandleCompat.myUserHandle();
-    }
-
-    @Override
-    public int compare(T lhs, T rhs) {
-        if (mMyUser.equals(lhs.user)) {
-            return -1;
-        } else {
-            Long aUserSerial = mUserManager.getSerialNumberForUser(lhs.user);
-            Long bUserSerial = mUserManager.getSerialNumberForUser(rhs.user);
-            return aUserSerial.compareTo(bUserSerial);
-        }
-    }
-}
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
new file mode 100644
index 0000000..9696054
--- /dev/null
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -0,0 +1,266 @@
+/*
+ * 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.os.UserHandle;
+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.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.util.GridOccupancy;
+import com.android.launcher3.util.Provider;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Task to add auto-created workspace items.
+ */
+public class AddWorkspaceItemsTask extends ExtendedModelTask {
+
+    private final Provider<List<ItemInfo>> mAppsProvider;
+
+    /**
+     * @param appsProvider items to add on the workspace
+     */
+    public AddWorkspaceItemsTask(Provider<List<ItemInfo>> appsProvider) {
+        mAppsProvider = appsProvider;
+    }
+
+    @Override
+    public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+        List<ItemInfo> workspaceApps = mAppsProvider.get();
+        if (workspaceApps.isEmpty()) {
+            return;
+        }
+        Context context = app.getContext();
+
+        final ArrayList<ItemInfo> addedItemsFinal = new ArrayList<>();
+        final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<>();
+
+        // 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 : workspaceApps) {
+                if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
+                        item.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
+                    // 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, item.spanX, item.spanY);
+                long screenId = coords.first;
+                int[] cordinates = coords.second;
+
+                ItemInfo itemInfo;
+                if (item instanceof ShortcutInfo || item instanceof FolderInfo ||
+                        item instanceof LauncherAppWidgetInfo) {
+                    itemInfo = item;
+                } else if (item instanceof AppInfo) {
+                    itemInfo = ((AppInfo) item).makeShortcut();
+                } else {
+                    throw new RuntimeException("Unexpected info type");
+                }
+
+                // Add the shortcut to the db
+                getModelWriter().addItemToDatabase(itemInfo,
+                        LauncherSettings.Favorites.CONTAINER_DESKTOP, screenId,
+                        cordinates[0], cordinates[1]);
+
+                // Save the ShortcutInfo for binding in the workspace
+                addedItemsFinal.add(itemInfo);
+            }
+        }
+
+        // Update the workspace screens
+        updateScreens(context, workspaceScreens);
+
+        if (!addedItemsFinal.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 (!addedItemsFinal.isEmpty()) {
+                        ItemInfo info = addedItemsFinal.get(addedItemsFinal.size() - 1);
+                        long lastScreenId = info.screenId;
+                        for (ItemInfo i : addedItemsFinal) {
+                            if (i.screenId == lastScreenId) {
+                                addAnimated.add(i);
+                            } else {
+                                addNotAnimated.add(i);
+                            }
+                        }
+                    }
+                    callbacks.bindAppsAdded(addedWorkspaceScreensFinal,
+                            addNotAnimated, addAnimated, null);
+                }
+            });
+        }
+    }
+
+    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, UserHandle user) {
+        final String intentWithPkg, intentWithoutPkg;
+        if (intent == null) {
+            // Skip items with null intents
+            return true;
+        }
+        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;
+                    if (item.getIntent() != null && info.user.equals(user)) {
+                        Intent copyIntent = new Intent(item.getIntent());
+                        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..0e73ca6
--- /dev/null
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -0,0 +1,374 @@
+/*
+ * 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.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.MutableInt;
+
+import com.android.launcher3.FolderInfo;
+import com.android.launcher3.InstallShortcutReceiver;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.logging.LoggerUtils;
+import com.android.launcher3.logging.DumpTargetWrapper;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.model.nano.LauncherDumpProto;
+import com.android.launcher3.model.nano.LauncherDumpProto.ContainerType;
+import com.android.launcher3.model.nano.LauncherDumpProto.DumpTarget;
+import com.android.launcher3.model.nano.LauncherDumpProto.ItemType;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.LongArrayMap;
+import com.android.launcher3.util.MultiHashMap;
+import com.google.protobuf.nano.MessageNano;
+
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+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 dump(String prefix, FileDescriptor fd, PrintWriter writer,
+             String[] args) {
+        if (args.length > 0 && TextUtils.equals(args[0], "--proto")) {
+            dumpProto(prefix, fd, writer, args);
+            return;
+        }
+        writer.println(prefix + "Data Model:");
+        writer.print(prefix + " ---- workspace screens: ");
+        for (int i = 0; i < workspaceScreens.size(); i++) {
+            writer.print(" " + workspaceScreens.get(i).toString());
+        }
+        writer.println();
+        writer.println(prefix + " ---- workspace items ");
+        for (int i = 0; i < workspaceItems.size(); i++) {
+            writer.println(prefix + '\t' + workspaceItems.get(i).toString());
+        }
+        writer.println(prefix + " ---- appwidget items ");
+        for (int i = 0; i < appWidgets.size(); i++) {
+            writer.println(prefix + '\t' + appWidgets.get(i).toString());
+        }
+        writer.println(prefix + " ---- folder items ");
+        for (int i = 0; i< folders.size(); i++) {
+            writer.println(prefix + '\t' + folders.valueAt(i).toString());
+        }
+        writer.println(prefix + " ---- items id map ");
+        for (int i = 0; i< itemsIdMap.size(); i++) {
+            writer.println(prefix + '\t' + itemsIdMap.valueAt(i).toString());
+        }
+
+        if (args.length > 0 && TextUtils.equals(args[0], "--all")) {
+            writer.println(prefix + "shortcuts");
+            for (ArrayList<String> map : deepShortcutMap.values()) {
+                writer.print(prefix + "  ");
+                for (String str : map) {
+                    writer.print(str.toString() + ", ");
+                }
+                writer.println();
+            }
+        }
+    }
+
+    private synchronized void dumpProto(String prefix, FileDescriptor fd, PrintWriter writer,
+            String[] args) {
+
+        // Add top parent nodes. (L1)
+        DumpTargetWrapper hotseat = new DumpTargetWrapper(ContainerType.HOTSEAT, 0);
+        LongArrayMap<DumpTargetWrapper> workspaces = new LongArrayMap<>();
+        for (int i = 0; i < workspaceScreens.size(); i++) {
+            workspaces.put(new Long(workspaceScreens.get(i)),
+                    new DumpTargetWrapper(ContainerType.WORKSPACE, i));
+        }
+        DumpTargetWrapper dtw;
+        // Add non leaf / non top nodes (L2)
+        for (int i = 0; i < folders.size(); i++) {
+            FolderInfo fInfo = folders.valueAt(i);
+            dtw = new DumpTargetWrapper(ContainerType.FOLDER, folders.size());
+            dtw.writeToDumpTarget(fInfo);
+            for(ShortcutInfo sInfo: fInfo.contents) {
+                DumpTargetWrapper child = new DumpTargetWrapper(sInfo);
+                child.writeToDumpTarget(sInfo);
+                dtw.add(child);
+            }
+            if (fInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+                hotseat.add(dtw);
+            } else if (fInfo.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+                workspaces.get(new Long(fInfo.screenId)).add(dtw);
+            }
+        }
+        // Add leaf nodes (L3): *Info
+        for (int i = 0; i < workspaceItems.size(); i++) {
+            ItemInfo info = workspaceItems.get(i);
+            if (info instanceof FolderInfo) {
+                continue;
+            }
+            dtw = new DumpTargetWrapper(info);
+            dtw.writeToDumpTarget(info);
+            if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+                hotseat.add(dtw);
+            } else if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+                workspaces.get(new Long(info.screenId)).add(dtw);
+            }
+        }
+        for (int i = 0; i < appWidgets.size(); i++) {
+            ItemInfo info = appWidgets.get(i);
+            dtw = new DumpTargetWrapper(info);
+            dtw.writeToDumpTarget(info);
+            if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+                hotseat.add(dtw);
+            } else if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+                workspaces.get(new Long(info.screenId)).add(dtw);
+            }
+        }
+
+
+        // Traverse target wrapper
+        ArrayList<DumpTarget> targetList = new ArrayList<>();
+        targetList.addAll(hotseat.getFlattenedList());
+        for (int i = 0; i < workspaces.size(); i++) {
+            targetList.addAll(workspaces.valueAt(i).getFlattenedList());
+        }
+
+        if (args.length > 1 && TextUtils.equals(args[1], "--debug")) {
+            for (int i = 0; i < targetList.size(); i++) {
+                writer.println(prefix + DumpTargetWrapper.getDumpTargetStr(targetList.get(i)));
+            }
+            return;
+        } else {
+            LauncherDumpProto.LauncherImpression proto = new LauncherDumpProto.LauncherImpression();
+            proto.targets = new DumpTarget[targetList.size()];
+            for (int i = 0; i < targetList.size(); i++) {
+                proto.targets[i] = targetList.get(i);
+            }
+            FileOutputStream fos = new FileOutputStream(fd);
+            try {
+
+                fos.write(MessageNano.toByteArray(proto));
+                Log.d(TAG, MessageNano.toByteArray(proto).length + "Bytes");
+            } catch (IOException e) {
+                Log.e(TAG, "Exception writing dumpsys --proto", e);
+            }
+        }
+    }
+
+    public synchronized void removeItem(Context context, ItemInfo... items) {
+        removeItem(context, Arrays.asList(items));
+    }
+
+    public synchronized void removeItem(Context context, 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.fromItemInfo(item);
+                    MutableInt count = pinnedShortcutCounts.get(pinnedShortcut);
+                    if ((count == null || --count.value == 0)
+                            && !InstallShortcutReceiver.getPendingShortcuts(context)
+                                .contains(pinnedShortcut)) {
+                        DeepShortcutManager.getInstance(context).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(Context context, 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.fromItemInfo(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(context).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, UserHandle 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..46130fc
--- /dev/null
+++ b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
@@ -0,0 +1,97 @@
+/*
+ * 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.os.UserHandle;
+
+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.LauncherSettings.Favorites;
+import com.android.launcher3.ShortcutInfo;
+
+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 UserHandle mUser;
+    private final HashSet<String> mPackages;
+
+    public CacheDataUpdatedTask(int op, UserHandle 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 (si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
+                            && isValidShortcut(si) && cn != null
+                            && mPackages.contains(cn.getPackageName())) {
+                        iconCache.getTitleAndIcon(si, si.usingLowResIcon);
+                        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 true;
+            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..0541966
--- /dev/null
+++ b/src/com/android/launcher3/model/ExtendedModelTask.java
@@ -0,0 +1,62 @@
+/*
+ * 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.os.UserHandle;
+
+import com.android.launcher3.LauncherModel.BaseModelUpdateTask;
+import com.android.launcher3.LauncherModel.CallbackTask;
+import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.ShortcutInfo;
+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, UserHandle user) {
+        bindUpdatedShortcuts(updatedShortcuts, new ArrayList<ShortcutInfo>(), user);
+    }
+
+    public void bindUpdatedShortcuts(
+            final ArrayList<ShortcutInfo> updatedShortcuts,
+            final ArrayList<ShortcutInfo> removedShortcuts,
+            final UserHandle 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..221798b 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;
@@ -895,12 +895,12 @@
      */
     public static boolean migrateGridIfNeeded(Context context) {
         SharedPreferences prefs = Utilities.getPrefs(context);
-        InvariantDeviceProfile idp = LauncherAppState.getInstance().getInvariantDeviceProfile();
+        InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
 
         String gridSizeString = getPointString(idp.numColumns, idp.numRows);
 
         if (gridSizeString.equals(prefs.getString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, "")) &&
-                idp.numHotseatIcons != prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, idp.numHotseatIcons)) {
+                idp.numHotseatIcons == prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, idp.numHotseatIcons)) {
             // Skip if workspace and hotseat sizes have not changed.
             return true;
         }
@@ -915,8 +915,7 @@
             if (srcHotseatCount != idp.numHotseatIcons) {
                 // Migrate hotseat.
 
-                dbChanged = new GridSizeMigrationTask(context,
-                        LauncherAppState.getInstance().getInvariantDeviceProfile(),
+                dbChanged = new GridSizeMigrationTask(context, LauncherAppState.getIDP(context),
                         validPackages, srcHotseatCount, idp.numHotseatIcons).migrateHotseat();
             }
 
@@ -978,9 +977,9 @@
      * @return a map with occupied hotseat position set to non-null value.
      */
     public static LongArrayMap<Object> removeBrokenHotseatItems(Context context) throws Exception {
-        GridSizeMigrationTask task = new GridSizeMigrationTask(context,
-                LauncherAppState.getInstance().getInvariantDeviceProfile(),
-                getValidPackages(context), Integer.MAX_VALUE, Integer.MAX_VALUE);
+        GridSizeMigrationTask task = new GridSizeMigrationTask(
+                context, LauncherAppState.getIDP(context), getValidPackages(context),
+                Integer.MAX_VALUE, Integer.MAX_VALUE);
 
         // Load all the valid entries
         ArrayList<DbEntry> items = task.loadHotseatEntries();
@@ -1038,8 +1037,7 @@
         }
 
         protected boolean runStepTask(Point sourceSize, Point nextSize) throws Exception {
-            return new GridSizeMigrationTask(mContext,
-                    LauncherAppState.getInstance().getInvariantDeviceProfile(),
+            return new GridSizeMigrationTask(mContext, LauncherAppState.getIDP(mContext),
                     mValidPackages, sourceSize, nextSize).migrateWorkspace();
         }
     }
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
new file mode 100644
index 0000000..36f60b9
--- /dev/null
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -0,0 +1,470 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.model;
+
+import android.content.ComponentName;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.Intent.ShortcutIconResource;
+import android.content.pm.LauncherActivityInfo;
+import android.database.Cursor;
+import android.database.CursorWrapper;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.UserHandle;
+import android.provider.BaseColumns;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.LongSparseArray;
+
+import com.android.launcher3.IconCache;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.graphics.LauncherIcons;
+import com.android.launcher3.logging.FileLog;
+import com.android.launcher3.util.ContentWriter;
+import com.android.launcher3.util.GridOccupancy;
+import com.android.launcher3.util.LongArrayMap;
+import com.android.launcher3.util.PackageManagerHelper;
+
+import java.net.URISyntaxException;
+import java.security.InvalidParameterException;
+import java.util.ArrayList;
+
+/**
+ * Extension of {@link Cursor} with utility methods for workspace loading.
+ */
+public class LoaderCursor extends CursorWrapper {
+
+    private static final String TAG = "LoaderCursor";
+
+    public final LongSparseArray<UserHandle> allUsers = new LongSparseArray<>();
+
+    private final Context mContext;
+    private final UserManagerCompat mUserManager;
+    private final IconCache mIconCache;
+    private final InvariantDeviceProfile mIDP;
+
+    private final ArrayList<Long> itemsToRemove = new ArrayList<>();
+    private final ArrayList<Long> restoredRows = new ArrayList<>();
+    private final LongArrayMap<GridOccupancy> occupied = new LongArrayMap<>();
+
+    private final int iconPackageIndex;
+    private final int iconResourceIndex;
+    private final int iconIndex;
+    public final int titleIndex;
+
+    private final int idIndex;
+    private final int containerIndex;
+    private final int itemTypeIndex;
+    private final int screenIndex;
+    private final int cellXIndex;
+    private final int cellYIndex;
+    private final int profileIdIndex;
+    private final int restoredIndex;
+    private final int intentIndex;
+
+    // Properties loaded per iteration
+    public long serialNumber;
+    public UserHandle user;
+    public long id;
+    public long container;
+    public int itemType;
+    public int restoreFlag;
+
+    public LoaderCursor(Cursor c, LauncherAppState app) {
+        super(c);
+        mContext = app.getContext();
+        mIconCache = app.getIconCache();
+        mIDP = app.getInvariantDeviceProfile();
+        mUserManager = UserManagerCompat.getInstance(mContext);
+
+        // Init column indices
+        iconIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
+        iconPackageIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE);
+        iconResourceIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE);
+        titleIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
+
+        idIndex = getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
+        containerIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
+        itemTypeIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
+        screenIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
+        cellXIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
+        cellYIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
+        profileIdIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.PROFILE_ID);
+        restoredIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.RESTORED);
+        intentIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
+    }
+
+    @Override
+    public boolean moveToNext() {
+        boolean result = super.moveToNext();
+        if (result) {
+            // Load common properties.
+            itemType = getInt(itemTypeIndex);
+            container = getInt(containerIndex);
+            id = getLong(idIndex);
+            serialNumber = getInt(profileIdIndex);
+            user = allUsers.get(serialNumber);
+            restoreFlag = getInt(restoredIndex);
+        }
+        return result;
+    }
+
+    public Intent parseIntent() {
+        String intentDescription = getString(intentIndex);
+        try {
+            return TextUtils.isEmpty(intentDescription) ?
+                    null : Intent.parseUri(intentDescription, 0);
+        } catch (URISyntaxException e) {
+            Log.e(TAG, "Error parsing Intent");
+            return null;
+        }
+    }
+
+    public ShortcutInfo loadSimpleShortcut() {
+        final ShortcutInfo info = new ShortcutInfo();
+        // Non-app shortcuts are only supported for current user.
+        info.user = user;
+        info.itemType = itemType;
+        info.title = getTitle();
+        info.iconBitmap = loadIcon(info);
+        // the fallback icon
+        if (info.iconBitmap == null) {
+            info.iconBitmap = mIconCache.getDefaultIcon(info.user);
+        }
+
+        // TODO: If there's an explicit component and we can't install that, delete it.
+
+        return info;
+    }
+
+    /**
+     * Loads the icon from the cursor and updates the {@param info} if the icon is an app resource.
+     */
+    protected Bitmap loadIcon(ShortcutInfo info) {
+        Bitmap icon = null;
+        if (itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
+            String packageName = getString(iconPackageIndex);
+            String resourceName = getString(iconResourceIndex);
+            if (!TextUtils.isEmpty(packageName) || !TextUtils.isEmpty(resourceName)) {
+                info.iconResource = new ShortcutIconResource();
+                info.iconResource.packageName = packageName;
+                info.iconResource.resourceName = resourceName;
+                icon = LauncherIcons.createIconBitmap(info.iconResource, mContext);
+            }
+        }
+        if (icon == null) {
+            // Failed to load from resource, try loading from DB.
+            byte[] data = getBlob(iconIndex);
+            try {
+                icon = LauncherIcons.createIconBitmap(
+                        BitmapFactory.decodeByteArray(data, 0, data.length), mContext);
+            } catch (Exception e) {
+                return null;
+            }
+        }
+        return icon;
+    }
+
+    /**
+     * Returns the title or empty string
+     */
+    private String getTitle() {
+        String title = getString(titleIndex);
+        return TextUtils.isEmpty(title) ? "" : Utilities.trim(title);
+    }
+
+
+    /**
+     * Make an ShortcutInfo object for a restored application or shortcut item that points
+     * to a package that is not yet installed on the system.
+     */
+    public ShortcutInfo getRestoredItemInfo(Intent intent) {
+        final ShortcutInfo info = new ShortcutInfo();
+        info.user = user;
+        info.intent = intent;
+
+        info.iconBitmap = loadIcon(info);
+        // the fallback icon
+        if (info.iconBitmap == null) {
+            mIconCache.getTitleAndIcon(info, false /* useLowResIcon */);
+        }
+
+        if (hasRestoreFlag(ShortcutInfo.FLAG_RESTORED_ICON)) {
+            String title = getTitle();
+            if (!TextUtils.isEmpty(title)) {
+                info.title = Utilities.trim(title);
+            }
+        } else if  (hasRestoreFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
+            if (TextUtils.isEmpty(info.title)) {
+                info.title = getTitle();
+            }
+        } else {
+            throw new InvalidParameterException("Invalid restoreType " + restoreFlag);
+        }
+
+        info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
+        info.itemType = itemType;
+        info.status = restoreFlag;
+        return info;
+    }
+
+    /**
+     * Make an ShortcutInfo object for a shortcut that is an application.
+     */
+    public ShortcutInfo getAppShortcutInfo(
+            Intent intent, boolean allowMissingTarget, boolean useLowResIcon) {
+        if (user == null) {
+            Log.d(TAG, "Null user found in getShortcutInfo");
+            return null;
+        }
+
+        ComponentName componentName = intent.getComponent();
+        if (componentName == null) {
+            Log.d(TAG, "Missing component found in getShortcutInfo");
+            return null;
+        }
+
+        Intent newIntent = new Intent(Intent.ACTION_MAIN, null);
+        newIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+        newIntent.setComponent(componentName);
+        LauncherActivityInfo lai = LauncherAppsCompat.getInstance(mContext)
+                .resolveActivity(newIntent, user);
+        if ((lai == null) && !allowMissingTarget) {
+            Log.d(TAG, "Missing activity found in getShortcutInfo: " + componentName);
+            return null;
+        }
+
+        final ShortcutInfo info = new ShortcutInfo();
+        info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+        info.user = user;
+        info.intent = newIntent;
+
+        mIconCache.getTitleAndIcon(info, lai, useLowResIcon);
+        if (mIconCache.isDefaultIcon(info.iconBitmap, user)) {
+            Bitmap icon = loadIcon(info);
+            info.iconBitmap = icon != null ? icon : info.iconBitmap;
+        }
+
+        if (lai != null && PackageManagerHelper.isAppSuspended(lai.getApplicationInfo())) {
+            info.isDisabled = ShortcutInfo.FLAG_DISABLED_SUSPENDED;
+        }
+
+        // from the db
+        if (TextUtils.isEmpty(info.title)) {
+            info.title = getTitle();
+        }
+
+        // fall back to the class name of the activity
+        if (info.title == null) {
+            info.title = componentName.getClassName();
+        }
+
+        info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
+        return info;
+    }
+
+    /**
+     * Returns a {@link ContentWriter} which can be used to update the current item.
+     */
+    public ContentWriter updater() {
+       return new ContentWriter(mContext, new ContentWriter.CommitParams(
+               BaseColumns._ID + "= ?", new String[]{Long.toString(id)}));
+    }
+
+    /**
+     * Marks the current item for removal
+     */
+    public void markDeleted(String reason) {
+        FileLog.e(TAG, reason);
+        itemsToRemove.add(id);
+    }
+
+    /**
+     * Removes any items marked for removal.
+     * @return true is any item was removed.
+     */
+    public boolean commitDeleted() {
+        if (itemsToRemove.size() > 0) {
+            // Remove dead items
+            mContext.getContentResolver().delete(LauncherSettings.Favorites.CONTENT_URI,
+                    Utilities.createDbSelectionQuery(
+                            LauncherSettings.Favorites._ID, itemsToRemove), null);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Marks the current item as restored
+     */
+    public void markRestored() {
+        if (restoreFlag != 0) {
+            restoredRows.add(id);
+            restoreFlag = 0;
+        }
+    }
+
+    public boolean hasRestoreFlag(int flagMask) {
+        return (restoreFlag & flagMask) != 0;
+    }
+
+    public void commitRestoredItems() {
+        if (restoredRows.size() > 0) {
+            // Update restored items that no longer require special handling
+            ContentValues values = new ContentValues();
+            values.put(LauncherSettings.Favorites.RESTORED, 0);
+            mContext.getContentResolver().update(LauncherSettings.Favorites.CONTENT_URI, values,
+                    Utilities.createDbSelectionQuery(
+                            LauncherSettings.Favorites._ID, restoredRows), null);
+        }
+    }
+
+    /**
+     * Returns true is the item is on workspace or hotseat
+     */
+    public boolean isOnWorkspaceOrHotseat() {
+        return container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
+                container == LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+    }
+
+    /**
+     * Applies the following properties:
+     * {@link ItemInfo#id}
+     * {@link ItemInfo#container}
+     * {@link ItemInfo#screenId}
+     * {@link ItemInfo#cellX}
+     * {@link ItemInfo#cellY}
+     */
+    public void applyCommonProperties(ItemInfo info) {
+        info.id = id;
+        info.container = container;
+        info.screenId = getInt(screenIndex);
+        info.cellX = getInt(cellXIndex);
+        info.cellY = getInt(cellYIndex);
+    }
+
+    /**
+     * Adds the {@param info} to {@param dataModel} if it does not overlap with any other item,
+     * otherwise marks it for deletion.
+     */
+    public void checkAndAddItem(ItemInfo info, BgDataModel dataModel) {
+        if (checkItemPlacement(info, dataModel.workspaceScreens)) {
+            dataModel.addItem(mContext, info, false);
+        } else {
+            markDeleted("Item position overlap");
+        }
+    }
+
+    /**
+     * check & update map of what's occupied; used to discard overlapping/invalid items
+     */
+    protected boolean checkItemPlacement(ItemInfo item, ArrayList<Long> workspaceScreens) {
+        long containerIndex = item.screenId;
+        if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+            // Return early if we detect that an item is under the hotseat button
+            if (!FeatureFlags.NO_ALL_APPS_ICON &&
+                    mIDP.isAllAppsButtonRank((int) item.screenId)) {
+                Log.e(TAG, "Error loading shortcut into hotseat " + item
+                        + " into position (" + item.screenId + ":" + item.cellX + ","
+                        + item.cellY + ") occupied by all apps");
+                return false;
+            }
+
+            final GridOccupancy hotseatOccupancy =
+                    occupied.get((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT);
+
+            if (item.screenId >= mIDP.numHotseatIcons) {
+                Log.e(TAG, "Error loading shortcut " + item
+                        + " into hotseat position " + item.screenId
+                        + ", position out of bounds: (0 to " + (mIDP.numHotseatIcons - 1)
+                        + ")");
+                return false;
+            }
+
+            if (hotseatOccupancy != null) {
+                if (hotseatOccupancy.cells[(int) item.screenId][0]) {
+                    Log.e(TAG, "Error loading shortcut into hotseat " + item
+                            + " into position (" + item.screenId + ":" + item.cellX + ","
+                            + item.cellY + ") already occupied");
+                    return false;
+                } else {
+                    hotseatOccupancy.cells[(int) item.screenId][0] = true;
+                    return true;
+                }
+            } else {
+                final GridOccupancy occupancy = new GridOccupancy(mIDP.numHotseatIcons, 1);
+                occupancy.cells[(int) item.screenId][0] = true;
+                occupied.put((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT, occupancy);
+                return true;
+            }
+        } else if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+            if (!workspaceScreens.contains((Long) item.screenId)) {
+                // The item has an invalid screen id.
+                return false;
+            }
+        } else {
+            // Skip further checking if it is not the hotseat or workspace container
+            return true;
+        }
+
+        final int countX = mIDP.numColumns;
+        final int countY = mIDP.numRows;
+        if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
+                item.cellX < 0 || item.cellY < 0 ||
+                item.cellX + item.spanX > countX || item.cellY + item.spanY > countY) {
+            Log.e(TAG, "Error loading shortcut " + item
+                    + " into cell (" + containerIndex + "-" + item.screenId + ":"
+                    + item.cellX + "," + item.cellY
+                    + ") out of screen bounds ( " + countX + "x" + countY + ")");
+            return false;
+        }
+
+        if (!occupied.containsKey(item.screenId)) {
+            GridOccupancy screen = new GridOccupancy(countX + 1, countY + 1);
+            if (item.screenId == Workspace.FIRST_SCREEN_ID) {
+                // Mark the first row as occupied (if the feature is enabled)
+                // in order to account for the QSB.
+                screen.markCells(0, 0, countX + 1, 1, FeatureFlags.QSB_ON_FIRST_SCREEN);
+            }
+            occupied.put(item.screenId, screen);
+        }
+        final GridOccupancy occupancy = occupied.get(item.screenId);
+
+        // Check if any workspace icons overlap with each other
+        if (occupancy.isRegionVacant(item.cellX, item.cellY, item.spanX, item.spanY)) {
+            occupancy.markCells(item, true);
+            return true;
+        } else {
+            Log.e(TAG, "Error loading shortcut " + item
+                    + " into cell (" + containerIndex + "-" + item.screenId + ":"
+                    + item.cellX + "," + item.cellX + "," + item.spanX + "," + item.spanY
+                    + ") already occupied");
+            return false;
+        }
+    }
+}
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
new file mode 100644
index 0000000..4931dca
--- /dev/null
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -0,0 +1,374 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.model;
+
+import android.content.ContentProviderOperation;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.net.Uri;
+import android.util.Log;
+
+import com.android.launcher3.FolderInfo;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherProvider;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.LauncherSettings.Settings;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.util.ContentWriter;
+import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.launcher3.util.LooperExecuter;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.concurrent.Executor;
+
+/**
+ * Class for handling model updates.
+ */
+public class ModelWriter {
+
+    private static final String TAG = "ModelWriter";
+
+    private final Context mContext;
+    private final BgDataModel mBgDataModel;
+    private final Executor mWorkerExecutor;
+    private final boolean mHasVerticalHotseat;
+
+    public ModelWriter(Context context, BgDataModel dataModel, boolean hasVerticalHotseat) {
+        mContext = context;
+        mBgDataModel = dataModel;
+        mWorkerExecutor = new LooperExecuter(LauncherModel.getWorkerLooper());
+        mHasVerticalHotseat = hasVerticalHotseat;
+    }
+
+    private void updateItemInfoProps(
+            ItemInfo item, long container, long screenId, int cellX, int cellY) {
+        item.container = container;
+        item.cellX = cellX;
+        item.cellY = cellY;
+        // We store hotseat items in canonical form which is this orientation invariant position
+        // in the hotseat
+        if (container == Favorites.CONTAINER_HOTSEAT) {
+            item.screenId = mHasVerticalHotseat
+                    ? LauncherAppState.getIDP(mContext).numHotseatIcons - cellY - 1 : cellX;
+        } else {
+            item.screenId = screenId;
+        }
+    }
+
+    /**
+     * Adds an item to the DB if it was not created previously, or move it to a new
+     * <container, screen, cellX, cellY>
+     */
+    public void addOrMoveItemInDatabase(ItemInfo item,
+            long container, long screenId, int cellX, int cellY) {
+        if (item.container == ItemInfo.NO_ID) {
+            // From all apps
+            addItemToDatabase(item, container, screenId, cellX, cellY);
+        } else {
+            // From somewhere else
+            moveItemInDatabase(item, container, screenId, cellX, cellY);
+        }
+    }
+
+    private void checkItemInfoLocked(long itemId, ItemInfo item, StackTraceElement[] stackTrace) {
+        ItemInfo modelItem = mBgDataModel.itemsIdMap.get(itemId);
+        if (modelItem != null && item != modelItem) {
+            // check all the data is consistent
+            if (modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
+                ShortcutInfo modelShortcut = (ShortcutInfo) modelItem;
+                ShortcutInfo shortcut = (ShortcutInfo) item;
+                if (modelShortcut.title.toString().equals(shortcut.title.toString()) &&
+                        modelShortcut.intent.filterEquals(shortcut.intent) &&
+                        modelShortcut.id == shortcut.id &&
+                        modelShortcut.itemType == shortcut.itemType &&
+                        modelShortcut.container == shortcut.container &&
+                        modelShortcut.screenId == shortcut.screenId &&
+                        modelShortcut.cellX == shortcut.cellX &&
+                        modelShortcut.cellY == shortcut.cellY &&
+                        modelShortcut.spanX == shortcut.spanX &&
+                        modelShortcut.spanY == shortcut.spanY) {
+                    // For all intents and purposes, this is the same object
+                    return;
+                }
+            }
+
+            // the modelItem needs to match up perfectly with item if our model is
+            // to be consistent with the database-- for now, just require
+            // modelItem == item or the equality check above
+            String msg = "item: " + ((item != null) ? item.toString() : "null") +
+                    "modelItem: " +
+                    ((modelItem != null) ? modelItem.toString() : "null") +
+                    "Error: ItemInfo passed to checkItemInfo doesn't match original";
+            RuntimeException e = new RuntimeException(msg);
+            if (stackTrace != null) {
+                e.setStackTrace(stackTrace);
+            }
+            throw e;
+        }
+    }
+
+    /**
+     * Move an item in the DB to a new <container, screen, cellX, cellY>
+     */
+    public void moveItemInDatabase(final ItemInfo item,
+            long container, long screenId, int cellX, int cellY) {
+        updateItemInfoProps(item, container, screenId, cellX, cellY);
+
+        final ContentWriter writer = new ContentWriter(mContext)
+                .put(Favorites.CONTAINER, item.container)
+                .put(Favorites.CELLX, item.cellX)
+                .put(Favorites.CELLY, item.cellY)
+                .put(Favorites.RANK, item.rank)
+                .put(Favorites.SCREEN, item.screenId);
+
+        mWorkerExecutor.execute(new UpdateItemRunnable(item, writer));
+    }
+
+    /**
+     * Move items in the DB to a new <container, screen, cellX, cellY>. We assume that the
+     * cellX, cellY have already been updated on the ItemInfos.
+     */
+    public void moveItemsInDatabase(final ArrayList<ItemInfo> items, long container, int screen) {
+        ArrayList<ContentValues> contentValues = new ArrayList<>();
+        int count = items.size();
+
+        for (int i = 0; i < count; i++) {
+            ItemInfo item = items.get(i);
+            updateItemInfoProps(item, container, screen, item.cellX, item.cellY);
+
+            final ContentValues values = new ContentValues();
+            values.put(Favorites.CONTAINER, item.container);
+            values.put(Favorites.CELLX, item.cellX);
+            values.put(Favorites.CELLY, item.cellY);
+            values.put(Favorites.RANK, item.rank);
+            values.put(Favorites.SCREEN, item.screenId);
+
+            contentValues.add(values);
+        }
+        mWorkerExecutor.execute(new UpdateItemsRunnable(items, contentValues));
+    }
+
+    /**
+     * Move and/or resize item in the DB to a new <container, screen, cellX, cellY, spanX, spanY>
+     */
+    public void modifyItemInDatabase(final ItemInfo item,
+            long container, long screenId, int cellX, int cellY, int spanX, int spanY) {
+        updateItemInfoProps(item, container, screenId, cellX, cellY);
+        item.spanX = spanX;
+        item.spanY = spanY;
+
+        final ContentWriter writer = new ContentWriter(mContext)
+                .put(Favorites.CONTAINER, item.container)
+                .put(Favorites.CELLX, item.cellX)
+                .put(Favorites.CELLY, item.cellY)
+                .put(Favorites.RANK, item.rank)
+                .put(Favorites.SPANX, item.spanX)
+                .put(Favorites.SPANY, item.spanY)
+                .put(Favorites.SCREEN, item.screenId);
+
+        mWorkerExecutor.execute(new UpdateItemRunnable(item, writer));
+    }
+
+    /**
+     * Update an item to the database in a specified container.
+     */
+    public void updateItemInDatabase(ItemInfo item) {
+        ContentWriter writer = new ContentWriter(mContext);
+        item.onAddToDatabase(writer);
+        mWorkerExecutor.execute(new UpdateItemRunnable(item, writer));
+    }
+
+    /**
+     * Add an item to the database in a specified container. Sets the container, screen, cellX and
+     * cellY fields of the item. Also assigns an ID to the item.
+     */
+    public void addItemToDatabase(final ItemInfo item,
+            long container, long screenId, int cellX, int cellY) {
+        updateItemInfoProps(item, container, screenId, cellX, cellY);
+
+        final ContentWriter writer = new ContentWriter(mContext);
+        final ContentResolver cr = mContext.getContentResolver();
+        item.onAddToDatabase(writer);
+
+        item.id = Settings.call(cr, Settings.METHOD_NEW_ITEM_ID).getLong(Settings.EXTRA_VALUE);
+        writer.put(Favorites._ID, item.id);
+
+        final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
+        mWorkerExecutor.execute(new Runnable() {
+            public void run() {
+                cr.insert(Favorites.CONTENT_URI, writer.getValues(mContext));
+
+                synchronized (mBgDataModel) {
+                    checkItemInfoLocked(item.id, item, stackTrace);
+                    mBgDataModel.addItem(mContext, item, true);
+                }
+            }
+        });
+    }
+
+    /**
+     * Removes the specified item from the database
+     */
+    public void deleteItemFromDatabase(ItemInfo item) {
+        deleteItemsFromDatabase(Arrays.asList(item));
+    }
+
+    /**
+     * Removes all the items from the database matching {@param matcher}.
+     */
+    public void deleteItemsFromDatabase(ItemInfoMatcher matcher) {
+        deleteItemsFromDatabase(matcher.filterItemInfos(mBgDataModel.itemsIdMap));
+    }
+
+    /**
+     * Removes the specified items from the database
+     */
+    public void deleteItemsFromDatabase(final Iterable<? extends ItemInfo> items) {
+        mWorkerExecutor.execute(new Runnable() {
+            public void run() {
+                for (ItemInfo item : items) {
+                    final Uri uri = Favorites.getContentUri(item.id);
+                    mContext.getContentResolver().delete(uri, null, null);
+
+                    mBgDataModel.removeItem(mContext, item);
+                }
+            }
+        });
+    }
+
+    /**
+     * Remove the specified folder and all its contents from the database.
+     */
+    public void deleteFolderAndContentsFromDatabase(final FolderInfo info) {
+        mWorkerExecutor.execute(new Runnable() {
+            public void run() {
+                ContentResolver cr = mContext.getContentResolver();
+                cr.delete(LauncherSettings.Favorites.CONTENT_URI,
+                        LauncherSettings.Favorites.CONTAINER + "=" + info.id, null);
+                mBgDataModel.removeItem(mContext, info.contents);
+                info.contents.clear();
+
+                cr.delete(LauncherSettings.Favorites.getContentUri(info.id), null, null);
+                mBgDataModel.removeItem(mContext, info);
+            }
+        });
+    }
+
+    private class UpdateItemRunnable extends UpdateItemBaseRunnable {
+        private final ItemInfo mItem;
+        private final ContentWriter mWriter;
+        private final long mItemId;
+
+        UpdateItemRunnable(ItemInfo item, ContentWriter writer) {
+            mItem = item;
+            mWriter = writer;
+            mItemId = item.id;
+        }
+
+        @Override
+        public void run() {
+            Uri uri = Favorites.getContentUri(mItemId);
+            mContext.getContentResolver().update(uri, mWriter.getValues(mContext), null, null);
+            updateItemArrays(mItem, mItemId);
+        }
+    }
+
+    private class UpdateItemsRunnable extends UpdateItemBaseRunnable {
+        private final ArrayList<ContentValues> mValues;
+        private final ArrayList<ItemInfo> mItems;
+
+        UpdateItemsRunnable(ArrayList<ItemInfo> items, ArrayList<ContentValues> values) {
+            mValues = values;
+            mItems = items;
+        }
+
+        @Override
+        public void run() {
+            ArrayList<ContentProviderOperation> ops = new ArrayList<>();
+            int count = mItems.size();
+            for (int i = 0; i < count; i++) {
+                ItemInfo item = mItems.get(i);
+                final long itemId = item.id;
+                final Uri uri = Favorites.getContentUri(itemId);
+                ContentValues values = mValues.get(i);
+
+                ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build());
+                updateItemArrays(item, itemId);
+            }
+            try {
+                mContext.getContentResolver().applyBatch(LauncherProvider.AUTHORITY, ops);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    private abstract class UpdateItemBaseRunnable implements Runnable {
+        private final StackTraceElement[] mStackTrace;
+
+        UpdateItemBaseRunnable() {
+            mStackTrace = new Throwable().getStackTrace();
+        }
+
+        protected void updateItemArrays(ItemInfo item, long itemId) {
+            // Lock on mBgLock *after* the db operation
+            synchronized (mBgDataModel) {
+                checkItemInfoLocked(itemId, item, mStackTrace);
+
+                if (item.container != Favorites.CONTAINER_DESKTOP &&
+                        item.container != Favorites.CONTAINER_HOTSEAT) {
+                    // Item is in a folder, make sure this folder exists
+                    if (!mBgDataModel.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: " +
+                                item.container + ", not in the list of folders";
+                        Log.e(TAG, msg);
+                    }
+                }
+
+                // 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 = mBgDataModel.itemsIdMap.get(itemId);
+                if (modelItem != null &&
+                        (modelItem.container == Favorites.CONTAINER_DESKTOP ||
+                                modelItem.container == Favorites.CONTAINER_HOTSEAT)) {
+                    switch (modelItem.itemType) {
+                        case Favorites.ITEM_TYPE_APPLICATION:
+                        case Favorites.ITEM_TYPE_SHORTCUT:
+                        case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
+                        case Favorites.ITEM_TYPE_FOLDER:
+                            if (!mBgDataModel.workspaceItems.contains(modelItem)) {
+                                mBgDataModel.workspaceItems.add(modelItem);
+                            }
+                            break;
+                        default:
+                            break;
+                    }
+                } else {
+                    mBgDataModel.workspaceItems.remove(modelItem);
+                }
+            }
+        }
+    }
+}
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..baeaa94 100644
--- a/src/com/android/launcher3/model/PackageItemInfo.java
+++ b/src/com/android/launcher3/model/PackageItemInfo.java
@@ -16,37 +16,19 @@
 
 package com.android.launcher3.model;
 
-import android.graphics.Bitmap;
-
-import com.android.launcher3.ItemInfo;
+import com.android.launcher3.ItemInfoWithIcon;
 
 /**
  * Represents a {@link Package} in the widget tray section.
  */
-public class PackageItemInfo extends ItemInfo {
+public class PackageItemInfo extends ItemInfoWithIcon {
 
     /**
-     * A bitmap version of the application icon.
-     */
-    public Bitmap iconBitmap;
-
-    /**
-     * Indicates whether we're using a low res icon.
-     */
-    public boolean usingLowResIcon;
-
-    /**
-     * Package name of the {@link ItemInfo}.
+     * Package name of the {@link PackageItemInfo}.
      */
     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) {
+    public 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..f03c9c7
--- /dev/null
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -0,0 +1,384 @@
+/*
+ * 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.os.UserHandle;
+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.LauncherSettings.Favorites;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.LauncherAppsCompat;
+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 com.android.launcher3.util.PackageUserKey;
+
+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 UserHandle mUser;
+    private final String[] mPackages;
+
+    public PackageUpdatedTask(int op, UserHandle 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, context);
+                            if (icon != null) {
+                                si.iconBitmap = 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.intent = intent;
+                                    }
+                                }
+
+                                si.status = ShortcutInfo.DEFAULT;
+                                infoUpdated = true;
+                                if (si.itemType == Favorites.ITEM_TYPE_APPLICATION) {
+                                    iconCache.getTitleAndIcon(si, si.usingLowResIcon);
+                                }
+                            }
+
+                            if (appInfo != null && Intent.ACTION_MAIN.equals(si.intent.getAction())
+                                    && si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
+                                iconCache.getTitleAndIcon(si, si.usingLowResIcon);
+                                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) {
+                            getModelWriter().updateItemInDatabase(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);
+                            getModelWriter().updateItemInDatabase(widgetInfo);
+                        }
+                    }
+                }
+            }
+
+            bindUpdatedShortcuts(updatedShortcuts, removedShortcuts, mUser);
+            if (!removedShortcuts.isEmpty()) {
+                getModelWriter().deleteItemsFromDatabase(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()) {
+            getModelWriter().deleteItemsFromDatabase(
+                    ItemInfoMatcher.ofPackages(removedPackages, mUser));
+            getModelWriter().deleteItemsFromDatabase(
+                    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();
+                }
+            });
+        } else if (Utilities.isAtLeastO() && mOp == OP_ADD) {
+            // Load widgets for the new package.
+            for (int i = 0; i < N; i++) {
+                LauncherModel model = app.getModel();
+                model.refreshAndBindWidgetsAndShortcuts(
+                        model.getCallback(), false /* bindFirst */,
+                        new PackageUserKey(packages[i], mUser) /* packageUser */);
+            }
+        }
+    }
+}
diff --git a/src/com/android/launcher3/model/SdCardAvailableReceiver.java b/src/com/android/launcher3/model/SdCardAvailableReceiver.java
new file mode 100644
index 0000000..278669b
--- /dev/null
+++ b/src/com/android/launcher3/model/SdCardAvailableReceiver.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.model;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.UserHandle;
+
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.compat.LauncherAppsCompat;
+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<UserHandle, String> mPackages;
+
+    public SdCardAvailableReceiver(LauncherModel model, Context context,
+            MultiHashMap<UserHandle, String> packages) {
+        mModel = model;
+        mContext = context;
+        mPackages = packages;
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
+        final PackageManagerHelper pmHelper = new PackageManagerHelper(context);
+        for (Entry<UserHandle, ArrayList<String>> entry : mPackages.entrySet()) {
+            UserHandle 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 (pmHelper.isAppOnSdcard(pkg, user)) {
+                        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..d8a429c
--- /dev/null
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java
@@ -0,0 +1,116 @@
+/*
+ * 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.os.UserHandle;
+
+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.graphics.LauncherIcons;
+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 UserHandle mUser;
+    private final boolean mUpdateIdMap;
+
+    public ShortcutsChangedTask(String packageName, List<ShortcutInfoCompat> shortcuts,
+            UserHandle 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.getIntent().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);
+                    shortcutInfo.iconBitmap =
+                            LauncherIcons.createShortcutIcon(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()) {
+            getModelWriter().deleteItemsFromDatabase(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..363f1ee
--- /dev/null
+++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java
@@ -0,0 +1,115 @@
+/*
+ * 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.os.UserHandle;
+
+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.UserManagerCompat;
+import com.android.launcher3.graphics.LauncherIcons;
+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 UserHandle mUser;
+
+    public UserLockStateChangedTask(UserHandle 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.fromItemInfo(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);
+                    si.iconBitmap = LauncherIcons.createShortcutIcon(shortcut, context);
+                } else {
+                    si.isDisabled |= ShortcutInfo.FLAG_DISABLED_LOCKED_USER;
+                }
+                updatedShortcutInfos.add(si);
+            }
+        }
+        bindUpdatedShortcuts(updatedShortcutInfos, deletedShortcutInfos, mUser);
+        if (!deletedShortcutInfos.isEmpty()) {
+            getModelWriter().deleteItemsFromDatabase(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/WidgetItem.java b/src/com/android/launcher3/model/WidgetItem.java
index 0d7ba1e..1e96dec 100644
--- a/src/com/android/launcher3/model/WidgetItem.java
+++ b/src/com/android/launcher3/model/WidgetItem.java
@@ -1,16 +1,14 @@
 package com.android.launcher3.model;
 
-import android.content.ComponentName;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
+import android.os.Process;
+import android.os.UserHandle;
 
 import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.compat.AppWidgetManagerCompat;
-import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.compat.ShortcutConfigActivityInfo;
 import com.android.launcher3.util.ComponentKey;
 
 import java.text.Collator;
@@ -22,33 +20,32 @@
  */
 public class WidgetItem extends ComponentKey implements Comparable<WidgetItem> {
 
-    private static UserHandleCompat sMyUserHandle;
+    private static UserHandle sMyUserHandle;
     private static Collator sCollator;
 
     public final LauncherAppWidgetProviderInfo widgetInfo;
-    public final ActivityInfo activityInfo;
+    public final ShortcutConfigActivityInfo activityInfo;
 
     public final String label;
     public final int spanX, spanY;
 
-    public WidgetItem(LauncherAppWidgetProviderInfo info, AppWidgetManagerCompat widgetManager) {
-        super(info.provider, widgetManager.getUser(info));
+    public WidgetItem(LauncherAppWidgetProviderInfo info, PackageManager pm,
+            InvariantDeviceProfile idp) {
+        super(info.provider, info.getProfile());
 
-        label = Utilities.trim(widgetManager.loadLabel(info));
+        label = Utilities.trim(info.getLabel(pm));
         widgetInfo = info;
         activityInfo = null;
 
-        InvariantDeviceProfile idv = LauncherAppState.getInstance().getInvariantDeviceProfile();
-        spanX = Math.min(info.spanX, idv.numColumns);
-        spanY = Math.min(info.spanY, idv.numRows);
+        spanX = Math.min(info.spanX, idp.numColumns);
+        spanY = Math.min(info.spanY, idp.numRows);
     }
 
-    public WidgetItem(ResolveInfo info, PackageManager pm) {
-        super(new ComponentName(info.activityInfo.packageName, info.activityInfo.name),
-                UserHandleCompat.myUserHandle());
-        label = Utilities.trim(info.loadLabel(pm));
+    public WidgetItem(ShortcutConfigActivityInfo info) {
+        super(info.getComponent(), info.getUser());
+        label = Utilities.trim(info.getLabel());
         widgetInfo = null;
-        activityInfo = info.activityInfo;
+        activityInfo = info;
         spanX = spanY = 1;
     }
 
@@ -56,7 +53,7 @@
     public int compareTo(WidgetItem another) {
         if (sMyUserHandle == null) {
             // Delay these object creation until required.
-            sMyUserHandle = UserHandleCompat.myUserHandle();
+            sMyUserHandle = Process.myUserHandle();
             sCollator = Collator.getInstance();
         }
 
diff --git a/src/com/android/launcher3/model/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java
index b2a94bb..e5215c7 100644
--- a/src/com/android/launcher3/model/WidgetsModel.java
+++ b/src/com/android/launcher3/model/WidgetsModel.java
@@ -3,29 +3,29 @@
 
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
-import android.content.Intent;
 import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.os.DeadObjectException;
-import android.os.TransactionTooLargeException;
+import android.os.Process;
+import android.os.UserHandle;
+import android.support.annotation.Nullable;
 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.LauncherAppsCompat;
+import com.android.launcher3.compat.ShortcutConfigActivityInfo;
 import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.util.MultiHashMap;
+import com.android.launcher3.util.PackageUserKey;
 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;
+import java.util.Iterator;
 
 /**
  * Widgets data model that is used by the adapters of the widget views and controllers.
@@ -37,93 +37,53 @@
     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) {
+    /**
+     * @param packageUser If null, all widgets and shortcuts are updated and returned, otherwise
+     *                    only widgets and shortcuts associated with the package/user are.
+     */
+    public ArrayList<WidgetItem> update(Context context, @Nullable PackageUserKey packageUser) {
         Preconditions.assertWorkerThread();
 
+        final ArrayList<WidgetItem> widgetsAndShortcuts = new ArrayList<>();
         try {
-            final ArrayList<WidgetItem> widgetsAndShortcuts = new ArrayList<>();
+            PackageManager pm = context.getPackageManager();
+            InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
+
             // Widgets
             AppWidgetManagerCompat widgetManager = AppWidgetManagerCompat.getInstance(context);
-            for (AppWidgetProviderInfo widgetInfo : widgetManager.getAllProviders()) {
-                widgetsAndShortcuts.add(new WidgetItem(
-                        LauncherAppWidgetProviderInfo.fromProviderInfo(context, widgetInfo),
-                        widgetManager));
+            for (AppWidgetProviderInfo widgetInfo : widgetManager.getAllProviders(packageUser)) {
+                widgetsAndShortcuts.add(new WidgetItem(LauncherAppWidgetProviderInfo
+                        .fromProviderInfo(context, widgetInfo), pm, idp));
             }
 
             // Shortcuts
-            PackageManager pm = context.getPackageManager();
-            for (ResolveInfo info :
-                    pm.queryIntentActivities(new Intent(Intent.ACTION_CREATE_SHORTCUT), 0)) {
-                widgetsAndShortcuts.add(new WidgetItem(info, pm));
+            for (ShortcutConfigActivityInfo info : LauncherAppsCompat.getInstance(context)
+                    .getCustomShortcutActivityList(packageUser)) {
+                widgetsAndShortcuts.add(new WidgetItem(info));
             }
-            setWidgetsAndShortcuts(widgetsAndShortcuts);
+            setWidgetsAndShortcuts(widgetsAndShortcuts, context, packageUser);
         } 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 +92,11 @@
                 throw e;
             }
         }
-        return clone();
+        return widgetsAndShortcuts;
     }
 
-    private void setWidgetsAndShortcuts(ArrayList<WidgetItem> rawWidgetsShortcuts) {
-        mRawList = rawWidgetsShortcuts;
+    private void setWidgetsAndShortcuts(ArrayList<WidgetItem> rawWidgetsShortcuts,
+            Context context, @Nullable PackageUserKey packageUser) {
         if (DEBUG) {
             Log.d(TAG, "addWidgetsAndShortcuts, widgetsShortcuts#=" + rawWidgetsShortcuts.size());
         }
@@ -146,13 +106,38 @@
         HashMap<String, PackageItemInfo> tmpPackageItemInfos = new HashMap<>();
 
         // clear the lists.
-        mWidgetsList.clear();
-        mPackageItemInfos.clear();
+        if (packageUser == null) {
+            mWidgetsList.clear();
+        } else {
+            // Only clear the widgets for the given package/user.
+            PackageItemInfo packageItem = null;
+            for (PackageItemInfo item : mWidgetsList.keySet()) {
+                if (item.packageName.equals(packageUser.mPackageName)) {
+                    packageItem = item;
+                    break;
+                }
+            }
+            if (packageItem != null) {
+                // We want to preserve the user that was on the packageItem previously,
+                // so add it to tmpPackageItemInfos here to avoid creating a new entry.
+                tmpPackageItemInfos.put(packageItem.packageName, packageItem);
 
-        InvariantDeviceProfile idp = LauncherAppState.getInstance().getInvariantDeviceProfile();
+                Iterator<WidgetItem> widgetItemIterator = mWidgetsList.get(packageItem).iterator();
+                while (widgetItemIterator.hasNext()) {
+                    WidgetItem nextWidget = widgetItemIterator.next();
+                    if (nextWidget.componentName.getPackageName().equals(packageUser.mPackageName)
+                            && nextWidget.user.equals(packageUser.mUser)) {
+                        widgetItemIterator.remove();
+                    }
+                }
+            }
+        }
+
+        InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
+        UserHandle myUser = Process.myUserHandle();
 
         // add and update.
-        for (WidgetItem item: rawWidgetsShortcuts) {
+        for (WidgetItem item : rawWidgetsShortcuts) {
             if (item.widgetInfo != null) {
                 // Ensure that all widgets we show can be added on a workspace of this size
                 int minSpanX = Math.min(item.widgetInfo.spanX, item.widgetInfo.minSpanX);
@@ -167,7 +152,7 @@
                 }
             }
 
-            if (mAppFilter != null && !mAppFilter.shouldShowApp(item.componentName)) {
+            if (!mAppFilter.shouldShowApp(item.componentName)) {
                 if (DEBUG) {
                     Log.d(TAG, String.format("%s is filtered and not added to the widget tray.",
                             item.componentName));
@@ -177,43 +162,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/notification/FlingAnimationUtils.java b/src/com/android/launcher3/notification/FlingAnimationUtils.java
new file mode 100644
index 0000000..a1f7e49
--- /dev/null
+++ b/src/com/android/launcher3/notification/FlingAnimationUtils.java
@@ -0,0 +1,356 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.notification;
+
+import android.animation.Animator;
+import android.content.Context;
+import android.view.ViewPropertyAnimator;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+
+/**
+ * Utility class to calculate general fling animation when the finger is released.
+ *
+ * This class was copied from com.android.systemui.statusbar.
+ */
+public class FlingAnimationUtils {
+
+    private static final float LINEAR_OUT_SLOW_IN_X2 = 0.35f;
+    private static final float LINEAR_OUT_SLOW_IN_X2_MAX = 0.68f;
+    private static final float LINEAR_OUT_FASTER_IN_X2 = 0.5f;
+    private static final float LINEAR_OUT_FASTER_IN_Y2_MIN = 0.4f;
+    private static final float LINEAR_OUT_FASTER_IN_Y2_MAX = 0.5f;
+    private static final float MIN_VELOCITY_DP_PER_SECOND = 250;
+    private static final float HIGH_VELOCITY_DP_PER_SECOND = 3000;
+
+    private static final float LINEAR_OUT_SLOW_IN_START_GRADIENT = 0.75f;
+    private final float mSpeedUpFactor;
+    private final float mY2;
+
+    private float mMinVelocityPxPerSecond;
+    private float mMaxLengthSeconds;
+    private float mHighVelocityPxPerSecond;
+    private float mLinearOutSlowInX2;
+
+    private AnimatorProperties mAnimatorProperties = new AnimatorProperties();
+    private PathInterpolator mInterpolator;
+    private float mCachedStartGradient = -1;
+    private float mCachedVelocityFactor = -1;
+
+    public FlingAnimationUtils(Context ctx, float maxLengthSeconds) {
+        this(ctx, maxLengthSeconds, 0.0f);
+    }
+
+    /**
+     * @param maxLengthSeconds the longest duration an animation can become in seconds
+     * @param speedUpFactor a factor from 0 to 1 how much the slow down should be shifted towards
+     *                      the end of the animation. 0 means it's at the beginning and no
+     *                      acceleration will take place.
+     */
+    public FlingAnimationUtils(Context ctx, float maxLengthSeconds, float speedUpFactor) {
+        this(ctx, maxLengthSeconds, speedUpFactor, -1.0f, 1.0f);
+    }
+
+    /**
+     * @param maxLengthSeconds the longest duration an animation can become in seconds
+     * @param speedUpFactor a factor from 0 to 1 how much the slow down should be shifted towards
+     *                      the end of the animation. 0 means it's at the beginning and no
+     *                      acceleration will take place.
+     * @param x2 the x value to take for the second point of the bezier spline. If a value below 0
+     *           is provided, the value is automatically calculated.
+     * @param y2 the y value to take for the second point of the bezier spline
+     */
+    public FlingAnimationUtils(Context ctx, float maxLengthSeconds, float speedUpFactor, float x2,
+            float y2) {
+        mMaxLengthSeconds = maxLengthSeconds;
+        mSpeedUpFactor = speedUpFactor;
+        if (x2 < 0) {
+            mLinearOutSlowInX2 = interpolate(LINEAR_OUT_SLOW_IN_X2,
+                    LINEAR_OUT_SLOW_IN_X2_MAX,
+                    mSpeedUpFactor);
+        } else {
+            mLinearOutSlowInX2 = x2;
+        }
+        mY2 = y2;
+
+        mMinVelocityPxPerSecond
+                = MIN_VELOCITY_DP_PER_SECOND * ctx.getResources().getDisplayMetrics().density;
+        mHighVelocityPxPerSecond
+                = HIGH_VELOCITY_DP_PER_SECOND * ctx.getResources().getDisplayMetrics().density;
+    }
+
+    private static float interpolate(float start, float end, float amount) {
+        return start * (1.0f - amount) + end * amount;
+    }
+
+    /**
+     * Applies the interpolator and length to the animator, such that the fling animation is
+     * consistent with the finger motion.
+     *
+     * @param animator the animator to apply
+     * @param currValue the current value
+     * @param endValue the end value of the animator
+     * @param velocity the current velocity of the motion
+     */
+    public void apply(Animator animator, float currValue, float endValue, float velocity) {
+        apply(animator, currValue, endValue, velocity, Math.abs(endValue - currValue));
+    }
+
+    /**
+     * Applies the interpolator and length to the animator, such that the fling animation is
+     * consistent with the finger motion.
+     *
+     * @param animator the animator to apply
+     * @param currValue the current value
+     * @param endValue the end value of the animator
+     * @param velocity the current velocity of the motion
+     */
+    public void apply(ViewPropertyAnimator animator, float currValue, float endValue,
+            float velocity) {
+        apply(animator, currValue, endValue, velocity, Math.abs(endValue - currValue));
+    }
+
+    /**
+     * Applies the interpolator and length to the animator, such that the fling animation is
+     * consistent with the finger motion.
+     *
+     * @param animator the animator to apply
+     * @param currValue the current value
+     * @param endValue the end value of the animator
+     * @param velocity the current velocity of the motion
+     * @param maxDistance the maximum distance for this interaction; the maximum animation length
+     *                    gets multiplied by the ratio between the actual distance and this value
+     */
+    public void apply(Animator animator, float currValue, float endValue, float velocity,
+            float maxDistance) {
+        AnimatorProperties properties = getProperties(currValue, endValue, velocity,
+                maxDistance);
+        animator.setDuration(properties.duration);
+        animator.setInterpolator(properties.interpolator);
+    }
+
+    /**
+     * Applies the interpolator and length to the animator, such that the fling animation is
+     * consistent with the finger motion.
+     *
+     * @param animator the animator to apply
+     * @param currValue the current value
+     * @param endValue the end value of the animator
+     * @param velocity the current velocity of the motion
+     * @param maxDistance the maximum distance for this interaction; the maximum animation length
+     *                    gets multiplied by the ratio between the actual distance and this value
+     */
+    public void apply(ViewPropertyAnimator animator, float currValue, float endValue,
+            float velocity, float maxDistance) {
+        AnimatorProperties properties = getProperties(currValue, endValue, velocity,
+                maxDistance);
+        animator.setDuration(properties.duration);
+        animator.setInterpolator(properties.interpolator);
+    }
+
+    private AnimatorProperties getProperties(float currValue,
+            float endValue, float velocity, float maxDistance) {
+        float maxLengthSeconds = (float) (mMaxLengthSeconds
+                * Math.sqrt(Math.abs(endValue - currValue) / maxDistance));
+        float diff = Math.abs(endValue - currValue);
+        float velAbs = Math.abs(velocity);
+        float velocityFactor = mSpeedUpFactor == 0.0f
+                ? 1.0f : Math.min(velAbs / HIGH_VELOCITY_DP_PER_SECOND, 1.0f);
+        float startGradient = interpolate(LINEAR_OUT_SLOW_IN_START_GRADIENT,
+                mY2 / mLinearOutSlowInX2, velocityFactor);
+        float durationSeconds = startGradient * diff / velAbs;
+        Interpolator slowInInterpolator = getInterpolator(startGradient, velocityFactor);
+        if (durationSeconds <= maxLengthSeconds) {
+            mAnimatorProperties.interpolator = slowInInterpolator;
+        } else if (velAbs >= mMinVelocityPxPerSecond) {
+
+            // Cross fade between fast-out-slow-in and linear interpolator with current velocity.
+            durationSeconds = maxLengthSeconds;
+            VelocityInterpolator velocityInterpolator
+                    = new VelocityInterpolator(durationSeconds, velAbs, diff);
+            InterpolatorInterpolator superInterpolator = new InterpolatorInterpolator(
+                    velocityInterpolator, slowInInterpolator, Interpolators.LINEAR_OUT_SLOW_IN);
+            mAnimatorProperties.interpolator = superInterpolator;
+        } else {
+
+            // Just use a normal interpolator which doesn't take the velocity into account.
+            durationSeconds = maxLengthSeconds;
+            mAnimatorProperties.interpolator = Interpolators.FAST_OUT_SLOW_IN;
+        }
+        mAnimatorProperties.duration = (long) (durationSeconds * 1000);
+        return mAnimatorProperties;
+    }
+
+    private Interpolator getInterpolator(float startGradient, float velocityFactor) {
+        if (startGradient != mCachedStartGradient
+                || velocityFactor != mCachedVelocityFactor) {
+            float speedup = mSpeedUpFactor * (1.0f - velocityFactor);
+            mInterpolator = new PathInterpolator(speedup,
+                    speedup * startGradient,
+                    mLinearOutSlowInX2, mY2);
+            mCachedStartGradient = startGradient;
+            mCachedVelocityFactor = velocityFactor;
+        }
+        return mInterpolator;
+    }
+
+    /**
+     * Applies the interpolator and length to the animator, such that the fling animation is
+     * consistent with the finger motion for the case when the animation is making something
+     * disappear.
+     *
+     * @param animator the animator to apply
+     * @param currValue the current value
+     * @param endValue the end value of the animator
+     * @param velocity the current velocity of the motion
+     * @param maxDistance the maximum distance for this interaction; the maximum animation length
+     *                    gets multiplied by the ratio between the actual distance and this value
+     */
+    public void applyDismissing(Animator animator, float currValue, float endValue,
+            float velocity, float maxDistance) {
+        AnimatorProperties properties = getDismissingProperties(currValue, endValue, velocity,
+                maxDistance);
+        animator.setDuration(properties.duration);
+        animator.setInterpolator(properties.interpolator);
+    }
+
+    /**
+     * Applies the interpolator and length to the animator, such that the fling animation is
+     * consistent with the finger motion for the case when the animation is making something
+     * disappear.
+     *
+     * @param animator the animator to apply
+     * @param currValue the current value
+     * @param endValue the end value of the animator
+     * @param velocity the current velocity of the motion
+     * @param maxDistance the maximum distance for this interaction; the maximum animation length
+     *                    gets multiplied by the ratio between the actual distance and this value
+     */
+    public void applyDismissing(ViewPropertyAnimator animator, float currValue, float endValue,
+            float velocity, float maxDistance) {
+        AnimatorProperties properties = getDismissingProperties(currValue, endValue, velocity,
+                maxDistance);
+        animator.setDuration(properties.duration);
+        animator.setInterpolator(properties.interpolator);
+    }
+
+    private AnimatorProperties getDismissingProperties(float currValue, float endValue,
+            float velocity, float maxDistance) {
+        float maxLengthSeconds = (float) (mMaxLengthSeconds
+                * Math.pow(Math.abs(endValue - currValue) / maxDistance, 0.5f));
+        float diff = Math.abs(endValue - currValue);
+        float velAbs = Math.abs(velocity);
+        float y2 = calculateLinearOutFasterInY2(velAbs);
+
+        float startGradient = y2 / LINEAR_OUT_FASTER_IN_X2;
+        Interpolator mLinearOutFasterIn = new PathInterpolator(0, 0, LINEAR_OUT_FASTER_IN_X2, y2);
+        float durationSeconds = startGradient * diff / velAbs;
+        if (durationSeconds <= maxLengthSeconds) {
+            mAnimatorProperties.interpolator = mLinearOutFasterIn;
+        } else if (velAbs >= mMinVelocityPxPerSecond) {
+
+            // Cross fade between linear-out-faster-in and linear interpolator with current
+            // velocity.
+            durationSeconds = maxLengthSeconds;
+            VelocityInterpolator velocityInterpolator
+                    = new VelocityInterpolator(durationSeconds, velAbs, diff);
+            InterpolatorInterpolator superInterpolator = new InterpolatorInterpolator(
+                    velocityInterpolator, mLinearOutFasterIn, Interpolators.LINEAR_OUT_SLOW_IN);
+            mAnimatorProperties.interpolator = superInterpolator;
+        } else {
+
+            // Just use a normal interpolator which doesn't take the velocity into account.
+            durationSeconds = maxLengthSeconds;
+            mAnimatorProperties.interpolator = Interpolators.FAST_OUT_LINEAR_IN;
+        }
+        mAnimatorProperties.duration = (long) (durationSeconds * 1000);
+        return mAnimatorProperties;
+    }
+
+    /**
+     * Calculates the y2 control point for a linear-out-faster-in path interpolator depending on the
+     * velocity. The faster the velocity, the more "linear" the interpolator gets.
+     *
+     * @param velocity the velocity of the gesture.
+     * @return the y2 control point for a cubic bezier path interpolator
+     */
+    private float calculateLinearOutFasterInY2(float velocity) {
+        float t = (velocity - mMinVelocityPxPerSecond)
+                / (mHighVelocityPxPerSecond - mMinVelocityPxPerSecond);
+        t = Math.max(0, Math.min(1, t));
+        return (1 - t) * LINEAR_OUT_FASTER_IN_Y2_MIN + t * LINEAR_OUT_FASTER_IN_Y2_MAX;
+    }
+
+    /**
+     * @return the minimum velocity a gesture needs to have to be considered a fling
+     */
+    public float getMinVelocityPxPerSecond() {
+        return mMinVelocityPxPerSecond;
+    }
+
+    /**
+     * An interpolator which interpolates two interpolators with an interpolator.
+     */
+    private static final class InterpolatorInterpolator implements Interpolator {
+
+        private Interpolator mInterpolator1;
+        private Interpolator mInterpolator2;
+        private Interpolator mCrossfader;
+
+        InterpolatorInterpolator(Interpolator interpolator1, Interpolator interpolator2,
+                Interpolator crossfader) {
+            mInterpolator1 = interpolator1;
+            mInterpolator2 = interpolator2;
+            mCrossfader = crossfader;
+        }
+
+        @Override
+        public float getInterpolation(float input) {
+            float t = mCrossfader.getInterpolation(input);
+            return (1 - t) * mInterpolator1.getInterpolation(input)
+                    + t * mInterpolator2.getInterpolation(input);
+        }
+    }
+
+    /**
+     * An interpolator which interpolates with a fixed velocity.
+     */
+    private static final class VelocityInterpolator implements Interpolator {
+
+        private float mDurationSeconds;
+        private float mVelocity;
+        private float mDiff;
+
+        private VelocityInterpolator(float durationSeconds, float velocity, float diff) {
+            mDurationSeconds = durationSeconds;
+            mVelocity = velocity;
+            mDiff = diff;
+        }
+
+        @Override
+        public float getInterpolation(float input) {
+            float time = input * mDurationSeconds;
+            return time * mVelocity / mDiff;
+        }
+    }
+
+    private static class AnimatorProperties {
+        Interpolator interpolator;
+        long duration;
+    }
+
+}
diff --git a/src/com/android/launcher3/notification/Interpolators.java b/src/com/android/launcher3/notification/Interpolators.java
new file mode 100644
index 0000000..5c3b22a
--- /dev/null
+++ b/src/com/android/launcher3/notification/Interpolators.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.notification;
+
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+
+/**
+ * Utility class to receive interpolators from.
+ *
+ * This class was copied from com.android.systemui.
+ */
+public class Interpolators {
+    public static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
+    public static final Interpolator FAST_OUT_LINEAR_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
+    public static final Interpolator LINEAR_OUT_SLOW_IN = new PathInterpolator(0f, 0f, 0.2f, 1f);
+
+    /**
+     * Interpolator to be used when animating a move based on a click. Pair with enough duration.
+     */
+    public static final Interpolator TOUCH_RESPONSE =
+            new PathInterpolator(0.3f, 0f, 0.1f, 1f);
+}
diff --git a/src/com/android/launcher3/notification/NotificationFooterLayout.java b/src/com/android/launcher3/notification/NotificationFooterLayout.java
new file mode 100644
index 0000000..1eef743
--- /dev/null
+++ b/src/com/android/launcher3/notification/NotificationFooterLayout.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.notification;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAnimUtils;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.PropertyListBuilder;
+import com.android.launcher3.anim.PropertyResetListener;
+import com.android.launcher3.popup.PopupContainerWithArrow;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * A {@link FrameLayout} that contains only icons of notifications.
+ * If there are more than {@link #MAX_FOOTER_NOTIFICATIONS} icons, we add a "..." overflow.
+ */
+public class NotificationFooterLayout extends FrameLayout {
+
+    public interface IconAnimationEndListener {
+        void onIconAnimationEnd(NotificationInfo animatedNotification);
+    }
+
+    private static final int MAX_FOOTER_NOTIFICATIONS = 5;
+
+    private static final Rect sTempRect = new Rect();
+
+    private final List<NotificationInfo> mNotifications = new ArrayList<>();
+    private final List<NotificationInfo> mOverflowNotifications = new ArrayList<>();
+    private final boolean mRtl;
+
+    FrameLayout.LayoutParams mIconLayoutParams;
+    private View mOverflowEllipsis;
+    private LinearLayout mIconRow;
+    private int mBackgroundColor;
+
+    public NotificationFooterLayout(Context context) {
+        this(context, null, 0);
+    }
+
+    public NotificationFooterLayout(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public NotificationFooterLayout(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        Resources res = getResources();
+        mRtl = Utilities.isRtl(res);
+
+        int iconSize = res.getDimensionPixelSize(R.dimen.notification_footer_icon_size);
+        mIconLayoutParams = new LayoutParams(iconSize, iconSize);
+        mIconLayoutParams.gravity = Gravity.CENTER_VERTICAL;
+        // Compute margin start for each icon such that the icons between the first one
+        // and the ellipsis are evenly spaced out.
+        int paddingEnd = res.getDimensionPixelSize(R.dimen.notification_footer_icon_row_padding);
+        int ellipsisSpace = res.getDimensionPixelSize(R.dimen.horizontal_ellipsis_offset)
+                + res.getDimensionPixelSize(R.dimen.horizontal_ellipsis_size);
+        int footerWidth = res.getDimensionPixelSize(R.dimen.bg_popup_item_width);
+        int availableIconRowSpace = footerWidth - paddingEnd - ellipsisSpace
+                - iconSize * MAX_FOOTER_NOTIFICATIONS;
+        mIconLayoutParams.setMarginStart(availableIconRowSpace / MAX_FOOTER_NOTIFICATIONS);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mOverflowEllipsis = findViewById(R.id.overflow);
+        mIconRow = (LinearLayout) findViewById(R.id.icon_row);
+        mBackgroundColor = ((ColorDrawable) getBackground()).getColor();
+    }
+
+    /**
+     * Keep track of the NotificationInfo, and then update the UI when
+     * {@link #commitNotificationInfos()} is called.
+     */
+    public void addNotificationInfo(final NotificationInfo notificationInfo) {
+        if (mNotifications.size() < MAX_FOOTER_NOTIFICATIONS) {
+            mNotifications.add(notificationInfo);
+        } else {
+            mOverflowNotifications.add(notificationInfo);
+        }
+    }
+
+    /**
+     * Adds icons and potentially overflow text for all of the NotificationInfo's
+     * added using {@link #addNotificationInfo(NotificationInfo)}.
+     */
+    public void commitNotificationInfos() {
+        mIconRow.removeAllViews();
+
+        for (int i = 0; i < mNotifications.size(); i++) {
+            NotificationInfo info = mNotifications.get(i);
+            addNotificationIconForInfo(info);
+        }
+        updateOverflowEllipsisVisibility();
+    }
+
+    private void updateOverflowEllipsisVisibility() {
+        mOverflowEllipsis.setVisibility(mOverflowNotifications.isEmpty() ? GONE : VISIBLE);
+    }
+
+    /**
+     * Creates an icon for the given NotificationInfo, and adds it to the icon row.
+     * @return the icon view that was added
+     */
+    private View addNotificationIconForInfo(NotificationInfo info) {
+        View icon = new View(getContext());
+        icon.setBackground(info.getIconForBackground(getContext(), mBackgroundColor));
+        icon.setOnClickListener(info);
+        icon.setTag(info);
+        mIconRow.addView(icon, 0, mIconLayoutParams);
+        return icon;
+    }
+
+    public void animateFirstNotificationTo(Rect toBounds,
+            final IconAnimationEndListener callback) {
+        AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
+        final View firstNotification = mIconRow.getChildAt(mIconRow.getChildCount() - 1);
+
+        Rect fromBounds = sTempRect;
+        firstNotification.getGlobalVisibleRect(fromBounds);
+        float scale = (float) toBounds.height() / fromBounds.height();
+        Animator moveAndScaleIcon = LauncherAnimUtils.ofPropertyValuesHolder(firstNotification,
+                new PropertyListBuilder().scale(scale).translationY(toBounds.top - fromBounds.top
+                        + (fromBounds.height() * scale - fromBounds.height()) / 2).build());
+        moveAndScaleIcon.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                callback.onIconAnimationEnd((NotificationInfo) firstNotification.getTag());
+                removeViewFromIconRow(firstNotification);
+            }
+        });
+        animation.play(moveAndScaleIcon);
+
+        // Shift all notifications (not the overflow) over to fill the gap.
+        int gapWidth = mIconLayoutParams.width + mIconLayoutParams.getMarginStart();
+        if (mRtl) {
+            gapWidth = -gapWidth;
+        }
+        if (!mOverflowNotifications.isEmpty()) {
+            NotificationInfo notification = mOverflowNotifications.remove(0);
+            mNotifications.add(notification);
+            View iconFromOverflow = addNotificationIconForInfo(notification);
+            animation.play(ObjectAnimator.ofFloat(iconFromOverflow, ALPHA, 0, 1));
+        }
+        int numIcons = mIconRow.getChildCount() - 1; // All children besides the one leaving.
+        // We have to reset the translation X to 0 when the new main notification
+        // is removed from the footer.
+        PropertyResetListener<View, Float> propertyResetListener
+                = new PropertyResetListener<>(TRANSLATION_X, 0f);
+        for (int i = 0; i < numIcons; i++) {
+            final View child = mIconRow.getChildAt(i);
+            Animator shiftChild = ObjectAnimator.ofFloat(child, TRANSLATION_X, gapWidth);
+            shiftChild.addListener(propertyResetListener);
+            animation.play(shiftChild);
+        }
+        animation.start();
+    }
+
+    private void removeViewFromIconRow(View child) {
+        mIconRow.removeView(child);
+        mNotifications.remove((NotificationInfo) child.getTag());
+        updateOverflowEllipsisVisibility();
+        if (mIconRow.getChildCount() == 0) {
+            // There are no more icons in the footer, so hide it.
+            PopupContainerWithArrow popup = PopupContainerWithArrow.getOpen(
+                    Launcher.getLauncher(getContext()));
+            if (popup != null) {
+                Animator collapseFooter = popup.reduceNotificationViewHeight(getHeight(),
+                        getResources().getInteger(R.integer.config_removeNotificationViewDuration));
+                collapseFooter.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        ((ViewGroup) getParent()).removeView(NotificationFooterLayout.this);
+                    }
+                });
+                collapseFooter.start();
+            }
+        }
+    }
+
+    public void trimNotifications(List<String> notifications) {
+        if (!isAttachedToWindow() || mIconRow.getChildCount() == 0) {
+            return;
+        }
+        Iterator<NotificationInfo> overflowIterator = mOverflowNotifications.iterator();
+        while (overflowIterator.hasNext()) {
+            if (!notifications.contains(overflowIterator.next().notificationKey)) {
+                overflowIterator.remove();
+            }
+        }
+        for (int i = mIconRow.getChildCount() - 1; i >= 0; i--) {
+            View child = mIconRow.getChildAt(i);
+            NotificationInfo childInfo = (NotificationInfo) child.getTag();
+            if (!notifications.contains(childInfo.notificationKey)) {
+                removeViewFromIconRow(child);
+            }
+        }
+    }
+}
diff --git a/src/com/android/launcher3/notification/NotificationInfo.java b/src/com/android/launcher3/notification/NotificationInfo.java
new file mode 100644
index 0000000..1a93e11
--- /dev/null
+++ b/src/com/android/launcher3/notification/NotificationInfo.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.notification;
+
+import android.app.ActivityOptions;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.service.notification.StatusBarNotification;
+import android.view.View;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.graphics.IconPalette;
+import com.android.launcher3.popup.PopupContainerWithArrow;
+import com.android.launcher3.util.PackageUserKey;
+
+/**
+ * An object that contains relevant information from a {@link StatusBarNotification}. This should
+ * only be created when we need to show the notification contents on the UI; until then, a
+ * {@link com.android.launcher3.badge.BadgeInfo} with only the notification key should
+ * be passed around, and then this can be constructed using the StatusBarNotification from
+ * {@link NotificationListener#getNotificationsForKeys(java.util.List)}.
+ */
+public class NotificationInfo implements View.OnClickListener {
+
+    public final PackageUserKey packageUserKey;
+    public final String notificationKey;
+    public final CharSequence title;
+    public final CharSequence text;
+    public final PendingIntent intent;
+    public final boolean autoCancel;
+    public final boolean dismissable;
+
+    private int mBadgeIcon;
+    private Drawable mIconDrawable;
+    private int mIconColor;
+    private boolean mIsIconLarge;
+
+    /**
+     * Extracts the data that we need from the StatusBarNotification.
+     */
+    public NotificationInfo(Context context, StatusBarNotification statusBarNotification) {
+        packageUserKey = PackageUserKey.fromNotification(statusBarNotification);
+        notificationKey = statusBarNotification.getKey();
+        Notification notification = statusBarNotification.getNotification();
+        title = notification.extras.getCharSequence(Notification.EXTRA_TITLE);
+        text = notification.extras.getCharSequence(Notification.EXTRA_TEXT);
+
+        mBadgeIcon = notification.getBadgeIconType();
+        // Load the icon. Since it is backed by ashmem, we won't copy the entire bitmap
+        // into our process as long as we don't touch it and it exists in systemui.
+        Icon icon = mBadgeIcon == Notification.BADGE_ICON_SMALL ? null : notification.getLargeIcon();
+        if (icon == null) {
+            // Use the small icon.
+            icon = notification.getSmallIcon();
+            mIconDrawable = icon.loadDrawable(context);
+            mIconColor = statusBarNotification.getNotification().color;
+            mIsIconLarge = false;
+        } else {
+            // Use the large icon.
+            mIconDrawable = icon.loadDrawable(context);
+            mIsIconLarge = true;
+        }
+        if (mIconDrawable == null) {
+            mIconDrawable = new BitmapDrawable(context.getResources(), LauncherAppState
+                    .getInstance(context).getIconCache()
+                    .getDefaultIcon(statusBarNotification.getUser()));
+            mBadgeIcon = Notification.BADGE_ICON_NONE;
+        }
+        intent = notification.contentIntent;
+        autoCancel = (notification.flags & Notification.FLAG_AUTO_CANCEL) != 0;
+        dismissable = (notification.flags & Notification.FLAG_ONGOING_EVENT) == 0;
+    }
+
+    @Override
+    public void onClick(View view) {
+        final Launcher launcher = Launcher.getLauncher(view.getContext());
+        Bundle activityOptions = ActivityOptions.makeClipRevealAnimation(
+                view, 0, 0, view.getWidth(), view.getHeight()).toBundle();
+        try {
+            intent.send(null, 0, null, null, null, null, activityOptions);
+            launcher.getUserEventDispatcher().logNotificationLaunch(view, intent);
+        } catch (PendingIntent.CanceledException e) {
+            e.printStackTrace();
+        }
+        if (autoCancel) {
+            launcher.getPopupDataProvider().cancelNotification(notificationKey);
+        }
+        PopupContainerWithArrow.getOpen(launcher).close(true);
+    }
+
+    public Drawable getIconForBackground(Context context, int background) {
+        if (mIsIconLarge) {
+            // Only small icons should be tinted.
+            return mIconDrawable;
+        }
+        mIconColor = IconPalette.resolveContrastColor(context, mIconColor, background);
+        Drawable icon = mIconDrawable.mutate();
+        // DrawableContainer ignores the color filter if it's already set, so clear it first to
+        // get it set and invalidated properly.
+        icon.setTintList(null);
+        icon.setTint(mIconColor);
+        return icon;
+    }
+
+    public boolean isIconLarge() {
+        return mIsIconLarge;
+    }
+
+    public boolean shouldShowIconInBadge() {
+        // If the icon we're using for this notification matches what the Notification
+        // specified should show in the badge, then return true.
+        return mIsIconLarge && mBadgeIcon == Notification.BADGE_ICON_LARGE
+                || !mIsIconLarge && mBadgeIcon == Notification.BADGE_ICON_SMALL;
+    }
+}
diff --git a/src/com/android/launcher3/notification/NotificationItemView.java b/src/com/android/launcher3/notification/NotificationItemView.java
new file mode 100644
index 0000000..dd272b3
--- /dev/null
+++ b/src/com/android/launcher3/notification/NotificationItemView.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.notification;
+
+import android.animation.Animator;
+import android.app.Notification;
+import android.content.Context;
+import android.graphics.Rect;
+import android.support.annotation.Nullable;
+import android.support.v4.content.ContextCompat;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.R;
+import com.android.launcher3.anim.PillHeightRevealOutlineProvider;
+import com.android.launcher3.graphics.IconPalette;
+import com.android.launcher3.logging.UserEventDispatcher.LogContainerProvider;
+import com.android.launcher3.popup.PopupItemView;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+
+import java.util.List;
+
+/**
+ * A {@link FrameLayout} that contains a header, main view and a footer.
+ * The main view contains the icon and text (title + subtext) of the first notification.
+ * The footer contains: A list of just the icons of all the notifications past the first one.
+ * @see NotificationFooterLayout
+ */
+public class NotificationItemView extends PopupItemView implements LogContainerProvider {
+
+    private static final Rect sTempRect = new Rect();
+
+    private TextView mHeaderCount;
+    private NotificationMainView mMainView;
+    private NotificationFooterLayout mFooter;
+    private SwipeHelper mSwipeHelper;
+    private boolean mAnimatingNextIcon;
+    private int mNotificationHeaderTextColor = Notification.COLOR_DEFAULT;
+
+    public NotificationItemView(Context context) {
+        this(context, null, 0);
+    }
+
+    public NotificationItemView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public NotificationItemView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mHeaderCount = (TextView) findViewById(R.id.notification_count);
+        mMainView = (NotificationMainView) findViewById(R.id.main_view);
+        mFooter = (NotificationFooterLayout) findViewById(R.id.footer);
+        mSwipeHelper = new SwipeHelper(SwipeHelper.X, mMainView, getContext());
+        mSwipeHelper.setDisableHardwareLayers(true);
+    }
+
+    public int getHeightMinusFooter() {
+        int footerHeight = mFooter.getParent() == null ? 0 : mFooter.getHeight();
+        return getHeight() - footerHeight;
+    }
+
+    public Animator animateHeightRemoval(int heightToRemove) {
+        final int newHeight = getHeight() - heightToRemove;
+        return new PillHeightRevealOutlineProvider(mPillRect,
+                getBackgroundRadius(), newHeight).createRevealAnimator(this, true /* isReversed */);
+    }
+
+    public void updateHeader(int notificationCount, @Nullable IconPalette palette) {
+        mHeaderCount.setText(notificationCount <= 1 ? "" : String.valueOf(notificationCount));
+        if (palette != null) {
+            if (mNotificationHeaderTextColor == Notification.COLOR_DEFAULT) {
+                mNotificationHeaderTextColor =
+                        IconPalette.resolveContrastColor(getContext(), palette.dominantColor,
+                            getResources().getColor(R.color.popup_header_background_color));
+            }
+            mHeaderCount.setTextColor(mNotificationHeaderTextColor);
+        }
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        if (mMainView.getNotificationInfo() == null) {
+            // The notification hasn't been populated yet.
+            return false;
+        }
+        getParent().requestDisallowInterceptTouchEvent(true);
+        return mSwipeHelper.onInterceptTouchEvent(ev);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        if (mMainView.getNotificationInfo() == null) {
+            // The notification hasn't been populated yet.
+            return false;
+        }
+        return mSwipeHelper.onTouchEvent(ev) || super.onTouchEvent(ev);
+    }
+
+    public void applyNotificationInfos(final List<NotificationInfo> notificationInfos) {
+        if (notificationInfos.isEmpty()) {
+            return;
+        }
+
+        NotificationInfo mainNotification = notificationInfos.get(0);
+        mMainView.applyNotificationInfo(mainNotification, mIconView);
+
+        for (int i = 1; i < notificationInfos.size(); i++) {
+            mFooter.addNotificationInfo(notificationInfos.get(i));
+        }
+        mFooter.commitNotificationInfos();
+    }
+
+    public void trimNotifications(final List<String> notificationKeys) {
+        boolean dismissedMainNotification = !notificationKeys.contains(
+                mMainView.getNotificationInfo().notificationKey);
+        if (dismissedMainNotification && !mAnimatingNextIcon) {
+            // Animate the next icon into place as the new main notification.
+            mAnimatingNextIcon = true;
+            mMainView.setVisibility(INVISIBLE);
+            mMainView.setTranslationX(0);
+            mIconView.getGlobalVisibleRect(sTempRect);
+            mFooter.animateFirstNotificationTo(sTempRect,
+                    new NotificationFooterLayout.IconAnimationEndListener() {
+                @Override
+                public void onIconAnimationEnd(NotificationInfo newMainNotification) {
+                    if (newMainNotification != null) {
+                        mMainView.applyNotificationInfo(newMainNotification, mIconView, true);
+                        mMainView.setVisibility(VISIBLE);
+                    }
+                    mAnimatingNextIcon = false;
+                }
+            });
+        } else {
+            mFooter.trimNotifications(notificationKeys);
+        }
+    }
+
+    @Override
+    public int getArrowColor(boolean isArrowAttachedToBottom) {
+        return ContextCompat.getColor(getContext(), isArrowAttachedToBottom
+                ? R.color.popup_background_color
+                : R.color.popup_header_background_color);
+    }
+
+    @Override
+    public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target,
+            LauncherLogProto.Target targetParent) {
+        target.itemType = LauncherLogProto.ItemType.NOTIFICATION;
+        targetParent.containerType = LauncherLogProto.ContainerType.DEEPSHORTCUTS;
+    }
+}
diff --git a/src/com/android/launcher3/notification/NotificationKeyData.java b/src/com/android/launcher3/notification/NotificationKeyData.java
new file mode 100644
index 0000000..bf7ae1a
--- /dev/null
+++ b/src/com/android/launcher3/notification/NotificationKeyData.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.notification;
+
+import android.app.Notification;
+import android.service.notification.StatusBarNotification;
+import android.support.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The key data associated with the notification, used to determine what to include
+ * in badges and dummy popup views before they are populated.
+ *
+ * @see NotificationInfo for the full data used when populating the dummy views.
+ */
+public class NotificationKeyData {
+    public final String notificationKey;
+    public final String shortcutId;
+    public int count;
+
+    private NotificationKeyData(String notificationKey, String shortcutId, int count) {
+        this.notificationKey = notificationKey;
+        this.shortcutId = shortcutId;
+        this.count = Math.max(1, count);
+    }
+
+    public static NotificationKeyData fromNotification(StatusBarNotification sbn) {
+        Notification notif = sbn.getNotification();
+        return new NotificationKeyData(sbn.getKey(), notif.getShortcutId(), notif.number);
+    }
+
+    public static List<String> extractKeysOnly(@NonNull List<NotificationKeyData> notificationKeys) {
+        List<String> keysOnly = new ArrayList<>(notificationKeys.size());
+        for (NotificationKeyData notificationKeyData : notificationKeys) {
+            keysOnly.add(notificationKeyData.notificationKey);
+        }
+        return keysOnly;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof NotificationKeyData)) {
+            return false;
+        }
+        // Only compare the keys.
+        return ((NotificationKeyData) obj).notificationKey.equals(notificationKey);
+    }
+}
diff --git a/src/com/android/launcher3/notification/NotificationListener.java b/src/com/android/launcher3/notification/NotificationListener.java
new file mode 100644
index 0000000..8dca699
--- /dev/null
+++ b/src/com/android/launcher3/notification/NotificationListener.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.notification;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+import android.support.annotation.Nullable;
+import android.support.v4.util.Pair;
+import android.text.TextUtils;
+
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.util.PackageUserKey;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A {@link NotificationListenerService} that sends updates to its
+ * {@link NotificationsChangedListener} when notifications are posted or canceled,
+ * as well and when this service first connects. An instance of NotificationListener,
+ * and its methods for getting notifications, can be obtained via {@link #getInstanceIfConnected()}.
+ */
+public class NotificationListener extends NotificationListenerService {
+
+    private static final int MSG_NOTIFICATION_POSTED = 1;
+    private static final int MSG_NOTIFICATION_REMOVED = 2;
+    private static final int MSG_NOTIFICATION_FULL_REFRESH = 3;
+
+    private static NotificationListener sNotificationListenerInstance = null;
+    private static NotificationsChangedListener sNotificationsChangedListener;
+    private static boolean sIsConnected;
+
+    private final Handler mWorkerHandler;
+    private final Handler mUiHandler;
+
+    private Ranking mTempRanking = new Ranking();
+
+    private Handler.Callback mWorkerCallback = new Handler.Callback() {
+        @Override
+        public boolean handleMessage(Message message) {
+            switch (message.what) {
+                case MSG_NOTIFICATION_POSTED:
+                    mUiHandler.obtainMessage(message.what, message.obj).sendToTarget();
+                    break;
+                case MSG_NOTIFICATION_REMOVED:
+                    mUiHandler.obtainMessage(message.what, message.obj).sendToTarget();
+                    break;
+                case MSG_NOTIFICATION_FULL_REFRESH:
+                    final List<StatusBarNotification> activeNotifications = sIsConnected
+                            ? filterNotifications(getActiveNotifications())
+                            : new ArrayList<StatusBarNotification>();
+                    mUiHandler.obtainMessage(message.what, activeNotifications).sendToTarget();
+                    break;
+            }
+            return true;
+        }
+    };
+
+    private Handler.Callback mUiCallback = new Handler.Callback() {
+        @Override
+        public boolean handleMessage(Message message) {
+            switch (message.what) {
+                case MSG_NOTIFICATION_POSTED:
+                    if (sNotificationsChangedListener != null) {
+                        NotificationPostedMsg msg = (NotificationPostedMsg) message.obj;
+                        sNotificationsChangedListener.onNotificationPosted(msg.packageUserKey,
+                                msg.notificationKey, msg.shouldBeFilteredOut);
+                    }
+                    break;
+                case MSG_NOTIFICATION_REMOVED:
+                    if (sNotificationsChangedListener != null) {
+                        Pair<PackageUserKey, NotificationKeyData> pair
+                                = (Pair<PackageUserKey, NotificationKeyData>) message.obj;
+                        sNotificationsChangedListener.onNotificationRemoved(pair.first, pair.second);
+                    }
+                    break;
+                case MSG_NOTIFICATION_FULL_REFRESH:
+                    if (sNotificationsChangedListener != null) {
+                        sNotificationsChangedListener.onNotificationFullRefresh(
+                                (List<StatusBarNotification>) message.obj);
+                    }
+                    break;
+            }
+            return true;
+        }
+    };
+
+    public NotificationListener() {
+        super();
+        mWorkerHandler = new Handler(LauncherModel.getWorkerLooper(), mWorkerCallback);
+        mUiHandler = new Handler(Looper.getMainLooper(), mUiCallback);
+        sNotificationListenerInstance = this;
+    }
+
+    public static @Nullable NotificationListener getInstanceIfConnected() {
+        return sIsConnected ? sNotificationListenerInstance : null;
+    }
+
+    public static void setNotificationsChangedListener(NotificationsChangedListener listener) {
+        if (!FeatureFlags.BADGE_ICONS) {
+            return;
+        }
+        sNotificationsChangedListener = listener;
+
+        if (sNotificationListenerInstance != null) {
+            sNotificationListenerInstance.onNotificationFullRefresh();
+        }
+    }
+
+    public static void removeNotificationsChangedListener() {
+        sNotificationsChangedListener = null;
+    }
+
+    @Override
+    public void onListenerConnected() {
+        super.onListenerConnected();
+        sIsConnected = true;
+        onNotificationFullRefresh();
+    }
+
+    private void onNotificationFullRefresh() {
+        mWorkerHandler.obtainMessage(MSG_NOTIFICATION_FULL_REFRESH).sendToTarget();
+    }
+
+    @Override
+    public void onListenerDisconnected() {
+        super.onListenerDisconnected();
+        sIsConnected = false;
+    }
+
+    @Override
+    public void onNotificationPosted(final StatusBarNotification sbn) {
+        super.onNotificationPosted(sbn);
+        mWorkerHandler.obtainMessage(MSG_NOTIFICATION_POSTED, new NotificationPostedMsg(sbn))
+                .sendToTarget();
+    }
+
+    /**
+     * An object containing data to send to MSG_NOTIFICATION_POSTED targets.
+     */
+    private class NotificationPostedMsg {
+        PackageUserKey packageUserKey;
+        NotificationKeyData notificationKey;
+        boolean shouldBeFilteredOut;
+
+        NotificationPostedMsg(StatusBarNotification sbn) {
+            packageUserKey = PackageUserKey.fromNotification(sbn);
+            notificationKey = NotificationKeyData.fromNotification(sbn);
+            shouldBeFilteredOut = shouldBeFilteredOut(sbn);
+        }
+    }
+
+    @Override
+    public void onNotificationRemoved(final StatusBarNotification sbn) {
+        super.onNotificationRemoved(sbn);
+        Pair<PackageUserKey, NotificationKeyData> packageUserKeyAndNotificationKey
+                = new Pair<>(PackageUserKey.fromNotification(sbn),
+                        NotificationKeyData.fromNotification(sbn));
+        mWorkerHandler.obtainMessage(MSG_NOTIFICATION_REMOVED, packageUserKeyAndNotificationKey)
+                .sendToTarget();
+    }
+
+    /** This makes a potentially expensive binder call and should be run on a background thread. */
+    public List<StatusBarNotification> getNotificationsForKeys(List<NotificationKeyData> keys) {
+        StatusBarNotification[] notifications = NotificationListener.this
+                .getActiveNotifications(NotificationKeyData.extractKeysOnly(keys)
+                        .toArray(new String[keys.size()]));
+        return notifications == null ? Collections.EMPTY_LIST : Arrays.asList(notifications);
+    }
+
+    /**
+     * Filter out notifications that don't have an intent
+     * or are headers for grouped notifications.
+     *
+     * @see #shouldBeFilteredOut(StatusBarNotification)
+     */
+    private List<StatusBarNotification> filterNotifications(
+            StatusBarNotification[] notifications) {
+        if (notifications == null) return null;
+        Set<Integer> removedNotifications = new HashSet<>();
+        for (int i = 0; i < notifications.length; i++) {
+            if (shouldBeFilteredOut(notifications[i])) {
+                removedNotifications.add(i);
+            }
+        }
+        List<StatusBarNotification> filteredNotifications = new ArrayList<>(
+                notifications.length - removedNotifications.size());
+        for (int i = 0; i < notifications.length; i++) {
+            if (!removedNotifications.contains(i)) {
+                filteredNotifications.add(notifications[i]);
+            }
+        }
+        return filteredNotifications;
+    }
+
+    private boolean shouldBeFilteredOut(StatusBarNotification sbn) {
+        getCurrentRanking().getRanking(sbn.getKey(), mTempRanking);
+        if (!mTempRanking.canShowBadge()) {
+            return true;
+        }
+        Notification notification = sbn.getNotification();
+        if (mTempRanking.getChannel().getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID)) {
+            // Special filtering for the default, legacy "Miscellaneous" channel.
+            if ((notification.flags & Notification.FLAG_ONGOING_EVENT) != 0) {
+                return true;
+            }
+        }
+        boolean isGroupHeader = (notification.flags & Notification.FLAG_GROUP_SUMMARY) != 0;
+        CharSequence title = notification.extras.getCharSequence(Notification.EXTRA_TITLE);
+        CharSequence text = notification.extras.getCharSequence(Notification.EXTRA_TEXT);
+        boolean missingTitleAndText = TextUtils.isEmpty(title) && TextUtils.isEmpty(text);
+        return (isGroupHeader || missingTitleAndText);
+    }
+
+    public interface NotificationsChangedListener {
+        void onNotificationPosted(PackageUserKey postedPackageUserKey,
+                NotificationKeyData notificationKey, boolean shouldBeFilteredOut);
+        void onNotificationRemoved(PackageUserKey removedPackageUserKey,
+                NotificationKeyData notificationKey);
+        void onNotificationFullRefresh(List<StatusBarNotification> activeNotifications);
+    }
+}
diff --git a/src/com/android/launcher3/notification/NotificationMainView.java b/src/com/android/launcher3/notification/NotificationMainView.java
new file mode 100644
index 0000000..d6e0272
--- /dev/null
+++ b/src/com/android/launcher3/notification/NotificationMainView.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.notification;
+
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.RippleDrawable;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.util.Themes;
+
+/**
+ * A {@link android.widget.FrameLayout} that contains a single notification,
+ * e.g. icon + title + text.
+ */
+public class NotificationMainView extends FrameLayout implements SwipeHelper.Callback {
+
+    private NotificationInfo mNotificationInfo;
+    private ViewGroup mTextAndBackground;
+    private int mBackgroundColor;
+    private TextView mTitleView;
+    private TextView mTextView;
+
+    public NotificationMainView(Context context) {
+        this(context, null, 0);
+    }
+
+    public NotificationMainView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public NotificationMainView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        mTextAndBackground = (ViewGroup) findViewById(R.id.text_and_background);
+        ColorDrawable colorBackground = (ColorDrawable) mTextAndBackground.getBackground();
+        mBackgroundColor = colorBackground.getColor();
+        RippleDrawable rippleBackground = new RippleDrawable(ColorStateList.valueOf(
+                Themes.getAttrColor(getContext(), android.R.attr.colorControlHighlight)),
+                colorBackground, null);
+        mTextAndBackground.setBackground(rippleBackground);
+        mTitleView = (TextView) mTextAndBackground.findViewById(R.id.title);
+        mTextView = (TextView) mTextAndBackground.findViewById(R.id.text);
+    }
+
+    public void applyNotificationInfo(NotificationInfo mainNotification, View iconView) {
+        applyNotificationInfo(mainNotification, iconView, false);
+    }
+
+    /**
+     * Sets the content of this view, animating it after a new icon shifts up if necessary.
+     */
+    public void applyNotificationInfo(NotificationInfo mainNotification, View iconView,
+           boolean animate) {
+        mNotificationInfo = mainNotification;
+        CharSequence title = mNotificationInfo.title;
+        CharSequence text = mNotificationInfo.text;
+        if (!TextUtils.isEmpty(title) && !TextUtils.isEmpty(text)) {
+            mTitleView.setText(title);
+            mTextView.setText(text);
+        } else {
+            mTitleView.setMaxLines(2);
+            mTitleView.setText(TextUtils.isEmpty(title) ? text : title);
+            mTextView.setVisibility(GONE);
+        }
+        iconView.setBackground(mNotificationInfo.getIconForBackground(getContext(),
+                mBackgroundColor));
+        if (mNotificationInfo.intent != null) {
+            setOnClickListener(mNotificationInfo);
+        }
+        setTranslationX(0);
+        // Add a dummy ItemInfo so that logging populates the correct container and item types
+        // instead of DEFAULT_CONTAINERTYPE and DEFAULT_ITEMTYPE, respectively.
+        setTag(new ItemInfo());
+        if (animate) {
+            ObjectAnimator.ofFloat(mTextAndBackground, ALPHA, 0, 1).setDuration(150).start();
+        }
+    }
+
+    public NotificationInfo getNotificationInfo() {
+        return mNotificationInfo;
+    }
+
+
+    // SwipeHelper.Callback's
+
+    @Override
+    public View getChildAtPosition(MotionEvent ev) {
+        return this;
+    }
+
+    @Override
+    public boolean canChildBeDismissed(View v) {
+        return mNotificationInfo.dismissable;
+    }
+
+    @Override
+    public boolean isAntiFalsingNeeded() {
+        return false;
+    }
+
+    @Override
+    public void onBeginDrag(View v) {
+    }
+
+    @Override
+    public void onChildDismissed(View v) {
+        Launcher launcher = Launcher.getLauncher(getContext());
+        launcher.getPopupDataProvider().cancelNotification(
+                mNotificationInfo.notificationKey);
+        launcher.getUserEventDispatcher().logActionOnItem(
+                LauncherLogProto.Action.Touch.SWIPE,
+                LauncherLogProto.Action.Direction.RIGHT, // Assume all swipes are right for logging.
+                LauncherLogProto.ItemType.NOTIFICATION);
+    }
+
+    @Override
+    public void onDragCancelled(View v) {
+    }
+
+    @Override
+    public void onChildSnappedBack(View animView, float targetLeft) {
+    }
+
+    @Override
+    public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) {
+        // Don't fade out.
+        return true;
+    }
+
+    @Override
+    public float getFalsingThresholdFactor() {
+        return 1;
+    }
+}
diff --git a/src/com/android/launcher3/notification/SwipeHelper.java b/src/com/android/launcher3/notification/SwipeHelper.java
new file mode 100644
index 0000000..5f03252
--- /dev/null
+++ b/src/com/android/launcher3/notification/SwipeHelper.java
@@ -0,0 +1,690 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.notification;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.content.Context;
+import android.graphics.RectF;
+import android.os.Handler;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.accessibility.AccessibilityEvent;
+
+import com.android.launcher3.R;
+
+import java.util.HashMap;
+
+/**
+ * This class was copied from com.android.systemui.
+ */
+public class SwipeHelper {
+    static final String TAG = "SwipeHelper";
+    private static final boolean DEBUG = false;
+    private static final boolean DEBUG_INVALIDATE = false;
+    private static final boolean SLOW_ANIMATIONS = false; // DEBUG;
+    private static final boolean CONSTRAIN_SWIPE = true;
+    private static final boolean FADE_OUT_DURING_SWIPE = true;
+    private static final boolean DISMISS_IF_SWIPED_FAR_ENOUGH = true;
+
+    public static final int X = 0;
+    public static final int Y = 1;
+
+    private float SWIPE_ESCAPE_VELOCITY = 100f; // dp/sec
+    private int DEFAULT_ESCAPE_ANIMATION_DURATION = 200; // ms
+    private int MAX_ESCAPE_ANIMATION_DURATION = 400; // ms
+    private int MAX_DISMISS_VELOCITY = 4000; // dp/sec
+    private static final int SNAP_ANIM_LEN = SLOW_ANIMATIONS ? 1000 : 150; // ms
+
+    static final float SWIPE_PROGRESS_FADE_END = 0.5f; // fraction of thumbnail width
+                                                // beyond which swipe progress->0
+    private float mMinSwipeProgress = 0f;
+    private float mMaxSwipeProgress = 1f;
+
+    private FlingAnimationUtils mFlingAnimationUtils;
+    private float mPagingTouchSlop;
+    private Callback mCallback;
+    private Handler mHandler;
+    private int mSwipeDirection;
+    private VelocityTracker mVelocityTracker;
+
+    private float mInitialTouchPos;
+    private float mPerpendicularInitialTouchPos;
+    private boolean mDragging;
+    private boolean mSnappingChild;
+    private View mCurrView;
+    private boolean mCanCurrViewBeDimissed;
+    private float mDensityScale;
+    private float mTranslation = 0;
+
+    private boolean mLongPressSent;
+    private LongPressListener mLongPressListener;
+    private Runnable mWatchLongPress;
+    private long mLongPressTimeout;
+
+    final private int[] mTmpPos = new int[2];
+    private int mFalsingThreshold;
+    private boolean mTouchAboveFalsingThreshold;
+    private boolean mDisableHwLayers;
+
+    private HashMap<View, Animator> mDismissPendingMap = new HashMap<>();
+
+    public SwipeHelper(int swipeDirection, Callback callback, Context context) {
+        mCallback = callback;
+        mHandler = new Handler();
+        mSwipeDirection = swipeDirection;
+        mVelocityTracker = VelocityTracker.obtain();
+        mDensityScale =  context.getResources().getDisplayMetrics().density;
+        mPagingTouchSlop = ViewConfiguration.get(context).getScaledPagingTouchSlop();
+
+        mLongPressTimeout = (long) (ViewConfiguration.getLongPressTimeout() * 1.5f); // extra long-press!
+        mFalsingThreshold = context.getResources().getDimensionPixelSize(
+                R.dimen.swipe_helper_falsing_threshold);
+        mFlingAnimationUtils = new FlingAnimationUtils(context, getMaxEscapeAnimDuration() / 1000f);
+    }
+
+    public void setLongPressListener(LongPressListener listener) {
+        mLongPressListener = listener;
+    }
+
+    public void setDensityScale(float densityScale) {
+        mDensityScale = densityScale;
+    }
+
+    public void setPagingTouchSlop(float pagingTouchSlop) {
+        mPagingTouchSlop = pagingTouchSlop;
+    }
+
+    public void setDisableHardwareLayers(boolean disableHwLayers) {
+        mDisableHwLayers = disableHwLayers;
+    }
+
+    private float getPos(MotionEvent ev) {
+        return mSwipeDirection == X ? ev.getX() : ev.getY();
+    }
+
+    private float getPerpendicularPos(MotionEvent ev) {
+        return mSwipeDirection == X ? ev.getY() : ev.getX();
+    }
+
+    protected float getTranslation(View v) {
+        return mSwipeDirection == X ? v.getTranslationX() : v.getTranslationY();
+    }
+
+    private float getVelocity(VelocityTracker vt) {
+        return mSwipeDirection == X ? vt.getXVelocity() :
+                vt.getYVelocity();
+    }
+
+    protected ObjectAnimator createTranslationAnimation(View v, float newPos) {
+        ObjectAnimator anim = ObjectAnimator.ofFloat(v,
+                mSwipeDirection == X ? View.TRANSLATION_X : View.TRANSLATION_Y, newPos);
+        return anim;
+    }
+
+    private float getPerpendicularVelocity(VelocityTracker vt) {
+        return mSwipeDirection == X ? vt.getYVelocity() :
+                vt.getXVelocity();
+    }
+
+    protected Animator getViewTranslationAnimator(View v, float target,
+            AnimatorUpdateListener listener) {
+        ObjectAnimator anim = createTranslationAnimation(v, target);
+        if (listener != null) {
+            anim.addUpdateListener(listener);
+        }
+        return anim;
+    }
+
+    protected void setTranslation(View v, float translate) {
+        if (v == null) {
+            return;
+        }
+        if (mSwipeDirection == X) {
+            v.setTranslationX(translate);
+        } else {
+            v.setTranslationY(translate);
+        }
+    }
+
+    protected float getSize(View v) {
+        return mSwipeDirection == X ? v.getMeasuredWidth() :
+                v.getMeasuredHeight();
+    }
+
+    public void setMinSwipeProgress(float minSwipeProgress) {
+        mMinSwipeProgress = minSwipeProgress;
+    }
+
+    public void setMaxSwipeProgress(float maxSwipeProgress) {
+        mMaxSwipeProgress = maxSwipeProgress;
+    }
+
+    private float getSwipeProgressForOffset(View view, float translation) {
+        float viewSize = getSize(view);
+        float result = Math.abs(translation / viewSize);
+        return Math.min(Math.max(mMinSwipeProgress, result), mMaxSwipeProgress);
+    }
+
+    private float getSwipeAlpha(float progress) {
+        return Math.min(0, Math.max(1, progress / SWIPE_PROGRESS_FADE_END));
+    }
+
+    private void updateSwipeProgressFromOffset(View animView, boolean dismissable) {
+        updateSwipeProgressFromOffset(animView, dismissable, getTranslation(animView));
+    }
+
+    private void updateSwipeProgressFromOffset(View animView, boolean dismissable,
+            float translation) {
+        float swipeProgress = getSwipeProgressForOffset(animView, translation);
+        if (!mCallback.updateSwipeProgress(animView, dismissable, swipeProgress)) {
+            if (FADE_OUT_DURING_SWIPE && dismissable) {
+                float alpha = swipeProgress;
+                if (!mDisableHwLayers) {
+                    if (alpha != 0f && alpha != 1f) {
+                        animView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+                    } else {
+                        animView.setLayerType(View.LAYER_TYPE_NONE, null);
+                    }
+                }
+                animView.setAlpha(getSwipeAlpha(swipeProgress));
+            }
+        }
+        invalidateGlobalRegion(animView);
+    }
+
+    // invalidate the view's own bounds all the way up the view hierarchy
+    public static void invalidateGlobalRegion(View view) {
+        invalidateGlobalRegion(
+                view,
+                new RectF(view.getLeft(), view.getTop(), view.getRight(), view.getBottom()));
+    }
+
+    // invalidate a rectangle relative to the view's coordinate system all the way up the view
+    // hierarchy
+    public static void invalidateGlobalRegion(View view, RectF childBounds) {
+        //childBounds.offset(view.getTranslationX(), view.getTranslationY());
+        if (DEBUG_INVALIDATE)
+            Log.v(TAG, "-------------");
+        while (view.getParent() != null && view.getParent() instanceof View) {
+            view = (View) view.getParent();
+            view.getMatrix().mapRect(childBounds);
+            view.invalidate((int) Math.floor(childBounds.left),
+                    (int) Math.floor(childBounds.top),
+                    (int) Math.ceil(childBounds.right),
+                    (int) Math.ceil(childBounds.bottom));
+            if (DEBUG_INVALIDATE) {
+                Log.v(TAG, "INVALIDATE(" + (int) Math.floor(childBounds.left)
+                        + "," + (int) Math.floor(childBounds.top)
+                        + "," + (int) Math.ceil(childBounds.right)
+                        + "," + (int) Math.ceil(childBounds.bottom));
+            }
+        }
+    }
+
+    public void removeLongPressCallback() {
+        if (mWatchLongPress != null) {
+            mHandler.removeCallbacks(mWatchLongPress);
+            mWatchLongPress = null;
+        }
+    }
+
+    public boolean onInterceptTouchEvent(final MotionEvent ev) {
+        final int action = ev.getAction();
+
+        switch (action) {
+            case MotionEvent.ACTION_DOWN:
+                mTouchAboveFalsingThreshold = false;
+                mDragging = false;
+                mSnappingChild = false;
+                mLongPressSent = false;
+                mVelocityTracker.clear();
+                mCurrView = mCallback.getChildAtPosition(ev);
+
+                if (mCurrView != null) {
+                    onDownUpdate(mCurrView);
+                    mCanCurrViewBeDimissed = mCallback.canChildBeDismissed(mCurrView);
+                    mVelocityTracker.addMovement(ev);
+                    mInitialTouchPos = getPos(ev);
+                    mPerpendicularInitialTouchPos = getPerpendicularPos(ev);
+                    mTranslation = getTranslation(mCurrView);
+                    if (mLongPressListener != null) {
+                        if (mWatchLongPress == null) {
+                            mWatchLongPress = new Runnable() {
+                                @Override
+                                public void run() {
+                                    if (mCurrView != null && !mLongPressSent) {
+                                        mLongPressSent = true;
+                                        mCurrView.sendAccessibilityEvent(
+                                                AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
+                                        mCurrView.getLocationOnScreen(mTmpPos);
+                                        final int x = (int) ev.getRawX() - mTmpPos[0];
+                                        final int y = (int) ev.getRawY() - mTmpPos[1];
+                                        mLongPressListener.onLongPress(mCurrView, x, y);
+                                    }
+                                }
+                            };
+                        }
+                        mHandler.postDelayed(mWatchLongPress, mLongPressTimeout);
+                    }
+                }
+                break;
+
+            case MotionEvent.ACTION_MOVE:
+                if (mCurrView != null && !mLongPressSent) {
+                    mVelocityTracker.addMovement(ev);
+                    float pos = getPos(ev);
+                    float perpendicularPos = getPerpendicularPos(ev);
+                    float delta = pos - mInitialTouchPos;
+                    float deltaPerpendicular = perpendicularPos - mPerpendicularInitialTouchPos;
+                    if (Math.abs(delta) > mPagingTouchSlop
+                            && Math.abs(delta) > Math.abs(deltaPerpendicular)) {
+                        mCallback.onBeginDrag(mCurrView);
+                        mDragging = true;
+                        mInitialTouchPos = getPos(ev);
+                        mTranslation = getTranslation(mCurrView);
+                        removeLongPressCallback();
+                    }
+                }
+                break;
+
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL:
+                final boolean captured = (mDragging || mLongPressSent);
+                mDragging = false;
+                mCurrView = null;
+                mLongPressSent = false;
+                removeLongPressCallback();
+                if (captured) return true;
+                break;
+        }
+        return mDragging || mLongPressSent;
+    }
+
+    /**
+     * @param view The view to be dismissed
+     * @param velocity The desired pixels/second speed at which the view should move
+     * @param useAccelerateInterpolator Should an accelerating Interpolator be used
+     */
+    public void dismissChild(final View view, float velocity, boolean useAccelerateInterpolator) {
+        dismissChild(view, velocity, null /* endAction */, 0 /* delay */,
+                useAccelerateInterpolator, 0 /* fixedDuration */, false /* isDismissAll */);
+    }
+
+    /**
+     * @param animView The view to be dismissed
+     * @param velocity The desired pixels/second speed at which the view should move
+     * @param endAction The action to perform at the end
+     * @param delay The delay after which we should start
+     * @param useAccelerateInterpolator Should an accelerating Interpolator be used
+     * @param fixedDuration If not 0, this exact duration will be taken
+     */
+    public void dismissChild(final View animView, float velocity, final Runnable endAction,
+            long delay, boolean useAccelerateInterpolator, long fixedDuration,
+            boolean isDismissAll) {
+        final boolean canBeDismissed = mCallback.canChildBeDismissed(animView);
+        float newPos;
+        boolean isLayoutRtl = animView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+
+        // if we use the Menu to dismiss an item in landscape, animate up
+        boolean animateUpForMenu = velocity == 0 && (getTranslation(animView) == 0 || isDismissAll)
+                && mSwipeDirection == Y;
+        // if the language is rtl we prefer swiping to the left
+        boolean animateLeftForRtl = velocity == 0 && (getTranslation(animView) == 0 || isDismissAll)
+                && isLayoutRtl;
+        boolean animateLeft = velocity < 0
+                || (velocity == 0 && getTranslation(animView) < 0 && !isDismissAll);
+
+        if (animateLeft || animateLeftForRtl || animateUpForMenu) {
+            newPos = -getSize(animView);
+        } else {
+            newPos = getSize(animView);
+        }
+        long duration;
+        if (fixedDuration == 0) {
+            duration = MAX_ESCAPE_ANIMATION_DURATION;
+            if (velocity != 0) {
+                duration = Math.min(duration,
+                        (int) (Math.abs(newPos - getTranslation(animView)) * 1000f / Math
+                                .abs(velocity))
+                );
+            } else {
+                duration = DEFAULT_ESCAPE_ANIMATION_DURATION;
+            }
+        } else {
+            duration = fixedDuration;
+        }
+
+        if (!mDisableHwLayers) {
+            animView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+        }
+        AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
+            public void onAnimationUpdate(ValueAnimator animation) {
+                onTranslationUpdate(animView, (float) animation.getAnimatedValue(), canBeDismissed);
+            }
+        };
+
+        Animator anim = getViewTranslationAnimator(animView, newPos, updateListener);
+        if (anim == null) {
+            return;
+        }
+        if (useAccelerateInterpolator) {
+            anim.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
+            anim.setDuration(duration);
+        } else {
+            mFlingAnimationUtils.applyDismissing(anim, getTranslation(animView),
+                    newPos, velocity, getSize(animView));
+        }
+        if (delay > 0) {
+            anim.setStartDelay(delay);
+        }
+        anim.addListener(new AnimatorListenerAdapter() {
+            private boolean mCancelled;
+
+            public void onAnimationCancel(Animator animation) {
+                mCancelled = true;
+            }
+
+            public void onAnimationEnd(Animator animation) {
+                updateSwipeProgressFromOffset(animView, canBeDismissed);
+                mDismissPendingMap.remove(animView);
+                if (!mCancelled) {
+                    mCallback.onChildDismissed(animView);
+                }
+                if (endAction != null) {
+                    endAction.run();
+                }
+                if (!mDisableHwLayers) {
+                    animView.setLayerType(View.LAYER_TYPE_NONE, null);
+                }
+            }
+        });
+
+        prepareDismissAnimation(animView, anim);
+        mDismissPendingMap.put(animView, anim);
+        anim.start();
+    }
+
+    /**
+     * Called to update the dismiss animation.
+     */
+    protected void prepareDismissAnimation(View view, Animator anim) {
+        // Do nothing
+    }
+
+    public void snapChild(final View animView, final float targetLeft, float velocity) {
+        final boolean canBeDismissed = mCallback.canChildBeDismissed(animView);
+        AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
+            public void onAnimationUpdate(ValueAnimator animation) {
+                onTranslationUpdate(animView, (float) animation.getAnimatedValue(), canBeDismissed);
+            }
+        };
+
+        Animator anim = getViewTranslationAnimator(animView, targetLeft, updateListener);
+        if (anim == null) {
+            return;
+        }
+        int duration = SNAP_ANIM_LEN;
+        anim.setDuration(duration);
+        anim.addListener(new AnimatorListenerAdapter() {
+            public void onAnimationEnd(Animator animator) {
+                mSnappingChild = false;
+                updateSwipeProgressFromOffset(animView, canBeDismissed);
+                mCallback.onChildSnappedBack(animView, targetLeft);
+            }
+        });
+        prepareSnapBackAnimation(animView, anim);
+        mSnappingChild = true;
+        anim.start();
+    }
+
+    /**
+     * Called to update the snap back animation.
+     */
+    protected void prepareSnapBackAnimation(View view, Animator anim) {
+        // Do nothing
+    }
+
+    /**
+     * Called when there's a down event.
+     */
+    public void onDownUpdate(View currView) {
+        // Do nothing
+    }
+
+    /**
+     * Called on a move event.
+     */
+    protected void onMoveUpdate(View view, float totalTranslation, float delta) {
+        // Do nothing
+    }
+
+    /**
+     * Called in {@link AnimatorUpdateListener#onAnimationUpdate(ValueAnimator)} when the current
+     * view is being animated to dismiss or snap.
+     */
+    public void onTranslationUpdate(View animView, float value, boolean canBeDismissed) {
+        updateSwipeProgressFromOffset(animView, canBeDismissed, value);
+    }
+
+    private void snapChildInstantly(final View view) {
+        final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(view);
+        setTranslation(view, 0);
+        updateSwipeProgressFromOffset(view, canAnimViewBeDismissed);
+    }
+
+    /**
+     * Called when a view is updated to be non-dismissable, if the view was being dismissed before
+     * the update this will handle snapping it back into place.
+     *
+     * @param view the view to snap if necessary.
+     * @param animate whether to animate the snap or not.
+     * @param targetLeft the target to snap to.
+     */
+    public void snapChildIfNeeded(final View view, boolean animate, float targetLeft) {
+        if ((mDragging && mCurrView == view) || mSnappingChild) {
+            return;
+        }
+        boolean needToSnap = false;
+        Animator dismissPendingAnim = mDismissPendingMap.get(view);
+        if (dismissPendingAnim != null) {
+            needToSnap = true;
+            dismissPendingAnim.cancel();
+        } else if (getTranslation(view) != 0) {
+            needToSnap = true;
+        }
+        if (needToSnap) {
+            if (animate) {
+                snapChild(view, targetLeft, 0.0f /* velocity */);
+            } else {
+                snapChildInstantly(view);
+            }
+        }
+    }
+
+    public boolean onTouchEvent(MotionEvent ev) {
+        if (mLongPressSent) {
+            return true;
+        }
+
+        if (!mDragging) {
+            if (mCallback.getChildAtPosition(ev) != null) {
+
+                // We are dragging directly over a card, make sure that we also catch the gesture
+                // even if nobody else wants the touch event.
+                onInterceptTouchEvent(ev);
+                 return true;
+            } else {
+
+                // We are not doing anything, make sure the long press callback
+                // is not still ticking like a bomb waiting to go off.
+                removeLongPressCallback();
+                return false;
+            }
+        }
+
+        mVelocityTracker.addMovement(ev);
+        final int action = ev.getAction();
+        switch (action) {
+            case MotionEvent.ACTION_OUTSIDE:
+            case MotionEvent.ACTION_MOVE:
+                if (mCurrView != null) {
+                    float delta = getPos(ev) - mInitialTouchPos;
+                    float absDelta = Math.abs(delta);
+                    if (absDelta >= getFalsingThreshold()) {
+                        mTouchAboveFalsingThreshold = true;
+                    }
+                    // don't let items that can't be dismissed be dragged more than
+                    // maxScrollDistance
+                    if (CONSTRAIN_SWIPE && !mCallback.canChildBeDismissed(mCurrView)) {
+                        float size = getSize(mCurrView);
+                        float maxScrollDistance = 0.25f * size;
+                        if (absDelta >= size) {
+                            delta = delta > 0 ? maxScrollDistance : -maxScrollDistance;
+                        } else {
+                            delta = maxScrollDistance * (float) Math.sin((delta/size)*(Math.PI/2));
+                        }
+                    }
+
+                    setTranslation(mCurrView, mTranslation + delta);
+                    updateSwipeProgressFromOffset(mCurrView, mCanCurrViewBeDimissed);
+                    onMoveUpdate(mCurrView, mTranslation + delta, delta);
+                }
+                break;
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL:
+                if (mCurrView == null) {
+                    break;
+                }
+                mVelocityTracker.computeCurrentVelocity(1000 /* px/sec */, getMaxVelocity());
+                float velocity = getVelocity(mVelocityTracker);
+
+                if (!handleUpEvent(ev, mCurrView, velocity, getTranslation(mCurrView))) {
+                    if (isDismissGesture(ev)) {
+                        // flingadingy
+                        dismissChild(mCurrView, velocity,
+                                !swipedFastEnough() /* useAccelerateInterpolator */);
+                    } else {
+                        // snappity
+                        mCallback.onDragCancelled(mCurrView);
+                        snapChild(mCurrView, 0 /* leftTarget */, velocity);
+                    }
+                    mCurrView = null;
+                }
+                mDragging = false;
+                break;
+        }
+        return true;
+    }
+
+    private int getFalsingThreshold() {
+        float factor = mCallback.getFalsingThresholdFactor();
+        return (int) (mFalsingThreshold * factor);
+    }
+
+    private float getMaxVelocity() {
+        return MAX_DISMISS_VELOCITY * mDensityScale;
+    }
+
+    protected float getEscapeVelocity() {
+        return getUnscaledEscapeVelocity() * mDensityScale;
+    }
+
+    protected float getUnscaledEscapeVelocity() {
+        return SWIPE_ESCAPE_VELOCITY;
+    }
+
+    protected long getMaxEscapeAnimDuration() {
+        return MAX_ESCAPE_ANIMATION_DURATION;
+    }
+
+    protected boolean swipedFarEnough() {
+        float translation = getTranslation(mCurrView);
+        return DISMISS_IF_SWIPED_FAR_ENOUGH && Math.abs(translation) > 0.4 * getSize(mCurrView);
+    }
+
+    protected boolean isDismissGesture(MotionEvent ev) {
+        boolean falsingDetected = mCallback.isAntiFalsingNeeded() && !mTouchAboveFalsingThreshold;
+        return !falsingDetected && (swipedFastEnough() || swipedFarEnough())
+                && ev.getActionMasked() == MotionEvent.ACTION_UP
+                && mCallback.canChildBeDismissed(mCurrView);
+    }
+
+    protected boolean swipedFastEnough() {
+        float velocity = getVelocity(mVelocityTracker);
+        float translation = getTranslation(mCurrView);
+        boolean ret = (Math.abs(velocity) > getEscapeVelocity())
+                && (velocity > 0) == (translation > 0);
+        return ret;
+    }
+
+    protected boolean handleUpEvent(MotionEvent ev, View animView, float velocity,
+            float translation) {
+        return false;
+    }
+
+    public interface Callback {
+        View getChildAtPosition(MotionEvent ev);
+
+        boolean canChildBeDismissed(View v);
+
+        boolean isAntiFalsingNeeded();
+
+        void onBeginDrag(View v);
+
+        void onChildDismissed(View v);
+
+        void onDragCancelled(View v);
+
+        /**
+         * Called when the child is snapped to a position.
+         *
+         * @param animView the view that was snapped.
+         * @param targetLeft the left position the view was snapped to.
+         */
+        void onChildSnappedBack(View animView, float targetLeft);
+
+        /**
+         * Updates the swipe progress on a child.
+         *
+         * @return if true, prevents the default alpha fading.
+         */
+        boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress);
+
+        /**
+         * @return The factor the falsing threshold should be multiplied with
+         */
+        float getFalsingThresholdFactor();
+    }
+
+    /**
+     * Equivalent to View.OnLongClickListener with coordinates
+     */
+    public interface LongPressListener {
+        /**
+         * Equivalent to {@link View.OnLongClickListener#onLongClick(View)} with coordinates
+         * @return whether the longpress was handled
+         */
+        boolean onLongPress(View v, int x, int y);
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/pageindicators/CaretDrawable.java b/src/com/android/launcher3/pageindicators/CaretDrawable.java
index 4789f69..0a00e24 100644
--- a/src/com/android/launcher3/pageindicators/CaretDrawable.java
+++ b/src/com/android/launcher3/pageindicators/CaretDrawable.java
@@ -24,6 +24,7 @@
 import android.graphics.PixelFormat;
 
 import com.android.launcher3.R;
+import com.android.launcher3.util.Themes;
 
 import android.graphics.drawable.Drawable;
 
@@ -45,14 +46,15 @@
         final int strokeWidth = res.getDimensionPixelSize(R.dimen.all_apps_caret_stroke_width);
         final int shadowSpread = res.getDimensionPixelSize(R.dimen.all_apps_caret_shadow_spread);
 
-        mCaretPaint.setColor(res.getColor(R.color.all_apps_caret_color));
+        mCaretPaint.setColor(res.getColor(R.color.workspace_icon_text_color));
         mCaretPaint.setAntiAlias(true);
         mCaretPaint.setStrokeWidth(strokeWidth);
         mCaretPaint.setStyle(Paint.Style.STROKE);
         mCaretPaint.setStrokeCap(Paint.Cap.SQUARE);
         mCaretPaint.setStrokeJoin(Paint.Join.MITER);
 
-        mShadowPaint.setColor(res.getColor(R.color.all_apps_caret_shadow_color));
+        mShadowPaint.setColor(res.getColor(R.color.default_shadow_color_no_alpha));
+        mShadowPaint.setAlpha(Themes.getAlpha(context, android.R.attr.spotShadowAlpha));
         mShadowPaint.setAntiAlias(true);
         mShadowPaint.setStrokeWidth(strokeWidth + (shadowSpread * 2));
         mShadowPaint.setStyle(Paint.Style.STROKE);
diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
index fb9d2f7..6276c80 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
@@ -37,6 +37,7 @@
 
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.util.Themes;
 
 /**
  * {@link PageIndicator} which shows dots per page. The active page is shown with the current
@@ -72,18 +73,6 @@
         }
     };
 
-    /**
-     * Listener for keep running the animation until the final state is reached.
-     */
-    private final AnimatorListenerAdapter mAnimCycleListener = new AnimatorListenerAdapter() {
-
-        @Override
-        public void onAnimationEnd(Animator animation) {
-            mAnimator = null;
-            animateToPostion(mFinalPosition);
-        }
-    };
-
     private final Paint mCirclePaint;
     private final float mDotRadius;
     private final int mActiveColor;
@@ -123,8 +112,8 @@
         mDotRadius = getResources().getDimension(R.dimen.page_indicator_dot_size) / 2;
         setOutlineProvider(new MyOutlineProver());
 
-        mActiveColor = Utilities.getColorAccent(context);
-        mInActiveColor = getResources().getColor(R.color.page_indicator_dot_color);
+        mActiveColor = Themes.getColorAccent(context);
+        mInActiveColor = Themes.getAttrColor(context, android.R.attr.colorControlHighlight);
 
         mIsRtl = Utilities.isRtl(getResources());
     }
@@ -136,22 +125,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;
@@ -160,7 +152,7 @@
             float positionForThisAnim = mCurrentPosition > mFinalPosition ?
                     mCurrentPosition - SHIFT_PER_ANIMATION : mCurrentPosition + SHIFT_PER_ANIMATION;
             mAnimator = ObjectAnimator.ofFloat(this, CURRENT_POSITION, positionForThisAnim);
-            mAnimator.addListener(mAnimCycleListener);
+            mAnimator.addListener(new AnimationCycleListener());
             mAnimator.setDuration(ANIMATION_DURATION);
             mAnimator.start();
         }
@@ -168,7 +160,6 @@
 
     public void stopAllAnimations() {
         if (mAnimator != null) {
-            mAnimator.removeAllListeners();
             mAnimator.cancel();
             mAnimator = null;
         }
@@ -323,4 +314,25 @@
             }
         }
     }
+
+    /**
+     * Listener for keep running the animation until the final state is reached.
+     */
+    private class AnimationCycleListener extends AnimatorListenerAdapter {
+
+        private boolean mCancelled = false;
+
+        @Override
+        public void onAnimationCancel(Animator animation) {
+            mCancelled = true;
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            if (!mCancelled) {
+                mAnimator = null;
+                animateToPosition(mFinalPosition);
+            }
+        }
+    }
 }
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
new file mode 100644
index 0000000..d4ee3b8
--- /dev/null
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -0,0 +1,849 @@
+/*
+ * 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.popup;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.CornerPathEffect;
+import android.graphics.Paint;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.drawable.ShapeDrawable;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.AttributeSet;
+import android.view.Gravity;
+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.FrameLayout;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.DragSource;
+import com.android.launcher3.DropTarget;
+import com.android.launcher3.FastBitmapDrawable;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAnimUtils;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.LogAccelerateInterpolator;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
+import com.android.launcher3.accessibility.ShortcutMenuAccessibilityDelegate;
+import com.android.launcher3.anim.PropertyListBuilder;
+import com.android.launcher3.anim.PropertyResetListener;
+import com.android.launcher3.badge.BadgeInfo;
+import com.android.launcher3.dragndrop.DragController;
+import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.graphics.IconPalette;
+import com.android.launcher3.graphics.TriangleShape;
+import com.android.launcher3.notification.NotificationItemView;
+import com.android.launcher3.notification.NotificationKeyData;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.shortcuts.DeepShortcutView;
+import com.android.launcher3.shortcuts.ShortcutsItemView;
+import com.android.launcher3.util.PackageUserKey;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+
+/**
+ * A container for shortcuts to deep links within apps.
+ */
+@TargetApi(Build.VERSION_CODES.N)
+public class PopupContainerWithArrow extends AbstractFloatingView implements DragSource,
+        DragController.DragListener {
+
+    protected final Launcher mLauncher;
+    private final int mStartDragThreshold;
+    private LauncherAccessibilityDelegate mAccessibilityDelegate;
+    private final boolean mIsRtl;
+
+    public ShortcutsItemView mShortcutsItemView;
+    private NotificationItemView mNotificationItemView;
+
+    protected BubbleTextView mOriginalIcon;
+    private final Rect mTempRect = new Rect();
+    private PointF mInterceptTouchDown = new PointF();
+    private boolean mIsLeftAligned;
+    protected boolean mIsAboveIcon;
+    private View mArrow;
+
+    protected Animator mOpenCloseAnimator;
+    private boolean mDeferContainerRemoval;
+    private AnimatorSet mReduceHeightAnimatorSet;
+
+    public PopupContainerWithArrow(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        mLauncher = Launcher.getLauncher(context);
+
+        mStartDragThreshold = getResources().getDimensionPixelSize(
+                R.dimen.deep_shortcuts_start_drag_threshold);
+        // TODO: make sure the delegate works for all items, not just shortcuts.
+        mAccessibilityDelegate = new ShortcutMenuAccessibilityDelegate(mLauncher);
+        mIsRtl = Utilities.isRtl(getResources());
+    }
+
+    public PopupContainerWithArrow(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public PopupContainerWithArrow(Context context) {
+        this(context, null, 0);
+    }
+
+    public LauncherAccessibilityDelegate getAccessibilityDelegate() {
+        return mAccessibilityDelegate;
+    }
+
+    /**
+     * Shows the notifications and deep shortcuts associated with {@param icon}.
+     * @return the container if shown or null.
+     */
+    public static PopupContainerWithArrow showForIcon(BubbleTextView icon) {
+        Launcher launcher = Launcher.getLauncher(icon.getContext());
+        if (getOpen(launcher) != null) {
+            // There is already an items container open, so don't open this one.
+            icon.clearFocus();
+            return null;
+        }
+        ItemInfo itemInfo = (ItemInfo) icon.getTag();
+        if (!DeepShortcutManager.supportsShortcuts(itemInfo)) {
+            return null;
+        }
+
+        PopupDataProvider popupDataProvider = launcher.getPopupDataProvider();
+        List<String> shortcutIds = popupDataProvider.getShortcutIdsForItem(itemInfo);
+        List<NotificationKeyData> notificationKeys = popupDataProvider
+                .getNotificationKeysForItem(itemInfo);
+        List<SystemShortcut> systemShortcuts = popupDataProvider
+                .getEnabledSystemShortcutsForItem(itemInfo);
+
+        final PopupContainerWithArrow container =
+                (PopupContainerWithArrow) launcher.getLayoutInflater().inflate(
+                        R.layout.popup_container, launcher.getDragLayer(), false);
+        container.setVisibility(View.INVISIBLE);
+        launcher.getDragLayer().addView(container);
+        container.populateAndShow(icon, shortcutIds, notificationKeys, systemShortcuts);
+        return container;
+    }
+
+    public void populateAndShow(final BubbleTextView originalIcon, final List<String> shortcutIds,
+            final List<NotificationKeyData> notificationKeys, List<SystemShortcut> systemShortcuts) {
+        final Resources resources = getResources();
+        final int arrowWidth = resources.getDimensionPixelSize(R.dimen.popup_arrow_width);
+        final int arrowHeight = resources.getDimensionPixelSize(R.dimen.popup_arrow_height);
+        final int arrowVerticalOffset = resources.getDimensionPixelSize(
+                R.dimen.popup_arrow_vertical_offset);
+
+        mOriginalIcon = originalIcon;
+
+        // Add dummy views first, and populate with real info when ready.
+        PopupPopulator.Item[] itemsToPopulate = PopupPopulator
+                .getItemsToPopulate(shortcutIds, notificationKeys, systemShortcuts);
+        addDummyViews(originalIcon, itemsToPopulate, notificationKeys.size() > 1);
+
+        measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+        orientAboutIcon(originalIcon, arrowHeight + arrowVerticalOffset);
+
+        boolean reverseOrder = mIsAboveIcon;
+        if (reverseOrder) {
+            removeAllViews();
+            mNotificationItemView = null;
+            mShortcutsItemView = null;
+            itemsToPopulate = PopupPopulator.reverseItems(itemsToPopulate);
+            addDummyViews(originalIcon, itemsToPopulate, notificationKeys.size() > 1);
+
+            measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+            orientAboutIcon(originalIcon, arrowHeight + arrowVerticalOffset);
+        }
+
+        ItemInfo originalItemInfo = (ItemInfo) originalIcon.getTag();
+        List<DeepShortcutView> shortcutViews = mShortcutsItemView == null
+                ? Collections.EMPTY_LIST
+                : mShortcutsItemView.getDeepShortcutViews(reverseOrder);
+        List<View> systemShortcutViews = mShortcutsItemView == null
+                ? Collections.EMPTY_LIST
+                : mShortcutsItemView.getSystemShortcutViews(reverseOrder);
+        if (mNotificationItemView != null) {
+            updateNotificationHeader();
+        }
+
+        // Add the arrow.
+        final int arrowHorizontalOffset = resources.getDimensionPixelSize(isAlignedWithStart() ?
+                R.dimen.popup_arrow_horizontal_offset_start :
+                R.dimen.popup_arrow_horizontal_offset_end);
+        mArrow = addArrowView(arrowHorizontalOffset, arrowVerticalOffset, arrowWidth, arrowHeight);
+        mArrow.setPivotX(arrowWidth / 2);
+        mArrow.setPivotY(mIsAboveIcon ? 0 : arrowHeight);
+
+        animateOpen();
+
+        mLauncher.getDragController().addDragListener(this);
+        mOriginalIcon.forceHideBadge(true);
+
+        // Load the shortcuts on a background thread and update the container as it animates.
+        final Looper workerLooper = LauncherModel.getWorkerLooper();
+        new Handler(workerLooper).postAtFrontOfQueue(PopupPopulator.createUpdateRunnable(
+                mLauncher, originalItemInfo, new Handler(Looper.getMainLooper()),
+                this, shortcutIds, shortcutViews, notificationKeys, mNotificationItemView,
+                systemShortcuts, systemShortcutViews));
+    }
+
+    private void addDummyViews(BubbleTextView originalIcon,
+            PopupPopulator.Item[] itemTypesToPopulate, boolean notificationFooterHasIcons) {
+        final Resources res = getResources();
+        final int spacing = res.getDimensionPixelSize(R.dimen.popup_items_spacing);
+        final LayoutInflater inflater = mLauncher.getLayoutInflater();
+
+        int numItems = itemTypesToPopulate.length;
+        for (int i = 0; i < numItems; i++) {
+            PopupPopulator.Item itemTypeToPopulate = itemTypesToPopulate[i];
+            PopupPopulator.Item nextItemTypeToPopulate =
+                    i < numItems - 1 ? itemTypesToPopulate[i + 1] : null;
+            final View item = inflater.inflate(itemTypeToPopulate.layoutId, this, false);
+
+            if (itemTypeToPopulate == PopupPopulator.Item.NOTIFICATION) {
+                mNotificationItemView = (NotificationItemView) item;
+                int footerHeight = notificationFooterHasIcons ?
+                        res.getDimensionPixelSize(R.dimen.notification_footer_height) : 0;
+                item.findViewById(R.id.footer).getLayoutParams().height = footerHeight;
+            }
+
+            boolean shouldAddBottomMargin = nextItemTypeToPopulate != null
+                    && itemTypeToPopulate.isShortcut ^ nextItemTypeToPopulate.isShortcut;
+
+            item.setAccessibilityDelegate(mAccessibilityDelegate);
+            if (itemTypeToPopulate.isShortcut) {
+                if (mShortcutsItemView == null) {
+                    mShortcutsItemView = (ShortcutsItemView) inflater.inflate(
+                            R.layout.shortcuts_item, this, false);
+                    addView(mShortcutsItemView);
+                }
+                mShortcutsItemView.addShortcutView(item, itemTypeToPopulate);
+                if (shouldAddBottomMargin) {
+                    ((LayoutParams) mShortcutsItemView.getLayoutParams()).bottomMargin = spacing;
+                }
+            } else {
+                addView(item);
+                if (shouldAddBottomMargin) {
+                    ((LayoutParams) item.getLayoutParams()).bottomMargin = spacing;
+                }
+            }
+        }
+        // TODO: update this, since not all items are shortcuts
+        setContentDescription(getContext().getString(R.string.shortcuts_menu_description,
+                numItems, originalIcon.getContentDescription().toString()));
+    }
+
+    protected PopupItemView getItemViewAt(int index) {
+        if (!mIsAboveIcon) {
+            // Opening down, so arrow is the first view.
+            index++;
+        }
+        return (PopupItemView) getChildAt(index);
+    }
+
+    protected int getItemCount() {
+        // All children except the arrow are items.
+        return getChildCount() - 1;
+    }
+
+    private void animateOpen() {
+        setVisibility(View.VISIBLE);
+        mIsOpen = true;
+
+        final AnimatorSet shortcutAnims = LauncherAnimUtils.createAnimatorSet();
+        final int itemCount = getItemCount();
+
+        final long duration = getResources().getInteger(
+                R.integer.config_deepShortcutOpenDuration);
+        final long arrowScaleDuration = getResources().getInteger(
+                R.integer.config_deepShortcutArrowOpenDuration);
+        final long arrowScaleDelay = duration - arrowScaleDuration;
+        final long stagger = getResources().getInteger(
+                R.integer.config_deepShortcutOpenStagger);
+        final TimeInterpolator fadeInterpolator = new LogAccelerateInterpolator(100, 0);
+
+        // Animate shortcuts
+        DecelerateInterpolator interpolator = new DecelerateInterpolator();
+        for (int i = 0; i < itemCount; i++) {
+            final PopupItemView popupItemView = getItemViewAt(i);
+            popupItemView.setVisibility(INVISIBLE);
+            popupItemView.setAlpha(0);
+
+            Animator anim = popupItemView.createOpenAnimation(mIsAboveIcon, mIsLeftAligned);
+            anim.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    popupItemView.setVisibility(VISIBLE);
+                }
+            });
+            anim.setDuration(duration);
+            int animationIndex = mIsAboveIcon ? itemCount - i - 1 : i;
+            anim.setStartDelay(stagger * animationIndex);
+            anim.setInterpolator(interpolator);
+            shortcutAnims.play(anim);
+
+            Animator fadeAnim = ObjectAnimator.ofFloat(popupItemView, View.ALPHA, 1);
+            fadeAnim.setInterpolator(fadeInterpolator);
+            // We want the shortcut to be fully opaque before the arrow starts animating.
+            fadeAnim.setDuration(arrowScaleDelay);
+            shortcutAnims.play(fadeAnim);
+        }
+        shortcutAnims.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mOpenCloseAnimator = null;
+                Utilities.sendCustomAccessibilityEvent(
+                        PopupContainerWithArrow.this,
+                        AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
+                        getContext().getString(R.string.action_deep_shortcut));
+            }
+        });
+
+        // Animate the arrow
+        mArrow.setScaleX(0);
+        mArrow.setScaleY(0);
+        Animator arrowScale = createArrowScaleAnim(1).setDuration(arrowScaleDuration);
+        arrowScale.setStartDelay(arrowScaleDelay);
+        shortcutAnims.play(arrowScale);
+
+        mOpenCloseAnimator = shortcutAnims;
+        shortcutAnims.start();
+    }
+
+    /**
+     * Orients this container above or below the given icon, aligning with the left or right.
+     *
+     * These are the preferred orientations, in order (RTL prefers right-aligned over left):
+     * - Above and left-aligned
+     * - Above and right-aligned
+     * - Below and left-aligned
+     * - Below and right-aligned
+     *
+     * So we always align left if there is enough horizontal space
+     * and align above if there is enough vertical space.
+     */
+    private void orientAboutIcon(BubbleTextView icon, int arrowHeight) {
+        int width = getMeasuredWidth();
+        int height = getMeasuredHeight() + arrowHeight;
+
+        DragLayer dragLayer = mLauncher.getDragLayer();
+        dragLayer.getDescendantRectRelativeToSelf(icon, mTempRect);
+        Rect insets = dragLayer.getInsets();
+
+        // Align left (right in RTL) if there is room.
+        int leftAlignedX = mTempRect.left + icon.getPaddingLeft();
+        int rightAlignedX = mTempRect.right - width - icon.getPaddingRight();
+        int x = leftAlignedX;
+        boolean canBeLeftAligned = leftAlignedX + width + insets.left
+                < dragLayer.getRight() - insets.right;
+        boolean canBeRightAligned = rightAlignedX > dragLayer.getLeft() + insets.left;
+        if (!canBeLeftAligned || (mIsRtl && canBeRightAligned)) {
+            x = rightAlignedX;
+        }
+        mIsLeftAligned = x == leftAlignedX;
+        if (mIsRtl) {
+            x -= dragLayer.getWidth() - width;
+        }
+
+        // Offset x so that the arrow and shortcut icons are center-aligned with the original icon.
+        int iconWidth = icon.getWidth() - icon.getTotalPaddingLeft() - icon.getTotalPaddingRight();
+        iconWidth *= icon.getScaleX();
+        Resources resources = getResources();
+        int xOffset;
+        if (isAlignedWithStart()) {
+            // Aligning with the shortcut icon.
+            int shortcutIconWidth = resources.getDimensionPixelSize(R.dimen.deep_shortcut_icon_size);
+            int shortcutPaddingStart = resources.getDimensionPixelSize(
+                    R.dimen.popup_padding_start);
+            xOffset = iconWidth / 2 - shortcutIconWidth / 2 - shortcutPaddingStart;
+        } else {
+            // Aligning with the drag handle.
+            int shortcutDragHandleWidth = resources.getDimensionPixelSize(
+                    R.dimen.deep_shortcut_drag_handle_size);
+            int shortcutPaddingEnd = resources.getDimensionPixelSize(
+                    R.dimen.popup_padding_end);
+            xOffset = iconWidth / 2 - shortcutDragHandleWidth / 2 - shortcutPaddingEnd;
+        }
+        x += mIsLeftAligned ? xOffset : -xOffset;
+
+        // Open above icon if there is room.
+        int iconHeight = icon.getIcon().getBounds().height();
+        int y = mTempRect.top + icon.getPaddingTop() - height;
+        mIsAboveIcon = y > dragLayer.getTop() + insets.top;
+        if (!mIsAboveIcon) {
+            y = mTempRect.top + icon.getPaddingTop() + iconHeight;
+        }
+
+        // Insets are added later, so subtract them now.
+        if (mIsRtl) {
+            x += insets.right;
+        } else {
+            x -= insets.left;
+        }
+        y -= insets.top;
+
+        if (y < dragLayer.getTop() || y + height > dragLayer.getBottom()) {
+            // The container is opening off the screen, so just center it in the drag layer instead.
+            ((FrameLayout.LayoutParams) getLayoutParams()).gravity = Gravity.CENTER_VERTICAL;
+            // Put the container next to the icon, preferring the right side in ltr (left in rtl).
+            int rightSide = leftAlignedX + iconWidth - insets.left;
+            int leftSide = rightAlignedX - iconWidth - insets.left;
+            if (!mIsRtl) {
+                if (rightSide + width < dragLayer.getRight()) {
+                    x = rightSide;
+                    mIsLeftAligned = true;
+                } else {
+                    x = leftSide;
+                    mIsLeftAligned = false;
+                }
+            } else {
+                if (leftSide > dragLayer.getLeft()) {
+                    x = leftSide;
+                    mIsLeftAligned = false;
+                } else {
+                    x = rightSide;
+                    mIsLeftAligned = true;
+                }
+            }
+            mIsAboveIcon = true;
+        }
+
+        if (x < dragLayer.getLeft() || x + width > dragLayer.getRight()) {
+            // If we are still off screen, center horizontally too.
+            ((FrameLayout.LayoutParams) getLayoutParams()).gravity |= Gravity.CENTER_HORIZONTAL;
+        }
+
+        int gravity = ((FrameLayout.LayoutParams) getLayoutParams()).gravity;
+        if (!Gravity.isHorizontal(gravity)) {
+            setX(x);
+        }
+        if (!Gravity.isVertical(gravity)) {
+            setY(y);
+        }
+    }
+
+    private boolean isAlignedWithStart() {
+        return mIsLeftAligned && !mIsRtl || !mIsLeftAligned && mIsRtl;
+    }
+
+    /**
+     * Adds an arrow view pointing at the original icon.
+     * @param horizontalOffset the horizontal offset of the arrow, so that it
+     *                              points at the center of the original icon
+     */
+    private View addArrowView(int horizontalOffset, int verticalOffset, int width, int height) {
+        LayoutParams layoutParams = new LayoutParams(width, height);
+        if (mIsLeftAligned) {
+            layoutParams.gravity = Gravity.LEFT;
+            layoutParams.leftMargin = horizontalOffset;
+        } else {
+            layoutParams.gravity = Gravity.RIGHT;
+            layoutParams.rightMargin = horizontalOffset;
+        }
+        if (mIsAboveIcon) {
+            layoutParams.topMargin = verticalOffset;
+        } else {
+            layoutParams.bottomMargin = verticalOffset;
+        }
+
+        View arrowView = new View(getContext());
+        if (Gravity.isVertical(((FrameLayout.LayoutParams) getLayoutParams()).gravity)) {
+            // This is only true if there wasn't room for the container next to the icon,
+            // so we centered it instead. In that case we don't want to show the arrow.
+            arrowView.setVisibility(INVISIBLE);
+        } else {
+            ShapeDrawable arrowDrawable = new ShapeDrawable(TriangleShape.create(
+                    width, height, !mIsAboveIcon));
+            Paint arrowPaint = arrowDrawable.getPaint();
+            // Note that we have to use getChildAt() instead of getItemViewAt(),
+            // since the latter expects the arrow which hasn't been added yet.
+            PopupItemView itemAttachedToArrow = (PopupItemView)
+                    (getChildAt(mIsAboveIcon ? getChildCount() - 1 : 0));
+            arrowPaint.setColor(itemAttachedToArrow.getArrowColor(mIsAboveIcon));
+            // The corner path effect won't be reflected in the shadow, but shouldn't be noticeable.
+            int radius = getResources().getDimensionPixelSize(R.dimen.popup_arrow_corner_radius);
+            arrowPaint.setPathEffect(new CornerPathEffect(radius));
+            arrowView.setBackground(arrowDrawable);
+            arrowView.setElevation(getElevation());
+        }
+        addView(arrowView, mIsAboveIcon ? getChildCount() : 0, layoutParams);
+        return arrowView;
+    }
+
+    @Override
+    public View getExtendedTouchView() {
+        return mOriginalIcon;
+    }
+
+    /**
+     * Determines when the deferred drag should be started.
+     *
+     * Current behavior:
+     * - Start the drag if the touch passes a certain distance from the original touch down.
+     */
+    public DragOptions.PreDragCondition createPreDragCondition() {
+        return new DragOptions.PreDragCondition() {
+            @Override
+            public boolean shouldStartDrag(double distanceDragged) {
+                return distanceDragged > mStartDragThreshold;
+            }
+
+            @Override
+            public void onPreDragStart(DropTarget.DragObject dragObject) {
+                mOriginalIcon.setVisibility(INVISIBLE);
+            }
+
+            @Override
+            public void onPreDragEnd(DropTarget.DragObject dragObject, 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();
+    }
+
+    /**
+     * Updates the notification header if the original icon's badge updated.
+     */
+    public void updateNotificationHeader(Set<PackageUserKey> updatedBadges) {
+        ItemInfo itemInfo = (ItemInfo) mOriginalIcon.getTag();
+        PackageUserKey packageUser = PackageUserKey.fromItemInfo(itemInfo);
+        if (updatedBadges.contains(packageUser)) {
+            updateNotificationHeader();
+        }
+    }
+
+    private void updateNotificationHeader() {
+        ItemInfo itemInfo = (ItemInfo) mOriginalIcon.getTag();
+        BadgeInfo badgeInfo = mLauncher.getPopupDataProvider().getBadgeInfoForItem(itemInfo);
+        if (mNotificationItemView != null && badgeInfo != null) {
+            IconPalette palette = mOriginalIcon.getIcon() instanceof FastBitmapDrawable
+                    ? ((FastBitmapDrawable) mOriginalIcon.getIcon()).getIconPalette()
+                    : null;
+            mNotificationItemView.updateHeader(badgeInfo.getNotificationCount(), palette);
+        }
+    }
+
+    public void trimNotifications(Map<PackageUserKey, BadgeInfo> updatedBadges) {
+        if (mNotificationItemView == null) {
+            return;
+        }
+        ItemInfo originalInfo = (ItemInfo) mOriginalIcon.getTag();
+        BadgeInfo badgeInfo = updatedBadges.get(PackageUserKey.fromItemInfo(originalInfo));
+        if (badgeInfo == null || badgeInfo.getNotificationKeys().size() == 0) {
+            AnimatorSet removeNotification = LauncherAnimUtils.createAnimatorSet();
+            final int duration = getResources().getInteger(
+                    R.integer.config_removeNotificationViewDuration);
+            final int spacing = getResources().getDimensionPixelSize(R.dimen.popup_items_spacing);
+            removeNotification.play(reduceNotificationViewHeight(
+                    mNotificationItemView.getHeightMinusFooter() + spacing, duration));
+            final View removeMarginView = mIsAboveIcon ? getItemViewAt(getItemCount() - 2)
+                    : mNotificationItemView;
+            if (removeMarginView != null) {
+                ValueAnimator removeMargin = ValueAnimator.ofFloat(1, 0).setDuration(duration);
+                removeMargin.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+                    @Override
+                    public void onAnimationUpdate(ValueAnimator valueAnimator) {
+                        ((MarginLayoutParams) removeMarginView.getLayoutParams()).bottomMargin
+                                = (int) (spacing * (float) valueAnimator.getAnimatedValue());
+                    }
+                });
+                removeNotification.play(removeMargin);
+            }
+            Animator fade = ObjectAnimator.ofFloat(mNotificationItemView, ALPHA, 0)
+                    .setDuration(duration);
+            fade.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    removeView(mNotificationItemView);
+                    mNotificationItemView = null;
+                    if (getItemCount() == 0) {
+                        close(false);
+                        return;
+                    }
+                }
+            });
+            removeNotification.play(fade);
+            final long arrowScaleDuration = getResources().getInteger(
+                    R.integer.config_deepShortcutArrowOpenDuration);
+            Animator hideArrow = createArrowScaleAnim(0).setDuration(arrowScaleDuration);
+            hideArrow.setStartDelay(0);
+            Animator showArrow = createArrowScaleAnim(1).setDuration(arrowScaleDuration);
+            showArrow.setStartDelay((long) (duration - arrowScaleDuration * 1.5));
+            removeNotification.playSequentially(hideArrow, showArrow);
+            removeNotification.start();
+            return;
+        }
+        mNotificationItemView.trimNotifications(NotificationKeyData.extractKeysOnly(
+                badgeInfo.getNotificationKeys()));
+    }
+
+    @Override
+    protected void onWidgetsBound() {
+        if (mShortcutsItemView != null) {
+            mShortcutsItemView.enableWidgetsIfExist(mOriginalIcon);
+        }
+    }
+
+    private ObjectAnimator createArrowScaleAnim(float scale) {
+        return LauncherAnimUtils.ofPropertyValuesHolder(
+                mArrow, new PropertyListBuilder().scale(scale).build());
+    }
+
+    /**
+     * Animates the height of the notification item and the translationY of other items accordingly.
+     */
+    public Animator reduceNotificationViewHeight(int heightToRemove, int duration) {
+        if (mReduceHeightAnimatorSet != null) {
+            mReduceHeightAnimatorSet.cancel();
+        }
+        final int translateYBy = mIsAboveIcon ? heightToRemove : -heightToRemove;
+        mReduceHeightAnimatorSet = LauncherAnimUtils.createAnimatorSet();
+        mReduceHeightAnimatorSet.play(mNotificationItemView.animateHeightRemoval(heightToRemove));
+        PropertyResetListener<View, Float> resetTranslationYListener
+                = new PropertyResetListener<>(TRANSLATION_Y, 0f);
+        for (int i = 0; i < getItemCount(); i++) {
+            final PopupItemView itemView = getItemViewAt(i);
+            if (!mIsAboveIcon && itemView == mNotificationItemView) {
+                // The notification view is already in the right place when container is below icon.
+                continue;
+            }
+            ValueAnimator translateItem = ObjectAnimator.ofFloat(itemView, TRANSLATION_Y,
+                    itemView.getTranslationY() + translateYBy).setDuration(duration);
+            translateItem.addListener(resetTranslationYListener);
+            mReduceHeightAnimatorSet.play(translateItem);
+        }
+        mReduceHeightAnimatorSet.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (mIsAboveIcon) {
+                    // All the items, including the notification item, translated down, but the
+                    // container itself did not. This means the items would jump back to their
+                    // original translation unless we update the container's translationY here.
+                    setTranslationY(getTranslationY() + translateYBy);
+                }
+                mReduceHeightAnimatorSet = null;
+            }
+        });
+        return mReduceHeightAnimatorSet;
+    }
+
+    @Override
+    public boolean supportsAppInfoDropTarget() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsDeleteDropTarget() {
+        return false;
+    }
+
+    @Override
+    public float getIntrinsicIconScaleFactor() {
+        return 1f;
+    }
+
+    @Override
+    public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete,
+            boolean success) {
+        if (!success) {
+            d.dragView.remove();
+            mLauncher.showWorkspace(true);
+            mLauncher.getDropTargetBar().onDragEnd();
+        }
+    }
+
+    @Override
+    public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
+        // Either the original icon or one of the shortcuts was dragged.
+        // Hide the container, but don't remove it yet because that interferes with touch events.
+        mDeferContainerRemoval = true;
+        animateClose();
+    }
+
+    @Override
+    public void onDragEnd() {
+        if (!mIsOpen) {
+            if (mOpenCloseAnimator != null) {
+                // Close animation is running.
+                mDeferContainerRemoval = false;
+            } else {
+                // Close animation is not running.
+                if (mDeferContainerRemoval) {
+                    closeComplete();
+                }
+            }
+        }
+    }
+
+    @Override
+    public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
+        target.itemType = ItemType.DEEPSHORTCUT;
+        targetParent.containerType = ContainerType.DEEPSHORTCUTS;
+    }
+
+    @Override
+    protected void handleClose(boolean animate) {
+        if (animate) {
+            animateClose();
+        } else {
+            closeComplete();
+        }
+    }
+
+    protected void animateClose() {
+        if (!mIsOpen) {
+            return;
+        }
+        if (mOpenCloseAnimator != null) {
+            mOpenCloseAnimator.cancel();
+        }
+        mIsOpen = false;
+
+        final AnimatorSet shortcutAnims = LauncherAnimUtils.createAnimatorSet();
+        final int itemCount = getItemCount();
+        int numOpenShortcuts = 0;
+        for (int i = 0; i < itemCount; i++) {
+            if (getItemViewAt(i).isOpenOrOpening()) {
+                numOpenShortcuts++;
+            }
+        }
+        final long duration = getResources().getInteger(
+                R.integer.config_deepShortcutCloseDuration);
+        final long arrowScaleDuration = getResources().getInteger(
+                R.integer.config_deepShortcutArrowOpenDuration);
+        final long stagger = getResources().getInteger(
+                R.integer.config_deepShortcutCloseStagger);
+        final TimeInterpolator fadeInterpolator = new LogAccelerateInterpolator(100, 0);
+
+        int firstOpenItemIndex = mIsAboveIcon ? itemCount - numOpenShortcuts : 0;
+        for (int i = firstOpenItemIndex; i < firstOpenItemIndex + numOpenShortcuts; i++) {
+            final PopupItemView view = getItemViewAt(i);
+            Animator anim;
+            anim = view.createCloseAnimation(mIsAboveIcon, mIsLeftAligned, duration);
+            int animationIndex = mIsAboveIcon ? i - firstOpenItemIndex
+                    : numOpenShortcuts - i - 1;
+            anim.setStartDelay(stagger * animationIndex);
+
+            Animator fadeAnim = ObjectAnimator.ofFloat(view, View.ALPHA, 0);
+            // Don't start fading until the arrow is gone.
+            fadeAnim.setStartDelay(stagger * animationIndex + arrowScaleDuration);
+            fadeAnim.setDuration(duration - arrowScaleDuration);
+            fadeAnim.setInterpolator(fadeInterpolator);
+            shortcutAnims.play(fadeAnim);
+            anim.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    view.setVisibility(INVISIBLE);
+                }
+            });
+            shortcutAnims.play(anim);
+        }
+        Animator arrowAnim = createArrowScaleAnim(0).setDuration(arrowScaleDuration);
+        arrowAnim.setStartDelay(0);
+        shortcutAnims.play(arrowAnim);
+
+        shortcutAnims.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mOpenCloseAnimator = null;
+                if (mDeferContainerRemoval) {
+                    setVisibility(INVISIBLE);
+                } else {
+                    closeComplete();
+                }
+            }
+        });
+        mOpenCloseAnimator = shortcutAnims;
+        shortcutAnims.start();
+        mOriginalIcon.forceHideBadge(false);
+    }
+
+    /**
+     * Closes the folder without animation.
+     */
+    protected void closeComplete() {
+        if (mOpenCloseAnimator != null) {
+            mOpenCloseAnimator.cancel();
+            mOpenCloseAnimator = null;
+        }
+        mIsOpen = false;
+        mDeferContainerRemoval = false;
+        boolean isInHotseat = ((ItemInfo) mOriginalIcon.getTag()).container
+                == LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+        mOriginalIcon.setTextVisibility(!isInHotseat);
+        mOriginalIcon.forceHideBadge(false);
+        mLauncher.getDragController().removeDragListener(this);
+        mLauncher.getDragLayer().removeView(this);
+    }
+
+    @Override
+    protected boolean isOfType(int type) {
+        return (type & TYPE_POPUP_CONTAINER_WITH_ARROW) != 0;
+    }
+
+    /**
+     * Returns a DeepShortcutsContainer which is already open or null
+     */
+    public static PopupContainerWithArrow getOpen(Launcher launcher) {
+        return getOpenView(launcher, TYPE_POPUP_CONTAINER_WITH_ARROW);
+    }
+
+    @Override
+    public int getLogContainerType() {
+        return ContainerType.DEEPSHORTCUTS;
+    }
+}
diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java
new file mode 100644
index 0000000..de9f25e
--- /dev/null
+++ b/src/com/android/launcher3/popup/PopupDataProvider.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.popup;
+
+import android.content.ComponentName;
+import android.service.notification.StatusBarNotification;
+import android.support.annotation.NonNull;
+import android.util.Log;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.badge.BadgeInfo;
+import com.android.launcher3.notification.NotificationInfo;
+import com.android.launcher3.notification.NotificationKeyData;
+import com.android.launcher3.notification.NotificationListener;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.MultiHashMap;
+import com.android.launcher3.util.PackageUserKey;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Provides data for the popup menu that appears after long-clicking on apps.
+ */
+public class PopupDataProvider implements NotificationListener.NotificationsChangedListener {
+
+    private static final boolean LOGD = false;
+    private static final String TAG = "PopupDataProvider";
+
+    /** Note that these are in order of priority. */
+    private static final SystemShortcut[] SYSTEM_SHORTCUTS = new SystemShortcut[] {
+            new SystemShortcut.AppInfo(),
+            new SystemShortcut.Widgets(),
+    };
+
+    private final Launcher mLauncher;
+
+    /** Maps launcher activity components to their list of shortcut ids. */
+    private MultiHashMap<ComponentKey, String> mDeepShortcutMap = new MultiHashMap<>();
+    /** Maps packages to their BadgeInfo's . */
+    private Map<PackageUserKey, BadgeInfo> mPackageUserToBadgeInfos = new HashMap<>();
+
+    public PopupDataProvider(Launcher launcher) {
+        mLauncher = launcher;
+    }
+
+    @Override
+    public void onNotificationPosted(PackageUserKey postedPackageUserKey,
+            NotificationKeyData notificationKey, boolean shouldBeFilteredOut) {
+        BadgeInfo badgeInfo = mPackageUserToBadgeInfos.get(postedPackageUserKey);
+        boolean badgeShouldBeRefreshed;
+        if (badgeInfo == null) {
+            if (!shouldBeFilteredOut) {
+                BadgeInfo newBadgeInfo = new BadgeInfo(postedPackageUserKey);
+                newBadgeInfo.addOrUpdateNotificationKey(notificationKey);
+                mPackageUserToBadgeInfos.put(postedPackageUserKey, newBadgeInfo);
+                badgeShouldBeRefreshed = true;
+            } else {
+                badgeShouldBeRefreshed = false;
+            }
+        } else {
+            badgeShouldBeRefreshed = shouldBeFilteredOut
+                    ? badgeInfo.removeNotificationKey(notificationKey)
+                    : badgeInfo.addOrUpdateNotificationKey(notificationKey);
+            if (badgeInfo.getNotificationKeys().size() == 0) {
+                mPackageUserToBadgeInfos.remove(postedPackageUserKey);
+            }
+        }
+        updateLauncherIconBadges(Utilities.singletonHashSet(postedPackageUserKey),
+                badgeShouldBeRefreshed);
+    }
+
+    @Override
+    public void onNotificationRemoved(PackageUserKey removedPackageUserKey,
+            NotificationKeyData notificationKey) {
+        BadgeInfo oldBadgeInfo = mPackageUserToBadgeInfos.get(removedPackageUserKey);
+        if (oldBadgeInfo != null && oldBadgeInfo.removeNotificationKey(notificationKey)) {
+            if (oldBadgeInfo.getNotificationKeys().size() == 0) {
+                mPackageUserToBadgeInfos.remove(removedPackageUserKey);
+            }
+            updateLauncherIconBadges(Utilities.singletonHashSet(removedPackageUserKey));
+
+            PopupContainerWithArrow openContainer = PopupContainerWithArrow.getOpen(mLauncher);
+            if (openContainer != null) {
+                openContainer.trimNotifications(mPackageUserToBadgeInfos);
+            }
+        }
+    }
+
+    @Override
+    public void onNotificationFullRefresh(List<StatusBarNotification> activeNotifications) {
+        if (activeNotifications == null) return;
+        // This will contain the PackageUserKeys which have updated badges.
+        HashMap<PackageUserKey, BadgeInfo> updatedBadges = new HashMap<>(mPackageUserToBadgeInfos);
+        mPackageUserToBadgeInfos.clear();
+        for (StatusBarNotification notification : activeNotifications) {
+            PackageUserKey packageUserKey = PackageUserKey.fromNotification(notification);
+            BadgeInfo badgeInfo = mPackageUserToBadgeInfos.get(packageUserKey);
+            if (badgeInfo == null) {
+                badgeInfo = new BadgeInfo(packageUserKey);
+                mPackageUserToBadgeInfos.put(packageUserKey, badgeInfo);
+            }
+            badgeInfo.addOrUpdateNotificationKey(NotificationKeyData
+                    .fromNotification(notification));
+        }
+
+        // Add and remove from updatedBadges so it contains the PackageUserKeys of updated badges.
+        for (PackageUserKey packageUserKey : mPackageUserToBadgeInfos.keySet()) {
+            BadgeInfo prevBadge = updatedBadges.get(packageUserKey);
+            BadgeInfo newBadge = mPackageUserToBadgeInfos.get(packageUserKey);
+            if (prevBadge == null) {
+                updatedBadges.put(packageUserKey, newBadge);
+            } else {
+                if (!prevBadge.shouldBeInvalidated(newBadge)) {
+                    updatedBadges.remove(packageUserKey);
+                }
+            }
+        }
+
+        if (!updatedBadges.isEmpty()) {
+            updateLauncherIconBadges(updatedBadges.keySet());
+        }
+
+        PopupContainerWithArrow openContainer = PopupContainerWithArrow.getOpen(mLauncher);
+        if (openContainer != null) {
+            openContainer.trimNotifications(updatedBadges);
+        }
+    }
+
+    private void updateLauncherIconBadges(Set<PackageUserKey> updatedBadges) {
+        updateLauncherIconBadges(updatedBadges, true);
+    }
+
+    /**
+     * Updates the icons on launcher (workspace, folders, all apps) to refresh their badges.
+     * @param updatedBadges The packages whose badges should be refreshed (either a notification was
+     *                      added or removed, or the badge should show the notification icon).
+     * @param shouldRefresh An optional parameter that will allow us to only refresh badges that
+     *                      have actually changed. If a notification updated its content but not
+     *                      its count or icon, then the badge doesn't change.
+     */
+    private void updateLauncherIconBadges(Set<PackageUserKey> updatedBadges,
+            boolean shouldRefresh) {
+        Iterator<PackageUserKey> iterator = updatedBadges.iterator();
+        while (iterator.hasNext()) {
+            BadgeInfo badgeInfo = mPackageUserToBadgeInfos.get(iterator.next());
+            if (badgeInfo != null && !updateBadgeIcon(badgeInfo) && !shouldRefresh) {
+                // The notification icon isn't used, and the badge hasn't changed
+                // so there is no update to be made.
+                iterator.remove();
+            }
+        }
+        if (!updatedBadges.isEmpty()) {
+            mLauncher.updateIconBadges(updatedBadges);
+        }
+    }
+
+    /**
+     * Determines whether the badge should show a notification icon rather than a number,
+     * and sets that icon on the BadgeInfo if so.
+     * @param badgeInfo The badge to update with an icon (null if it shouldn't show one).
+     * @return Whether the badge icon potentially changed (true unless it stayed null).
+     */
+    private boolean updateBadgeIcon(BadgeInfo badgeInfo) {
+        boolean hadNotificationToShow = badgeInfo.hasNotificationToShow();
+        NotificationInfo notificationInfo = null;
+        NotificationListener notificationListener = NotificationListener.getInstanceIfConnected();
+        if (notificationListener != null && badgeInfo.getNotificationKeys().size() >= 1) {
+            // Look for the most recent notification that has an icon that should be shown in badge.
+            for (NotificationKeyData notificationKeyData : badgeInfo.getNotificationKeys()) {
+                String notificationKey = notificationKeyData.notificationKey;
+                StatusBarNotification[] activeNotifications = notificationListener
+                        .getActiveNotifications(new String[]{notificationKey});
+                if (activeNotifications.length == 1) {
+                    notificationInfo = new NotificationInfo(mLauncher, activeNotifications[0]);
+                    if (notificationInfo.shouldShowIconInBadge()) {
+                        // Found an appropriate icon.
+                        break;
+                    } else {
+                        // Keep looking.
+                        notificationInfo = null;
+                    }
+                }
+            }
+        }
+        badgeInfo.setNotificationToShow(notificationInfo);
+        return hadNotificationToShow || badgeInfo.hasNotificationToShow();
+    }
+
+    public void setDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMapCopy) {
+        mDeepShortcutMap = deepShortcutMapCopy;
+        if (LOGD) Log.d(TAG, "bindDeepShortcutMap: " + mDeepShortcutMap);
+    }
+
+    public List<String> getShortcutIdsForItem(ItemInfo info) {
+        if (!DeepShortcutManager.supportsShortcuts(info)) {
+            return Collections.EMPTY_LIST;
+        }
+        ComponentName component = info.getTargetComponent();
+        if (component == null) {
+            return Collections.EMPTY_LIST;
+        }
+
+        List<String> ids = mDeepShortcutMap.get(new ComponentKey(component, info.user));
+        return ids == null ? Collections.EMPTY_LIST : ids;
+    }
+
+    public BadgeInfo getBadgeInfoForItem(ItemInfo info) {
+        if (!DeepShortcutManager.supportsShortcuts(info)) {
+            return null;
+        }
+
+        return mPackageUserToBadgeInfos.get(PackageUserKey.fromItemInfo(info));
+    }
+
+    public @NonNull List<NotificationKeyData> getNotificationKeysForItem(ItemInfo info) {
+        BadgeInfo badgeInfo = getBadgeInfoForItem(info);
+        return badgeInfo == null ? Collections.EMPTY_LIST : badgeInfo.getNotificationKeys();
+    }
+
+    /** This makes a potentially expensive binder call and should be run on a background thread. */
+    public @NonNull List<StatusBarNotification> getStatusBarNotificationsForKeys(
+            List<NotificationKeyData> notificationKeys) {
+        NotificationListener notificationListener = NotificationListener.getInstanceIfConnected();
+        return notificationListener == null ? Collections.EMPTY_LIST
+                : notificationListener.getNotificationsForKeys(notificationKeys);
+    }
+
+    public @NonNull List<SystemShortcut> getEnabledSystemShortcutsForItem(ItemInfo info) {
+        List<SystemShortcut> systemShortcuts = new ArrayList<>();
+        for (SystemShortcut systemShortcut : SYSTEM_SHORTCUTS) {
+            if (systemShortcut.getOnClickListener(mLauncher, info) != null) {
+                systemShortcuts.add(systemShortcut);
+            }
+        }
+        return systemShortcuts;
+    }
+
+    public void cancelNotification(String notificationKey) {
+        NotificationListener notificationListener = NotificationListener.getInstanceIfConnected();
+        if (notificationListener == null) {
+            return;
+        }
+        notificationListener.cancelNotification(notificationKey);
+    }
+}
diff --git a/src/com/android/launcher3/popup/PopupItemView.java b/src/com/android/launcher3/popup/PopupItemView.java
new file mode 100644
index 0000000..384f554
--- /dev/null
+++ b/src/com/android/launcher3/popup/PopupItemView.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.popup;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import com.android.launcher3.LogAccelerateInterpolator;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.util.PillRevealOutlineProvider;
+
+/**
+ * An abstract {@link FrameLayout} that supports animating an item's content
+ * (e.g. icon and text) separate from the item's background.
+ */
+public abstract class PopupItemView extends FrameLayout
+        implements ValueAnimator.AnimatorUpdateListener {
+
+    protected static final Point sTempPoint = new Point();
+
+    protected final Rect mPillRect;
+    private float mOpenAnimationProgress;
+    protected final boolean mIsRtl;
+    protected View mIconView;
+
+    private final Paint mBackgroundClipPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
+    private final Matrix mMatrix = new Matrix();
+    private Bitmap mRoundedCornerBitmap;
+
+    public PopupItemView(Context context) {
+        this(context, null, 0);
+    }
+
+    public PopupItemView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public PopupItemView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        mPillRect = new Rect();
+
+        // Initialize corner clipping Bitmap and Paint.
+        int radius = (int) getBackgroundRadius();
+        mRoundedCornerBitmap = Bitmap.createBitmap(radius, radius, Bitmap.Config.ALPHA_8);
+        Canvas canvas = new Canvas();
+        canvas.setBitmap(mRoundedCornerBitmap);
+        canvas.drawArc(0, 0, radius*2, radius*2, 180, 90, true, mBackgroundClipPaint);
+        mBackgroundClipPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
+
+        mIsRtl = Utilities.isRtl(getResources());
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mIconView = findViewById(R.id.popup_item_icon);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        mPillRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
+    }
+
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        int saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), null);
+        super.dispatchDraw(canvas);
+
+        int cornerWidth = mRoundedCornerBitmap.getWidth();
+        int cornerHeight = mRoundedCornerBitmap.getHeight();
+        // Clip top left corner.
+        mMatrix.reset();
+        canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint);
+        // Clip top right corner.
+        mMatrix.setRotate(90, cornerWidth / 2, cornerHeight / 2);
+        mMatrix.postTranslate(canvas.getWidth() - cornerWidth, 0);
+        canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint);
+        // Clip bottom right corner.
+        mMatrix.setRotate(180, cornerWidth / 2, cornerHeight / 2);
+        mMatrix.postTranslate(canvas.getWidth() - cornerWidth, canvas.getHeight() - cornerHeight);
+        canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint);
+        // Clip bottom left corner.
+        mMatrix.setRotate(270, cornerWidth / 2, cornerHeight / 2);
+        mMatrix.postTranslate(0, canvas.getHeight() - cornerHeight);
+        canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint);
+
+        canvas.restoreToCount(saveCount);
+    }
+
+    /**
+     * Creates an animator to play when the shortcut container is being opened.
+     */
+    public Animator createOpenAnimation(boolean isContainerAboveIcon, boolean pivotLeft) {
+        Point center = getIconCenter();
+        int arrowCenter = getResources().getDimensionPixelSize(pivotLeft ^ mIsRtl ?
+                R.dimen.popup_arrow_horizontal_center_start:
+                R.dimen.popup_arrow_horizontal_center_end);
+        ValueAnimator openAnimator =  new ZoomRevealOutlineProvider(center.x, center.y,
+                mPillRect, this, mIconView, isContainerAboveIcon, pivotLeft, arrowCenter)
+                        .createRevealAnimator(this, false);
+        mOpenAnimationProgress = 0f;
+        openAnimator.addUpdateListener(this);
+        return openAnimator;
+    }
+
+    @Override
+    public void onAnimationUpdate(ValueAnimator valueAnimator) {
+        mOpenAnimationProgress = valueAnimator.getAnimatedFraction();
+    }
+
+    public boolean isOpenOrOpening() {
+        return mOpenAnimationProgress > 0;
+    }
+
+    /**
+     * Creates an animator to play when the shortcut container is being closed.
+     */
+    public Animator createCloseAnimation(boolean isContainerAboveIcon, boolean pivotLeft,
+            long duration) {
+        Point center = getIconCenter();
+        int arrowCenter = getResources().getDimensionPixelSize(pivotLeft ^ mIsRtl ?
+                R.dimen.popup_arrow_horizontal_center_start :
+                R.dimen.popup_arrow_horizontal_center_end);
+        ValueAnimator closeAnimator = new ZoomRevealOutlineProvider(center.x, center.y,
+                mPillRect, this, mIconView, isContainerAboveIcon, pivotLeft, arrowCenter)
+                        .createRevealAnimator(this, true);
+        // Scale down the duration and interpolator according to the progress
+        // that the open animation was at when the close started.
+        closeAnimator.setDuration((long) (duration * mOpenAnimationProgress));
+        closeAnimator.setInterpolator(new CloseInterpolator(mOpenAnimationProgress));
+        closeAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mOpenAnimationProgress = 0;
+            }
+        });
+        return closeAnimator;
+    }
+
+    /**
+     * Returns the position of the center of the icon relative to the container.
+     */
+    public Point getIconCenter() {
+        sTempPoint.y = getMeasuredHeight() / 2;
+        sTempPoint.x = getResources().getDimensionPixelSize(R.dimen.bg_popup_item_height) / 2;
+        if (Utilities.isRtl(getResources())) {
+            sTempPoint.x = getMeasuredWidth() - sTempPoint.x;
+        }
+        return sTempPoint;
+    }
+
+    protected float getBackgroundRadius() {
+        return getResources().getDimensionPixelSize(R.dimen.bg_round_rect_radius);
+    }
+
+    public abstract int getArrowColor(boolean isArrowAttachedToBottom);
+
+    /**
+     * Extension of {@link PillRevealOutlineProvider} which scales the icon based on the height.
+     */
+    private static class ZoomRevealOutlineProvider extends PillRevealOutlineProvider {
+
+        private final View mTranslateView;
+        private final View mZoomView;
+
+        private final float mFullHeight;
+        private final float mTranslateYMultiplier;
+
+        private final boolean mPivotLeft;
+        private final float mTranslateX;
+        private final float mArrowCenter;
+
+        public ZoomRevealOutlineProvider(int x, int y, Rect pillRect, PopupItemView translateView,
+                View zoomView, boolean isContainerAboveIcon, boolean pivotLeft, float arrowCenter) {
+            super(x, y, pillRect, translateView.getBackgroundRadius());
+            mTranslateView = translateView;
+            mZoomView = zoomView;
+            mFullHeight = pillRect.height();
+
+            mTranslateYMultiplier = isContainerAboveIcon ? 0.5f : -0.5f;
+
+            mPivotLeft = pivotLeft;
+            mTranslateX = pivotLeft ? arrowCenter : pillRect.right - arrowCenter;
+            mArrowCenter = arrowCenter;
+        }
+
+        @Override
+        public void setProgress(float progress) {
+            super.setProgress(progress);
+
+            if (mZoomView != null) {
+                mZoomView.setScaleX(progress);
+                mZoomView.setScaleY(progress);
+            }
+
+            float height = mOutline.height();
+            mTranslateView.setTranslationY(mTranslateYMultiplier * (mFullHeight - height));
+
+            float offsetX = Math.min(mOutline.width(), mArrowCenter);
+            float pivotX = mPivotLeft ? (mOutline.left + offsetX) : (mOutline.right - offsetX);
+            mTranslateView.setTranslationX(mTranslateX - pivotX);
+        }
+    }
+
+    /**
+     * An interpolator that reverses the current open animation progress.
+     */
+    private static class CloseInterpolator extends LogAccelerateInterpolator {
+        private float mStartProgress;
+        private float mRemainingProgress;
+
+        /**
+         * @param openAnimationProgress The progress that the open interpolator ended at.
+         */
+        public CloseInterpolator(float openAnimationProgress) {
+            super(100, 0);
+            mStartProgress = 1f - openAnimationProgress;
+            mRemainingProgress = openAnimationProgress;
+        }
+
+        @Override
+        public float getInterpolation(float v) {
+            return mStartProgress + super.getInterpolation(v) * mRemainingProgress;
+        }
+    }
+}
diff --git a/src/com/android/launcher3/popup/PopupPopulator.java b/src/com/android/launcher3/popup/PopupPopulator.java
new file mode 100644
index 0000000..c62d877
--- /dev/null
+++ b/src/com/android/launcher3/popup/PopupPopulator.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.popup;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+import android.view.View;
+import android.widget.ImageView;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.graphics.LauncherIcons;
+import com.android.launcher3.notification.NotificationInfo;
+import com.android.launcher3.notification.NotificationItemView;
+import com.android.launcher3.notification.NotificationKeyData;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.shortcuts.DeepShortcutView;
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+import com.android.launcher3.util.PackageUserKey;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Contains logic relevant to populating a {@link PopupContainerWithArrow}. In particular,
+ * this class determines which items appear in the container, and in what order.
+ */
+public class PopupPopulator {
+
+    public static final int MAX_ITEMS = 4;
+    @VisibleForTesting static final int NUM_DYNAMIC = 2;
+    private static final int MAX_SHORTCUTS_IF_NOTIFICATIONS = 2;
+
+    public enum Item {
+        SHORTCUT(R.layout.deep_shortcut, true),
+        NOTIFICATION(R.layout.notification, false),
+        SYSTEM_SHORTCUT(R.layout.system_shortcut, true),
+        SYSTEM_SHORTCUT_ICON(R.layout.system_shortcut_icon_only, true);
+
+        public final int layoutId;
+        public final boolean isShortcut;
+
+        Item(int layoutId, boolean isShortcut) {
+            this.layoutId = layoutId;
+            this.isShortcut = isShortcut;
+        }
+    }
+
+    public static @NonNull Item[] getItemsToPopulate(@NonNull List<String> shortcutIds,
+            @NonNull List<NotificationKeyData> notificationKeys,
+            @NonNull List<SystemShortcut> systemShortcuts) {
+        boolean hasNotifications = notificationKeys.size() > 0;
+        int numNotificationItems = hasNotifications ? 1 : 0;
+        int numShortcuts = shortcutIds.size();
+        if (hasNotifications && numShortcuts > MAX_SHORTCUTS_IF_NOTIFICATIONS) {
+            numShortcuts = MAX_SHORTCUTS_IF_NOTIFICATIONS;
+        }
+        int numItems = Math.min(MAX_ITEMS, numShortcuts + numNotificationItems)
+                + systemShortcuts.size();
+        Item[] items = new Item[numItems];
+        for (int i = 0; i < numItems; i++) {
+            items[i] = Item.SHORTCUT;
+        }
+        if (hasNotifications) {
+            // The notification layout is always first.
+            items[0] = Item.NOTIFICATION;
+        }
+        // The system shortcuts are always last.
+        boolean iconsOnly = !shortcutIds.isEmpty();
+        for (int i = 0; i < systemShortcuts.size(); i++) {
+            items[numItems - 1 - i] = iconsOnly ? Item.SYSTEM_SHORTCUT_ICON : Item.SYSTEM_SHORTCUT;
+        }
+        return items;
+    }
+
+    public static Item[] reverseItems(Item[] items) {
+        if (items == null) return null;
+        int numItems = items.length;
+        Item[] reversedArray = new Item[numItems];
+        for (int i = 0; i < numItems; i++) {
+            reversedArray[i] = items[numItems - i - 1];
+        }
+        return reversedArray;
+    }
+
+    /**
+     * Sorts shortcuts in rank order, with manifest shortcuts coming before dynamic shortcuts.
+     */
+    private static final Comparator<ShortcutInfoCompat> SHORTCUT_RANK_COMPARATOR
+            = new Comparator<ShortcutInfoCompat>() {
+        @Override
+        public int compare(ShortcutInfoCompat a, ShortcutInfoCompat b) {
+            if (a.isDeclaredInManifest() && !b.isDeclaredInManifest()) {
+                return -1;
+            }
+            if (!a.isDeclaredInManifest() && b.isDeclaredInManifest()) {
+                return 1;
+            }
+            return Integer.compare(a.getRank(), b.getRank());
+        }
+    };
+
+    /**
+     * Filters the shortcuts so that only MAX_ITEMS or fewer shortcuts are retained.
+     * We want the filter to include both static and dynamic shortcuts, so we always
+     * include NUM_DYNAMIC dynamic shortcuts, if at least that many are present.
+     *
+     * @param shortcutIdToRemoveFirst An id that should be filtered out first, if any.
+     * @return a subset of shortcuts, in sorted order, with size <= MAX_ITEMS.
+     */
+    public static List<ShortcutInfoCompat> sortAndFilterShortcuts(
+            List<ShortcutInfoCompat> shortcuts, @Nullable String shortcutIdToRemoveFirst) {
+        // Remove up to one specific shortcut before sorting and doing somewhat fancy filtering.
+        if (shortcutIdToRemoveFirst != null) {
+            Iterator<ShortcutInfoCompat> shortcutIterator = shortcuts.iterator();
+            while (shortcutIterator.hasNext()) {
+                if (shortcutIterator.next().getId().equals(shortcutIdToRemoveFirst)) {
+                    shortcutIterator.remove();
+                    break;
+                }
+            }
+        }
+
+        Collections.sort(shortcuts, SHORTCUT_RANK_COMPARATOR);
+        if (shortcuts.size() <= MAX_ITEMS) {
+            return shortcuts;
+        }
+
+        // The list of shortcuts is now sorted with static shortcuts followed by dynamic
+        // shortcuts. We want to preserve this order, but only keep MAX_ITEMS.
+        List<ShortcutInfoCompat> filteredShortcuts = new ArrayList<>(MAX_ITEMS);
+        int numDynamic = 0;
+        int size = shortcuts.size();
+        for (int i = 0; i < size; i++) {
+            ShortcutInfoCompat shortcut = shortcuts.get(i);
+            int filteredSize = filteredShortcuts.size();
+            if (filteredSize < MAX_ITEMS) {
+                // Always add the first MAX_ITEMS to the filtered list.
+                filteredShortcuts.add(shortcut);
+                if (shortcut.isDynamic()) {
+                    numDynamic++;
+                }
+                continue;
+            }
+            // At this point, we have MAX_ITEMS already, but they may all be static.
+            // If there are dynamic shortcuts, remove static shortcuts to add them.
+            if (shortcut.isDynamic() && numDynamic < NUM_DYNAMIC) {
+                numDynamic++;
+                int lastStaticIndex = filteredSize - numDynamic;
+                filteredShortcuts.remove(lastStaticIndex);
+                filteredShortcuts.add(shortcut);
+            }
+        }
+        return filteredShortcuts;
+    }
+
+    public static Runnable createUpdateRunnable(final Launcher launcher, final ItemInfo originalInfo,
+            final Handler uiHandler, final PopupContainerWithArrow container,
+            final List<String> shortcutIds, final List<DeepShortcutView> shortcutViews,
+            final List<NotificationKeyData> notificationKeys,
+            final NotificationItemView notificationView, final List<SystemShortcut> systemShortcuts,
+            final List<View> systemShortcutViews) {
+        final ComponentName activity = originalInfo.getTargetComponent();
+        final UserHandle user = originalInfo.user;
+        return new Runnable() {
+            @Override
+            public void run() {
+                if (notificationView != null) {
+                    List<StatusBarNotification> notifications = launcher.getPopupDataProvider()
+                            .getStatusBarNotificationsForKeys(notificationKeys);
+                    List<NotificationInfo> infos = new ArrayList<>(notifications.size());
+                    for (int i = 0; i < notifications.size(); i++) {
+                        StatusBarNotification notification = notifications.get(i);
+                        infos.add(new NotificationInfo(launcher, notification));
+                    }
+                    uiHandler.post(new UpdateNotificationChild(notificationView, infos));
+                }
+
+                List<ShortcutInfoCompat> shortcuts = DeepShortcutManager.getInstance(launcher)
+                        .queryForShortcutsContainer(activity, shortcutIds, user);
+                String shortcutIdToDeDupe = notificationKeys.isEmpty() ? null
+                        : notificationKeys.get(0).shortcutId;
+                shortcuts = PopupPopulator.sortAndFilterShortcuts(shortcuts, shortcutIdToDeDupe);
+                for (int i = 0; i < shortcuts.size() && i < shortcutViews.size(); i++) {
+                    final ShortcutInfoCompat shortcut = shortcuts.get(i);
+                    ShortcutInfo si = new ShortcutInfo(shortcut, launcher);
+                    // Use unbadged icon for the menu.
+                    si.iconBitmap = LauncherIcons.createShortcutIcon(
+                            shortcut, launcher, false /* badged */);
+                    si.rank = i;
+                    uiHandler.post(new UpdateShortcutChild(container, shortcutViews.get(i),
+                            si, shortcut));
+                }
+
+                // This ensures that mLauncher.getWidgetsForPackageUser()
+                // doesn't return null (it puts all the widgets in memory).
+                for (int i = 0; i < systemShortcuts.size(); i++) {
+                    final SystemShortcut systemShortcut = systemShortcuts.get(i);
+                    uiHandler.post(new UpdateSystemShortcutChild(container,
+                            systemShortcutViews.get(i), systemShortcut, launcher, originalInfo));
+                }
+                uiHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        launcher.refreshAndBindWidgetsForPackageUser(
+                                PackageUserKey.fromItemInfo(originalInfo));
+                    }
+                });
+            }
+        };
+    }
+
+    /** Updates the shortcut child of this container based on the given shortcut info. */
+    private static class UpdateShortcutChild implements Runnable {
+        private final PopupContainerWithArrow mContainer;
+        private final DeepShortcutView mShortcutChild;
+        private final ShortcutInfo mShortcutChildInfo;
+        private final ShortcutInfoCompat mDetail;
+
+        public UpdateShortcutChild(PopupContainerWithArrow container, DeepShortcutView shortcutChild,
+                ShortcutInfo shortcutChildInfo, ShortcutInfoCompat detail) {
+            mContainer = container;
+            mShortcutChild = shortcutChild;
+            mShortcutChildInfo = shortcutChildInfo;
+            mDetail = detail;
+        }
+
+        @Override
+        public void run() {
+            mShortcutChild.applyShortcutInfo(mShortcutChildInfo, mDetail,
+                    mContainer.mShortcutsItemView);
+        }
+    }
+
+    /** Updates the notification child based on the given notification info. */
+    private static class UpdateNotificationChild implements Runnable {
+        private NotificationItemView mNotificationView;
+        private List<NotificationInfo> mNotificationInfos;
+
+        public UpdateNotificationChild(NotificationItemView notificationView,
+                List<NotificationInfo> notificationInfos) {
+            mNotificationView = notificationView;
+            mNotificationInfos = notificationInfos;
+        }
+
+        @Override
+        public void run() {
+            mNotificationView.applyNotificationInfos(mNotificationInfos);
+        }
+    }
+
+    /** Updates the system shortcut child based on the given shortcut info. */
+    private static class UpdateSystemShortcutChild implements Runnable {
+
+        private final PopupContainerWithArrow mContainer;
+        private final View mSystemShortcutChild;
+        private final SystemShortcut mSystemShortcutInfo;
+        private final Launcher mLauncher;
+        private final ItemInfo mItemInfo;
+
+        public UpdateSystemShortcutChild(PopupContainerWithArrow container, View systemShortcutChild,
+                SystemShortcut systemShortcut, Launcher launcher, ItemInfo originalInfo) {
+            mContainer = container;
+            mSystemShortcutChild = systemShortcutChild;
+            mSystemShortcutInfo = systemShortcut;
+            mLauncher = launcher;
+            mItemInfo = originalInfo;
+        }
+
+        @Override
+        public void run() {
+            final Context context = mSystemShortcutChild.getContext();
+            initializeSystemShortcut(context, mSystemShortcutChild, mSystemShortcutInfo);
+            mSystemShortcutChild.setOnClickListener(mSystemShortcutInfo
+                    .getOnClickListener(mLauncher, mItemInfo));
+        }
+    }
+
+    public static void initializeSystemShortcut(Context context, View view, SystemShortcut info) {
+        if (view instanceof DeepShortcutView) {
+            // Expanded system shortcut, with both icon and text shown on white background.
+            final DeepShortcutView shortcutView = (DeepShortcutView) view;
+            shortcutView.getIconView().setBackground(info.getIcon(context,
+                    android.R.attr.textColorTertiary));
+            shortcutView.getBubbleText().setText(info.getLabel(context));
+        } else if (view instanceof ImageView) {
+            // Only the system shortcut icon shows on a gray background header.
+            final ImageView shortcutIcon = (ImageView) view;
+            shortcutIcon.setImageDrawable(info.getIcon(context,
+                    android.R.attr.textColorHint));
+            shortcutIcon.setContentDescription(info.getLabel(context));
+        }
+        view.setTag(info);
+    }
+}
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
new file mode 100644
index 0000000..f158f71
--- /dev/null
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -0,0 +1,94 @@
+package com.android.launcher3.popup;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.view.View;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.InfoDropTarget;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.util.Themes;
+import com.android.launcher3.widget.WidgetsBottomSheet;
+
+import java.util.List;
+
+/**
+ * Represents a system shortcut for a given app. The shortcut should have a static label and
+ * icon, and an onClickListener that depends on the item that the shortcut services.
+ *
+ * Example system shortcuts, defined as inner classes, include Widgets and AppInfo.
+ */
+public abstract class SystemShortcut {
+    private final int mIconResId;
+    private final int mLabelResId;
+
+    public SystemShortcut(int iconResId, int labelResId) {
+        mIconResId = iconResId;
+        mLabelResId = labelResId;
+    }
+
+    public Drawable getIcon(Context context, int colorAttr) {
+        Drawable icon = context.getResources().getDrawable(mIconResId, context.getTheme()).mutate();
+        icon.setTint(Themes.getAttrColor(context, colorAttr));
+        return icon;
+    }
+
+    public String getLabel(Context context) {
+        return context.getString(mLabelResId);
+    }
+
+    public abstract View.OnClickListener getOnClickListener(final Launcher launcher,
+            final ItemInfo itemInfo);
+
+    public static class Widgets extends SystemShortcut {
+
+        public Widgets() {
+            super(R.drawable.ic_widget, R.string.widget_button_text);
+        }
+
+        @Override
+        public View.OnClickListener getOnClickListener(final Launcher launcher,
+                final ItemInfo itemInfo) {
+            final List<WidgetItem> widgets = launcher.getWidgetsForPackageUser(new PackageUserKey(
+                    itemInfo.getTargetComponent().getPackageName(), itemInfo.user));
+            if (widgets == null) {
+                return null;
+            }
+            return new View.OnClickListener() {
+                @Override
+                public void onClick(View view) {
+                    AbstractFloatingView.closeAllOpenViews(launcher);
+                    WidgetsBottomSheet widgetsBottomSheet =
+                            (WidgetsBottomSheet) launcher.getLayoutInflater().inflate(
+                                    R.layout.widgets_bottom_sheet, launcher.getDragLayer(), false);
+                    widgetsBottomSheet.populateAndShow(itemInfo);
+                }
+            };
+        }
+    }
+
+    public static class AppInfo extends SystemShortcut {
+        public AppInfo() {
+            super(R.drawable.ic_info_no_shadow, R.string.app_info_drop_target_label);
+        }
+
+        @Override
+        public View.OnClickListener getOnClickListener(final Launcher launcher,
+                final ItemInfo itemInfo) {
+            return new View.OnClickListener() {
+                @Override
+                public void onClick(View view) {
+                    Rect sourceBounds = launcher.getViewBounds(view);
+                    Bundle opts = launcher.getActivityLaunchOptions(view);
+                    InfoDropTarget.startDetailsActivityForInfo(itemInfo, launcher, null, sourceBounds, opts);
+                }
+            };
+        }
+    }
+}
diff --git a/src/com/android/launcher3/provider/ImportDataTask.java b/src/com/android/launcher3/provider/ImportDataTask.java
index 5cb34e8..b0482f8 100644
--- a/src/com/android/launcher3/provider/ImportDataTask.java
+++ b/src/com/android/launcher3/provider/ImportDataTask.java
@@ -44,7 +44,6 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
-import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.config.ProviderConfig;
@@ -90,10 +89,12 @@
         ArrayList<Long> allScreens = LauncherDbUtils.getScreenIdsFromCursor(
                 mContext.getContentResolver().query(mOtherScreensUri, null, null, null,
                         LauncherSettings.WorkspaceScreens.SCREEN_RANK));
+        FileLog.d(TAG, "Importing DB from " + mOtherFavoritesUri);
 
         // During import we reset the screen IDs to 0-indexed values.
         if (allScreens.isEmpty()) {
             // No thing to migrate
+            FileLog.e(TAG, "No data found to import");
             return false;
         }
 
@@ -130,7 +131,7 @@
     private void importWorkspaceItems(
             long firsetScreenId, LongSparseArray<Long> screenIdMap) throws Exception {
         String profileId = Long.toString(UserManagerCompat.getInstance(mContext)
-                .getSerialNumberForUser(UserHandleCompat.myUserHandle()));
+                .getSerialNumberForUser(Process.myUserHandle()));
 
         boolean createEmptyRowOnFirstScreen = false;
         if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
@@ -294,6 +295,7 @@
                 }
             }
         }
+        FileLog.d(TAG, totalItemsOnWorkspace + " items imported from external source");
         if (totalItemsOnWorkspace < MIN_ITEM_COUNT_FOR_SUCCESSFUL_MIGRATION) {
             throw new Exception("Insufficient data");
         }
@@ -304,7 +306,7 @@
         }
 
         LongArrayMap<Object> hotseatItems = GridSizeMigrationTask.removeBrokenHotseatItems(mContext);
-        int myHotseatCount = LauncherAppState.getInstance().getInvariantDeviceProfile().numHotseatIcons;
+        int myHotseatCount = LauncherAppState.getIDP(mContext).numHotseatIcons;
         if (!FeatureFlags.NO_ALL_APPS_ICON) {
             myHotseatCount--;
         }
@@ -379,8 +381,8 @@
         return c.getSharedPreferences(LauncherFiles.DEVICE_PREFERENCES_KEY, Context.MODE_PRIVATE);
     }
 
-    private static final int getMyHotseatLayoutId() {
-        return LauncherAppState.getInstance().getInvariantDeviceProfile().numHotseatIcons <= 5
+    private static final int getMyHotseatLayoutId(Context context) {
+        return LauncherAppState.getIDP(context).numHotseatIcons <= 5
                 ? R.xml.dw_phone_hotseat
                 : R.xml.dw_tablet_hotseat;
     }
@@ -390,7 +392,7 @@
      */
     private static class HotseatLayoutParser extends DefaultLayoutParser {
         public HotseatLayoutParser(Context context, LayoutParserCallback callback) {
-            super(context, null, callback, context.getResources(), getMyHotseatLayoutId());
+            super(context, null, callback, context.getResources(), getMyHotseatLayoutId(context));
         }
 
         @Override
diff --git a/src/com/android/launcher3/provider/LauncherDbUtils.java b/src/com/android/launcher3/provider/LauncherDbUtils.java
index faa5fad..1758350 100644
--- a/src/com/android/launcher3/provider/LauncherDbUtils.java
+++ b/src/com/android/launcher3/provider/LauncherDbUtils.java
@@ -17,6 +17,7 @@
 package com.android.launcher3.provider;
 
 import android.content.ContentValues;
+import android.content.Context;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.util.Log;
@@ -42,7 +43,7 @@
      * the first row. The items in the first screen are moved and resized but the carry-forward
      * items are simply deleted.
      */
-    public static boolean prepareScreenZeroToHostQsb(SQLiteDatabase db) {
+    public static boolean prepareScreenZeroToHostQsb(Context context, SQLiteDatabase db) {
         db.beginTransaction();
         try {
             // Get the existing screens
@@ -56,7 +57,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);
@@ -75,8 +76,7 @@
                 }
             }
 
-            LauncherAppState app = LauncherAppState.getInstance();
-            new LossyScreenMigrationTask(app.getContext(), app.getInvariantDeviceProfile(), db)
+            new LossyScreenMigrationTask(context, LauncherAppState.getIDP(context), db)
                     .migrateScreen0();
             db.setTransactionSuccessful();
             return true;
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index 47bee06..dc85aba 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -27,6 +27,7 @@
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.logging.FileLog;
+import com.android.launcher3.util.LogConfig;
 
 import java.io.InvalidObjectException;
 
@@ -44,13 +45,6 @@
     private static final String INFO_COLUMN_NAME = "name";
     private static final String INFO_COLUMN_DEFAULT_VALUE = "dflt_value";
 
-    /**
-     * When enabled all icons are kept on the home screen, even if they don't have an active
-     * session. To enable use:
-     *      adb shell setprop log.tag.launcher_keep_all_icons VERBOSE
-     */
-    private static final String KEEP_ALL_ICONS = "launcher_keep_all_icons";
-
     public static boolean performRestore(DatabaseHelper helper) {
         SQLiteDatabase db = helper.getWritableDatabase();
         db.beginTransaction();
@@ -85,7 +79,7 @@
         }
 
         // Mark all items as restored.
-        boolean keepAllIcons = Utilities.isPropertyEnabled(KEEP_ALL_ICONS);
+        boolean keepAllIcons = Utilities.isPropertyEnabled(LogConfig.KEEP_ALL_ICONS);
         ContentValues values = new ContentValues();
         values.put(Favorites.RESTORED, ShortcutInfo.FLAG_RESTORED_ICON
                 | (keepAllIcons ? ShortcutInfo.FLAG_RESTORE_STARTED : 0));
@@ -142,6 +136,7 @@
     }
 
     public static void setPending(Context context, boolean isPending) {
+        FileLog.d(TAG, "Restore data received through full backup");
         Utilities.getPrefs(context).edit().putBoolean(RESTORE_TASK_PENDING, isPending).commit();
     }
 }
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 60%
rename from src/com/android/launcher3/QsbContainerView.java
rename to src/com/android/launcher3/qsb/QsbContainerView.java
index 02d8a13..4dc3c1c 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,7 +34,13 @@
 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;
+import com.android.launcher3.config.FeatureFlags;
 
 /**
  * A frame layout which contains a QSB. This internally uses fragment to bind the view, which
@@ -68,25 +73,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 +89,100 @@
         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));
+
+            // Only add the view when enabled
+            if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
+                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();
-            InvariantDeviceProfile idp = LauncherAppState.getInstance().getInvariantDeviceProfile();
+            AppWidgetManagerCompat widgetManager = AppWidgetManagerCompat.getInstance(activity);
+            InvariantDeviceProfile idp = LauncherAppState.getIDP(activity);
 
             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 +197,21 @@
 
         @Override
         public void onDestroy() {
-            getActivity().unregisterReceiver(mRebindReceiver);
+            mQsbWidgetHost.stopListening();
             super.onDestroy();
         }
 
         private void rebindFragment() {
+            // Exit if the embedded qsb is disabled
+            if (!FeatureFlags.QSB_ON_FIRST_SCREEN) {
+                return;
+            }
+
             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 +241,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..df7f695 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
@@ -25,12 +25,12 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
+import android.os.UserHandle;
 import android.util.Log;
 
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.compat.UserHandleCompat;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -42,23 +42,31 @@
 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);
     }
 
     public static boolean supportsShortcuts(ItemInfo info) {
-        return info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+        return info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
+                && !info.isDisabled();
     }
 
     public boolean wasLastCallSuccess() {
@@ -76,7 +84,7 @@
      * because we only get "key" fields in onShortcutsChanged().
      */
     public List<ShortcutInfoCompat> queryForFullDetails(String packageName,
-            List<String> shortcutIds, UserHandleCompat user) {
+            List<String> shortcutIds, UserHandle user) {
         return query(FLAG_GET_ALL, packageName, null, shortcutIds, user);
     }
 
@@ -85,8 +93,8 @@
      * to be displayed in the shortcuts container on long press.
      */
     public List<ShortcutInfoCompat> queryForShortcutsContainer(ComponentName activity,
-            List<String> ids, UserHandleCompat user) {
-        return query(FLAG_MATCH_MANIFEST | FLAG_MATCH_DYNAMIC,
+            List<String> ids, UserHandle user) {
+        return query(ShortcutQuery.FLAG_MATCH_MANIFEST | ShortcutQuery.FLAG_MATCH_DYNAMIC,
                 activity.getPackageName(), activity, ids, user);
     }
 
@@ -96,14 +104,14 @@
      */
     @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;
+            UserHandle user = key.user;
             List<String> pinnedIds = extractIds(queryForPinnedShortcuts(packageName, user));
             pinnedIds.remove(id);
             try {
-                mLauncherApps.pinShortcuts(packageName, pinnedIds, user.getUser());
+                mLauncherApps.pinShortcuts(packageName, pinnedIds, user);
                 mWasLastCallSuccess = true;
             } catch (SecurityException|IllegalStateException e) {
                 Log.w(TAG, "Failed to unpin shortcut", e);
@@ -118,14 +126,14 @@
      */
     @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;
+            UserHandle user = key.user;
             List<String> pinnedIds = extractIds(queryForPinnedShortcuts(packageName, user));
             pinnedIds.add(id);
             try {
-                mLauncherApps.pinShortcuts(packageName, pinnedIds, user.getUser());
+                mLauncherApps.pinShortcuts(packageName, pinnedIds, user);
                 mWasLastCallSuccess = true;
             } catch (SecurityException|IllegalStateException e) {
                 Log.w(TAG, "Failed to pin shortcut", e);
@@ -136,11 +144,11 @@
 
     @TargetApi(25)
     public void startShortcut(String packageName, String id, Rect sourceBounds,
-          Bundle startActivityOptions, UserHandleCompat user) {
-        if (Utilities.isNycMR1OrAbove()) {
+          Bundle startActivityOptions, UserHandle user) {
+        if (Utilities.ATLEAST_NOUGAT_MR1) {
             try {
                 mLauncherApps.startShortcut(packageName, id, sourceBounds,
-                        startActivityOptions, user.getUser());
+                        startActivityOptions, user);
                 mWasLastCallSuccess = true;
             } catch (SecurityException|IllegalStateException e) {
                 Log.e(TAG, "Failed to start shortcut", e);
@@ -151,7 +159,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);
@@ -170,12 +178,11 @@
      *
      * If packageName is null, returns all pinned shortcuts regardless of package.
      */
-    public List<ShortcutInfoCompat> queryForPinnedShortcuts(String packageName,
-            UserHandleCompat user) {
-        return query(FLAG_MATCH_PINNED, packageName, null, null, user);
+    public List<ShortcutInfoCompat> queryForPinnedShortcuts(String packageName, UserHandle user) {
+        return query(ShortcutQuery.FLAG_MATCH_PINNED, packageName, null, null, user);
     }
 
-    public List<ShortcutInfoCompat> queryForAllShortcuts(UserHandleCompat user) {
+    public List<ShortcutInfoCompat> queryForAllShortcuts(UserHandle user) {
         return query(FLAG_GET_ALL, null, null, null, user);
     }
 
@@ -195,8 +202,8 @@
      */
     @TargetApi(25)
     private List<ShortcutInfoCompat> query(int flags, String packageName,
-            ComponentName activity, List<String> shortcutIds, UserHandleCompat user) {
-        if (Utilities.isNycMR1OrAbove()) {
+            ComponentName activity, List<String> shortcutIds, UserHandle user) {
+        if (Utilities.ATLEAST_NOUGAT_MR1) {
             ShortcutQuery q = new ShortcutQuery();
             q.setQueryFlags(flags);
             if (packageName != null) {
@@ -206,7 +213,7 @@
             }
             List<ShortcutInfo> shortcutInfos = null;
             try {
-                shortcutInfos = mLauncherApps.getShortcuts(q, user.getUser());
+                shortcutInfos = mLauncherApps.getShortcuts(q, user);
                 mWasLastCallSuccess = true;
             } catch (SecurityException|IllegalStateException e) {
                 Log.e(TAG, "Failed to query for shortcuts", e);
@@ -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/DeepShortcutTextView.java b/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java
index 42086fc..1a5297d 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java
@@ -47,7 +47,7 @@
         super(context, attrs, defStyle);
 
         Resources resources = getResources();
-        mDragHandleWidth = resources.getDimensionPixelSize(R.dimen.deep_shortcut_padding_end)
+        mDragHandleWidth = resources.getDimensionPixelSize(R.dimen.popup_padding_end)
                 + resources.getDimensionPixelSize(R.dimen.deep_shortcut_drag_handle_size)
                 + resources.getDimensionPixelSize(R.dimen.deep_shortcut_drawable_padding) / 2;
     }
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutView.java b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
index e7fc415..75a4886 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutView.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
@@ -16,8 +16,6 @@
 
 package com.android.launcher3.shortcuts;
 
-import android.animation.Animator;
-import android.animation.ValueAnimator;
 import android.content.Context;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -26,32 +24,27 @@
 import android.view.View;
 import android.widget.FrameLayout;
 
-import com.android.launcher3.IconCache;
+import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LogAccelerateInterpolator;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.shortcuts.DeepShortcutsContainer.UnbadgedShortcutInfo;
-import com.android.launcher3.util.PillRevealOutlineProvider;
-import com.android.launcher3.util.PillWidthRevealOutlineProvider;
 
 /**
  * A {@link android.widget.FrameLayout} that contains a {@link DeepShortcutView}.
  * This lets us animate the DeepShortcutView (icon and text) separately from the background.
  */
-public class DeepShortcutView extends FrameLayout implements ValueAnimator.AnimatorUpdateListener {
+public class DeepShortcutView extends FrameLayout {
 
     private static final Point sTempPoint = new Point();
 
     private final Rect mPillRect;
 
-    private DeepShortcutTextView mBubbleText;
+    private BubbleTextView mBubbleText;
     private View mIconView;
-    private float mOpenAnimationProgress;
 
-    private UnbadgedShortcutInfo mInfo;
+    private ShortcutInfo mInfo;
+    private ShortcutInfoCompat mDetail;
 
     public DeepShortcutView(Context context) {
         this(context, null, 0);
@@ -70,11 +63,11 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mIconView = findViewById(R.id.deep_shortcut_icon);
-        mBubbleText = (DeepShortcutTextView) findViewById(R.id.deep_shortcut);
+        mBubbleText = findViewById(R.id.bubble_text);
+        mIconView = findViewById(R.id.icon);
     }
 
-    public DeepShortcutTextView getBubbleText() {
+    public BubbleTextView getBubbleText() {
         return mBubbleText;
     }
 
@@ -86,97 +79,6 @@
         return mIconView.getVisibility() == View.VISIBLE;
     }
 
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-        mPillRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
-    }
-
-    /** package private **/
-    void applyShortcutInfo(UnbadgedShortcutInfo info, DeepShortcutsContainer container) {
-        mInfo = info;
-        IconCache cache = LauncherAppState.getInstance().getIconCache();
-        mBubbleText.applyFromShortcutInfo(info, cache);
-        mIconView.setBackground(mBubbleText.getIcon());
-
-        // Use the long label as long as it exists and fits.
-        CharSequence longLabel = info.mDetail.getLongLabel();
-        int availableWidth = mBubbleText.getWidth() - mBubbleText.getTotalPaddingLeft()
-                - mBubbleText.getTotalPaddingRight();
-        boolean usingLongLabel = !TextUtils.isEmpty(longLabel)
-                && mBubbleText.getPaint().measureText(longLabel.toString()) <= availableWidth;
-        mBubbleText.setText(usingLongLabel ? longLabel : info.mDetail.getShortLabel());
-
-        // TODO: Add the click handler to this view directly and not the child view.
-        mBubbleText.setOnClickListener(Launcher.getLauncher(getContext()));
-        mBubbleText.setOnLongClickListener(container);
-        mBubbleText.setOnTouchListener(container);
-    }
-
-    /**
-     * Returns the shortcut info that is suitable to be added on the homescreen
-     */
-    public ShortcutInfo getFinalInfo() {
-        ShortcutInfo badged = new ShortcutInfo(mInfo);
-        // Queue an update task on the worker thread. This ensures that the badged
-        // shortcut eventually gets its icon updated.
-        Launcher.getLauncher(getContext()).getModel().updateShortcutInfo(mInfo.mDetail, badged);
-        return badged;
-    }
-
-    public View getIconView() {
-        return mIconView;
-    }
-
-    /**
-     * Creates an animator to play when the shortcut container is being opened.
-     */
-    public Animator createOpenAnimation(boolean isContainerAboveIcon, boolean pivotLeft) {
-        Point center = getIconCenter();
-        ValueAnimator openAnimator =  new ZoomRevealOutlineProvider(center.x, center.y,
-                mPillRect, this, mIconView, isContainerAboveIcon, pivotLeft)
-                        .createRevealAnimator(this, false);
-        mOpenAnimationProgress = 0f;
-        openAnimator.addUpdateListener(this);
-        return openAnimator;
-    }
-
-    @Override
-    public void onAnimationUpdate(ValueAnimator valueAnimator) {
-        mOpenAnimationProgress = valueAnimator.getAnimatedFraction();
-    }
-
-    public boolean isOpenOrOpening() {
-        return mOpenAnimationProgress > 0;
-    }
-
-    /**
-     * Creates an animator to play when the shortcut container is being closed.
-     */
-    public Animator createCloseAnimation(boolean isContainerAboveIcon, boolean pivotLeft,
-            long duration) {
-        Point center = getIconCenter();
-        ValueAnimator closeAnimator =  new ZoomRevealOutlineProvider(center.x, center.y,
-                mPillRect, this, mIconView, isContainerAboveIcon, pivotLeft)
-                        .createRevealAnimator(this, true);
-        // Scale down the duration and interpolator according to the progress
-        // that the open animation was at when the close started.
-        closeAnimator.setDuration((long) (duration * mOpenAnimationProgress));
-        closeAnimator.setInterpolator(new CloseInterpolator(mOpenAnimationProgress));
-        return closeAnimator;
-    }
-
-    /**
-     * Creates an animator which clips the container to form a circle around the icon.
-     */
-    public Animator collapseToIcon() {
-        int halfHeight = getMeasuredHeight() / 2;
-        int iconCenterX = getIconCenter().x;
-        return new PillWidthRevealOutlineProvider(mPillRect,
-                iconCenterX - halfHeight, iconCenterX + halfHeight)
-                        .createRevealAnimator(this, true);
-    }
-
     /**
      * Returns the position of the center of the icon relative to the container.
      */
@@ -188,67 +90,47 @@
         return sTempPoint;
     }
 
-    /**
-     * Extension of {@link PillRevealOutlineProvider} which scales the icon based on the height.
-     */
-    private static class ZoomRevealOutlineProvider extends PillRevealOutlineProvider {
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        mPillRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
+    }
 
-        private final View mTranslateView;
-        private final View mZoomView;
+    /** package private **/
+    public void applyShortcutInfo(ShortcutInfo info, ShortcutInfoCompat detail,
+            ShortcutsItemView container) {
+        mInfo = info;
+        mDetail = detail;
+        mBubbleText.applyFromShortcutInfo(info);
+        mIconView.setBackground(mBubbleText.getIcon());
 
-        private final float mFullHeight;
-        private final float mTranslateYMultiplier;
+        // Use the long label as long as it exists and fits.
+        CharSequence longLabel = mDetail.getLongLabel();
+        int availableWidth = mBubbleText.getWidth() - mBubbleText.getTotalPaddingLeft()
+                - mBubbleText.getTotalPaddingRight();
+        boolean usingLongLabel = !TextUtils.isEmpty(longLabel)
+                && mBubbleText.getPaint().measureText(longLabel.toString()) <= availableWidth;
+        mBubbleText.setText(usingLongLabel ? longLabel : mDetail.getShortLabel());
 
-        private final boolean mPivotLeft;
-        private final float mTranslateX;
-
-        public ZoomRevealOutlineProvider(int x, int y, Rect pillRect,
-                View translateView, View zoomView, boolean isContainerAboveIcon, boolean pivotLeft) {
-            super(x, y, pillRect);
-            mTranslateView = translateView;
-            mZoomView = zoomView;
-            mFullHeight = pillRect.height();
-
-            mTranslateYMultiplier = isContainerAboveIcon ? 0.5f : -0.5f;
-
-            mPivotLeft = pivotLeft;
-            mTranslateX = pivotLeft ? pillRect.height() / 2 : pillRect.right - pillRect.height() / 2;
-        }
-
-        @Override
-        public void setProgress(float progress) {
-            super.setProgress(progress);
-
-            mZoomView.setScaleX(progress);
-            mZoomView.setScaleY(progress);
-
-            float height = mOutline.height();
-            mTranslateView.setTranslationY(mTranslateYMultiplier * (mFullHeight - height));
-
-            float pivotX = mPivotLeft ? (mOutline.left + height / 2) : (mOutline.right - height / 2);
-            mTranslateView.setTranslationX(mTranslateX - pivotX);
-        }
+        // TODO: Add the click handler to this view directly and not the child view.
+        mBubbleText.setOnClickListener(Launcher.getLauncher(getContext()));
+        mBubbleText.setOnLongClickListener(container);
+        mBubbleText.setOnTouchListener(container);
     }
 
     /**
-     * An interpolator that reverses the current open animation progress.
+     * Returns the shortcut info that is suitable to be added on the homescreen
      */
-    private static class CloseInterpolator extends LogAccelerateInterpolator {
-        private float mStartProgress;
-        private float mRemainingProgress;
+    public ShortcutInfo getFinalInfo() {
+        final ShortcutInfo badged = new ShortcutInfo(mInfo);
+        // Queue an update task on the worker thread. This ensures that the badged
+        // shortcut eventually gets its icon updated.
+        Launcher.getLauncher(getContext()).getModel()
+                .updateAndBindShortcutInfo(badged, mDetail);
+        return badged;
+    }
 
-        /**
-         * @param openAnimationProgress The progress that the open interpolator ended at.
-         */
-        public CloseInterpolator(float openAnimationProgress) {
-            super(100, 0);
-            mStartProgress = 1f - openAnimationProgress;
-            mRemainingProgress = openAnimationProgress;
-        }
-
-        @Override
-        public float getInterpolation(float v) {
-            return mStartProgress + super.getInterpolation(v) * mRemainingProgress;
-        }
+    public View getIconView() {
+        return mIconView;
     }
 }
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java b/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
deleted file mode 100644
index 2702d4e..0000000
--- a/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
+++ /dev/null
@@ -1,675 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.shortcuts;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.TimeInterpolator;
-import android.annotation.TargetApi;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.drawable.ShapeDrawable;
-import android.os.Build;
-import android.os.Handler;
-import android.os.Looper;
-import android.util.AttributeSet;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.animation.DecelerateInterpolator;
-import android.widget.LinearLayout;
-
-import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.DragSource;
-import com.android.launcher3.DropTarget;
-import com.android.launcher3.IconCache;
-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;
-import com.android.launcher3.LogAccelerateInterpolator;
-import com.android.launcher3.R;
-import com.android.launcher3.ShortcutInfo;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.accessibility.ShortcutMenuAccessibilityDelegate;
-import com.android.launcher3.compat.UserHandleCompat;
-import com.android.launcher3.dragndrop.DragController;
-import com.android.launcher3.dragndrop.DragLayer;
-import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.dragndrop.DragView;
-import com.android.launcher3.graphics.TriangleShape;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
-
-import java.util.Collections;
-import java.util.List;
-
-/**
- * A container for shortcuts to deep links within apps.
- */
-@TargetApi(Build.VERSION_CODES.N)
-public class DeepShortcutsContainer extends LinearLayout implements View.OnLongClickListener,
-        View.OnTouchListener, DragSource, DragController.DragListener {
-    private static final String TAG = "ShortcutsContainer";
-
-    private final Point mIconShift = new Point();
-
-    private final Launcher mLauncher;
-    private final DeepShortcutManager mDeepShortcutsManager;
-    private final int mStartDragThreshold;
-    private final ShortcutMenuAccessibilityDelegate mAccessibilityDelegate;
-    private final boolean mIsRtl;
-
-    private BubbleTextView mDeferredDragIcon;
-    private final Rect mTempRect = new Rect();
-    private Point mIconLastTouchPos = new Point();
-    private boolean mIsLeftAligned;
-    private boolean mIsAboveIcon;
-    private View mArrow;
-
-    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();
-
-        mStartDragThreshold = getResources().getDimensionPixelSize(
-                R.dimen.deep_shortcuts_start_drag_threshold);
-        mAccessibilityDelegate = new ShortcutMenuAccessibilityDelegate(mLauncher);
-        mIsRtl = Utilities.isRtl(getResources());
-    }
-
-    public DeepShortcutsContainer(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public DeepShortcutsContainer(Context context) {
-        this(context, null, 0);
-    }
-
-    public void populateAndShow(final BubbleTextView originalIcon, final List<String> ids) {
-        final Resources resources = getResources();
-        final int arrowWidth = resources.getDimensionPixelSize(R.dimen.deep_shortcuts_arrow_width);
-        final int arrowHeight = resources.getDimensionPixelSize(R.dimen.deep_shortcuts_arrow_height);
-        final int arrowHorizontalOffset = resources.getDimensionPixelSize(
-                R.dimen.deep_shortcuts_arrow_horizontal_offset);
-        final int arrowVerticalOffset = resources.getDimensionPixelSize(
-                R.dimen.deep_shortcuts_arrow_vertical_offset);
-
-        // Add dummy views first, and populate with real shortcut info when ready.
-        final int spacing = getResources().getDimensionPixelSize(R.dimen.deep_shortcuts_spacing);
-        final LayoutInflater inflater = mLauncher.getLayoutInflater();
-        int numShortcuts = Math.min(ids.size(), ShortcutFilter.MAX_SHORTCUTS);
-        for (int i = 0; i < numShortcuts; i++) {
-            final DeepShortcutView shortcut =
-                    (DeepShortcutView) inflater.inflate(R.layout.deep_shortcut, this, false);
-            if (i < numShortcuts - 1) {
-                ((LayoutParams) shortcut.getLayoutParams()).bottomMargin = spacing;
-            }
-            shortcut.getBubbleText().setAccessibilityDelegate(mAccessibilityDelegate);
-            addView(shortcut);
-        }
-        setContentDescription(getContext().getString(R.string.shortcuts_menu_description,
-                numShortcuts, originalIcon.getContentDescription().toString()));
-
-        measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
-        orientAboutIcon(originalIcon, arrowHeight + arrowVerticalOffset);
-
-        // Add the arrow.
-        mArrow = addArrowView(arrowHorizontalOffset, arrowVerticalOffset, arrowWidth, arrowHeight);
-        mArrow.setPivotX(arrowWidth / 2);
-        mArrow.setPivotY(mIsAboveIcon ? 0 : arrowHeight);
-
-        animateOpen();
-
-        deferDrag(originalIcon);
-
-        // Load the shortcuts on a background thread and update the container as it animates.
-        final Looper workerLooper = LauncherModel.getWorkerLooper();
-        final Handler uiHandler = new Handler(Looper.getMainLooper());
-        final ItemInfo originalInfo = (ItemInfo) originalIcon.getTag();
-        final UserHandleCompat user = originalInfo.user;
-        final ComponentName activity = originalInfo.getTargetComponent();
-        new Handler(workerLooper).postAtFrontOfQueue(new Runnable() {
-            @Override
-            public void run() {
-                final List<ShortcutInfoCompat> shortcuts = ShortcutFilter.sortAndFilterShortcuts(
-                        mDeepShortcutsManager.queryForShortcutsContainer(activity, ids, user));
-                // We want the lowest rank to be closest to the user's finger.
-                if (mIsAboveIcon) {
-                    Collections.reverse(shortcuts);
-                }
-                for (int i = 0; i < shortcuts.size(); i++) {
-                    final ShortcutInfoCompat shortcut = shortcuts.get(i);
-                    uiHandler.post(new UpdateShortcutChild(
-                            i, new UnbadgedShortcutInfo(shortcut, mLauncher)));
-                }
-            }
-        });
-    }
-
-    /** Updates the child of this container at the given index based on the given shortcut info. */
-    private class UpdateShortcutChild implements Runnable {
-        private int mShortcutChildIndex;
-        private UnbadgedShortcutInfo mShortcutChildInfo;
-
-        public UpdateShortcutChild(int shortcutChildIndex, UnbadgedShortcutInfo shortcutChildInfo) {
-            mShortcutChildIndex = shortcutChildIndex;
-            mShortcutChildInfo = shortcutChildInfo;
-        }
-
-        @Override
-        public void run() {
-            getShortcutAt(mShortcutChildIndex)
-                    .applyShortcutInfo(mShortcutChildInfo, DeepShortcutsContainer.this);
-        }
-    }
-
-    private DeepShortcutView getShortcutAt(int index) {
-        if (!mIsAboveIcon) {
-            // Opening down, so arrow is the first view.
-            index++;
-        }
-        return (DeepShortcutView) getChildAt(index);
-    }
-
-    private int getShortcutCount() {
-        // All children except the arrow are shortcuts.
-        return getChildCount() - 1;
-    }
-
-    private void animateOpen() {
-        setVisibility(View.VISIBLE);
-        mIsOpen = true;
-
-        final AnimatorSet shortcutAnims = LauncherAnimUtils.createAnimatorSet();
-        final int shortcutCount = getShortcutCount();
-
-        final long duration = getResources().getInteger(
-                R.integer.config_deepShortcutOpenDuration);
-        final long arrowScaleDuration = getResources().getInteger(
-                R.integer.config_deepShortcutArrowOpenDuration);
-        final long arrowScaleDelay = duration - arrowScaleDuration;
-        final long stagger = getResources().getInteger(
-                R.integer.config_deepShortcutOpenStagger);
-        final TimeInterpolator fadeInterpolator = new LogAccelerateInterpolator(100, 0);
-
-        // Animate shortcuts
-        DecelerateInterpolator interpolator = new DecelerateInterpolator();
-        for (int i = 0; i < shortcutCount; i++) {
-            final DeepShortcutView deepShortcutView = getShortcutAt(i);
-            deepShortcutView.setVisibility(INVISIBLE);
-            deepShortcutView.setAlpha(0);
-
-            Animator anim = deepShortcutView.createOpenAnimation(mIsAboveIcon, mIsLeftAligned);
-            anim.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationStart(Animator animation) {
-                    deepShortcutView.setVisibility(VISIBLE);
-                }
-            });
-            anim.setDuration(duration);
-            int animationIndex = mIsAboveIcon ? shortcutCount - i - 1 : i;
-            anim.setStartDelay(stagger * animationIndex);
-            anim.setInterpolator(interpolator);
-            shortcutAnims.play(anim);
-
-            Animator fadeAnim = new LauncherViewPropertyAnimator(deepShortcutView).alpha(1);
-            fadeAnim.setInterpolator(fadeInterpolator);
-            // We want the shortcut to be fully opaque before the arrow starts animating.
-            fadeAnim.setDuration(arrowScaleDelay);
-            shortcutAnims.play(fadeAnim);
-        }
-        shortcutAnims.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mOpenCloseAnimator = null;
-                Utilities.sendCustomAccessibilityEvent(
-                        DeepShortcutsContainer.this,
-                        AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
-                        getContext().getString(R.string.action_deep_shortcut));
-            }
-        });
-
-        // Animate the arrow
-        mArrow.setScaleX(0);
-        mArrow.setScaleY(0);
-        Animator arrowScale = new LauncherViewPropertyAnimator(mArrow).scaleX(1).scaleY(1);
-        arrowScale.setStartDelay(arrowScaleDelay);
-        arrowScale.setDuration(arrowScaleDuration);
-        shortcutAnims.play(arrowScale);
-
-        mOpenCloseAnimator = shortcutAnims;
-        shortcutAnims.start();
-    }
-
-    /**
-     * Orients this container above or below the given icon, aligning with the left or right.
-     *
-     * These are the preferred orientations, in order (RTL prefers right-aligned over left):
-     * - Above and left-aligned
-     * - Above and right-aligned
-     * - Below and left-aligned
-     * - Below and right-aligned
-     *
-     * So we always align left if there is enough horizontal space
-     * and align above if there is enough vertical space.
-     */
-    private void orientAboutIcon(BubbleTextView icon, int arrowHeight) {
-        int width = getMeasuredWidth();
-        int height = getMeasuredHeight() + arrowHeight;
-
-        DragLayer dragLayer = mLauncher.getDragLayer();
-        dragLayer.getDescendantRectRelativeToSelf(icon, mTempRect);
-        Rect insets = dragLayer.getInsets();
-
-        // Align left (right in RTL) if there is room.
-        int leftAlignedX = mTempRect.left + icon.getPaddingLeft();
-        int rightAlignedX = mTempRect.right - width - icon.getPaddingRight();
-        int x = leftAlignedX;
-        boolean canBeLeftAligned = leftAlignedX + width < dragLayer.getRight() - insets.right;
-        boolean canBeRightAligned = rightAlignedX > dragLayer.getLeft() + insets.left;
-        if (!canBeLeftAligned || (mIsRtl && canBeRightAligned)) {
-            x = rightAlignedX;
-        }
-        mIsLeftAligned = x == leftAlignedX;
-        if (mIsRtl) {
-            x -= dragLayer.getWidth() - width;
-        }
-
-        // Offset x so that the arrow and shortcut icons are center-aligned with the original icon.
-        int iconWidth = icon.getWidth() - icon.getTotalPaddingLeft() - icon.getTotalPaddingRight();
-        iconWidth *= icon.getScaleX();
-        Resources resources = getResources();
-        int xOffset;
-        if (isAlignedWithStart()) {
-            // Aligning with the shortcut icon.
-            int shortcutIconWidth = resources.getDimensionPixelSize(R.dimen.deep_shortcut_icon_size);
-            int shortcutPaddingStart = resources.getDimensionPixelSize(
-                    R.dimen.deep_shortcut_padding_start);
-            xOffset = iconWidth / 2 - shortcutIconWidth / 2 - shortcutPaddingStart;
-        } else {
-            // Aligning with the drag handle.
-            int shortcutDragHandleWidth = resources.getDimensionPixelSize(
-                    R.dimen.deep_shortcut_drag_handle_size);
-            int shortcutPaddingEnd = resources.getDimensionPixelSize(
-                    R.dimen.deep_shortcut_padding_end);
-            xOffset = iconWidth / 2 - shortcutDragHandleWidth / 2 - shortcutPaddingEnd;
-        }
-        x += mIsLeftAligned ? xOffset : -xOffset;
-
-        // Open above icon if there is room.
-        int iconHeight = icon.getIcon().getBounds().height();
-        int y = mTempRect.top + icon.getPaddingTop() - height;
-        mIsAboveIcon = y > dragLayer.getTop() + insets.top;
-        if (!mIsAboveIcon) {
-            y = mTempRect.top + icon.getPaddingTop() + iconHeight;
-        }
-
-        // Insets are added later, so subtract them now.
-        y -= insets.top;
-
-        setX(x);
-        setY(y);
-    }
-
-    private boolean isAlignedWithStart() {
-        return mIsLeftAligned && !mIsRtl || !mIsLeftAligned && mIsRtl;
-    }
-
-    /**
-     * Adds an arrow view pointing at the original icon.
-     * @param horizontalOffset the horizontal offset of the arrow, so that it
-     *                              points at the center of the original icon
-     */
-    private View addArrowView(int horizontalOffset, int verticalOffset, int width, int height) {
-        LinearLayout.LayoutParams layoutParams = new LayoutParams(width, height);
-        if (mIsLeftAligned) {
-            layoutParams.gravity = Gravity.LEFT;
-            layoutParams.leftMargin = horizontalOffset;
-        } else {
-            layoutParams.gravity = Gravity.RIGHT;
-            layoutParams.rightMargin = horizontalOffset;
-        }
-        if (mIsAboveIcon) {
-            layoutParams.topMargin = verticalOffset;
-        } else {
-            layoutParams.bottomMargin = verticalOffset;
-        }
-
-        View arrowView = new View(getContext());
-        ShapeDrawable arrowDrawable = new ShapeDrawable(TriangleShape.create(
-                width, height, !mIsAboveIcon));
-        arrowDrawable.getPaint().setColor(Color.WHITE);
-        arrowView.setBackground(arrowDrawable);
-        arrowView.setElevation(getElevation());
-        addView(arrowView, mIsAboveIcon ? getChildCount() : 0, layoutParams);
-        return arrowView;
-    }
-
-    private void deferDrag(BubbleTextView originalIcon) {
-        mDeferredDragIcon = originalIcon;
-        mLauncher.getDragController().addDragListener(this);
-    }
-
-    public BubbleTextView getDeferredDragIcon() {
-        return mDeferredDragIcon;
-    }
-
-    /**
-     * Determines when the deferred drag should be started.
-     *
-     * 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() {
-            @Override
-            public boolean shouldStartDeferredDrag(double distanceDragged) {
-                return distanceDragged > mStartDragThreshold;
-            }
-
-            @Override
-            public void onDeferredDragStart() {
-                mDeferredDragIcon.setVisibility(INVISIBLE);
-            }
-
-            @Override
-            public void onDropBeforeDeferredDrag() {
-                mLauncher.getUserEventDispatcher().logDeepShortcutsOpen(mDeferredDragIcon);
-                if (!mIsAboveIcon) {
-                    mDeferredDragIcon.setTextVisibility(false);
-                }
-            }
-
-            @Override
-            public void onDragStart() {
-                if (onDragStart != null) {
-                    onDragStart.run();
-                }
-            }
-        };
-    }
-
-    @Override
-    public boolean onTouch(View v, MotionEvent ev) {
-        // Touched a shortcut, update where it was touched so we can drag from there on long click.
-        switch (ev.getAction()) {
-            case MotionEvent.ACTION_DOWN:
-            case MotionEvent.ACTION_MOVE:
-                mIconLastTouchPos.set((int) ev.getX(), (int) ev.getY());
-                break;
-        }
-        return false;
-    }
-
-    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
-        if (!mLauncher.isDraggingEnabled()) return false;
-
-        // Long clicked on a shortcut.
-        mDeferContainerRemoval = true;
-        DeepShortcutView sv = (DeepShortcutView) v.getParent();
-        sv.setWillDrawIcon(false);
-
-        // Move the icon to align with the center-top of the touch point
-        mIconShift.x = mIconLastTouchPos.x - sv.getIconCenter().x;
-        mIconShift.y = mIconLastTouchPos.y - mLauncher.getDeviceProfile().iconSizePx;
-
-        DragView dv = mLauncher.getWorkspace().beginDragShared(
-                sv.getBubbleText(), this, sv.getFinalInfo(),
-                new ShortcutDragPreviewProvider(sv.getIconView(), mIconShift), new DragOptions());
-        dv.animateShift(-mIconShift.x, -mIconShift.y);
-
-        // TODO: support dragging from within folder without having to close it
-        mLauncher.closeFolder();
-        return false;
-    }
-
-    @Override
-    public boolean supportsFlingToDelete() {
-        return true;
-    }
-
-    @Override
-    public boolean supportsAppInfoDropTarget() {
-        return true;
-    }
-
-    @Override
-    public boolean supportsDeleteDropTarget() {
-        return false;
-    }
-
-    @Override
-    public float getIntrinsicIconScaleFactor() {
-        return 1f;
-    }
-
-    @Override
-    public void onFlingToDeleteCompleted() {
-        // Don't care; ignore.
-    }
-
-    @Override
-    public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete,
-            boolean success) {
-        if (!success) {
-            d.dragView.remove();
-            mLauncher.showWorkspace(true);
-            mLauncher.getDropTargetBar().onDragEnd();
-        }
-    }
-
-    @Override
-    public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
-        // Either the original icon or one of the shortcuts was dragged.
-        // Hide the container, but don't remove it yet because that interferes with touch events.
-        animateClose();
-    }
-
-    @Override
-    public void onDragEnd() {
-        if (!mIsOpen) {
-            if (mOpenCloseAnimator != null) {
-                // Close animation is running.
-                mDeferContainerRemoval = false;
-            } else {
-                // Close animation is not running.
-                if (mDeferContainerRemoval) {
-                    close();
-                }
-            }
-        }
-        mDeferredDragIcon.setVisibility(VISIBLE);
-    }
-
-    @Override
-    public void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent) {
-        target.itemType = LauncherLogProto.DEEPSHORTCUT;
-        // TODO: add target.rank
-        targetParent.containerType = LauncherLogProto.DEEPSHORTCUTS;
-    }
-
-    public void animateClose() {
-        if (!mIsOpen) {
-            return;
-        }
-        if (mOpenCloseAnimator != null) {
-            mOpenCloseAnimator.cancel();
-        }
-        mIsOpen = false;
-
-        final AnimatorSet shortcutAnims = LauncherAnimUtils.createAnimatorSet();
-        final int shortcutCount = getShortcutCount();
-        int numOpenShortcuts = 0;
-        for (int i = 0; i < shortcutCount; i++) {
-            if (getShortcutAt(i).isOpenOrOpening()) {
-                numOpenShortcuts++;
-            }
-        }
-        final long duration = getResources().getInteger(
-                R.integer.config_deepShortcutCloseDuration);
-        final long arrowScaleDuration = getResources().getInteger(
-                R.integer.config_deepShortcutArrowOpenDuration);
-        final long stagger = getResources().getInteger(
-                R.integer.config_deepShortcutCloseStagger);
-        final TimeInterpolator fadeInterpolator = new LogAccelerateInterpolator(100, 0);
-
-        int firstOpenShortcutIndex = mIsAboveIcon ? shortcutCount - numOpenShortcuts : 0;
-        for (int i = firstOpenShortcutIndex; i < firstOpenShortcutIndex + numOpenShortcuts; i++) {
-            final DeepShortcutView view = getShortcutAt(i);
-            Animator anim;
-            if (view.willDrawIcon()) {
-                anim = view.createCloseAnimation(mIsAboveIcon, mIsLeftAligned, duration);
-                int animationIndex = mIsAboveIcon ? i - firstOpenShortcutIndex
-                        : numOpenShortcuts - i - 1;
-                anim.setStartDelay(stagger * animationIndex);
-
-                Animator fadeAnim = new LauncherViewPropertyAnimator(view).alpha(0);
-                // Don't start fading until the arrow is gone.
-                fadeAnim.setStartDelay(stagger * animationIndex + arrowScaleDuration);
-                fadeAnim.setDuration(duration - arrowScaleDuration);
-                fadeAnim.setInterpolator(fadeInterpolator);
-                shortcutAnims.play(fadeAnim);
-            } else {
-                // The view is being dragged. Animate it such that it collapses with the drag view
-                anim = view.collapseToIcon();
-                anim.setDuration(DragView.VIEW_ZOOM_DURATION);
-
-                // Scale and translate the view to follow the drag view.
-                Point iconCenter = view.getIconCenter();
-                view.setPivotX(iconCenter.x);
-                view.setPivotY(iconCenter.y);
-
-                float scale = ((float) mLauncher.getDeviceProfile().iconSizePx) / view.getHeight();
-                LauncherViewPropertyAnimator anim2 = new LauncherViewPropertyAnimator(view)
-                        .scaleX(scale)
-                        .scaleY(scale)
-                        .translationX(mIconShift.x)
-                        .translationY(mIconShift.y);
-                anim2.setDuration(DragView.VIEW_ZOOM_DURATION);
-                shortcutAnims.play(anim2);
-            }
-            anim.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    view.setVisibility(INVISIBLE);
-                }
-            });
-            shortcutAnims.play(anim);
-        }
-        Animator arrowAnim = new LauncherViewPropertyAnimator(mArrow)
-                .scaleX(0).scaleY(0).setDuration(arrowScaleDuration);
-        arrowAnim.setStartDelay(0);
-        shortcutAnims.play(arrowAnim);
-
-        shortcutAnims.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mOpenCloseAnimator = null;
-                if (mDeferContainerRemoval) {
-                    setVisibility(INVISIBLE);
-                } else {
-                    close();
-                }
-            }
-        });
-        mOpenCloseAnimator = shortcutAnims;
-        shortcutAnims.start();
-    }
-
-    /**
-     * Closes the folder without animation.
-     */
-    public void close() {
-        if (mOpenCloseAnimator != null) {
-            mOpenCloseAnimator.cancel();
-            mOpenCloseAnimator = null;
-        }
-        mIsOpen = false;
-        mDeferContainerRemoval = false;
-        boolean isInHotseat = ((ItemInfo) mDeferredDragIcon.getTag()).container
-                == LauncherSettings.Favorites.CONTAINER_HOTSEAT;
-        mDeferredDragIcon.setTextVisibility(!isInHotseat);
-        mLauncher.getDragController().removeDragListener(this);
-        mLauncher.getDragLayer().removeView(this);
-    }
-
-    public boolean isOpen() {
-        return mIsOpen;
-    }
-
-    /**
-     * Shows the shortcuts container for {@param icon}
-     * @return the container if shown or null.
-     */
-    public static DeepShortcutsContainer showForIcon(BubbleTextView icon) {
-        Launcher launcher = Launcher.getLauncher(icon.getContext());
-        if (launcher.getOpenShortcutsContainer() != 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);
-            container.setVisibility(View.INVISIBLE);
-            launcher.getDragLayer().addView(container);
-            container.populateAndShow(icon, ids);
-            return container;
-        }
-        return null;
-    }
-
-    /**
-     * Extension of {@link ShortcutInfo} which does not badge the icons.
-     */
-    static class UnbadgedShortcutInfo extends ShortcutInfo {
-        public final ShortcutInfoCompat mDetail;
-
-        public UnbadgedShortcutInfo(ShortcutInfoCompat shortcutInfo, Context context) {
-            super(shortcutInfo, context);
-            mDetail = shortcutInfo;
-        }
-
-        @Override
-        protected Bitmap getBadgedIcon(Bitmap unbadgedBitmap, ShortcutInfoCompat shortcutInfo,
-                IconCache cache, Context context) {
-            return unbadgedBitmap;
-        }
-    }
-}
diff --git a/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
index 2adb82e..ab8de6b 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;
@@ -64,13 +64,13 @@
         int size = Launcher.getLauncher(mView.getContext()).getDeviceProfile().iconSizePx;
 
         final Bitmap b = Bitmap.createBitmap(
-                size + DRAG_BITMAP_PADDING,
-                size + DRAG_BITMAP_PADDING,
+                size + blurSizeOutline,
+                size + blurSizeOutline,
                 config);
 
         canvas.setBitmap(b);
         canvas.save(Canvas.MATRIX_SAVE_FLAG);
-        canvas.translate(DRAG_BITMAP_PADDING / 2, DRAG_BITMAP_PADDING / 2);
+        canvas.translate(blurSizeOutline / 2, blurSizeOutline / 2);
         canvas.scale(((float) size) / bounds.width(), ((float) size) / bounds.height(), 0, 0);
         canvas.translate(bounds.left, bounds.top);
         d.draw(canvas);
diff --git a/src/com/android/launcher3/shortcuts/ShortcutFilter.java b/src/com/android/launcher3/shortcuts/ShortcutFilter.java
deleted file mode 100644
index ec68817..0000000
--- a/src/com/android/launcher3/shortcuts/ShortcutFilter.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.shortcuts;
-
-import android.support.annotation.VisibleForTesting;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-
-/**
- * Sorts and filters shortcuts.
- */
-public class ShortcutFilter {
-
-    public static final int MAX_SHORTCUTS = 4;
-    @VisibleForTesting static final int NUM_DYNAMIC = 2;
-
-    /**
-     * Sorts shortcuts in rank order, with manifest shortcuts coming before dynamic shortcuts.
-     */
-    private static final Comparator<ShortcutInfoCompat> RANK_COMPARATOR
-            = new Comparator<ShortcutInfoCompat>() {
-        @Override
-        public int compare(ShortcutInfoCompat a, ShortcutInfoCompat b) {
-            if (a.isDeclaredInManifest() && !b.isDeclaredInManifest()) {
-                return -1;
-            }
-            if (!a.isDeclaredInManifest() && b.isDeclaredInManifest()) {
-                return 1;
-            }
-            return Integer.compare(a.getRank(), b.getRank());
-        }
-    };
-
-    /**
-     * Filters the shortcuts so that only MAX_SHORTCUTS or fewer shortcuts are retained.
-     * We want the filter to include both static and dynamic shortcuts, so we always
-     * include NUM_DYNAMIC dynamic shortcuts, if at least that many are present.
-     *
-     * @return a subset of shortcuts, in sorted order, with size <= MAX_SHORTCUTS.
-     */
-    public static List<ShortcutInfoCompat> sortAndFilterShortcuts(
-            List<ShortcutInfoCompat> shortcuts) {
-        Collections.sort(shortcuts, RANK_COMPARATOR);
-        if (shortcuts.size() <= MAX_SHORTCUTS) {
-            return shortcuts;
-        }
-
-        // The list of shortcuts is now sorted with static shortcuts followed by dynamic
-        // shortcuts. We want to preserve this order, but only keep MAX_SHORTCUTS.
-        List<ShortcutInfoCompat> filteredShortcuts = new ArrayList<>(MAX_SHORTCUTS);
-        int numDynamic = 0;
-        int size = shortcuts.size();
-        for (int i = 0; i < size; i++) {
-            ShortcutInfoCompat shortcut = shortcuts.get(i);
-            int filteredSize = filteredShortcuts.size();
-            if (filteredSize < MAX_SHORTCUTS) {
-                // Always add the first MAX_SHORTCUTS to the filtered list.
-                filteredShortcuts.add(shortcut);
-                if (shortcut.isDynamic()) {
-                    numDynamic++;
-                }
-                continue;
-            }
-            // At this point, we have MAX_SHORTCUTS already, but they may all be static.
-            // If there are dynamic shortcuts, remove static shortcuts to add them.
-            if (shortcut.isDynamic() && numDynamic < NUM_DYNAMIC) {
-                numDynamic++;
-                int lastStaticIndex = filteredSize - numDynamic;
-                filteredShortcuts.remove(lastStaticIndex);
-                filteredShortcuts.add(shortcut);
-            }
-        }
-        return filteredShortcuts;
-    }
-}
diff --git a/src/com/android/launcher3/shortcuts/ShortcutInfoCompat.java b/src/com/android/launcher3/shortcuts/ShortcutInfoCompat.java
index a6da668..37047bb 100644
--- a/src/com/android/launcher3/shortcuts/ShortcutInfoCompat.java
+++ b/src/com/android/launcher3/shortcuts/ShortcutInfoCompat.java
@@ -22,11 +22,9 @@
 import android.content.Intent;
 import android.content.pm.ShortcutInfo;
 import android.os.Build;
+import android.os.UserHandle;
 
 import com.android.launcher3.ItemInfo;
-import com.android.launcher3.compat.DeferredLauncherActivityInfo;
-import com.android.launcher3.compat.LauncherActivityInfoCompat;
-import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 
 /**
@@ -46,15 +44,12 @@
     }
 
     @TargetApi(Build.VERSION_CODES.N)
-    public Intent makeIntent(Context context) {
-        long serialNumber = UserManagerCompat.getInstance(context)
-                .getSerialNumberForUser(getUserHandle());
+    public Intent makeIntent() {
         return new Intent(Intent.ACTION_MAIN)
                 .addCategory(INTENT_CATEGORY)
                 .setComponent(getActivity())
                 .setPackage(getPackage())
                 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
-                .putExtra(ItemInfo.EXTRA_PROFILE, serialNumber)
                 .putExtra(EXTRA_SHORTCUT_ID, getId());
     }
 
@@ -86,8 +81,8 @@
         return mShortcutInfo.getActivity();
     }
 
-    public UserHandleCompat getUserHandle() {
-        return UserHandleCompat.fromUser(mShortcutInfo.getUserHandle());
+    public UserHandle getUserHandle() {
+        return mShortcutInfo.getUserHandle();
     }
 
     public boolean hasKeyFieldsOnly() {
@@ -122,8 +117,4 @@
     public String toString() {
         return mShortcutInfo.toString();
     }
-
-    public LauncherActivityInfoCompat getActivityInfo(Context context) {
-        return new DeferredLauncherActivityInfo(getActivity(), getUserHandle(), context);
-    }
 }
diff --git a/src/com/android/launcher3/shortcuts/ShortcutKey.java b/src/com/android/launcher3/shortcuts/ShortcutKey.java
index a219c54..e86bfb2 100644
--- a/src/com/android/launcher3/shortcuts/ShortcutKey.java
+++ b/src/com/android/launcher3/shortcuts/ShortcutKey.java
@@ -2,9 +2,9 @@
 
 import android.content.ComponentName;
 import android.content.Intent;
+import android.os.UserHandle;
 
-import com.android.launcher3.ShortcutInfo;
-import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.ItemInfo;
 import com.android.launcher3.util.ComponentKey;
 
 /**
@@ -12,7 +12,7 @@
  */
 public class ShortcutKey extends ComponentKey {
 
-    public ShortcutKey(String packageName, UserHandleCompat user, String id) {
+    public ShortcutKey(String packageName, UserHandle user, String id) {
         // Use the id as the class name.
         super(new ComponentName(packageName, id), user);
     }
@@ -26,13 +26,13 @@
                 shortcutInfo.getId());
     }
 
-    public static ShortcutKey fromIntent(Intent intent, UserHandleCompat user) {
+    public static ShortcutKey fromIntent(Intent intent, UserHandle user) {
         String shortcutId = intent.getStringExtra(
                 ShortcutInfoCompat.EXTRA_SHORTCUT_ID);
         return new ShortcutKey(intent.getPackage(), user, shortcutId);
     }
 
-    public static ShortcutKey fromShortcutInfo(ShortcutInfo info) {
-        return fromIntent(info.getPromisedIntent(), info.user);
+    public static ShortcutKey fromItemInfo(ItemInfo info) {
+        return fromIntent(info.getIntent(), info.user);
     }
 }
diff --git a/src/com/android/launcher3/shortcuts/ShortcutsItemView.java b/src/com/android/launcher3/shortcuts/ShortcutsItemView.java
new file mode 100644
index 0000000..5b3b02e
--- /dev/null
+++ b/src/com/android/launcher3/shortcuts/ShortcutsItemView.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.shortcuts;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.content.Context;
+import android.graphics.Point;
+import android.support.v4.content.ContextCompat;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAnimUtils;
+import com.android.launcher3.R;
+import com.android.launcher3.anim.PropertyListBuilder;
+import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.logging.UserEventDispatcher.LogContainerProvider;
+import com.android.launcher3.popup.PopupContainerWithArrow;
+import com.android.launcher3.popup.PopupItemView;
+import com.android.launcher3.popup.PopupPopulator;
+import com.android.launcher3.popup.SystemShortcut;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A {@link PopupItemView} that contains all of the {@link DeepShortcutView}s for an app,
+ * as well as the system shortcuts such as Widgets and App Info.
+ */
+public class ShortcutsItemView extends PopupItemView implements View.OnLongClickListener,
+        View.OnTouchListener, LogContainerProvider {
+
+    private Launcher mLauncher;
+    private LinearLayout mShortcutsLayout;
+    private LinearLayout mSystemShortcutIcons;
+    private final Point mIconShift = new Point();
+    private final Point mIconLastTouchPos = new Point();
+    private final List<DeepShortcutView> mDeepShortcutViews = new ArrayList<>();
+    private final List<View> mSystemShortcutViews = new ArrayList<>();
+
+    public ShortcutsItemView(Context context) {
+        this(context, null, 0);
+    }
+
+    public ShortcutsItemView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public ShortcutsItemView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        mLauncher = Launcher.getLauncher(context);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mShortcutsLayout = findViewById(R.id.deep_shortcuts);
+    }
+
+    @Override
+    public boolean onTouch(View v, MotionEvent ev) {
+        // Touched a shortcut, update where it was touched so we can drag from there on long click.
+        switch (ev.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+            case MotionEvent.ACTION_MOVE:
+                mIconLastTouchPos.set((int) ev.getX(), (int) ev.getY());
+                break;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onLongClick(View v) {
+        // Return early if this is not initiated from a touch or not the correct view
+        if (!v.isInTouchMode() || !(v.getParent() instanceof DeepShortcutView)) return false;
+        // Return early if global dragging is not enabled
+        if (!mLauncher.isDraggingEnabled()) return false;
+        // Return early if an item is already being dragged (e.g. when long-pressing two shortcuts)
+        if (mLauncher.getDragController().isDragging()) return false;
+
+        // Long clicked on a shortcut.
+        DeepShortcutView sv = (DeepShortcutView) v.getParent();
+        sv.setWillDrawIcon(false);
+
+        // Move the icon to align with the center-top of the touch point
+        mIconShift.x = mIconLastTouchPos.x - sv.getIconCenter().x;
+        mIconShift.y = mIconLastTouchPos.y - mLauncher.getDeviceProfile().iconSizePx;
+
+        DragView dv = mLauncher.getWorkspace().beginDragShared(sv.getIconView(),
+                (PopupContainerWithArrow) getParent(), sv.getFinalInfo(),
+                new ShortcutDragPreviewProvider(sv.getIconView(), mIconShift), new DragOptions());
+        dv.animateShift(-mIconShift.x, -mIconShift.y);
+
+        // TODO: support dragging from within folder without having to close it
+        AbstractFloatingView.closeOpenContainer(mLauncher, AbstractFloatingView.TYPE_FOLDER);
+        return false;
+    }
+
+    public void addShortcutView(View shortcutView, PopupPopulator.Item shortcutType) {
+        addShortcutView(shortcutView, shortcutType, -1);
+    }
+
+    private void addShortcutView(View shortcutView, PopupPopulator.Item shortcutType, int index) {
+        if (shortcutType == PopupPopulator.Item.SHORTCUT) {
+            mDeepShortcutViews.add((DeepShortcutView) shortcutView);
+        } else {
+            mSystemShortcutViews.add(shortcutView);
+        }
+        if (shortcutType == PopupPopulator.Item.SYSTEM_SHORTCUT_ICON) {
+            // System shortcut icons are added to a header that is separate from the full shortcuts.
+            if (mSystemShortcutIcons == null) {
+                mSystemShortcutIcons = (LinearLayout) mLauncher.getLayoutInflater().inflate(
+                        R.layout.system_shortcut_icons, mShortcutsLayout, false);
+                mShortcutsLayout.addView(mSystemShortcutIcons, 0);
+            }
+            mSystemShortcutIcons.addView(shortcutView, index);
+        } else {
+            if (mShortcutsLayout.getChildCount() > 0) {
+                View prevChild = mShortcutsLayout.getChildAt(mShortcutsLayout.getChildCount() - 1);
+                if (prevChild instanceof DeepShortcutView) {
+                    prevChild.findViewById(R.id.divider).setVisibility(VISIBLE);
+                }
+            }
+            mShortcutsLayout.addView(shortcutView, index);
+        }
+    }
+
+    public List<DeepShortcutView> getDeepShortcutViews(boolean reverseOrder) {
+        if (reverseOrder) {
+            Collections.reverse(mDeepShortcutViews);
+        }
+        return mDeepShortcutViews;
+    }
+
+    public List<View> getSystemShortcutViews(boolean reverseOrder) {
+        // Always reverse system shortcut icons (in the header)
+        // so they are in priority order from right to left.
+        if (reverseOrder || mSystemShortcutIcons != null) {
+            Collections.reverse(mSystemShortcutViews);
+        }
+        return mSystemShortcutViews;
+    }
+
+    /**
+     * Adds a {@link SystemShortcut.Widgets} item if there are widgets for the given ItemInfo.
+     */
+    public void enableWidgetsIfExist(final BubbleTextView originalIcon) {
+        ItemInfo itemInfo = (ItemInfo) originalIcon.getTag();
+        SystemShortcut widgetInfo = new SystemShortcut.Widgets();
+        View.OnClickListener onClickListener = widgetInfo.getOnClickListener(mLauncher, itemInfo);
+        View widgetsView = null;
+        for (View systemShortcutView : mSystemShortcutViews) {
+            if (systemShortcutView.getTag() instanceof SystemShortcut.Widgets) {
+                widgetsView = systemShortcutView;
+                break;
+            }
+        }
+        final PopupPopulator.Item widgetsItem = mSystemShortcutIcons == null
+                ? PopupPopulator.Item.SYSTEM_SHORTCUT
+                : PopupPopulator.Item.SYSTEM_SHORTCUT_ICON;
+        if (onClickListener != null && widgetsView == null) {
+            // We didn't have any widgets cached but now there are some, so enable the shortcut.
+            widgetsView = mLauncher.getLayoutInflater().inflate(widgetsItem.layoutId, this, false);
+            PopupPopulator.initializeSystemShortcut(getContext(), widgetsView, widgetInfo);
+            widgetsView.setOnClickListener(onClickListener);
+            if (widgetsItem == PopupPopulator.Item.SYSTEM_SHORTCUT_ICON) {
+                addShortcutView(widgetsView, widgetsItem, 0);
+            } else {
+                // If using the expanded system shortcut (as opposed to just the icon), we need to
+                // reopen the container to ensure measurements etc. all work out. While this could
+                // be quite janky, in practice the user would typically see a small flicker as the
+                // animation restarts partway through, and this is a very rare edge case anyway.
+                ((PopupContainerWithArrow) getParent()).close(false);
+                PopupContainerWithArrow.showForIcon(originalIcon);
+            }
+        } else if (onClickListener == null && widgetsView != null) {
+            // No widgets exist, but we previously added the shortcut so remove it.
+            if (widgetsItem == PopupPopulator.Item.SYSTEM_SHORTCUT_ICON) {
+                mSystemShortcutViews.remove(widgetsView);
+                mSystemShortcutIcons.removeView(widgetsView);
+            } else {
+                ((PopupContainerWithArrow) getParent()).close(false);
+                PopupContainerWithArrow.showForIcon(originalIcon);
+            }
+        }
+    }
+
+    @Override
+    public Animator createOpenAnimation(boolean isContainerAboveIcon, boolean pivotLeft) {
+        AnimatorSet openAnimation = LauncherAnimUtils.createAnimatorSet();
+        openAnimation.play(super.createOpenAnimation(isContainerAboveIcon, pivotLeft));
+        for (int i = 0; i < mShortcutsLayout.getChildCount(); i++) {
+            if (!(mShortcutsLayout.getChildAt(i) instanceof DeepShortcutView)) {
+                continue;
+            }
+            DeepShortcutView shortcutView = ((DeepShortcutView) mShortcutsLayout.getChildAt(i));
+            View deepShortcutIcon = shortcutView.getIconView();
+            deepShortcutIcon.setScaleX(0);
+            deepShortcutIcon.setScaleY(0);
+            openAnimation.play(LauncherAnimUtils.ofPropertyValuesHolder(
+                    deepShortcutIcon, new PropertyListBuilder().scale(1).build()));
+        }
+        return openAnimation;
+    }
+
+    @Override
+    public Animator createCloseAnimation(boolean isContainerAboveIcon, boolean pivotLeft,
+            long duration) {
+        AnimatorSet closeAnimation = LauncherAnimUtils.createAnimatorSet();
+        closeAnimation.play(super.createCloseAnimation(isContainerAboveIcon, pivotLeft, duration));
+        for (int i = 0; i < mShortcutsLayout.getChildCount(); i++) {
+            if (!(mShortcutsLayout.getChildAt(i) instanceof DeepShortcutView)) {
+                continue;
+            }
+            DeepShortcutView shortcutView = ((DeepShortcutView) mShortcutsLayout.getChildAt(i));
+            View deepShortcutIcon = shortcutView.getIconView();
+            deepShortcutIcon.setScaleX(1);
+            deepShortcutIcon.setScaleY(1);
+            closeAnimation.play(LauncherAnimUtils.ofPropertyValuesHolder(
+                    deepShortcutIcon, new PropertyListBuilder().scale(0).build()));
+        }
+        return closeAnimation;
+    }
+
+    @Override
+    public int getArrowColor(boolean isArrowAttachedToBottom) {
+        return ContextCompat.getColor(getContext(),
+                isArrowAttachedToBottom || mSystemShortcutIcons == null
+                        ? R.color.popup_background_color
+                        : R.color.popup_header_background_color);
+    }
+
+    @Override
+    public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target,
+            LauncherLogProto.Target targetParent) {
+        target.itemType = LauncherLogProto.ItemType.DEEPSHORTCUT;
+        target.rank = info.rank;
+        targetParent.containerType = LauncherLogProto.ContainerType.DEEPSHORTCUTS;
+    }
+}
diff --git a/src/com/android/launcher3/testing/LauncherExtension.java b/src/com/android/launcher3/testing/LauncherExtension.java
index 6797c7b..aedca8d 100644
--- a/src/com/android/launcher3/testing/LauncherExtension.java
+++ b/src/com/android/launcher3/testing/LauncherExtension.java
@@ -180,9 +180,6 @@
         }
 
         @Override
-        public UserEventDispatcher getUserEventDispatcher() { return null; }
-
-        @Override
         public View getQsbBar() {
             return null;
         }
diff --git a/src/com/android/launcher3/testing/ToggleWeightWatcher.java b/src/com/android/launcher3/testing/ToggleWeightWatcher.java
index e08ec3a..f0c3920 100644
--- a/src/com/android/launcher3/testing/ToggleWeightWatcher.java
+++ b/src/com/android/launcher3/testing/ToggleWeightWatcher.java
@@ -22,7 +22,7 @@
         show = !show;
         sp.edit().putBoolean(TestingUtils.SHOW_WEIGHT_WATCHER, show).apply();
 
-        Launcher launcher = (Launcher) LauncherAppState.getInstance().getModel().getCallback();
+        Launcher launcher = (Launcher) LauncherAppState.getInstance(this).getModel().getCallback();
         if (launcher != null && launcher.mWeightWatcher != null) {
             launcher.mWeightWatcher.setVisibility(show ? View.VISIBLE : View.GONE);
         }
diff --git a/src/com/android/launcher3/util/CachedPackageTracker.java b/src/com/android/launcher3/util/CachedPackageTracker.java
index 293714e..314b4c0 100644
--- a/src/com/android/launcher3/util/CachedPackageTracker.java
+++ b/src/com/android/launcher3/util/CachedPackageTracker.java
@@ -18,12 +18,12 @@
 
 import android.content.Context;
 import android.content.SharedPreferences;
+import android.content.pm.LauncherActivityInfo;
+import android.os.UserHandle;
 
 import com.android.launcher3.Utilities;
-import com.android.launcher3.compat.LauncherActivityInfoCompat;
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.LauncherAppsCompat.OnAppsChangedCallbackCompat;
-import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 
 import java.util.ArrayList;
@@ -55,7 +55,7 @@
      * Checks the list of user apps, and generates package event accordingly.
      * {@see #onLauncherAppsAdded}, {@see #onLauncherPackageRemoved}
      */
-    public void processUserApps(List<LauncherActivityInfoCompat> apps, UserHandleCompat user) {
+    public void processUserApps(List<LauncherActivityInfo> apps, UserHandle user) {
         String prefKey = INSTALLED_PACKAGES_PREFIX + mUserManager.getSerialNumberForUser(user);
         HashSet<String> oldPackageSet = new HashSet<>();
         final boolean userAppsExisted = getUserApps(oldPackageSet, prefKey);
@@ -64,7 +64,7 @@
         HashSet<String> newPackageSet = new HashSet<>();
         ArrayList<LauncherActivityInstallInfo> packagesAdded = new ArrayList<>();
 
-        for (LauncherActivityInfoCompat info : apps) {
+        for (LauncherActivityInfo info : apps) {
             String packageName = info.getComponentName().getPackageName();
             newPackageSet.add(packageName);
             packagesRemoved.remove(packageName);
@@ -107,7 +107,7 @@
     }
 
     @Override
-    public void onPackageRemoved(String packageName, UserHandleCompat user) {
+    public void onPackageRemoved(String packageName, UserHandle user) {
         String prefKey = INSTALLED_PACKAGES_PREFIX + mUserManager.getSerialNumberForUser(user);
         HashSet<String> packageSet = new HashSet<>();
         if (getUserApps(packageSet, prefKey) && packageSet.remove(packageName)) {
@@ -118,15 +118,15 @@
     }
 
     @Override
-    public void onPackageAdded(String packageName, UserHandleCompat user) {
+    public void onPackageAdded(String packageName, UserHandle user) {
         String prefKey = INSTALLED_PACKAGES_PREFIX + mUserManager.getSerialNumberForUser(user);
         HashSet<String> packageSet = new HashSet<>();
         final boolean userAppsExisted = getUserApps(packageSet, prefKey);
         if (!packageSet.contains(packageName)) {
-            List<LauncherActivityInfoCompat> activities =
+            List<LauncherActivityInfo> activities =
                     mLauncherApps.getActivityList(packageName, user);
             if (!activities.isEmpty()) {
-                LauncherActivityInfoCompat activityInfo = activities.get(0);
+                LauncherActivityInfo activityInfo = activities.get(0);
 
                 packageSet.add(packageName);
                 mPrefs.edit().putStringSet(prefKey, packageSet).apply();
@@ -138,21 +138,21 @@
     }
 
     @Override
-    public void onPackageChanged(String packageName, UserHandleCompat user) { }
+    public void onPackageChanged(String packageName, UserHandle user) { }
 
     @Override
     public void onPackagesAvailable(
-            String[] packageNames, UserHandleCompat user, boolean replacing) { }
+            String[] packageNames, UserHandle user, boolean replacing) { }
 
     @Override
     public void onPackagesUnavailable(
-            String[] packageNames, UserHandleCompat user, boolean replacing) { }
+            String[] packageNames, UserHandle user, boolean replacing) { }
 
     @Override
-    public void onPackagesSuspended(String[] packageNames, UserHandleCompat user) { }
+    public void onPackagesSuspended(String[] packageNames, UserHandle user) { }
 
     @Override
-    public void onPackagesUnsuspended(String[] packageNames, UserHandleCompat user) { }
+    public void onPackagesUnsuspended(String[] packageNames, UserHandle user) { }
 
     /**
      * Called when new launcher apps are added.
@@ -163,19 +163,19 @@
      *                        when Launcher was newly installed or a new user was added.
      */
     protected abstract void onLauncherAppsAdded(List<LauncherActivityInstallInfo> apps,
-            UserHandleCompat user, boolean userAppsExisted);
+            UserHandle user, boolean userAppsExisted);
 
     /**
      * Called when apps are removed from the system.
      */
-    protected abstract void onLauncherPackageRemoved(String packageName, UserHandleCompat user);
+    protected abstract void onLauncherPackageRemoved(String packageName, UserHandle user);
 
     public static class LauncherActivityInstallInfo
             implements Comparable<LauncherActivityInstallInfo> {
-        public final LauncherActivityInfoCompat info;
+        public final LauncherActivityInfo info;
         public final long installTime;
 
-        public LauncherActivityInstallInfo(LauncherActivityInfoCompat info, long installTime) {
+        public LauncherActivityInstallInfo(LauncherActivityInfo info, long installTime) {
             this.info = info;
             this.installTime = installTime;
         }
diff --git a/src/com/android/launcher3/util/CircleRevealOutlineProvider.java b/src/com/android/launcher3/util/CircleRevealOutlineProvider.java
index c192120..9fe5147 100644
--- a/src/com/android/launcher3/util/CircleRevealOutlineProvider.java
+++ b/src/com/android/launcher3/util/CircleRevealOutlineProvider.java
@@ -16,10 +16,6 @@
 
 package com.android.launcher3.util;
 
-import android.annotation.TargetApi;
-import android.os.Build;
-
-@TargetApi(Build.VERSION_CODES.LOLLIPOP)
 public class CircleRevealOutlineProvider extends RevealOutlineAnimation {
 
     private int mCenterX;
diff --git a/src/com/android/launcher3/util/ComponentKey.java b/src/com/android/launcher3/util/ComponentKey.java
index 5882f21..242df2e 100644
--- a/src/com/android/launcher3/util/ComponentKey.java
+++ b/src/com/android/launcher3/util/ComponentKey.java
@@ -18,8 +18,9 @@
 
 import android.content.ComponentName;
 import android.content.Context;
+import android.os.Process;
+import android.os.UserHandle;
 
-import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 
 import java.util.Arrays;
@@ -27,11 +28,11 @@
 public class ComponentKey {
 
     public final ComponentName componentName;
-    public final UserHandleCompat user;
+    public final UserHandle user;
 
     private final int mHashCode;
 
-    public ComponentKey(ComponentName componentName, UserHandleCompat user) {
+    public ComponentKey(ComponentName componentName, UserHandle user) {
         Preconditions.assertNotNull(componentName);
         Preconditions.assertNotNull(user);
         this.componentName = componentName;
@@ -56,7 +57,7 @@
         } else {
             // No user provided, default to the current user
             componentName = ComponentName.unflattenFromString(componentKeyStr);
-            user = UserHandleCompat.myUserHandle();
+            user = Process.myUserHandle();
         }
         Preconditions.assertNotNull(componentName);
         Preconditions.assertNotNull(user);
diff --git a/src/com/android/launcher3/util/ConfigMonitor.java b/src/com/android/launcher3/util/ConfigMonitor.java
index bdb1639..2489d30 100644
--- a/src/com/android/launcher3/util/ConfigMonitor.java
+++ b/src/com/android/launcher3/util/ConfigMonitor.java
@@ -23,8 +23,6 @@
 import android.content.res.Configuration;
 import android.util.Log;
 
-import com.android.launcher3.Utilities;
-
 /**
  * {@link BroadcastReceiver} which watches configuration changes and
  * restarts the process in case changes which affect the device profile occur.
@@ -40,13 +38,13 @@
 
         Configuration config = context.getResources().getConfiguration();
         mFontScale = config.fontScale;
-        mDensity = getDensity(config);
+        mDensity = config.densityDpi;
     }
 
     @Override
     public void onReceive(Context context, Intent intent) {
         Configuration config = context.getResources().getConfiguration();
-        if (mFontScale != config.fontScale || mDensity != getDensity(config)) {
+        if (mFontScale != config.fontScale || mDensity != config.densityDpi) {
             Log.d("ConfigMonitor", "Configuration changed, restarting launcher");
             mContext.unregisterReceiver(this);
             android.os.Process.killProcess(android.os.Process.myPid());
@@ -56,8 +54,4 @@
     public void register() {
         mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED));
     }
-
-    private static int getDensity(Configuration config) {
-        return Utilities.ATLEAST_JB_MR1 ? config.densityDpi : 0;
-    }
 }
diff --git a/src/com/android/launcher3/util/ContentWriter.java b/src/com/android/launcher3/util/ContentWriter.java
new file mode 100644
index 0000000..4384328
--- /dev/null
+++ b/src/com/android/launcher3/util/ContentWriter.java
@@ -0,0 +1,126 @@
+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 android.net.Uri;
+import android.os.UserHandle;
+
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.Utilities;
+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 CommitParams mCommitParams;
+    private Bitmap mIcon;
+    private UserHandle mUser;
+
+    public ContentWriter(Context context, CommitParams commitParams) {
+        this(context);
+        mCommitParams = commitParams;
+    }
+
+    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, UserHandle user) {
+        mIcon = value;
+        mUser = user;
+        return this;
+    }
+
+    public ContentWriter put(String key, UserHandle 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(Context context) {
+        Preconditions.assertNonUiThread();
+        if (mIcon != null && !LauncherAppState.getInstance(context).getIconCache()
+                .isDefaultIcon(mIcon, mUser)) {
+            mValues.put(LauncherSettings.Favorites.ICON, Utilities.flattenBitmap(mIcon));
+            mIcon = null;
+        }
+        return mValues;
+    }
+
+    public int commit() {
+        if (mCommitParams != null) {
+            return mContext.getContentResolver().update(mCommitParams.mUri, getValues(mContext),
+                    mCommitParams.mWhere, mCommitParams.mSelectionArgs);
+        }
+        return 0;
+    }
+
+    public static final class CommitParams {
+
+        final Uri mUri = LauncherSettings.Favorites.CONTENT_URI;
+        String mWhere;
+        String[] mSelectionArgs;
+
+        public CommitParams(String where, String[] selectionArgs) {
+            mWhere = where;
+            mSelectionArgs = selectionArgs;
+        }
+
+    }
+}
diff --git a/src/com/android/launcher3/util/CursorIconInfo.java b/src/com/android/launcher3/util/CursorIconInfo.java
deleted file mode 100644
index 4fefa98..0000000
--- a/src/com/android/launcher3/util/CursorIconInfo.java
+++ /dev/null
@@ -1,85 +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.util;
-
-import android.content.Context;
-import android.content.Intent.ShortcutIconResource;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.text.TextUtils;
-
-import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.ShortcutInfo;
-import com.android.launcher3.Utilities;
-
-/**
- * Utility class to load icon from a cursor.
- */
-public class CursorIconInfo {
-    public final int iconPackageIndex;
-    public final int iconResourceIndex;
-    public final int iconIndex;
-
-    public final int titleIndex;
-
-    private final Context mContext;
-
-    public CursorIconInfo(Context context, Cursor c) {
-        mContext = context;
-
-        iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
-        iconPackageIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE);
-        iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE);
-
-        titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
-    }
-
-    /**
-     * Loads the icon from the cursor and updates the {@param info} if the icon is an app resource.
-     */
-    public Bitmap loadIcon(Cursor c, ShortcutInfo info) {
-        Bitmap icon = null;
-        String packageName = c.getString(iconPackageIndex);
-        String resourceName = c.getString(iconResourceIndex);
-        if (!TextUtils.isEmpty(packageName) || !TextUtils.isEmpty(resourceName)) {
-            info.iconResource = new ShortcutIconResource();
-            info.iconResource.packageName = packageName;
-            info.iconResource.resourceName = resourceName;
-            icon = Utilities.createIconBitmap(packageName, resourceName, mContext);
-        }
-        if (icon == null) {
-            // Failed to load from resource, try loading from DB.
-            icon = loadIcon(c);
-        }
-        return icon;
-    }
-
-    /**
-     * Loads the fixed bitmap from the icon if available.
-     */
-    public Bitmap loadIcon(Cursor c) {
-        return Utilities.createIconBitmap(c, iconIndex, mContext);
-    }
-
-    /**
-     * Returns the title or empty string
-     */
-    public String getTitle(Cursor c) {
-        String title = c.getString(titleIndex);
-        return TextUtils.isEmpty(title) ? "" : Utilities.trim(c.getString(titleIndex));
-    }
-}
diff --git a/src/com/android/launcher3/util/FlingAnimation.java b/src/com/android/launcher3/util/FlingAnimation.java
index da8bae7..d475ee9 100644
--- a/src/com/android/launcher3/util/FlingAnimation.java
+++ b/src/com/android/launcher3/util/FlingAnimation.java
@@ -5,13 +5,16 @@
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.graphics.PointF;
 import android.graphics.Rect;
+import android.view.animation.AnimationUtils;
 import android.view.animation.DecelerateInterpolator;
 
+import com.android.launcher3.ButtonDropTarget;
 import com.android.launcher3.DropTarget.DragObject;
+import com.android.launcher3.Launcher;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragView;
 
-public class FlingAnimation implements AnimatorUpdateListener {
+public class FlingAnimation implements AnimatorUpdateListener, Runnable {
 
     /**
      * Maximum acceleration in one dimension (pixels per milliseconds)
@@ -19,40 +22,86 @@
     private static final float MAX_ACCELERATION = 0.5f;
     private static final int DRAG_END_DELAY = 300;
 
+    private final ButtonDropTarget mDropTarget;
+    private final Launcher mLauncher;
+
     protected final DragObject mDragObject;
-    protected final Rect mIconRect;
     protected final DragLayer mDragLayer;
-    protected final Rect mFrom;
-    protected final int mDuration;
-    protected final float mUX, mUY;
-    protected final float mAnimationTimeFraction;
     protected final TimeInterpolator mAlphaInterpolator = new DecelerateInterpolator(0.75f);
+    protected final float mUX, mUY;
+
+    protected Rect mIconRect;
+    protected Rect mFrom;
+    protected int mDuration;
+    protected float mAnimationTimeFraction;
 
     protected float mAX, mAY;
 
-    /**
-     * @param vel initial fling velocity in pixels per second.
-     */
-    public FlingAnimation(DragObject d, PointF vel, Rect iconRect, DragLayer dragLayer) {
+    public FlingAnimation(DragObject d, PointF vel, ButtonDropTarget dropTarget, Launcher launcher) {
+        mDropTarget = dropTarget;
+        mLauncher = launcher;
         mDragObject = d;
         mUX = vel.x / 1000;
         mUY = vel.y / 1000;
-        mIconRect = iconRect;
+        mDragLayer = mLauncher.getDragLayer();
+    }
 
-        mDragLayer = dragLayer;
+    @Override
+    public void run() {
+        mIconRect = mDropTarget.getIconRect(mDragObject);
+
+        // Initiate from
         mFrom = new Rect();
-        dragLayer.getViewRectRelativeToSelf(d.dragView, mFrom);
-
-        float scale = d.dragView.getScaleX();
-        float xOffset = ((scale - 1f) * d.dragView.getMeasuredWidth()) / 2f;
-        float yOffset = ((scale - 1f) * d.dragView.getMeasuredHeight()) / 2f;
+        mDragLayer.getViewRectRelativeToSelf(mDragObject.dragView, mFrom);
+        float scale = mDragObject.dragView.getScaleX();
+        float xOffset = ((scale - 1f) * mDragObject.dragView.getMeasuredWidth()) / 2f;
+        float yOffset = ((scale - 1f) * mDragObject.dragView.getMeasuredHeight()) / 2f;
         mFrom.left += xOffset;
         mFrom.right -= xOffset;
         mFrom.top += yOffset;
         mFrom.bottom -= yOffset;
+        mDuration = Math.abs(mUY) > Math.abs(mUX) ? initFlingUpDuration() : initFlingLeftDuration();
 
-        mDuration = Math.abs(vel.y) > Math.abs(vel.x) ? initFlingUpDuration() : initFlingLeftDuration();
         mAnimationTimeFraction = ((float) mDuration) / (mDuration + DRAG_END_DELAY);
+
+        // Don't highlight the icon as it's animating
+        mDragObject.dragView.setColor(0);
+
+        final int duration = mDuration + DRAG_END_DELAY;
+        final long startTime = AnimationUtils.currentAnimationTimeMillis();
+
+        // NOTE: Because it takes time for the first frame of animation to actually be
+        // called and we expect the animation to be a continuation of the fling, we have
+        // to account for the time that has elapsed since the fling finished.  And since
+        // we don't have a startDelay, we will always get call to update when we call
+        // start() (which we want to ignore).
+        final TimeInterpolator tInterpolator = new TimeInterpolator() {
+            private int mCount = -1;
+            private float mOffset = 0f;
+
+            @Override
+            public float getInterpolation(float t) {
+                if (mCount < 0) {
+                    mCount++;
+                } else if (mCount == 0) {
+                    mOffset = Math.min(0.5f, (float) (AnimationUtils.currentAnimationTimeMillis() -
+                            startTime) / duration);
+                    mCount++;
+                }
+                return Math.min(1f, mOffset + t);
+            }
+        };
+
+        Runnable onAnimationEndRunnable = new Runnable() {
+            @Override
+            public void run() {
+                mLauncher.exitSpringLoadedDragMode();
+                mDropTarget.completeDrop(mDragObject);
+            }
+        };
+
+        mDragLayer.animateView(mDragObject.dragView, this, duration, tInterpolator,
+                onAnimationEndRunnable, DragLayer.ANIMATION_END_DISAPPEAR, null);
     }
 
     /**
@@ -111,10 +160,6 @@
         return (int) Math.round(t);
     }
 
-    public final int getDuration() {
-        return mDuration + DRAG_END_DELAY;
-    }
-
     @Override
     public void onAnimationUpdate(ValueAnimator animation) {
         float t = animation.getAnimatedFraction();
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/IconNormalizer.java b/src/com/android/launcher3/util/IconNormalizer.java
deleted file mode 100644
index 040a1b5..0000000
--- a/src/com/android/launcher3/util/IconNormalizer.java
+++ /dev/null
@@ -1,250 +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.util;
-
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.RectF;
-import android.graphics.drawable.Drawable;
-
-import com.android.launcher3.LauncherAppState;
-
-import java.nio.ByteBuffer;
-
-public class IconNormalizer {
-
-    // Ratio of icon visible area to full icon size for a square shaped icon
-    private static final float MAX_SQUARE_AREA_FACTOR = 375.0f / 576;
-    // Ratio of icon visible area to full icon size for a circular shaped icon
-    private static final float MAX_CIRCLE_AREA_FACTOR = 380.0f / 576;
-
-    private static final float CIRCLE_AREA_BY_RECT = (float) Math.PI / 4;
-
-    // Slope used to calculate icon visible area to full icon size for any generic shaped icon.
-    private static final float LINEAR_SCALE_SLOPE =
-            (MAX_CIRCLE_AREA_FACTOR - MAX_SQUARE_AREA_FACTOR) / (1 - CIRCLE_AREA_BY_RECT);
-
-    private static final int MIN_VISIBLE_ALPHA = 40;
-
-    private static final Object LOCK = new Object();
-    private static IconNormalizer sIconNormalizer;
-
-    private final int mMaxSize;
-    private final Bitmap mBitmap;
-    private final Canvas mCanvas;
-    private final byte[] mPixels;
-
-    // for each y, stores the position of the leftmost x and the rightmost x
-    private final float[] mLeftBorder;
-    private final float[] mRightBorder;
-
-    private IconNormalizer() {
-        // Use twice the icon size as maximum size to avoid scaling down twice.
-        mMaxSize = LauncherAppState.getInstance().getInvariantDeviceProfile().iconBitmapSize * 2;
-        mBitmap = Bitmap.createBitmap(mMaxSize, mMaxSize, Bitmap.Config.ALPHA_8);
-        mCanvas = new Canvas(mBitmap);
-        mPixels = new byte[mMaxSize * mMaxSize];
-
-        mLeftBorder = new float[mMaxSize];
-        mRightBorder = new float[mMaxSize];
-    }
-
-    /**
-     * Returns the amount by which the {@param d} should be scaled (in both dimensions) so that it
-     * matches the design guidelines for a launcher icon.
-     *
-     * We first calculate the convex hull of the visible portion of the icon.
-     * This hull then compared with the bounding rectangle of the hull to find how closely it
-     * resembles a circle and a square, by comparing the ratio of the areas. Note that this is not an
-     * ideal solution but it gives satisfactory result without affecting the performance.
-     *
-     * This closeness is used to determine the ratio of hull area to the full icon size.
-     * Refer {@link #MAX_CIRCLE_AREA_FACTOR} and {@link #MAX_SQUARE_AREA_FACTOR}
-     *
-     * @param outBounds optional rect to receive the fraction distance from each edge.
-     */
-    public synchronized float getScale(Drawable d, RectF outBounds) {
-        int width = d.getIntrinsicWidth();
-        int height = d.getIntrinsicHeight();
-        if (width <= 0 || height <= 0) {
-            width = width <= 0 || width > mMaxSize ? mMaxSize : width;
-            height = height <= 0 || height > mMaxSize ? mMaxSize : height;
-        } else if (width > mMaxSize || height > mMaxSize) {
-            int max = Math.max(width, height);
-            width = mMaxSize * width / max;
-            height = mMaxSize * height / max;
-        }
-
-        mBitmap.eraseColor(Color.TRANSPARENT);
-        d.setBounds(0, 0, width, height);
-        d.draw(mCanvas);
-
-        ByteBuffer buffer = ByteBuffer.wrap(mPixels);
-        buffer.rewind();
-        mBitmap.copyPixelsToBuffer(buffer);
-
-        // Overall bounds of the visible icon.
-        int topY = -1;
-        int bottomY = -1;
-        int leftX = mMaxSize + 1;
-        int rightX = -1;
-
-        // Create border by going through all pixels one row at a time and for each row find
-        // the first and the last non-transparent pixel. Set those values to mLeftBorder and
-        // mRightBorder and use -1 if there are no visible pixel in the row.
-
-        // buffer position
-        int index = 0;
-        // buffer shift after every row, width of buffer = mMaxSize
-        int rowSizeDiff = mMaxSize - width;
-        // first and last position for any row.
-        int firstX, lastX;
-
-        for (int y = 0; y < height; y++) {
-            firstX = lastX = -1;
-            for (int x = 0; x < width; x++) {
-                if ((mPixels[index] & 0xFF) > MIN_VISIBLE_ALPHA) {
-                    if (firstX == -1) {
-                        firstX = x;
-                    }
-                    lastX = x;
-                }
-                index++;
-            }
-            index += rowSizeDiff;
-
-            mLeftBorder[y] = firstX;
-            mRightBorder[y] = lastX;
-
-            // If there is at least one visible pixel, update the overall bounds.
-            if (firstX != -1) {
-                bottomY = y;
-                if (topY == -1) {
-                    topY = y;
-                }
-
-                leftX = Math.min(leftX, firstX);
-                rightX = Math.max(rightX, lastX);
-            }
-        }
-
-        if (topY == -1 || rightX == -1) {
-            // No valid pixels found. Do not scale.
-            return 1;
-        }
-
-        convertToConvexArray(mLeftBorder, 1, topY, bottomY);
-        convertToConvexArray(mRightBorder, -1, topY, bottomY);
-
-        // Area of the convex hull
-        float area = 0;
-        for (int y = 0; y < height; y++) {
-            if (mLeftBorder[y] <= -1) {
-                continue;
-            }
-            area += mRightBorder[y] - mLeftBorder[y] + 1;
-        }
-
-        // Area of the rectangle required to fit the convex hull
-        float rectArea = (bottomY + 1 - topY) * (rightX + 1 - leftX);
-        float hullByRect = area / rectArea;
-
-        float scaleRequired;
-        if (hullByRect < CIRCLE_AREA_BY_RECT) {
-            scaleRequired = MAX_CIRCLE_AREA_FACTOR;
-        } else {
-            scaleRequired = MAX_SQUARE_AREA_FACTOR + LINEAR_SCALE_SLOPE * (1  - hullByRect);
-        }
-
-        if (outBounds != null) {
-            outBounds.left = ((float) leftX) / width;
-            outBounds.right = 1 - ((float) rightX) / width;
-
-            outBounds.top = ((float) topY) / height;
-            outBounds.bottom = 1 - ((float) bottomY) / height;
-        }
-
-        float areaScale = area / (width * height);
-        // Use sqrt of the final ratio as the images is scaled across both width and height.
-        float scale = areaScale > scaleRequired ? (float) Math.sqrt(scaleRequired / areaScale) : 1;
-        return scale;
-    }
-
-    /**
-     * Modifies {@param xCordinates} 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 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;
-        // The tangent at each pixel.
-        float[] angles = new float[total - 1];
-
-        int first = topY; // First valid y coordinate
-        int last = -1;    // Last valid y coordinate which didn't have a missing value
-
-        float lastAngle = Float.MAX_VALUE;
-
-        for (int i = topY + 1; i <= bottomY; i++) {
-            if (xCordinates[i] <= -1) {
-                continue;
-            }
-            int start;
-
-            if (lastAngle == Float.MAX_VALUE) {
-                start = first;
-            } else {
-                float currentAngle = (xCordinates[i] - xCordinates[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);
-                        if ((currentAngle - angles[start]) * direction >= 0) {
-                            break;
-                        }
-                    }
-                }
-            }
-
-            // Reset from last check
-            lastAngle = (xCordinates[i] - xCordinates[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);
-            }
-            last = i;
-        }
-    }
-
-    public static IconNormalizer getInstance() {
-        synchronized (LOCK) {
-            if (sIconNormalizer == null) {
-                sIconNormalizer = new IconNormalizer();
-            }
-        }
-        return sIconNormalizer;
-    }
-}
diff --git a/src/com/android/launcher3/util/ItemInfoMatcher.java b/src/com/android/launcher3/util/ItemInfoMatcher.java
index 46e9184..42de284 100644
--- a/src/com/android/launcher3/util/ItemInfoMatcher.java
+++ b/src/com/android/launcher3/util/ItemInfoMatcher.java
@@ -17,11 +17,13 @@
 package com.android.launcher3.util;
 
 import android.content.ComponentName;
+import android.os.UserHandle;
 
+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;
 import com.android.launcher3.shortcuts.ShortcutKey;
 
 import java.util.HashSet;
@@ -33,8 +35,48 @@
 
     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 UserHandle 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) {
+            final HashSet<ComponentName> components, final UserHandle user) {
         return new ItemInfoMatcher() {
             @Override
             public boolean matches(ItemInfo info, ComponentName cn) {
@@ -44,7 +86,7 @@
     }
 
     public static ItemInfoMatcher ofPackages(
-            final HashSet<String> packageNames, final UserHandleCompat user) {
+            final HashSet<String> packageNames, final UserHandle user) {
         return new ItemInfoMatcher() {
             @Override
             public boolean matches(ItemInfo info, ComponentName cn) {
@@ -58,7 +100,7 @@
             @Override
             public boolean matches(ItemInfo info, ComponentName cn) {
                 return info.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT &&
-                        keys.contains(ShortcutKey.fromShortcutInfo((ShortcutInfo) info));
+                        keys.contains(ShortcutKey.fromItemInfo(info));
             }
         };
     }
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/LogConfig.java b/src/com/android/launcher3/util/LogConfig.java
new file mode 100644
index 0000000..4acdb5c
--- /dev/null
+++ b/src/com/android/launcher3/util/LogConfig.java
@@ -0,0 +1,31 @@
+package com.android.launcher3.util;
+
+/**
+ * This is a utility class that keeps track of all the tag that can be enabled to debug
+ * a behavior in runtime.
+ *
+ * To use any of the strings defined in this class, execute the following command.
+ *
+ * $ adb shell setprop log.tag.TAGNAME VERBOSE
+ */
+
+public class LogConfig {
+    // These are list of strings that can be used to replace TAGNAME.
+
+    /**
+     * After this tag is turned on, whenever there is n user event, debug information is
+     * printed out to logcat.
+     */
+    public static final String USEREVENT = "UserEvent";
+
+    /**
+     * When turned on, all icons are kept on the home screen, even if they don't have an active
+     * session.
+     */
+    public static final String KEEP_ALL_ICONS = "KeepAllIcons";
+
+    /**
+     * When turned on, icon cache is only fetched from memory and not disk.
+     */
+    public static final String MEMORY_ONLY_ICON_CACHE = "MemoryOnlyIconCache";
+}
diff --git a/src/com/android/launcher3/util/LooperExecuter.java b/src/com/android/launcher3/util/LooperExecuter.java
new file mode 100644
index 0000000..4db999b
--- /dev/null
+++ b/src/com/android/launcher3/util/LooperExecuter.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.launcher3.util;
+
+import android.os.Handler;
+import android.os.Looper;
+
+import java.util.List;
+import java.util.concurrent.AbstractExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Extension of {@link AbstractExecutorService} which executed on a provided looper.
+ */
+public class LooperExecuter extends AbstractExecutorService {
+
+    private final Handler mHandler;
+
+    public LooperExecuter(Looper looper) {
+        mHandler = new Handler(looper);
+    }
+
+    @Override
+    public void execute(Runnable runnable) {
+        if (mHandler.getLooper() == Looper.myLooper()) {
+            runnable.run();
+        } else {
+            mHandler.post(runnable);
+        }
+    }
+
+    /**
+     * Not supported and throws an exception when used.
+     */
+    @Override
+    @Deprecated
+    public void shutdown() {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Not supported and throws an exception when used.
+     */
+    @Override
+    @Deprecated
+    public List<Runnable> shutdownNow() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isShutdown() {
+        return false;
+    }
+
+    @Override
+    public boolean isTerminated() {
+        return false;
+    }
+
+    /**
+     * Not supported and throws an exception when used.
+     */
+    @Override
+    @Deprecated
+    public boolean awaitTermination(long l, TimeUnit timeUnit) throws InterruptedException {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/src/com/android/launcher3/util/ManagedProfileHeuristic.java b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
index 6661429..ce603c4 100644
--- a/src/com/android/launcher3/util/ManagedProfileHeuristic.java
+++ b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
@@ -18,20 +18,24 @@
 
 import android.content.Context;
 import android.content.SharedPreferences;
+import android.content.pm.LauncherActivityInfo;
+import android.os.Process;
+import android.os.UserHandle;
+import android.support.v4.os.BuildCompat;
 
+import com.android.launcher3.AppInfo;
 import com.android.launcher3.FolderInfo;
+import com.android.launcher3.IconCache;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherFiles;
 import com.android.launcher3.LauncherModel;
 import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.R;
+import com.android.launcher3.SessionCommitReceiver;
 import com.android.launcher3.ShortcutInfo;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.compat.LauncherActivityInfoCompat;
-import com.android.launcher3.shortcuts.ShortcutInfoCompat;
-import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -55,8 +59,8 @@
      */
     private static final long AUTO_ADD_TO_FOLDER_DURATION = 8 * 60 * 60 * 1000;
 
-    public static ManagedProfileHeuristic get(Context context, UserHandleCompat user) {
-        if (Utilities.ATLEAST_LOLLIPOP && !UserHandleCompat.myUserHandle().equals(user)) {
+    public static ManagedProfileHeuristic get(Context context, UserHandle user) {
+        if (!Process.myUserHandle().equals(user)) {
             return new ManagedProfileHeuristic(context, user);
         }
         return null;
@@ -64,12 +68,17 @@
 
     private final Context mContext;
     private final LauncherModel mModel;
-    private final UserHandleCompat mUser;
+    private final UserHandle mUser;
+    private final IconCache mIconCache;
+    private final boolean mAddIconsToHomescreen;
 
-    private ManagedProfileHeuristic(Context context, UserHandleCompat user) {
+    private ManagedProfileHeuristic(Context context, UserHandle user) {
         mContext = context;
         mUser = user;
-        mModel = LauncherAppState.getInstance().getModel();
+        mModel = LauncherAppState.getInstance(context).getModel();
+        mIconCache = LauncherAppState.getInstance(context).getIconCache();
+        mAddIconsToHomescreen =
+                !BuildCompat.isAtLeastO() || SessionCommitReceiver.isEnabled(context);
     }
 
     public void processPackageRemoved(String[] packages) {
@@ -88,7 +97,7 @@
         }
     }
 
-    public void processUserApps(List<LauncherActivityInfoCompat> apps) {
+    public void processUserApps(List<LauncherActivityInfo> apps) {
         Preconditions.assertWorkerThread();
         new ManagedProfilePackageHandler().processUserApps(apps, mUser);
     }
@@ -100,7 +109,7 @@
         }
 
         protected void onLauncherAppsAdded(
-                List<LauncherActivityInstallInfo> apps, UserHandleCompat user, boolean userAppsExisted) {
+                List<LauncherActivityInstallInfo> apps, UserHandle user, boolean userAppsExisted) {
             ArrayList<ShortcutInfo> workFolderApps = new ArrayList<>();
             ArrayList<ShortcutInfo> homescreenApps = new ArrayList<>();
 
@@ -108,10 +117,13 @@
             long folderCreationTime =
                     mUserManager.getUserCreationTime(user) + AUTO_ADD_TO_FOLDER_DURATION;
 
+            boolean quietModeEnabled = UserManagerCompat.getInstance(mContext)
+                    .isQuietModeEnabled(user);
             for (int i = 0; i < count; i++) {
                 LauncherActivityInstallInfo info = apps.get(i);
-
-                ShortcutInfo si = new ShortcutInfo(info.info, mContext);
+                AppInfo appInfo = new AppInfo(info.info, user, quietModeEnabled);
+                mIconCache.getTitleAndIcon(appInfo, info.info, false /* useLowResIcon */);
+                ShortcutInfo si = appInfo.makeShortcut();
                 ((info.installTime <= folderCreationTime) ? workFolderApps : homescreenApps).add(si);
             }
 
@@ -120,26 +132,33 @@
             // Do not add shortcuts on the homescreen for the first time. This prevents the launcher
             // 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);
+            if (userAppsExisted && !homescreenApps.isEmpty() && mAddIconsToHomescreen) {
+                mModel.addAndBindAddedWorkspaceItems(new ArrayList<ItemInfo>(homescreenApps));
             }
         }
 
         @Override
-        protected void onLauncherPackageRemoved(String packageName, UserHandleCompat user) {
+        protected void onLauncherPackageRemoved(String packageName, UserHandle user) {
         }
 
         /**
          * Adds and binds shortcuts marked to be added to the work folder.
          */
         private void finalizeWorkFolder(
-                UserHandleCompat user, final ArrayList<ShortcutInfo> workFolderApps,
+                UserHandle user, final ArrayList<ShortcutInfo> workFolderApps,
                 ArrayList<ShortcutInfo> homescreenApps) {
             if (workFolderApps.isEmpty()) {
                 return;
             }
             // Try to get a work folder.
             String folderIdKey = USER_FOLDER_ID_PREFIX + mUserManager.getSerialNumberForUser(user);
+            if (!mAddIconsToHomescreen) {
+                if (!mPrefs.contains(folderIdKey)) {
+                    // Just mark the folder id preference to avoid new folder creation later.
+                    mPrefs.edit().putLong(folderIdKey, -1).apply();
+                }
+                return;
+            }
             if (mPrefs.contains(folderIdKey)) {
                 long folderId = mPrefs.getLong(folderIdKey, 0);
                 final FolderInfo workFolder = mModel.findFolderById(folderId);
@@ -156,6 +175,7 @@
 
                     @Override
                     public void run() {
+                        workFolder.prepareAutoUpdate();
                         for (ShortcutInfo info : workFolderApps) {
                             workFolder.add(info, false);
                         }
@@ -173,9 +193,9 @@
                 }
 
                 // Add the item to home screen and DB. This also generates an item id synchronously.
-                ArrayList<ItemInfo> itemList = new ArrayList<ItemInfo>(1);
+                ArrayList<ItemInfo> itemList = new ArrayList<>(1);
                 itemList.add(workFolder);
-                mModel.addAndBindAddedWorkspaceItems(mContext, itemList);
+                mModel.addAndBindAddedWorkspaceItems(itemList);
                 mPrefs.edit().putLong(folderIdKey, workFolder.id).apply();
 
                 saveWorkFolderShortcuts(workFolder.id, 0, workFolderApps);
@@ -184,7 +204,7 @@
 
         @Override
         public void onShortcutsChanged(String packageName, List<ShortcutInfoCompat> shortcuts,
-                UserHandleCompat user) {
+                UserHandle user) {
             // Do nothing
         }
     }
@@ -196,21 +216,17 @@
             long workFolderId, int startingRank, ArrayList<ShortcutInfo> workFolderApps) {
         for (ItemInfo info : workFolderApps) {
             info.rank = startingRank++;
-            LauncherModel.addItemToDatabase(mContext, info, workFolderId, 0, 0, 0);
+            mModel.getWriter(false).addItemToDatabase(info, workFolderId, 0, 0, 0);
         }
     }
 
-
     /**
      * Verifies that entries corresponding to {@param users} exist and removes all invalid entries.
      */
-    public static void processAllUsers(List<UserHandleCompat> users, Context context) {
-        if (!Utilities.ATLEAST_LOLLIPOP) {
-            return;
-        }
+    public static void processAllUsers(List<UserHandle> users, Context context) {
         UserManagerCompat userManager = UserManagerCompat.getInstance(context);
         HashSet<String> validKeys = new HashSet<String>();
-        for (UserHandleCompat user : users) {
+        for (UserHandle user : users) {
             addAllUserKeys(userManager.getSerialNumberForUser(user), validKeys);
         }
 
@@ -237,10 +253,10 @@
      */
     public static void markExistingUsersForNoFolderCreation(Context context) {
         UserManagerCompat userManager = UserManagerCompat.getInstance(context);
-        UserHandleCompat myUser = UserHandleCompat.myUserHandle();
+        UserHandle myUser = Process.myUserHandle();
 
         SharedPreferences prefs = null;
-        for (UserHandleCompat user : userManager.getUserProfiles()) {
+        for (UserHandle user : userManager.getUserProfiles()) {
             if (myUser.equals(user)) {
                 continue;
             }
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..e12b2d4 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -20,97 +20,87 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
+import android.net.Uri;
 import android.os.Build;
+import android.os.UserHandle;
 import android.text.TextUtils;
 
+import com.android.launcher3.AppInfo;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.LauncherAppsCompat;
 
-import java.util.ArrayList;
+import java.util.List;
 
 /**
  * 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";
+    private final Context mContext;
+    private final PackageManager mPm;
+    private final LauncherAppsCompat mLauncherApps;
+
+    public PackageManagerHelper(Context context) {
+        mContext = context;
+        mPm = context.getPackageManager();
+        mLauncherApps = LauncherAppsCompat.getInstance(context);
+    }
 
     /**
      * Returns true if the app can possibly be on the SDCard. This is just a workaround and doesn't
      * guarantee that the app is on SD card.
      */
-    public static boolean isAppOnSdcard(PackageManager pm, String packageName) {
-        return isAppEnabled(pm, packageName, PackageManager.GET_UNINSTALLED_PACKAGES);
+    public boolean isAppOnSdcard(String packageName, UserHandle user) {
+        ApplicationInfo info = mLauncherApps.getApplicationInfo(
+                packageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, user);
+        return info != null && (info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
     }
 
-    public static boolean isAppEnabled(PackageManager pm, String packageName) {
-        return isAppEnabled(pm, packageName, 0);
+    /**
+     * Returns whether the target app is suspended for a given user as per
+     * {@link android.app.admin.DevicePolicyManager#isPackageSuspended}.
+     */
+    public boolean isAppSuspended(String packageName, UserHandle user) {
+        ApplicationInfo info = mLauncherApps.getApplicationInfo(packageName, 0, user);
+        return info != null && isAppSuspended(info);
     }
 
-    public static boolean isAppEnabled(PackageManager pm, String packageName, int flags) {
-        try {
-            ApplicationInfo info = pm.getApplicationInfo(packageName, flags);
-            return info != null && info.enabled;
-        } catch (PackageManager.NameNotFoundException e) {
-            return false;
-        }
+    public boolean isSafeMode() {
+        return mContext.getPackageManager().isSafeMode();
     }
 
-    public static boolean isAppSuspended(PackageManager pm, String packageName) {
-        try {
-            ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
-            return info != null && isAppSuspended(info);
-        } catch (PackageManager.NameNotFoundException e) {
-            return false;
-        }
+    public Intent getAppLaunchIntent(String pkg, UserHandle user) {
+        List<LauncherActivityInfo> activities = mLauncherApps.getActivityList(pkg, user);
+        return activities.isEmpty() ? null :
+                AppInfo.makeLaunchIntent(activities.get(0));
     }
 
+    /**
+     * Returns whether an application is suspended as per
+     * {@link android.app.admin.DevicePolicyManager#isPackageSuspended}.
+     */
     public static boolean isAppSuspended(ApplicationInfo info) {
         // The value of FLAG_SUSPENDED was reused by a hidden constant
         // ApplicationInfo.FLAG_PRIVILEGED prior to N, so only check for suspended flag on N
         // or later.
-        if (Utilities.isNycOrAbove()) {
-            return (info.flags & FLAG_SUSPENDED) != 0;
+        if (Utilities.ATLEAST_NOUGAT) {
+            return (info.flags & ApplicationInfo.FLAG_SUSPENDED) != 0;
         } else {
             return false;
         }
     }
 
     /**
-     * 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
      */
-    public static boolean hasPermissionForActivity(Context context, Intent intent,
-            String srcPackage) {
-        PackageManager pm = context.getPackageManager();
-        ResolveInfo target = pm.resolveActivity(intent, 0);
+    public boolean hasPermissionForActivity(Intent intent, String srcPackage) {
+        ResolveInfo target = mPm.resolveActivity(intent, 0);
         if (target == null) {
             // Not a valid target
             return false;
@@ -125,7 +115,7 @@
         }
 
         // Source does not have sufficient permissions.
-        if(pm.checkPermission(target.activityInfo.permission, srcPackage) !=
+        if(mPm.checkPermission(target.activityInfo.permission, srcPackage) !=
                 PackageManager.PERMISSION_GRANTED) {
             return false;
         }
@@ -145,9 +135,18 @@
         // app-op is only enabled for apps running in compatibility mode, simply block such apps.
 
         try {
-            return pm.getApplicationInfo(srcPackage, 0).targetSdkVersion >= Build.VERSION_CODES.M;
+            return mPm.getApplicationInfo(srcPackage, 0).targetSdkVersion >= Build.VERSION_CODES.M;
         } catch (NameNotFoundException e) { }
 
         return false;
     }
+
+    public static Intent getMarketIntent(String packageName) {
+        return new Intent(Intent.ACTION_VIEW)
+                .setData(new Uri.Builder()
+                        .scheme("market")
+                        .authority("details")
+                        .appendQueryParameter("id", packageName)
+                        .build());
+    }
 }
diff --git a/src/com/android/launcher3/util/PackageUserKey.java b/src/com/android/launcher3/util/PackageUserKey.java
new file mode 100644
index 0000000..1ce2822
--- /dev/null
+++ b/src/com/android/launcher3/util/PackageUserKey.java
@@ -0,0 +1,59 @@
+package com.android.launcher3.util;
+
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
+
+import java.util.Arrays;
+
+/** Creates a hash key based on package name and user. */
+public class PackageUserKey {
+
+    public String mPackageName;
+    public UserHandle mUser;
+    private int mHashCode;
+
+    public static PackageUserKey fromItemInfo(ItemInfo info) {
+        return new PackageUserKey(info.getTargetComponent().getPackageName(), info.user);
+    }
+
+    public static PackageUserKey fromNotification(StatusBarNotification notification) {
+        return new PackageUserKey(notification.getPackageName(), notification.getUser());
+    }
+
+    public PackageUserKey(String packageName, UserHandle user) {
+        update(packageName, user);
+    }
+
+    private void update(String packageName, UserHandle user) {
+        mPackageName = packageName;
+        mUser = user;
+        mHashCode = Arrays.hashCode(new Object[] {packageName, user});
+    }
+
+    /**
+     * This should only be called to avoid new object creations in a loop.
+     * @return Whether this PackageUserKey was successfully updated - it shouldn't be used if not.
+     */
+    public boolean updateFromItemInfo(ItemInfo info) {
+        if (DeepShortcutManager.supportsShortcuts(info)) {
+            update(info.getTargetComponent().getPackageName(), info.user);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return mHashCode;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof PackageUserKey)) return false;
+        PackageUserKey otherKey = (PackageUserKey) obj;
+        return mPackageName.equals(otherKey.mPackageName) && mUser.equals(otherKey.mUser);
+    }
+}
diff --git a/src/com/android/launcher3/util/PendingRequestArgs.java b/src/com/android/launcher3/util/PendingRequestArgs.java
index bade967..dabd40d 100644
--- a/src/com/android/launcher3/util/PendingRequestArgs.java
+++ b/src/com/android/launcher3/util/PendingRequestArgs.java
@@ -21,7 +21,7 @@
 import android.os.Parcelable;
 
 import com.android.launcher3.ItemInfo;
-import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.widget.WidgetAddFlowHandler;
 
 /**
  * Utility class to store information regarding a pending request made by launcher. This information
@@ -53,16 +53,11 @@
 
     public PendingRequestArgs(Parcel parcel) {
         readFromValues(ContentValues.CREATOR.createFromParcel(parcel));
+        user = parcel.readParcelable(null);
 
         mArg1 = parcel.readInt();
         mObjectType = parcel.readInt();
-        if (parcel.readInt() != 0) {
-            mObject = mObjectType == TYPE_INTENT
-                    ? Intent.CREATOR.createFromParcel(parcel)
-                    : new LauncherAppWidgetProviderInfo(parcel);
-        } else {
-            mObject = null;
-        }
+        mObject = parcel.readParcelable(null);
     }
 
     @Override
@@ -73,21 +68,17 @@
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         ContentValues itemValues = new ContentValues();
-        writeToValues(itemValues);
+        writeToValues(new ContentWriter(itemValues, null));
         itemValues.writeToParcel(dest, flags);
+        dest.writeParcelable(user, flags);
 
         dest.writeInt(mArg1);
         dest.writeInt(mObjectType);
-        if (mObject != null) {
-            dest.writeInt(1);
-            mObject.writeToParcel(dest, flags);
-        } else {
-            dest.writeInt(0);
-        }
+        dest.writeParcelable(mObject, flags);
     }
 
-    public LauncherAppWidgetProviderInfo getWidgetProvider() {
-        return mObjectType == TYPE_APP_WIDGET ? (LauncherAppWidgetProviderInfo) mObject : null;
+    public WidgetAddFlowHandler getWidgetHandler() {
+        return mObjectType == TYPE_APP_WIDGET ? (WidgetAddFlowHandler) mObject : null;
     }
 
     public int getWidgetId() {
@@ -103,8 +94,9 @@
     }
 
     public static PendingRequestArgs forWidgetInfo(
-            int appWidgetId, LauncherAppWidgetProviderInfo widgetInfo, ItemInfo info) {
-        PendingRequestArgs args = new PendingRequestArgs(appWidgetId, TYPE_APP_WIDGET, widgetInfo);
+            int appWidgetId, WidgetAddFlowHandler widgetHandler, ItemInfo info) {
+        PendingRequestArgs args =
+                new PendingRequestArgs(appWidgetId, TYPE_APP_WIDGET, widgetHandler);
         args.copyFrom(info);
         return args;
     }
diff --git a/src/com/android/launcher3/util/PillRevealOutlineProvider.java b/src/com/android/launcher3/util/PillRevealOutlineProvider.java
index 3f1e11a..a57d69f 100644
--- a/src/com/android/launcher3/util/PillRevealOutlineProvider.java
+++ b/src/com/android/launcher3/util/PillRevealOutlineProvider.java
@@ -16,12 +16,9 @@
 
 package com.android.launcher3.util;
 
-import android.annotation.TargetApi;
 import android.graphics.Rect;
-import android.os.Build;
 import android.view.ViewOutlineProvider;
 
-@TargetApi(Build.VERSION_CODES.LOLLIPOP)
 /**
  * A {@link ViewOutlineProvider} that animates a reveal in a "pill" shape.
  * A pill is simply a round rect, but we assume the width is greater than
@@ -31,6 +28,7 @@
 
     private int mCenterX;
     private int mCenterY;
+    private float mFinalRadius;
     protected Rect mPillRect;
 
     /**
@@ -39,10 +37,14 @@
      * @param pillRect round rect that represents the final pill shape
      */
     public PillRevealOutlineProvider(int x, int y, Rect pillRect) {
+        this(x, y, pillRect, pillRect.height() / 2f);
+    }
+
+    public PillRevealOutlineProvider(int x, int y, Rect pillRect, float radius) {
         mCenterX = x;
         mCenterY = y;
         mPillRect = pillRect;
-        mOutlineRadius = pillRect.height() / 2f;
+        mOutlineRadius = mFinalRadius = radius;
     }
 
     @Override
@@ -61,6 +63,6 @@
         mOutline.top = Math.max(mPillRect.top, mCenterY - currentSize);
         mOutline.right = Math.min(mPillRect.right, mCenterX + currentSize);
         mOutline.bottom = Math.min(mPillRect.bottom, mCenterY + currentSize);
-        mOutlineRadius = mOutline.height() / 2;
+        mOutlineRadius = Math.min(mFinalRadius, mOutline.height() / 2);
     }
 }
diff --git a/src/com/android/launcher3/util/PillWidthRevealOutlineProvider.java b/src/com/android/launcher3/util/PillWidthRevealOutlineProvider.java
deleted file mode 100644
index 89dda3b..0000000
--- a/src/com/android/launcher3/util/PillWidthRevealOutlineProvider.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.util;
-
-import android.graphics.Rect;
-
-/**
- * Extension of {@link PillRevealOutlineProvider} which only changes the width of the pill.
- */
-public class PillWidthRevealOutlineProvider extends PillRevealOutlineProvider {
-
-    private final int mStartLeft;
-    private final int mStartRight;
-
-    public PillWidthRevealOutlineProvider(Rect pillRect, int left, int right) {
-        super(0, 0, pillRect);
-        mOutline.set(pillRect);
-        mStartLeft = left;
-        mStartRight = right;
-    }
-
-    @Override
-    public void setProgress(float progress) {
-        mOutline.left = (int) (progress * mPillRect.left + (1 - progress) * mStartLeft);
-        mOutline.right = (int) (progress * mPillRect.right + (1 - progress) * mStartRight);
-    }
-}
diff --git a/src/com/android/launcher3/util/Provider.java b/src/com/android/launcher3/util/Provider.java
new file mode 100644
index 0000000..1cdd8d6
--- /dev/null
+++ b/src/com/android/launcher3/util/Provider.java
@@ -0,0 +1,38 @@
+/*
+ * 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;
+
+/**
+ * Utility class to allow lazy initialization of objects.
+ */
+public abstract class Provider<T> {
+
+    /**
+     * Initializes and returns the object. This may contain expensive operations not suitable
+     * to UI thread.
+     */
+    public abstract T get();
+
+    public static <T> Provider<T> of (final T value) {
+        return new Provider<T>() {
+            @Override
+            public T get() {
+                return value;
+            }
+        };
+    }
+}
diff --git a/src/com/android/launcher3/util/SQLiteCacheHelper.java b/src/com/android/launcher3/util/SQLiteCacheHelper.java
index d1cfe42..ef10f97 100644
--- a/src/com/android/launcher3/util/SQLiteCacheHelper.java
+++ b/src/com/android/launcher3/util/SQLiteCacheHelper.java
@@ -9,6 +9,9 @@
 import android.database.sqlite.SQLiteOpenHelper;
 import android.util.Log;
 
+import com.android.launcher3.Utilities;
+import com.android.launcher3.config.ProviderConfig;
+
 /**
  * An extension of {@link SQLiteOpenHelper} with utility methods for a single table cache DB.
  * Any exception during write operations are ignored, and any version change causes a DB reset.
@@ -16,12 +19,18 @@
 public abstract class SQLiteCacheHelper {
     private static final String TAG = "SQLiteCacheHelper";
 
+    private static final boolean NO_ICON_CACHE = ProviderConfig.IS_DOGFOOD_BUILD &&
+            Utilities.isPropertyEnabled(LogConfig.MEMORY_ONLY_ICON_CACHE);
+
     private final String mTableName;
     private final MySQLiteOpenHelper mOpenHelper;
 
     private boolean mIgnoreWrites;
 
     public SQLiteCacheHelper(Context context, String name, int version, String tableName) {
+        if (NO_ICON_CACHE) {
+            name = null;
+        }
         mTableName = tableName;
         mOpenHelper = new MySQLiteOpenHelper(context, name, version);
 
@@ -74,6 +83,10 @@
                 mTableName, columns, selection, selectionArgs, null, null, null);
     }
 
+    public void clear() {
+        mOpenHelper.clearDB(mOpenHelper.getWritableDatabase());
+    }
+
     protected abstract void onCreateTable(SQLiteDatabase db);
 
     /**
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/Themes.java b/src/com/android/launcher3/util/Themes.java
new file mode 100644
index 0000000..d863339
--- /dev/null
+++ b/src/com/android/launcher3/util/Themes.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.ColorMatrix;
+import android.view.ContextThemeWrapper;
+
+/**
+ * Various utility methods associated with theming.
+ */
+public class Themes {
+
+    public static int getColorAccent(Context context) {
+        return getAttrColor(context, android.R.attr.colorAccent);
+    }
+
+    public static int getColorPrimary(Context context, int theme) {
+        return getAttrColor(new ContextThemeWrapper(context, theme), android.R.attr.colorPrimary);
+    }
+
+    public static int getAttrColor(Context context, int attr) {
+        TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
+        int colorAccent = ta.getColor(0, 0);
+        ta.recycle();
+        return colorAccent;
+    }
+
+    /**
+     * Returns the alpha corresponding to the theme attribute {@param attr}, in the range [0, 255].
+     */
+    public static int getAlpha(Context context, int attr) {
+        TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
+        float alpha = ta.getFloat(0, 0);
+        ta.recycle();
+        return (int) (255 * alpha + 0.5f);
+    }
+
+    /**
+     * Scales a color matrix such that, when applied to color R G B A, it produces R' G' B' A' where
+     * R' = r * R
+     * G' = g * G
+     * B' = b * B
+     * A' = a * A
+     *
+     * The matrix will, for instance, turn white into r g b a, and black will remain black.
+     *
+     * @param color The color r g b a
+     * @param target The ColorMatrix to scale
+     */
+    public static void setColorScaleOnMatrix(int color, ColorMatrix target) {
+        target.setScale(Color.red(color) / 255f, Color.green(color) / 255f,
+                Color.blue(color) / 255f, Color.alpha(color) / 255f);
+    }
+}
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/PendingAddShortcutInfo.java b/src/com/android/launcher3/widget/PendingAddShortcutInfo.java
index 486b18e..e8f13a1 100644
--- a/src/com/android/launcher3/widget/PendingAddShortcutInfo.java
+++ b/src/com/android/launcher3/widget/PendingAddShortcutInfo.java
@@ -15,11 +15,9 @@
  */
 package com.android.launcher3.widget;
 
-import android.content.ComponentName;
-import android.content.pm.ActivityInfo;
-
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.PendingAddItemInfo;
+import com.android.launcher3.compat.ShortcutConfigActivityInfo;
 
 /**
  * Meta data used for late binding of the short cuts.
@@ -28,11 +26,12 @@
  */
 public class PendingAddShortcutInfo extends PendingAddItemInfo {
 
-    ActivityInfo activityInfo;
+    public ShortcutConfigActivityInfo activityInfo;
 
-    public PendingAddShortcutInfo(ActivityInfo activityInfo) {
+    public PendingAddShortcutInfo(ShortcutConfigActivityInfo activityInfo) {
         this.activityInfo = activityInfo;
-        componentName = new ComponentName(activityInfo.packageName, activityInfo.name);
-        itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
+        componentName = activityInfo.getComponent();
+        user = activityInfo.getUser();
+        itemType = activityInfo.getItemType();
     }
 }
diff --git a/src/com/android/launcher3/widget/PendingAddWidgetInfo.java b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
index f800ac4..ad05ce9 100644
--- a/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
+++ b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
@@ -16,15 +16,11 @@
 package com.android.launcher3.widget;
 
 import android.appwidget.AppWidgetHostView;
-import android.content.Context;
 import android.os.Bundle;
-import android.os.Parcelable;
 
-import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.PendingAddItemInfo;
-import com.android.launcher3.compat.AppWidgetManagerCompat;
 
 /**
  * Meta data used for late binding of {@link LauncherAppWidgetProviderInfo}.
@@ -38,14 +34,14 @@
     public AppWidgetHostView boundWidget;
     public Bundle bindOptions = null;
 
-    public PendingAddWidgetInfo(Context context, LauncherAppWidgetProviderInfo i) {
+    public PendingAddWidgetInfo(LauncherAppWidgetProviderInfo i) {
         if (i.isCustomWidget) {
             itemType = LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
         } else {
             itemType = LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
         }
         this.info = i;
-        user = AppWidgetManagerCompat.getInstance(context).getUser(i);
+        user = i.getUser();
         componentName = i.provider;
         previewImage = i.previewImage;
         icon = i.icon;
@@ -56,7 +52,7 @@
         minSpanY = i.minSpanY;
     }
 
-    public boolean isCustomWidget() {
-        return itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
+    public WidgetAddFlowHandler getHandler() {
+        return new WidgetAddFlowHandler(info);
     }
 }
diff --git a/src/com/android/launcher3/widget/PendingItemDragHelper.java b/src/com/android/launcher3/widget/PendingItemDragHelper.java
new file mode 100644
index 0000000..6628971
--- /dev/null
+++ b/src/com/android/launcher3/widget/PendingItemDragHelper.java
@@ -0,0 +1,208 @@
+/*
+ * 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 android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.view.View;
+import android.widget.RemoteViews;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.DragSource;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.PendingAddItemInfo;
+import com.android.launcher3.R;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.dragndrop.LivePreviewWidgetCell;
+import com.android.launcher3.graphics.DragPreviewProvider;
+import com.android.launcher3.graphics.HolographicOutlineHelper;
+import com.android.launcher3.graphics.LauncherIcons;
+
+/**
+ * Extension of {@link DragPreviewProvider} with logic specific to pending widgets/shortcuts
+ * dragged from the widget tray.
+ */
+public class PendingItemDragHelper extends DragPreviewProvider {
+
+    private static final float MAX_WIDGET_SCALE = 1.25f;
+
+    private final PendingAddItemInfo mAddInfo;
+
+    private Bitmap mPreviewBitmap;
+    private RemoteViews mPreview;
+
+    public PendingItemDragHelper(View view) {
+        super(view);
+        mAddInfo = (PendingAddItemInfo) view.getTag();
+    }
+
+    public void setPreview(RemoteViews preview) {
+        mPreview = preview;
+    }
+
+    /**
+     * Starts the drag for the pending item associated with the view.
+     *
+     * @param previewBounds The bounds where the image was displayed,
+     *                      {@link WidgetImageView#getBitmapBounds()}
+     * @param previewBitmapWidth The actual width of the bitmap displayed in the view.
+     * @param previewViewWidth The width of {@link WidgetImageView} displaying the preview
+     * @param screenPos Position of {@link WidgetImageView} on the screen
+     */
+    public void startDrag(Rect previewBounds, int previewBitmapWidth, int previewViewWidth,
+            Point screenPos, DragSource source, DragOptions options) {
+        final Launcher launcher = Launcher.getLauncher(mView.getContext());
+        LauncherAppState app = LauncherAppState.getInstance(launcher);
+
+        Bitmap preview = null;
+        final float scale;
+        final Point dragOffset;
+        final Rect dragRegion;
+
+
+        if (mAddInfo instanceof PendingAddWidgetInfo) {
+            PendingAddWidgetInfo createWidgetInfo = (PendingAddWidgetInfo) mAddInfo;
+            int[] size = launcher.getWorkspace().estimateItemSize(createWidgetInfo, true, false);
+
+            int maxWidth = Math.min((int) (previewBitmapWidth * MAX_WIDGET_SCALE), size[0]);
+
+            int[] previewSizeBeforeScale = new int[1];
+
+            if (mPreview != null) {
+                preview = LivePreviewWidgetCell.generateFromRemoteViews(launcher, mPreview,
+                        createWidgetInfo.info, maxWidth, previewSizeBeforeScale);
+            }
+            if (preview == null) {
+                preview = app.getWidgetCache().generateWidgetPreview(
+                        launcher, createWidgetInfo.info, maxWidth, null, previewSizeBeforeScale);
+            }
+
+            if (previewSizeBeforeScale[0] < previewBitmapWidth) {
+                // The icon has extra padding around it.
+                int padding = (previewBitmapWidth - previewSizeBeforeScale[0]) / 2;
+                if (previewBitmapWidth > previewViewWidth) {
+                    padding = padding * previewViewWidth / previewBitmapWidth;
+                }
+
+                previewBounds.left += padding;
+                previewBounds.right -= padding;
+            }
+            scale = previewBounds.width() / (float) preview.getWidth();
+            launcher.getDragController().addDragListener(new WidgetHostViewLoader(launcher, mView));
+
+            dragOffset = null;
+            dragRegion = null;
+        } else {
+            PendingAddShortcutInfo createShortcutInfo = (PendingAddShortcutInfo) mAddInfo;
+            Drawable icon = createShortcutInfo.activityInfo.getFullResIcon(app.getIconCache());
+            preview = LauncherIcons.createScaledBitmapWithoutShadow(icon, launcher, Build.VERSION_CODES.O);
+            mAddInfo.spanX = mAddInfo.spanY = 1;
+            scale = ((float) launcher.getDeviceProfile().iconSizePx) / preview.getWidth();
+
+            dragOffset = new Point(previewPadding / 2, previewPadding / 2);
+
+            // Create a preview same as the workspace cell size and draw the icon at the
+            // appropriate position.
+            int[] size = launcher.getWorkspace().estimateItemSize(mAddInfo, false, true);
+            DeviceProfile dp = launcher.getDeviceProfile();
+            int iconSize = dp.iconSizePx;
+
+            int padding = launcher.getResources()
+                    .getDimensionPixelSize(R.dimen.widget_preview_shortcut_padding);
+            previewBounds.left += padding;
+            previewBounds.top += padding;
+
+            dragRegion = new Rect();
+            dragRegion.left = (size[0] - iconSize) / 2;
+            dragRegion.right = dragRegion.left + iconSize;
+            dragRegion.top = (size[1] - iconSize - dp.iconTextSizePx - dp.iconDrawablePaddingPx) / 2;
+            dragRegion.bottom = dragRegion.top + iconSize;
+        }
+
+        // Since we are not going through the workspace for starting the drag, set drag related
+        // information on the workspace before starting the drag.
+        launcher.getWorkspace().prepareDragWithProvider(this);
+
+        int dragLayerX = screenPos.x + previewBounds.left
+                + (int) ((scale * preview.getWidth() - preview.getWidth()) / 2);
+        int dragLayerY = screenPos.y + previewBounds.top
+                + (int) ((scale * preview.getHeight() - preview.getHeight()) / 2);
+
+        mPreviewBitmap = preview;
+        // Start the drag
+        launcher.getDragController().startDrag(preview, dragLayerX, dragLayerY, source, mAddInfo,
+                dragOffset, dragRegion, scale, options);
+    }
+
+
+    @Override
+    public Bitmap createDragOutline(Canvas canvas) {
+        if (mAddInfo instanceof PendingAddShortcutInfo) {
+            int width = mPreviewBitmap.getWidth();
+            int height = mPreviewBitmap.getHeight();
+            Bitmap b = Bitmap.createBitmap(width + blurSizeOutline, height + blurSizeOutline,
+                    Bitmap.Config.ALPHA_8);
+            canvas.setBitmap(b);
+
+            Launcher launcher = Launcher.getLauncher(mView.getContext());
+            int size = launcher.getDeviceProfile().iconSizePx;
+
+            Rect src = new Rect(0, 0, mPreviewBitmap.getWidth(), mPreviewBitmap.getHeight());
+            Rect dst = new Rect(0, 0, size, size);
+            dst.offset(blurSizeOutline / 2, blurSizeOutline / 2);
+            canvas.drawBitmap(mPreviewBitmap, src, dst, new Paint(Paint.FILTER_BITMAP_FLAG));
+
+            HolographicOutlineHelper.getInstance(mView.getContext())
+                    .applyExpensiveOutlineWithBlur(b, canvas);
+
+            canvas.setBitmap(null);
+            return b;
+        }
+
+        Workspace workspace = Launcher.getLauncher(mView.getContext()).getWorkspace();
+        int[] size = workspace.estimateItemSize(mAddInfo, false, false);
+
+        int w = size[0];
+        int h = size[1];
+        final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ALPHA_8);
+        canvas.setBitmap(b);
+
+        Rect src = new Rect(0, 0, mPreviewBitmap.getWidth(), mPreviewBitmap.getHeight());
+        float scaleFactor = Math.min((w - blurSizeOutline) / (float) mPreviewBitmap.getWidth(),
+                (h - blurSizeOutline) / (float) mPreviewBitmap.getHeight());
+        int scaledWidth = (int) (scaleFactor * mPreviewBitmap.getWidth());
+        int scaledHeight = (int) (scaleFactor * mPreviewBitmap.getHeight());
+        Rect dst = new Rect(0, 0, scaledWidth, scaledHeight);
+
+        // center the image
+        dst.offset((w - scaledWidth) / 2, (h - scaledHeight) / 2);
+
+        canvas.drawBitmap(mPreviewBitmap, src, dst, null);
+        HolographicOutlineHelper.getInstance(mView.getContext())
+                .applyExpensiveOutlineWithBlur(b, canvas);
+        canvas.setBitmap(null);
+
+        return b;
+    }
+}
diff --git a/src/com/android/launcher3/widget/PendingItemPreviewProvider.java b/src/com/android/launcher3/widget/PendingItemPreviewProvider.java
deleted file mode 100644
index eaa0bb3..0000000
--- a/src/com/android/launcher3/widget/PendingItemPreviewProvider.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.widget;
-
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.view.View;
-
-import com.android.launcher3.HolographicOutlineHelper;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.PendingAddItemInfo;
-import com.android.launcher3.Workspace;
-import com.android.launcher3.graphics.DragPreviewProvider;
-
-/**
- * Extension of {@link DragPreviewProvider} with logic specific to pending widgets/shortcuts
- * dragged from the widget tray.
- */
-public class PendingItemPreviewProvider extends DragPreviewProvider {
-
-    private final PendingAddItemInfo mAddInfo;
-    private final Bitmap mPreviewBitmap;
-
-    public PendingItemPreviewProvider(View view, PendingAddItemInfo addInfo, Bitmap preview) {
-        super(view);
-        mAddInfo = addInfo;
-        mPreviewBitmap = preview;
-    }
-
-    @Override
-    public Bitmap createDragOutline(Canvas canvas) {
-        Workspace workspace = Launcher.getLauncher(mView.getContext()).getWorkspace();
-        int[] size = workspace.estimateItemSize(mAddInfo, false);
-
-        int w = size[0];
-        int h = size[1];
-        final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ALPHA_8);
-        canvas.setBitmap(b);
-
-        Rect src = new Rect(0, 0, mPreviewBitmap.getWidth(), mPreviewBitmap.getHeight());
-        float scaleFactor = Math.min((w - DRAG_BITMAP_PADDING) / (float) mPreviewBitmap.getWidth(),
-                (h - DRAG_BITMAP_PADDING) / (float) mPreviewBitmap.getHeight());
-        int scaledWidth = (int) (scaleFactor * mPreviewBitmap.getWidth());
-        int scaledHeight = (int) (scaleFactor * mPreviewBitmap.getHeight());
-        Rect dst = new Rect(0, 0, scaledWidth, scaledHeight);
-
-        // center the image
-        dst.offset((w - scaledWidth) / 2, (h - scaledHeight) / 2);
-
-        canvas.drawBitmap(mPreviewBitmap, src, dst, null);
-
-        // Don't clip alpha values for the drag outline if we're using the default widget preview
-        boolean clipAlpha = !(mAddInfo instanceof PendingAddWidgetInfo &&
-                (((PendingAddWidgetInfo) mAddInfo).previewImage == 0));
-        HolographicOutlineHelper.obtain(mView.getContext())
-                .applyExpensiveOutlineWithBlur(b, canvas, clipAlpha);
-        canvas.setBitmap(null);
-
-        return b;
-    }
-}
diff --git a/src/com/android/launcher3/widget/WidgetAddFlowHandler.java b/src/com/android/launcher3/widget/WidgetAddFlowHandler.java
new file mode 100644
index 0000000..629f30c
--- /dev/null
+++ b/src/com/android/launcher3/widget/WidgetAddFlowHandler.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.widget;
+
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.compat.AppWidgetManagerCompat;
+import com.android.launcher3.util.PendingRequestArgs;
+
+/**
+ * Utility class to handle app widget add flow.
+ */
+public class WidgetAddFlowHandler implements Parcelable {
+
+    private final AppWidgetProviderInfo mProviderInfo;
+
+    public WidgetAddFlowHandler(AppWidgetProviderInfo providerInfo) {
+        mProviderInfo = providerInfo;
+    }
+
+    protected WidgetAddFlowHandler(Parcel parcel) {
+        mProviderInfo = AppWidgetProviderInfo.CREATOR.createFromParcel(parcel);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int i) {
+        mProviderInfo.writeToParcel(parcel, i);
+    }
+
+    public void startBindFlow(Launcher launcher, int appWidgetId, ItemInfo info, int requestCode) {
+        launcher.setWaitingForResult(PendingRequestArgs.forWidgetInfo(appWidgetId, this, info));
+
+        Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
+        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
+        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, mProviderInfo.provider);
+        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE,
+                mProviderInfo.getProfile());
+        // TODO: we need to make sure that this accounts for the options bundle.
+        // intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options);
+        launcher.startActivityForResult(intent, requestCode);
+    }
+
+    /**
+     * @see #startConfigActivity(Launcher, int, ItemInfo, int)
+     */
+    public boolean startConfigActivity(Launcher launcher, LauncherAppWidgetInfo info,
+            int requestCode) {
+        return startConfigActivity(launcher, info.appWidgetId, info, requestCode);
+    }
+
+    /**
+     * Starts the widget configuration flow if needed.
+     * @return true if the configuration flow was started, false otherwise.
+     */
+    public boolean startConfigActivity(Launcher launcher, int appWidgetId, ItemInfo info,
+            int requestCode) {
+        if (!needsConfigure()) {
+            return false;
+        }
+        launcher.setWaitingForResult(PendingRequestArgs.forWidgetInfo(appWidgetId, this, info));
+
+        AppWidgetManagerCompat.getInstance(launcher).startConfigActivity(
+                mProviderInfo, appWidgetId, launcher, launcher.getAppWidgetHost(), requestCode);
+        return true;
+    }
+
+    public boolean needsConfigure() {
+        return mProviderInfo.configure != null;
+    }
+
+    public LauncherAppWidgetProviderInfo getProviderInfo(Context context) {
+        return LauncherAppWidgetProviderInfo.fromProviderInfo(context, mProviderInfo);
+    }
+
+    public static final Parcelable.Creator<WidgetAddFlowHandler> CREATOR =
+            new Parcelable.Creator<WidgetAddFlowHandler>() {
+                public WidgetAddFlowHandler createFromParcel(Parcel source) {
+                    return new WidgetAddFlowHandler(source);
+                }
+
+                public WidgetAddFlowHandler[] newArray(int size) {
+                    return new WidgetAddFlowHandler[size];
+                }
+            };
+}
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index 293585d..40dbd52 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -17,26 +17,25 @@
 package com.android.launcher3.widget;
 
 import android.content.Context;
-import android.content.res.Resources;
 import android.graphics.Bitmap;
+import android.os.CancellationSignal;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnLayoutChangeListener;
+import android.view.ViewGroup;
 import android.view.ViewPropertyAnimator;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import com.android.launcher3.BaseActivity;
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.SimpleOnStylusPressListener;
 import com.android.launcher3.R;
+import com.android.launcher3.SimpleOnStylusPressListener;
 import com.android.launcher3.StylusEventHelper;
 import com.android.launcher3.WidgetPreviewLoader;
-import com.android.launcher3.WidgetPreviewLoader.PreviewLoadRequest;
+import com.android.launcher3.graphics.DrawableFactory;
 import com.android.launcher3.model.WidgetItem;
 
 /**
@@ -61,20 +60,22 @@
     /** Widget preview width is calculated by multiplying this factor to the widget cell width. */
     private static final float PREVIEW_SCALE = 0.8f;
 
-    private int mPresetPreviewSize;
-    int cellSize;
+    protected int mPresetPreviewSize;
+    private int mCellSize;
 
     private WidgetImageView mWidgetImage;
     private TextView mWidgetName;
     private TextView mWidgetDims;
 
-    private WidgetItem mItem;
+    protected WidgetItem mItem;
 
     private WidgetPreviewLoader mWidgetPreviewLoader;
-    private PreviewLoadRequest mActiveRequest;
     private StylusEventHelper mStylusEventHelper;
 
-    private final Launcher mLauncher;
+    protected CancellationSignal mActiveRequest;
+    private boolean mAnimatePreview = true;
+
+    protected final BaseActivity mActivity;
 
     public WidgetCell(Context context) {
         this(context, null);
@@ -87,20 +88,19 @@
     public WidgetCell(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
 
-        final Resources r = context.getResources();
-        mLauncher = Launcher.getLauncher(context);
+        mActivity = BaseActivity.fromContext(context);
         mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
 
         setContainerWidth();
         setWillNotDraw(false);
         setClipToPadding(false);
-        setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
+        setAccessibilityDelegate(mActivity.getAccessibilityDelegate());
     }
 
     private void setContainerWidth() {
-        DeviceProfile profile = mLauncher.getDeviceProfile();
-        cellSize = (int) (profile.cellWidthPx * WIDTH_SCALE);
-        mPresetPreviewSize = (int) (cellSize * PREVIEW_SCALE);
+        DeviceProfile profile = mActivity.getDeviceProfile();
+        mCellSize = (int) (profile.cellWidthPx * WIDTH_SCALE);
+        mPresetPreviewSize = (int) (mCellSize * PREVIEW_SCALE);
     }
 
     @Override
@@ -120,12 +120,12 @@
             Log.d(TAG, "reset called on:" + mWidgetName.getText());
         }
         mWidgetImage.animate().cancel();
-        mWidgetImage.setBitmap(null);
+        mWidgetImage.setBitmap(null, null);
         mWidgetName.setText(null);
         mWidgetDims.setText(null);
 
         if (mActiveRequest != null) {
-            mActiveRequest.cleanup();
+            mActiveRequest.cancel();
             mActiveRequest = null;
         }
     }
@@ -142,37 +142,46 @@
         if (item.activityInfo != null) {
             setTag(new PendingAddShortcutInfo(item.activityInfo));
         } else {
-            setTag(new PendingAddWidgetInfo(mLauncher, item.widgetInfo));
+            setTag(new PendingAddWidgetInfo(item.widgetInfo));
         }
     }
 
-    public int[] getPreviewSize() {
-        int[] maxSize = new int[2];
+    public WidgetImageView getWidgetView() {
+        return mWidgetImage;
+    }
 
-        maxSize[0] = mPresetPreviewSize;
-        maxSize[1] = mPresetPreviewSize;
-        return maxSize;
+    public void setAnimatePreview(boolean shouldAnimate) {
+        mAnimatePreview = shouldAnimate;
     }
 
     public void applyPreview(Bitmap bitmap) {
+        applyPreview(bitmap, true);
+    }
+
+    public void applyPreview(Bitmap bitmap, boolean animate) {
         if (bitmap != null) {
-            mWidgetImage.setBitmap(bitmap);
-            mWidgetImage.setAlpha(0f);
-            ViewPropertyAnimator anim = mWidgetImage.animate();
-            anim.alpha(1.0f).setDuration(FADE_IN_DURATION_MS);
+            mWidgetImage.setBitmap(bitmap,
+                    DrawableFactory.get(getContext()).getBadgeForUser(mItem.user, getContext()));
+            if (mAnimatePreview) {
+                mWidgetImage.setAlpha(0f);
+                ViewPropertyAnimator anim = mWidgetImage.animate();
+                anim.alpha(1.0f).setDuration(FADE_IN_DURATION_MS);
+            } else {
+                mWidgetImage.setAlpha(1f);
+            }
         }
     }
 
     public void ensurePreview() {
+        ensurePreview(true);
+    }
+
+    public void ensurePreview(boolean animate) {
         if (mActiveRequest != null) {
             return;
         }
-        int[] size = getPreviewSize();
-        if (DEBUG) {
-            Log.d(TAG, String.format("[tag=%s] ensurePreview (%d, %d):",
-                    getTagToString(), size[0], size[1]));
-        }
-        mActiveRequest = mWidgetPreviewLoader.getPreview(mItem, size[0], size[1], this);
+        mActiveRequest = mWidgetPreviewLoader.getPreview(
+                mItem, mPresetPreviewSize, mPresetPreviewSize, this, animate);
     }
 
     @Override
@@ -182,14 +191,6 @@
         ensurePreview();
     }
 
-    public int getActualItemWidth() {
-        ItemInfo info = (ItemInfo) getTag();
-        int[] size = getPreviewSize();
-        int cellWidth = mLauncher.getDeviceProfile().cellWidthPx;
-
-        return Math.min(size[0], info.spanX * cellWidth);
-    }
-
     @Override
     public boolean onTouchEvent(MotionEvent ev) {
         boolean handled = super.onTouchEvent(ev);
@@ -211,6 +212,12 @@
     }
 
     @Override
+    public void setLayoutParams(ViewGroup.LayoutParams params) {
+        params.width = params.height = mCellSize;
+        super.setLayoutParams(params);
+    }
+
+    @Override
     public CharSequence getAccessibilityClassName() {
         return WidgetCell.class.getName();
     }
diff --git a/src/com/android/launcher3/widget/WidgetHostViewLoader.java b/src/com/android/launcher3/widget/WidgetHostViewLoader.java
index 049871f..5eeea44 100644
--- a/src/com/android/launcher3/widget/WidgetHostViewLoader.java
+++ b/src/com/android/launcher3/widget/WidgetHostViewLoader.java
@@ -10,12 +10,9 @@
 import android.view.View;
 
 import com.android.launcher3.AppWidgetResizeFrame;
-import com.android.launcher3.DragSource;
 import com.android.launcher3.DropTarget;
-import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragLayer;
@@ -49,7 +46,9 @@
     }
 
     @Override
-    public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { }
+    public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
+        preloadWidget();
+    }
 
     @Override
     public void onDragEnd() {
@@ -83,7 +82,7 @@
     /**
      * Start preloading the widget.
      */
-    public boolean preloadWidget() {
+    private boolean preloadWidget() {
         final LauncherAppWidgetProviderInfo pInfo = mInfo.info;
 
         if (pInfo.isCustomWidget) {
@@ -92,7 +91,7 @@
         final Bundle options = getDefaultOptionsForWidget(mLauncher, mInfo);
 
         // If there is a configuration activity, do not follow thru bound and inflate.
-        if (pInfo.configure != null) {
+        if (mInfo.getHandler().needsConfigure()) {
             mInfo.bindOptions = options;
             return false;
         }
@@ -130,7 +129,7 @@
                 mWidgetLoadingId = -1;
 
                 hostView.setVisibility(View.INVISIBLE);
-                int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(mInfo, false);
+                int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(mInfo, false, true);
                 // We want the first widget layout to be the correct size. This will be important
                 // for width size reporting to the AppWidgetManager.
                 DragLayer.LayoutParams lp = new DragLayer.LayoutParams(unScaledSize[0],
@@ -154,27 +153,24 @@
     }
 
     public static Bundle getDefaultOptionsForWidget(Context context, PendingAddWidgetInfo info) {
-        Bundle options = null;
-        if (Utilities.ATLEAST_JB_MR1) {
-            Rect rect = new Rect();
-            AppWidgetResizeFrame.getWidgetSizeRanges(context, info.spanX, info.spanY, rect);
-            Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(context,
-                    info.componentName, null);
+        Rect rect = new Rect();
+        AppWidgetResizeFrame.getWidgetSizeRanges(context, info.spanX, info.spanY, rect);
+        Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(context,
+                info.componentName, null);
 
-            float density = context.getResources().getDisplayMetrics().density;
-            int xPaddingDips = (int) ((padding.left + padding.right) / density);
-            int yPaddingDips = (int) ((padding.top + padding.bottom) / density);
+        float density = context.getResources().getDisplayMetrics().density;
+        int xPaddingDips = (int) ((padding.left + padding.right) / density);
+        int yPaddingDips = (int) ((padding.top + padding.bottom) / density);
 
-            options = new Bundle();
-            options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH,
-                    rect.left - xPaddingDips);
-            options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT,
-                    rect.top - yPaddingDips);
-            options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH,
-                    rect.right - xPaddingDips);
-            options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT,
-                    rect.bottom - yPaddingDips);
-        }
+        Bundle options = new Bundle();
+        options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH,
+                rect.left - xPaddingDips);
+        options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT,
+                rect.top - yPaddingDips);
+        options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH,
+                rect.right - xPaddingDips);
+        options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT,
+                rect.bottom - yPaddingDips);
         return options;
     }
 }
diff --git a/src/com/android/launcher3/widget/WidgetImageView.java b/src/com/android/launcher3/widget/WidgetImageView.java
index b0fbe1e..df2bcff 100644
--- a/src/com/android/launcher3/widget/WidgetImageView.java
+++ b/src/com/android/launcher3/widget/WidgetImageView.java
@@ -22,9 +22,13 @@
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.view.View;
 
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+
 /**
  * View that draws a bitmap horizontally centered. If the image width is greater than the view
  * width, the image is scaled down appropriately.
@@ -33,22 +37,29 @@
 
     private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
     private final RectF mDstRectF = new RectF();
+    private final int mBadgeMargin;
+
     private Bitmap mBitmap;
+    private Drawable mBadge;
 
     public WidgetImageView(Context context) {
-        super(context);
+        this(context, null);
     }
 
     public WidgetImageView(Context context, AttributeSet attrs) {
-        super(context, attrs);
+        this(context, attrs, 0);
     }
 
     public WidgetImageView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
+
+        mBadgeMargin = context.getResources()
+                .getDimensionPixelSize(R.dimen.profile_badge_margin);
     }
 
-    public void setBitmap(Bitmap bitmap) {
+    public void setBitmap(Bitmap bitmap, Drawable badge) {
         mBitmap = bitmap;
+        mBadge = badge;
         invalidate();
     }
 
@@ -61,6 +72,11 @@
         if (mBitmap != null) {
             updateDstRectF();
             canvas.drawBitmap(mBitmap, null, mDstRectF, mPaint);
+
+            // Only draw the badge if a preview was drawn.
+            if (mBadge != null) {
+                mBadge.draw(canvas);
+            }
         }
     }
 
@@ -73,15 +89,34 @@
     }
 
     private void updateDstRectF() {
-        if (mBitmap.getWidth() > getWidth()) {
-            float scale = ((float) getWidth()) / mBitmap.getWidth();
-            mDstRectF.set(0, 0, getWidth(), scale * mBitmap.getHeight());
+        float myWidth = getWidth();
+        float myHeight = getHeight();
+        float bitmapWidth = mBitmap.getWidth();
+
+        final float scale = bitmapWidth > myWidth ? myWidth / bitmapWidth : 1;
+        float scaledWidth = bitmapWidth * scale;
+        float scaledHeight = mBitmap.getHeight() * scale;
+
+        mDstRectF.left = (myWidth - scaledWidth) / 2;
+        mDstRectF.right = (myWidth + scaledWidth) / 2;
+
+        if (scaledHeight > myHeight) {
+            mDstRectF.top = 0;
+            mDstRectF.bottom = scaledHeight;
         } else {
-            mDstRectF.set(
-                    (getWidth() - mBitmap.getWidth()) * 0.5f,
-                    0,
-                    (getWidth() + mBitmap.getWidth()) * 0.5f,
-                    mBitmap.getHeight());
+            mDstRectF.top = (myHeight - scaledHeight) / 2;
+            mDstRectF.bottom = (myHeight + scaledHeight) / 2;
+        }
+
+        if (mBadge != null) {
+            Rect bounds = mBadge.getBounds();
+            int left = Utilities.boundToRange(
+                    (int) (mDstRectF.right + mBadgeMargin - bounds.width()),
+                    mBadgeMargin, getWidth() - bounds.width());
+            int top = Utilities.boundToRange(
+                    (int) (mDstRectF.bottom + mBadgeMargin - bounds.height()),
+                    mBadgeMargin, getHeight() - bounds.height());
+            mBadge.setBounds(left, top, bounds.width() + left, bounds.height() + top);
         }
     }
 
diff --git a/src/com/android/launcher3/widget/WidgetItemComparator.java b/src/com/android/launcher3/widget/WidgetItemComparator.java
new file mode 100644
index 0000000..ee23cfb
--- /dev/null
+++ b/src/com/android/launcher3/widget/WidgetItemComparator.java
@@ -0,0 +1,57 @@
+/*
+ * 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 android.os.Process;
+import android.os.UserHandle;
+
+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 UserHandle mMyUserHandle = Process.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/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
new file mode 100644
index 0000000..5fe00c2
--- /dev/null
+++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.graphics.Rect;
+import android.support.v4.view.animation.FastOutSlowInInterpolator;
+import android.util.AttributeSet;
+import android.view.ContextThemeWrapper;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.Interpolator;
+import android.widget.TextView;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.DropTarget;
+import com.android.launcher3.Insettable;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAnimUtils;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.allapps.VerticalPullDetector;
+import com.android.launcher3.anim.PropertyListBuilder;
+import com.android.launcher3.dragndrop.DragController;
+import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.util.TouchController;
+
+import java.util.List;
+
+/**
+ * Bottom sheet for the "Widgets" system shortcut in the long-press popup.
+ */
+public class WidgetsBottomSheet extends AbstractFloatingView implements Insettable, TouchController,
+        VerticalPullDetector.Listener, View.OnClickListener, View.OnLongClickListener,
+        DragController.DragListener {
+
+    private int mTranslationYOpen;
+    private int mTranslationYClosed;
+    private float mTranslationYRange;
+
+    private Launcher mLauncher;
+    private ItemInfo mOriginalItemInfo;
+    private ObjectAnimator mOpenCloseAnimator;
+    private Interpolator mFastOutSlowInInterpolator;
+    private VerticalPullDetector.ScrollInterpolator mScrollInterpolator;
+    private Rect mInsets;
+    private boolean mWasNavBarLight;
+    private VerticalPullDetector mVerticalPullDetector;
+
+    public WidgetsBottomSheet(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public WidgetsBottomSheet(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(new ContextThemeWrapper(context, R.style.WidgetContainerTheme), attrs, defStyleAttr);
+        setWillNotDraw(false);
+        mLauncher = Launcher.getLauncher(context);
+        mOpenCloseAnimator = LauncherAnimUtils.ofPropertyValuesHolder(this);
+        mFastOutSlowInInterpolator = new FastOutSlowInInterpolator();
+        mScrollInterpolator = new VerticalPullDetector.ScrollInterpolator();
+        mInsets = new Rect();
+        mVerticalPullDetector = new VerticalPullDetector(context);
+        mVerticalPullDetector.setListener(this);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        mTranslationYOpen = 0;
+        mTranslationYClosed = getMeasuredHeight();
+        mTranslationYRange = mTranslationYClosed - mTranslationYOpen;
+    }
+
+    public void populateAndShow(ItemInfo itemInfo) {
+        mOriginalItemInfo = itemInfo;
+        ((TextView) findViewById(R.id.title)).setText(getContext().getString(
+                R.string.widgets_bottom_sheet_title, mOriginalItemInfo.title));
+
+        onWidgetsBound();
+
+        mWasNavBarLight = (mLauncher.getWindow().getDecorView().getSystemUiVisibility()
+                & View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR) != 0;
+        mLauncher.getDragLayer().addView(this);
+        measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+        setTranslationY(mTranslationYClosed);
+        mIsOpen = false;
+        open(true);
+    }
+
+    @Override
+    protected void onWidgetsBound() {
+        List<WidgetItem> widgets = mLauncher.getWidgetsForPackageUser(new PackageUserKey(
+                mOriginalItemInfo.getTargetComponent().getPackageName(), mOriginalItemInfo.user));
+
+        ViewGroup widgetRow = (ViewGroup) findViewById(R.id.widgets);
+        ViewGroup widgetCells = (ViewGroup) widgetRow.findViewById(R.id.widgets_cell_list);
+
+        widgetCells.removeAllViews();
+
+        for (int i = 0; i < widgets.size(); i++) {
+            WidgetCell widget = addItemCell(widgetCells);
+            widget.applyFromCellItem(widgets.get(i), LauncherAppState.getInstance(mLauncher)
+                    .getWidgetCache());
+            widget.ensurePreview();
+            widget.setVisibility(View.VISIBLE);
+            if (i < widgets.size() - 1) {
+                addDivider(widgetCells);
+            }
+        }
+
+        if (widgets.size() == 1) {
+            // If there is only one widget, we want to center it instead of left-align.
+            WidgetsBottomSheet.LayoutParams params = (WidgetsBottomSheet.LayoutParams)
+                    widgetRow.getLayoutParams();
+            params.gravity = Gravity.CENTER_HORIZONTAL;
+        } else {
+            // Otherwise, add an empty view to the start as padding (but still scroll edge to edge).
+            View leftPaddingView = LayoutInflater.from(getContext()).inflate(
+                    R.layout.widget_list_divider, widgetRow, false);
+            leftPaddingView.getLayoutParams().width = Utilities.pxFromDp(
+                    16, getResources().getDisplayMetrics());
+            widgetCells.addView(leftPaddingView, 0);
+        }
+    }
+
+    private void addDivider(ViewGroup parent) {
+        LayoutInflater.from(getContext()).inflate(R.layout.widget_list_divider, parent, true);
+    }
+
+    private WidgetCell addItemCell(ViewGroup parent) {
+        WidgetCell widget = (WidgetCell) LayoutInflater.from(getContext()).inflate(
+                R.layout.widget_cell, parent, false);
+
+        widget.setOnClickListener(this);
+        widget.setOnLongClickListener(this);
+        widget.setAnimatePreview(false);
+
+        parent.addView(widget);
+        return widget;
+    }
+
+    @Override
+    public void onClick(View view) {
+        mLauncher.getWidgetsView().handleClick();
+    }
+
+    @Override
+    public boolean onLongClick(View view) {
+        mLauncher.getDragController().addDragListener(this);
+        return mLauncher.getWidgetsView().handleLongClick(view);
+    }
+
+    private void open(boolean animate) {
+        if (mIsOpen || mOpenCloseAnimator.isRunning()) {
+            return;
+        }
+        mIsOpen = true;
+        setLightNavBar(true);
+        if (animate) {
+            mOpenCloseAnimator.setValues(new PropertyListBuilder()
+                    .translationY(mTranslationYOpen).build());
+            mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mVerticalPullDetector.finishedScrolling();
+                }
+            });
+            mOpenCloseAnimator.setInterpolator(mFastOutSlowInInterpolator);
+            mOpenCloseAnimator.start();
+        } else {
+            setTranslationY(mTranslationYOpen);
+        }
+    }
+
+    @Override
+    protected void handleClose(boolean animate) {
+        if (!mIsOpen || mOpenCloseAnimator.isRunning()) {
+            return;
+        }
+        if (animate) {
+            mOpenCloseAnimator.setValues(new PropertyListBuilder()
+                    .translationY(mTranslationYClosed).build());
+            mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mIsOpen = false;
+                    mVerticalPullDetector.finishedScrolling();
+                    ((ViewGroup) getParent()).removeView(WidgetsBottomSheet.this);
+                    setLightNavBar(mWasNavBarLight);
+                }
+            });
+            mOpenCloseAnimator.setInterpolator(mVerticalPullDetector.isIdleState()
+                    ? mFastOutSlowInInterpolator : mScrollInterpolator);
+            mOpenCloseAnimator.start();
+        } else {
+            setTranslationY(mTranslationYClosed);
+            setLightNavBar(mWasNavBarLight);
+            mIsOpen = false;
+        }
+    }
+
+    private void setLightNavBar(boolean lightNavBar) {
+        mLauncher.activateLightSystemBars(lightNavBar, false /* statusBar */, true /* navBar */);
+    }
+
+    @Override
+    protected boolean isOfType(@FloatingViewType int type) {
+        return (type & TYPE_WIDGETS_BOTTOM_SHEET) != 0;
+    }
+
+    @Override
+    public int getLogContainerType() {
+        return LauncherLogProto.ContainerType.WIDGETS; // TODO: be more specific
+    }
+
+    /**
+     * Returns a {@link WidgetsBottomSheet} which is already open or null
+     */
+    public static WidgetsBottomSheet getOpen(Launcher launcher) {
+        return getOpenView(launcher, TYPE_WIDGETS_BOTTOM_SHEET);
+    }
+
+    @Override
+    public void setInsets(Rect insets) {
+        // Extend behind left, right, and bottom insets.
+        int leftInset = insets.left - mInsets.left;
+        int rightInset = insets.right - mInsets.right;
+        int bottomInset = insets.bottom - mInsets.bottom;
+        mInsets.set(insets);
+        setPadding(getPaddingLeft() + leftInset, getPaddingTop(),
+                getPaddingRight() + rightInset, getPaddingBottom() + bottomInset);
+    }
+
+    /* VerticalPullDetector.Listener */
+
+    @Override
+    public void onDragStart(boolean start) {
+    }
+
+    @Override
+    public boolean onDrag(float displacement, float velocity) {
+        setTranslationY(Utilities.boundToRange(displacement, mTranslationYOpen,
+                mTranslationYClosed));
+        return true;
+    }
+
+    @Override
+    public void onDragEnd(float velocity, boolean fling) {
+        if ((fling && velocity > 0) || getTranslationY() > (mTranslationYRange) / 2) {
+            mScrollInterpolator.setVelocityAtZero(velocity);
+            mOpenCloseAnimator.setDuration(mVerticalPullDetector.calculateDuration(velocity,
+                    (mTranslationYClosed - getTranslationY()) / mTranslationYRange));
+            close(true);
+        } else {
+            mIsOpen = false;
+            mOpenCloseAnimator.setDuration(mVerticalPullDetector.calculateDuration(velocity,
+                    (getTranslationY() - mTranslationYOpen) / mTranslationYRange));
+            open(true);
+        }
+    }
+
+    @Override
+    public boolean onControllerTouchEvent(MotionEvent ev) {
+        return mVerticalPullDetector.onTouchEvent(ev);
+    }
+
+    @Override
+    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+        int directionsToDetectScroll = mVerticalPullDetector.isIdleState() ?
+                VerticalPullDetector.DIRECTION_DOWN : 0;
+        mVerticalPullDetector.setDetectableScrollConditions(
+                directionsToDetectScroll, false);
+        mVerticalPullDetector.onTouchEvent(ev);
+        return mVerticalPullDetector.isDraggingOrSettling();
+    }
+
+    /* DragListener */
+
+    @Override
+    public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
+        // A widget or custom shortcut was dragged.
+        close(true);
+    }
+
+    @Override
+    public void onDragEnd() {
+    }
+}
diff --git a/src/com/android/launcher3/widget/WidgetsContainerView.java b/src/com/android/launcher3/widget/WidgetsContainerView.java
index 89c44c8..4e296bf 100644
--- a/src/com/android/launcher3/widget/WidgetsContainerView.java
+++ b/src/com/android/launcher3/widget/WidgetsContainerView.java
@@ -17,11 +17,8 @@
 package com.android.launcher3.widget;
 
 import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
+import android.graphics.Point;
 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,27 +26,24 @@
 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;
-import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.folder.Folder;
-import com.android.launcher3.IconCache;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.PendingAddItemInfo;
 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.model.WidgetsModel;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.folder.Folder;
+import com.android.launcher3.model.PackageItemInfo;
+import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+import com.android.launcher3.util.MultiHashMap;
+import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.Thunk;
-import com.android.launcher3.util.TransformingTouchDelegate;
+
+import java.util.List;
 
 /**
  * The widgets list view container.
@@ -61,22 +55,14 @@
 
     /* Global instances that are used inside this container. */
     @Thunk Launcher mLauncher;
-    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;
 
-    /* Rendering related. */
-    private WidgetPreviewLoader mWidgetPreviewLoader;
-
     public WidgetsContainerView(Context context) {
         this(context, null);
     }
@@ -88,23 +74,15 @@
     public WidgetsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
         mLauncher = Launcher.getLauncher(context);
-        mDragController = mLauncher.getDragController();
         mAdapter = new WidgetsListAdapter(this, this, context);
-        mIconCache = (LauncherAppState.getInstance()).getIconCache();
         if (LOGD) {
             Log.d(TAG, "WidgetsContainerView constructor");
         }
     }
 
     @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 +91,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);
     }
 
     //
@@ -141,6 +112,10 @@
                 || mLauncher.getWorkspace().isSwitchingState()
                 || !(v instanceof WidgetCell)) return;
 
+        handleClick();
+    }
+
+    public void handleClick() {
         // Let the user know that they have to long press to add a widget
         if (mWidgetInstructionToast != null) {
             mWidgetInstructionToast.cancel();
@@ -155,27 +130,23 @@
 
     @Override
     public boolean onLongClick(View v) {
+        // When we have exited the widget tray, disregard long clicks
+        if (!mLauncher.isWidgetsViewVisible()) return false;
+        return handleLongClick(v);
+    }
+
+    public boolean handleLongClick(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;
-        // When we have exited all apps or are in transition, disregard long clicks
-        if (!mLauncher.isWidgetsViewVisible() ||
-                mLauncher.getWorkspace().isSwitchingState()) return false;
+        // When we  are in transition, disregard long clicks
+        if (mLauncher.getWorkspace().isSwitchingState()) return false;
         // Return if global dragging is not enabled
         if (!mLauncher.isDraggingEnabled()) return false;
 
-        boolean status = beginDragging(v);
-        if (status && v.getTag() instanceof PendingAddWidgetInfo) {
-            WidgetHostViewLoader hostLoader = new WidgetHostViewLoader(mLauncher, v);
-            boolean preloadStatus = hostLoader.preloadWidget();
-            if (LOGD) {
-                Log.d(TAG, String.format("preloading widget [status=%s]", preloadStatus));
-            }
-            mLauncher.getDragController().addDragListener(hostLoader);
-        }
-        return status;
+        return beginDragging(v);
     }
 
     private boolean beginDragging(View v) {
@@ -199,7 +170,6 @@
     private boolean beginDraggingWidget(WidgetCell v) {
         // Get the widget preview as the drag representation
         WidgetImageView image = (WidgetImageView) v.findViewById(R.id.widget_preview);
-        PendingAddItemInfo createItemInfo = (PendingAddItemInfo) v.getTag();
 
         // If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and
         // we abort the drag.
@@ -207,53 +177,12 @@
             return false;
         }
 
-        // Compose the drag image
-        Bitmap preview;
-        final float scale;
-        final Rect bounds = image.getBitmapBounds();
+        int[] loc = new int[2];
+        mLauncher.getDragLayer().getLocationInDragLayer(image, loc);
 
-        if (createItemInfo instanceof PendingAddWidgetInfo) {
-            // This can happen in some weird cases involving multi-touch. We can't start dragging
-            // the widget if this is null, so we break out.
-
-            PendingAddWidgetInfo createWidgetInfo = (PendingAddWidgetInfo) createItemInfo;
-            int[] size = mLauncher.getWorkspace().estimateItemSize(createWidgetInfo, true);
-
-            Bitmap icon = image.getBitmap();
-            float minScale = 1.25f;
-            int maxWidth = Math.min((int) (icon.getWidth() * minScale), size[0]);
-
-            int[] previewSizeBeforeScale = new int[1];
-            preview = getWidgetPreviewLoader().generateWidgetPreview(mLauncher,
-                    createWidgetInfo.info, maxWidth, null, previewSizeBeforeScale);
-
-            if (previewSizeBeforeScale[0] < icon.getWidth()) {
-                // The icon has extra padding around it.
-                int padding = (icon.getWidth() - previewSizeBeforeScale[0]) / 2;
-                if (icon.getWidth() > image.getWidth()) {
-                    padding = padding * image.getWidth() / icon.getWidth();
-                }
-
-                bounds.left += padding;
-                bounds.right -= padding;
-            }
-            scale = bounds.width() / (float) preview.getWidth();
-        } else {
-            PendingAddShortcutInfo createShortcutInfo = (PendingAddShortcutInfo) v.getTag();
-            Drawable icon = mIconCache.getFullResIcon(createShortcutInfo.activityInfo);
-            preview = Utilities.createIconBitmap(icon, mLauncher);
-            createItemInfo.spanX = createItemInfo.spanY = 1;
-            scale = ((float) mLauncher.getDeviceProfile().iconSizePx) / preview.getWidth();
-        }
-
-        // Since we are not going through the workspace for starting the drag, set drag related
-        // information on the workspace before starting the drag.
-        mLauncher.getWorkspace().prepareDragWithProvider(
-                new PendingItemPreviewProvider(v, createItemInfo, preview));
-
-        // Start the drag
-        mDragController.startDrag(image, preview, this, createItemInfo,
-                bounds, scale, new DragOptions());
+        new PendingItemDragHelper(v).startDrag(
+                image.getBitmapBounds(), image.getBitmap().getWidth(), image.getWidth(),
+                new Point(loc[0], loc[1]), this, new DragOptions());
         return true;
     }
 
@@ -262,11 +191,6 @@
     //
 
     @Override
-    public boolean supportsFlingToDelete() {
-        return true;
-    }
-
-    @Override
     public boolean supportsAppInfoDropTarget() {
         return true;
     }
@@ -286,14 +210,6 @@
     }
 
     @Override
-    public void onFlingToDeleteCompleted() {
-        // We just dismiss the drag when we fling, so cleanup here
-        mLauncher.exitSpringLoadedDragModeDelayed(true,
-                Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
-        mLauncher.unlockScreenOrientation(false);
-    }
-
-    @Override
     public void onDropCompleted(View target, DragObject d, boolean isFlingToDelete,
             boolean success) {
         if (LOGD) {
@@ -308,23 +224,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 +232,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);
@@ -347,15 +246,12 @@
         return mAdapter.getItemCount() == 0;
     }
 
-    private WidgetPreviewLoader getWidgetPreviewLoader() {
-        if (mWidgetPreviewLoader == null) {
-            mWidgetPreviewLoader = LauncherAppState.getInstance().getWidgetCache();
-        }
-        return mWidgetPreviewLoader;
+    public List<WidgetItem> getWidgetsForPackageUser(PackageUserKey packageUserKey) {
+        return mAdapter.copyWidgetsForPackageUser(packageUserKey);
     }
 
     @Override
-    public void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent) {
-        targetParent.containerType = LauncherLogProto.WIDGETS;
+    public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
+        targetParent.containerType = ContainerType.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..a1eb0ab 100644
--- a/src/com/android/launcher3/widget/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/WidgetsListAdapter.java
@@ -15,28 +15,30 @@
  */
 package com.android.launcher3.widget;
 
-import android.annotation.TargetApi;
 import android.content.Context;
-import android.os.Build;
 import android.support.v7.widget.RecyclerView;
 import android.support.v7.widget.RecyclerView.Adapter;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-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 com.android.launcher3.util.PackageUserKey;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 
 /**
  * List view adapter for the widget tray.
@@ -57,7 +59,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;
 
@@ -65,28 +68,62 @@
             View.OnLongClickListener iconLongClickListener,
             Context context) {
         mLayoutInflater = LayoutInflater.from(context);
-        mWidgetPreviewLoader = LauncherAppState.getInstance().getWidgetCache();
+        mWidgetPreviewLoader = LauncherAppState.getInstance(context).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 mEntries.size();
+    }
+
+    public String getSectionName(int pos) {
+        return mEntries.get(pos).titleSectionName;
+    }
+
+    /**
+     * Copies and returns the widgets associated with the package and user of the ComponentKey.
+     */
+    public List<WidgetItem> copyWidgetsForPackageUser(PackageUserKey packageUserKey) {
+        for (WidgetListRowEntry entry : mEntries) {
+            if (entry.pkgItem.packageName.equals(packageUserKey.mPackageName)) {
+                ArrayList<WidgetItem> widgets = new ArrayList<>(entry.widgets);
+                // Remove widgets not associated with the correct user.
+                Iterator<WidgetItem> iterator = widgets.iterator();
+                while (iterator.hasNext()) {
+                    if (!iterator.next().user.equals(packageUserKey.mUser)) {
+                        iterator.remove();
+                    }
+                }
+                return widgets.isEmpty() ? null : widgets;
+            }
         }
-        return mWidgetsModel.getPackageSize();
+        return null;
     }
 
     @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) {
@@ -97,42 +134,47 @@
 
         // Add more views.
         // if there are too many, hide them.
-        int diff = infoList.size() - row.getChildCount();
+        int expectedChildCount = infoList.size() + Math.max(0, infoList.size() - 1);
+        int childCount = row.getChildCount();
 
-        if (diff > 0) {
-            for (int i = 0; i < diff; i++) {
-                WidgetCell widget = (WidgetCell) mLayoutInflater.inflate(
-                        R.layout.widget_cell, row, false);
+        if (expectedChildCount > childCount) {
+            for (int i = childCount ; i < expectedChildCount; i++) {
+                if ((i & 1) == 1) {
+                    // Add a divider for odd index
+                    mLayoutInflater.inflate(R.layout.widget_list_divider, row);
+                } else {
+                    // Add cell for even index
+                    WidgetCell widget = (WidgetCell) mLayoutInflater.inflate(
+                            R.layout.widget_cell, row, false);
 
-                // set up touch.
-                widget.setOnClickListener(mIconClickListener);
-                widget.setOnLongClickListener(mIconLongClickListener);
-                LayoutParams lp = widget.getLayoutParams();
-                lp.height = widget.cellSize;
-                lp.width = widget.cellSize;
-                widget.setLayoutParams(lp);
-
-                row.addView(widget);
+                    // set up touch.
+                    widget.setOnClickListener(mIconClickListener);
+                    widget.setOnLongClickListener(mIconLongClickListener);
+                    row.addView(widget);
+                }
             }
-        } else if (diff < 0) {
-            for (int i=infoList.size() ; i < row.getChildCount(); i++) {
+        } else if (expectedChildCount < childCount) {
+            for (int i = expectedChildCount ; i < childCount; i++) {
                 row.getChildAt(i).setVisibility(View.GONE);
             }
         }
 
         // 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++) {
-            WidgetCell widget = (WidgetCell) row.getChildAt(i);
+            WidgetCell widget = (WidgetCell) row.getChildAt(2*i);
             widget.applyFromCellItem(infoList.get(i), mWidgetPreviewLoader);
             widget.ensurePreview();
             widget.setVisibility(View.VISIBLE);
+
+            if (i > 0) {
+                row.getChildAt(2*i - 1).setVisibility(View.VISIBLE);
+            }
         }
     }
 
-    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
     @Override
     public WidgetsRowViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
         if (DEBUG) {
@@ -141,15 +183,10 @@
 
         ViewGroup container = (ViewGroup) mLayoutInflater.inflate(
                 R.layout.widgets_list_row_view, parent, false);
-        LinearLayout cellList = (LinearLayout) container.findViewById(R.id.widgets_cell_list);
 
         // if the end padding is 0, then container view (horizontal scroll view) doesn't respect
         // the end of the linear layout width + the start padding and doesn't allow scrolling.
-        if (Utilities.ATLEAST_JB_MR1) {
-            cellList.setPaddingRelative(mIndent, 0, 1, 0);
-        } else {
-            cellList.setPadding(mIndent, 0, 1, 0);
-        }
+        container.findViewById(R.id.widgets_cell_list).setPaddingRelative(mIndent, 0, 1, 0);
 
         return new WidgetsRowViewHolder(container);
     }
@@ -157,7 +194,7 @@
     @Override
     public void onViewRecycled(WidgetsRowViewHolder holder) {
         int total = holder.cellContainer.getChildCount();
-        for (int i = 0; i < total; i++) {
+        for (int i = 0; i < total; i+=2) {
             WidgetCell widget = (WidgetCell) holder.cellContainer.getChildAt(i);
             widget.clear();
         }
@@ -175,4 +212,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/src_config/com/android/launcher3/config/FeatureFlags.java b/src_config/com/android/launcher3/config/FeatureFlags.java
index 8ee5497..4e337a2 100644
--- a/src_config/com/android/launcher3/config/FeatureFlags.java
+++ b/src_config/com/android/launcher3/config/FeatureFlags.java
@@ -24,12 +24,16 @@
 
     // Custom flags go below this
     public static boolean LAUNCHER3_DISABLE_ICON_NORMALIZATION = false;
-    // As opposed to the new spring-loaded workspace.
-    public static boolean LAUNCHER3_LEGACY_WORKSPACE_DND = false;
     public static boolean LAUNCHER3_LEGACY_FOLDER_ICON = false;
     public static boolean LAUNCHER3_USE_SYSTEM_DRAG_DRIVER = true;
     public static boolean LAUNCHER3_DISABLE_PINCH_TO_OVERVIEW = false;
     public static boolean LAUNCHER3_ALL_APPS_PULL_UP = true;
+    public static boolean LAUNCHER3_NEW_FOLDER_ANIMATION = false;
+    // When enabled allows to use any point on the fast scrollbar to start dragging.
+    public static boolean LAUNCHER3_DIRECT_SCROLL = true;
+    // When enabled while all-apps open, the soft input will be set to adjust resize .
+    public static boolean LAUNCHER3_UPDATE_SOFT_INPUT_MODE = false;
+
 
     // Feature flag to enable moving the QSB on the 0th screen of the workspace.
     public static final boolean QSB_ON_FIRST_SCREEN = true;
@@ -39,4 +43,12 @@
     public static final boolean PULLDOWN_SEARCH = false;
     // When enabled the status bar may show dark icons based on the top of the wallpaper.
     public static final boolean LIGHT_STATUS_BAR = false;
+    // When enabled icons are badged with the number of notifications associated with that app.
+    public static final boolean BADGE_ICONS = true;
+    // When enabled, icons not supporting {@link AdaptiveIconDrawable} will be wrapped in this class.
+    public static final boolean LEGACY_ICON_TREATMENT = true;
+    // When enabled, adaptive icons would have shadows baked when being stored to icon cache.
+    public static final boolean ADAPTIVE_ICON_SHADOW = true;
+    // When enabled, app discovery will be enabled if service is implemented
+    public static final boolean DISCOVERY_ENABLED = false;
 }
diff --git a/src_config/com/android/launcher3/config/ProviderConfig.java b/src_config/com/android/launcher3/config/ProviderConfig.java
index 1d964b1..491fa65 100644
--- a/src_config/com/android/launcher3/config/ProviderConfig.java
+++ b/src_config/com/android/launcher3/config/ProviderConfig.java
@@ -20,5 +20,5 @@
 
     public static final String AUTHORITY = "com.android.launcher3.settings".intern();
 
-    public static boolean IS_DOGFOOD_BUILD = true;
+    public static final boolean IS_DOGFOOD_BUILD = true;
 }
diff --git a/tests/Android.mk b/tests/Android.mk
index c91e793..6e6453c 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -20,11 +20,14 @@
 LOCAL_STATIC_JAVA_LIBRARIES := \
 	android-support-test \
 	ub-uiautomator \
-	legacy-android-test
+	legacy-android-test \
+	mockito-target-minus-junit4
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_FULL_LIBS_MANIFEST_FILES := $(LOCAL_PATH)/AndroidManifest-common.xml
 
 LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 21
 
 LOCAL_PACKAGE_NAME := Launcher3Tests
 
diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml
new file mode 100644
index 0000000..0a29147
--- /dev/null
+++ b/tests/AndroidManifest-common.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.launcher3.tests">
+
+    <uses-sdk android:targetSdkVersion="25" android:minSdkVersion="21"/>
+
+    <application android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+
+        <receiver
+            android:name="com.android.launcher3.testcomponent.AppWidgetNoConfig"
+            android:label="No Config">
+            <intent-filter>
+                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+            </intent-filter>
+            <meta-data android:name="android.appwidget.provider"
+                       android:resource="@xml/appwidget_no_config" />
+        </receiver>
+
+        <receiver
+            android:name="com.android.launcher3.testcomponent.AppWidgetWithConfig"
+            android:label="With Config">
+            <intent-filter>
+                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+            </intent-filter>
+            <meta-data android:name="android.appwidget.provider"
+                       android:resource="@xml/appwidget_with_config" />
+        </receiver>
+
+        <activity
+            android:name="com.android.launcher3.testcomponent.WidgetConfigActivity">
+            <intent-filter>
+                <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
+            </intent-filter>
+        </activity>
+        <activity
+            android:name="com.android.launcher3.testcomponent.RequestPinItemActivity"
+            android:label="Test Pin Item"
+            android:icon="@drawable/test_drawable_pin_item">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index afe8952..43030ae 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -18,6 +18,7 @@
     xmlns:tools="http://schemas.android.com/tools"
     package="com.android.launcher3.tests">
 
+    <uses-sdk android:targetSdkVersion="25" android:minSdkVersion="21"/>
     <uses-sdk tools:overrideLibrary="android.support.test.uiautomator.v18"/>
 
     <application android:debuggable="true">
diff --git a/tests/res/drawable/test_drawable_pin_item.xml b/tests/res/drawable/test_drawable_pin_item.xml
new file mode 100644
index 0000000..1d07256
--- /dev/null
+++ b/tests/res/drawable/test_drawable_pin_item.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (C) 2017 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="48dp"
+    android:height="48dp"
+    android:viewportWidth="48.0"
+    android:viewportHeight="48.0">
+
+    <path
+        android:fillColor="#66000000"
+        android:pathData="M0,24a24,24 0 1,0 48,0a24,24 0 1,0 -48,0"/>
+    <path
+        android:fillColor="#FF1B5E20"
+        android:pathData="M2,24a22,22 0 1,0 44,0a22,22 0 1,0 -44,0"/>
+
+    <group
+        android:translateX="12"
+        android:translateY="12">
+        <path
+            android:fillColor="#FFFFFFFF"
+            android:pathData="M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12Z"/>
+    </group>
+</vector>
\ No newline at end of file
diff --git a/tests/res/layout/test_layout_appwidget_blue.xml b/tests/res/layout/test_layout_appwidget_blue.xml
new file mode 100644
index 0000000..8111978
--- /dev/null
+++ b/tests/res/layout/test_layout_appwidget_blue.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:background="#FF0000FF"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" />
\ No newline at end of file
diff --git a/tests/res/layout/test_layout_appwidget_red.xml b/tests/res/layout/test_layout_appwidget_red.xml
new file mode 100644
index 0000000..48d3e81
--- /dev/null
+++ b/tests/res/layout/test_layout_appwidget_red.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:background="#FFFF0000"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" />
\ No newline at end of file
diff --git a/tests/res/layout/test_layout_appwidget_view.xml b/tests/res/layout/test_layout_appwidget_view.xml
new file mode 100644
index 0000000..7c87e60
--- /dev/null
+++ b/tests/res/layout/test_layout_appwidget_view.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="#FFFF00">
+
+    <ImageView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_margin="20dp"
+        android:background="#FF0000"
+        android:id="@android:id/icon" />
+</FrameLayout>
\ No newline at end of file
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..8199687
--- /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 intent=component=app1/class1
+allApps componentName=app1/class2 intent=component=app1/class2
+allApps componentName=app2/class1 intent=component=app2/class1
+allApps componentName=app2/class2 intent=component=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/res/xml/appwidget_no_config.xml b/tests/res/xml/appwidget_no_config.xml
new file mode 100644
index 0000000..d24dfe3
--- /dev/null
+++ b/tests/res/xml/appwidget_no_config.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<appwidget-provider
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:minWidth="180dp"
+    android:minHeight="110dp"
+    android:updatePeriodMillis="86400000"
+    android:initialLayout="@layout/test_layout_appwidget_red"
+    android:resizeMode="horizontal|vertical"
+    android:widgetCategory="home_screen">
+</appwidget-provider>
\ No newline at end of file
diff --git a/tests/res/xml/appwidget_with_config.xml b/tests/res/xml/appwidget_with_config.xml
new file mode 100644
index 0000000..3e96c6f
--- /dev/null
+++ b/tests/res/xml/appwidget_with_config.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<appwidget-provider
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:minWidth="180dp"
+    android:minHeight="110dp"
+    android:updatePeriodMillis="86400000"
+    android:initialLayout="@layout/test_layout_appwidget_blue"
+    android:configure="com.android.launcher3.testcomponent.WidgetConfigActivity"
+    android:resizeMode="horizontal|vertical"
+    android:widgetCategory="home_screen">
+</appwidget-provider>
\ No newline at end of file
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..d0ba907
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
@@ -0,0 +1,183 @@
+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 com.android.launcher3.util.Provider;
+
+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 AddWorkspaceItemsTask newTask(ItemInfo... items) {
+        return new AddWorkspaceItemsTask(Provider.of(Arrays.asList(items))) {
+
+            @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(targetContext, 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..b9944db
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
@@ -0,0 +1,226 @@
+package com.android.launcher3.model;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.LauncherActivityInfo;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.os.Process;
+import android.os.UserHandle;
+import android.support.annotation.NonNull;
+import android.support.test.InstrumentationRegistry;
+import android.test.ProviderTestCase2;
+
+import com.android.launcher3.AllAppsList;
+import com.android.launcher3.AppFilter;
+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.BaseModelUpdateTask;
+import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.Provider;
+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.Matchers.anyBoolean;
+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 UserHandle myUser;
+
+    public InvariantDeviceProfile idp;
+    public LauncherAppState appState;
+    public LauncherModel model;
+    public ModelWriter modelWriter;
+    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);
+        model = mock(LauncherModel.class);
+        modelWriter = mock(ModelWriter.class);
+        when(appState.getModel()).thenReturn(model);
+        when(model.getWriter(anyBoolean())).thenReturn(modelWriter);
+
+        myUser = Process.myUserHandle();
+
+        bgDataModel = new BgDataModel();
+        targetContext = InstrumentationRegistry.getTargetContext();
+        idp = new InvariantDeviceProfile();
+        iconCache = new MyIconCache(targetContext, idp);
+
+        allAppsList = new AllAppsList(iconCache, new AppFilter());
+
+        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(targetContext,
+                                (ItemInfo) initItem(classMap.get(commands[1]), commands, 2), false);
+                        break;
+                    case "allApps":
+                        allAppsList.add((AppInfo) initItem(AppInfo.class, commands, 1), null);
+                        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(
+                @NonNull ComponentName componentName,
+                @NonNull Provider<LauncherActivityInfo> infoProvider,
+                UserHandle 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, Process.myUserHandle()), entry);
+        }
+
+        public Bitmap newIcon() {
+            return Bitmap.createBitmap(1, 1, Config.ARGB_8888);
+        }
+
+        @Override
+        protected Bitmap makeDefaultIcon(UserHandle user) {
+            return newIcon();
+        }
+    }
+}
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..d595e6c
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
@@ -0,0 +1,82 @@
+package com.android.launcher3.model;
+
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.ShortcutInfo;
+
+import java.util.Arrays;
+import java.util.HashSet;
+
+/**
+ * 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 {
+        // Clear all icons from apps list so that its easy to check what was updated
+        for (AppInfo info : allAppsList.data) {
+            info.iconBitmap = null;
+        }
+
+        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));
+        for (ItemInfo info : bgDataModel.itemsIdMap) {
+            if (updates.contains(info.id)) {
+                assertEquals(NEW_LABEL_PREFIX + info.id, info.title);
+                assertNotNull(((ShortcutInfo) info).iconBitmap);
+            } else {
+                assertNotSame(NEW_LABEL_PREFIX + info.id, info.title);
+                assertNull(((ShortcutInfo) info).iconBitmap);
+            }
+        }
+    }
+}
diff --git a/tests/src/com/android/launcher3/model/LoaderCursorTest.java b/tests/src/com/android/launcher3/model/LoaderCursorTest.java
new file mode 100644
index 0000000..173c556
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/LoaderCursorTest.java
@@ -0,0 +1,236 @@
+package com.android.launcher3.model;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.database.MatrixCursor;
+import android.graphics.Bitmap;
+import android.os.Process;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.launcher3.IconCache;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.LauncherAppsCompat;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import static com.android.launcher3.LauncherSettings.BaseLauncherColumns.INTENT;
+import static com.android.launcher3.LauncherSettings.Favorites.CELLX;
+import static com.android.launcher3.LauncherSettings.Favorites.CELLY;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+import static com.android.launcher3.LauncherSettings.Favorites.ICON;
+import static com.android.launcher3.LauncherSettings.Favorites.ICON_PACKAGE;
+import static com.android.launcher3.LauncherSettings.Favorites.ICON_RESOURCE;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
+import static com.android.launcher3.LauncherSettings.Favorites.PROFILE_ID;
+import static com.android.launcher3.LauncherSettings.Favorites.RESTORED;
+import static com.android.launcher3.LauncherSettings.Favorites.SCREEN;
+import static com.android.launcher3.LauncherSettings.Favorites.TITLE;
+import static com.android.launcher3.LauncherSettings.Favorites._ID;
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for {@link LoaderCursor}
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class LoaderCursorTest {
+
+    private LauncherAppState mMockApp;
+    private IconCache mMockIconCache;
+
+    private MatrixCursor mCursor;
+    private InvariantDeviceProfile mIDP;
+    private Context mContext;
+    private LauncherAppsCompat mLauncherApps;
+
+    private LoaderCursor mLoaderCursor;
+
+    @Before
+    public void setup() {
+        mIDP = new InvariantDeviceProfile();
+        mCursor = new MatrixCursor(new String[] {
+                ICON, ICON_PACKAGE, ICON_RESOURCE, TITLE,
+                _ID, CONTAINER, ITEM_TYPE, PROFILE_ID,
+                SCREEN, CELLX, CELLY, RESTORED, INTENT
+        });
+        mContext = InstrumentationRegistry.getTargetContext();
+
+        mMockApp = mock(LauncherAppState.class);
+        mMockIconCache = mock(IconCache.class);
+        when(mMockApp.getIconCache()).thenReturn(mMockIconCache);
+        when(mMockApp.getInvariantDeviceProfile()).thenReturn(mIDP);
+        when(mMockApp.getContext()).thenReturn(mContext);
+        mLauncherApps = LauncherAppsCompat.getInstance(mContext);
+
+        mLoaderCursor = new LoaderCursor(mCursor, mMockApp);
+        mLoaderCursor.allUsers.put(0, Process.myUserHandle());
+    }
+
+    private void initCursor(int itemType, String title) {
+        mCursor.newRow()
+                .add(_ID, 1)
+                .add(PROFILE_ID, 0)
+                .add(ITEM_TYPE, itemType)
+                .add(TITLE, title)
+                .add(CONTAINER, CONTAINER_DESKTOP);
+    }
+
+    @Test
+    public void getAppShortcutInfo_dontAllowMissing_invalidComponent() {
+        initCursor(ITEM_TYPE_APPLICATION, "");
+        assertTrue(mLoaderCursor.moveToNext());
+        ComponentName cn = new ComponentName(mContext.getPackageName(), "dummy-do");
+        assertNull(mLoaderCursor.getAppShortcutInfo(
+                new Intent().setComponent(cn), false /* allowMissingTarget */, true));
+    }
+
+    @Test
+    public void getAppShortcutInfo_dontAllowMissing_validComponent() {
+        initCursor(ITEM_TYPE_APPLICATION, "");
+        assertTrue(mLoaderCursor.moveToNext());
+
+        ComponentName cn = mLauncherApps.getActivityList(null, mLoaderCursor.user)
+                .get(0).getComponentName();
+        ShortcutInfo info = mLoaderCursor.getAppShortcutInfo(
+                new Intent().setComponent(cn), false /* allowMissingTarget */, true);
+        assertNotNull(info);
+        assertTrue(Utilities.isLauncherAppTarget(info.intent));
+    }
+
+    @Test
+    public void getAppShortcutInfo_allowMissing_invalidComponent() {
+        initCursor(ITEM_TYPE_APPLICATION, "");
+        assertTrue(mLoaderCursor.moveToNext());
+
+        ComponentName cn = new ComponentName(mContext.getPackageName(), "dummy-do");
+        ShortcutInfo info = mLoaderCursor.getAppShortcutInfo(
+                new Intent().setComponent(cn), true  /* allowMissingTarget */, true);
+        assertNotNull(info);
+        assertTrue(Utilities.isLauncherAppTarget(info.intent));
+    }
+
+    @Test
+    public void loadSimpleShortcut() {
+        initCursor(ITEM_TYPE_SHORTCUT, "my-shortcut");
+        assertTrue(mLoaderCursor.moveToNext());
+
+        Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8);
+        when(mMockIconCache.getDefaultIcon(eq(mLoaderCursor.user))).thenReturn(icon);
+        ShortcutInfo info = mLoaderCursor.loadSimpleShortcut();
+        assertEquals(icon, info.iconBitmap);
+        assertEquals("my-shortcut", info.title);
+        assertEquals(ITEM_TYPE_SHORTCUT, info.itemType);
+    }
+
+    @Test
+    public void checkItemPlacement_wrongWorkspaceScreen() {
+        ArrayList<Long> workspaceScreens = new ArrayList<>(Arrays.asList(1L, 3L));
+        mIDP.numRows = 4;
+        mIDP.numColumns = 4;
+        mIDP.numHotseatIcons = 3;
+
+        // Item on unknown screen are not placed
+        assertFalse(mLoaderCursor.checkItemPlacement(
+                newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 4L), workspaceScreens));
+        assertFalse(mLoaderCursor.checkItemPlacement(
+                newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 5L), workspaceScreens));
+        assertFalse(mLoaderCursor.checkItemPlacement(
+                newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 2L), workspaceScreens));
+
+        assertTrue(mLoaderCursor.checkItemPlacement(
+                newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 1L), workspaceScreens));
+        assertTrue(mLoaderCursor.checkItemPlacement(
+                newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 3L), workspaceScreens));
+
+    }
+    @Test
+    public void checkItemPlacement_outsideBounds() {
+        ArrayList<Long> workspaceScreens = new ArrayList<>(Arrays.asList(1L, 2L));
+        mIDP.numRows = 4;
+        mIDP.numColumns = 4;
+        mIDP.numHotseatIcons = 3;
+
+        // Item outside screen bounds are not placed
+        assertFalse(mLoaderCursor.checkItemPlacement(
+                newItemInfo(4, 4, 1, 1, CONTAINER_DESKTOP, 1L), workspaceScreens));
+    }
+
+    @Test
+    public void checkItemPlacement_overlappingItems() {
+        ArrayList<Long> workspaceScreens = new ArrayList<>(Arrays.asList(1L, 2L));
+        mIDP.numRows = 4;
+        mIDP.numColumns = 4;
+        mIDP.numHotseatIcons = 3;
+
+        // Overlapping items are not placed
+        assertTrue(mLoaderCursor.checkItemPlacement(
+                newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 1L), workspaceScreens));
+        assertFalse(mLoaderCursor.checkItemPlacement(
+                newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 1L), workspaceScreens));
+
+        assertTrue(mLoaderCursor.checkItemPlacement(
+                newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 2L), workspaceScreens));
+        assertFalse(mLoaderCursor.checkItemPlacement(
+                newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 2L), workspaceScreens));
+
+        assertTrue(mLoaderCursor.checkItemPlacement(
+                newItemInfo(1, 1, 1, 1, CONTAINER_DESKTOP, 1L), workspaceScreens));
+        assertTrue(mLoaderCursor.checkItemPlacement(
+                newItemInfo(2, 2, 2, 2, CONTAINER_DESKTOP, 1L), workspaceScreens));
+
+        assertFalse(mLoaderCursor.checkItemPlacement(
+                newItemInfo(3, 2, 1, 2, CONTAINER_DESKTOP, 1L), workspaceScreens));
+    }
+
+    @Test
+    public void checkItemPlacement_hotseat() {
+        ArrayList<Long> workspaceScreens = new ArrayList<>();
+        mIDP.numRows = 4;
+        mIDP.numColumns = 4;
+        mIDP.numHotseatIcons = 3;
+
+        // Hotseat items are only placed based on screenId
+        assertTrue(mLoaderCursor.checkItemPlacement(
+                newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 1L), workspaceScreens));
+        assertTrue(mLoaderCursor.checkItemPlacement(
+                newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 2L), workspaceScreens));
+
+        assertFalse(mLoaderCursor.checkItemPlacement(
+                newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 3L), workspaceScreens));
+    }
+
+    private ItemInfo newItemInfo(int cellX, int cellY, int spanX, int spanY,
+            long container, long screenId) {
+        ItemInfo info = new ItemInfo();
+        info.cellX = cellX;
+        info.cellY = cellY;
+        info.spanX = spanX;
+        info.spanY = spanY;
+        info.container = container;
+        info.screenId = screenId;
+        return info;
+    }
+}
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/shortcuts/ShortcutFilterTest.java b/tests/src/com/android/launcher3/popup/PopupPopulatorTest.java
similarity index 63%
rename from tests/src/com/android/launcher3/shortcuts/ShortcutFilterTest.java
rename to tests/src/com/android/launcher3/popup/PopupPopulatorTest.java
index 05d0ffb..2ad9b35 100644
--- a/tests/src/com/android/launcher3/shortcuts/ShortcutFilterTest.java
+++ b/tests/src/com/android/launcher3/popup/PopupPopulatorTest.java
@@ -14,11 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.launcher3.shortcuts;
+package com.android.launcher3.popup;
 
 import android.content.pm.ShortcutInfo;
 import android.support.test.runner.AndroidJUnit4;
 
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -26,40 +28,64 @@
 import java.util.Collections;
 import java.util.List;
 
-import static com.android.launcher3.shortcuts.ShortcutFilter.MAX_SHORTCUTS;
-import static com.android.launcher3.shortcuts.ShortcutFilter.NUM_DYNAMIC;
+import static com.android.launcher3.popup.PopupPopulator.MAX_ITEMS;
+import static com.android.launcher3.popup.PopupPopulator.NUM_DYNAMIC;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 /**
- * Tests the sorting and filtering of shortcuts in {@link ShortcutFilter}.
+ * Tests the sorting and filtering of shortcuts in {@link PopupPopulator}.
  */
 @RunWith(AndroidJUnit4.class)
-public class ShortcutFilterTest {
+public class PopupPopulatorTest {
 
     @Test
     public void testSortAndFilterShortcuts() {
         filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(3, 0), 3, 0);
         filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(0, 3), 0, 3);
-        filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(5, 0), MAX_SHORTCUTS, 0);
-        filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(0, 5), 0, MAX_SHORTCUTS);
+        filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(5, 0), MAX_ITEMS, 0);
+        filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(0, 5), 0, MAX_ITEMS);
         filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(3, 3),
-                MAX_SHORTCUTS - NUM_DYNAMIC, NUM_DYNAMIC);
+                MAX_ITEMS - NUM_DYNAMIC, NUM_DYNAMIC);
         filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(5, 5),
-                MAX_SHORTCUTS - NUM_DYNAMIC, NUM_DYNAMIC);
-        filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(5, 1), MAX_SHORTCUTS - 1, 1);
-        filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(1, 5), 1, MAX_SHORTCUTS - 1);
+                MAX_ITEMS - NUM_DYNAMIC, NUM_DYNAMIC);
+        filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(5, 1), MAX_ITEMS - 1, 1);
+        filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(1, 5), 1, MAX_ITEMS - 1);
         filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(5, 3),
-                MAX_SHORTCUTS - NUM_DYNAMIC, NUM_DYNAMIC);
+                MAX_ITEMS - NUM_DYNAMIC, NUM_DYNAMIC);
         filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(3, 5),
-                MAX_SHORTCUTS - NUM_DYNAMIC, NUM_DYNAMIC);
+                MAX_ITEMS - NUM_DYNAMIC, NUM_DYNAMIC);
+    }
+
+    @Test
+    public void testDeDupeShortcutId() {
+        // Successfully remove one of the shortcuts
+        filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(3, 0), 2, 0, generateId(true, 1));
+        filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(0, 3), 0, 2, generateId(false, 1));
+        filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(2, 2), 2, 1, generateId(false, 1));
+        filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(2, 2), 1, 2, generateId(true, 1));
+        // Successfully keep all shortcuts when id doesn't exist
+        filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(3, 0), 3, 0, generateId(false, 1));
+        filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(3, 0), 3, 0, generateId(true, 4));
+        filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(2, 2), 2, 2, generateId(false, 4));
+        filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(2, 2), 2, 2, generateId(true, 4));
+    }
+
+    private String generateId(boolean isStatic, int rank) {
+        return (isStatic ? "static" : "dynamic") + rank;
     }
 
     private void filterShortcutsAndAssertNumStaticAndDynamic(
             List<ShortcutInfoCompat> shortcuts, int expectedStatic, int expectedDynamic) {
+        filterShortcutsAndAssertNumStaticAndDynamic(shortcuts, expectedStatic, expectedDynamic, null);
+    }
+
+    private void filterShortcutsAndAssertNumStaticAndDynamic(List<ShortcutInfoCompat> shortcuts,
+            int expectedStatic, int expectedDynamic, String shortcutIdToRemove) {
         Collections.shuffle(shortcuts);
-        List<ShortcutInfoCompat> filteredShortcuts = ShortcutFilter.sortAndFilterShortcuts(shortcuts);
+        List<ShortcutInfoCompat> filteredShortcuts = PopupPopulator.sortAndFilterShortcuts(
+                shortcuts, shortcutIdToRemove);
         assertIsSorted(filteredShortcuts);
 
         int numStatic = 0;
@@ -110,6 +136,7 @@
     private class Shortcut extends ShortcutInfoCompat {
         private boolean mIsStatic;
         private int mRank;
+        private String mId;
 
         public Shortcut(ShortcutInfo shortcutInfo) {
             super(shortcutInfo);
@@ -119,6 +146,7 @@
             this(null);
             mIsStatic = isStatic;
             mRank = rank;
+            mId = generateId(isStatic, rank);
         }
 
         @Override
@@ -135,5 +163,10 @@
         public int getRank() {
             return mRank;
         }
+
+        @Override
+        public String getId() {
+            return mId;
+        }
     }
 }
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/testcomponent/AppWidgetNoConfig.java b/tests/src/com/android/launcher3/testcomponent/AppWidgetNoConfig.java
new file mode 100644
index 0000000..9b320d8
--- /dev/null
+++ b/tests/src/com/android/launcher3/testcomponent/AppWidgetNoConfig.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.android.launcher3.testcomponent;
+
+import android.appwidget.AppWidgetProvider;
+
+/**
+ * A simple app widget without any configuration screen.
+ */
+public class AppWidgetNoConfig extends AppWidgetProvider {
+
+
+}
diff --git a/tests/src/com/android/launcher3/testcomponent/AppWidgetWithConfig.java b/tests/src/com/android/launcher3/testcomponent/AppWidgetWithConfig.java
new file mode 100644
index 0000000..033e6e6
--- /dev/null
+++ b/tests/src/com/android/launcher3/testcomponent/AppWidgetWithConfig.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.android.launcher3.testcomponent;
+
+/**
+ * A simple app widget with configuration sceen.
+ */
+public class AppWidgetWithConfig extends AppWidgetNoConfig {
+
+}
diff --git a/tests/src/com/android/launcher3/testcomponent/BaseTestingActivity.java b/tests/src/com/android/launcher3/testcomponent/BaseTestingActivity.java
new file mode 100644
index 0000000..904590c
--- /dev/null
+++ b/tests/src/com/android/launcher3/testcomponent/BaseTestingActivity.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.android.launcher3.testcomponent;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.util.TypedValue;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.LinearLayout.LayoutParams;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+/**
+ * Base activity with utility methods to help automate testing.
+ */
+public class BaseTestingActivity extends Activity implements View.OnClickListener {
+
+    public static final String SUFFIX_COMMAND = "-command";
+    public static final String EXTRA_METHOD = "method";
+    public static final String EXTRA_PARAM = "param_";
+
+    private static final int MARGIN_DP = 20;
+
+    private final String mAction = this.getClass().getName();
+
+    private LinearLayout mView;
+    private int mMargin;
+
+    private final BroadcastReceiver mCommandReceiver = new BroadcastReceiver() {
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            handleCommand(intent);
+        }
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mMargin = Math.round(TypedValue.applyDimension(
+                TypedValue.COMPLEX_UNIT_DIP, MARGIN_DP, getResources().getDisplayMetrics()));
+        mView = new LinearLayout(this);
+        mView.setPadding(mMargin, mMargin, mMargin, mMargin);
+        mView.setOrientation(LinearLayout.VERTICAL);
+        setContentView(mView);
+
+        registerReceiver(mCommandReceiver, new IntentFilter(mAction + SUFFIX_COMMAND));
+    }
+
+    protected void addButton(String title, String method) {
+        Button button = new Button(this);
+        button.setText(title);
+        button.setTag(method);
+        button.setOnClickListener(this);
+
+        LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+        lp.bottomMargin = mMargin;
+        mView.addView(button, lp);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        sendBroadcast(new Intent(mAction).putExtra(Intent.EXTRA_INTENT, getIntent()));
+    }
+
+    @Override
+    protected void onDestroy() {
+        unregisterReceiver(mCommandReceiver);
+        super.onDestroy();
+    }
+
+    @Override
+    public void onClick(View view) {
+        handleCommand(new Intent().putExtra(EXTRA_METHOD, (String) view.getTag()));
+    }
+
+    private void handleCommand(Intent cmd) {
+        String methodName = cmd.getStringExtra(EXTRA_METHOD);
+        try {
+            Method method = null;
+            for (Method m : this.getClass().getDeclaredMethods()) {
+                if (methodName.equals(m.getName()) &&
+                        !Modifier.isStatic(m.getModifiers()) &&
+                        Modifier.isPublic(m.getModifiers())) {
+                    method = m;
+                    break;
+                }
+            }
+            Object[] args = new Object[method.getParameterTypes().length];
+            Bundle extras = cmd.getExtras();
+            for (int i = 0; i < args.length; i++) {
+                args[i] = extras.get(EXTRA_PARAM + i);
+            }
+            method.invoke(this, args);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static Intent getCommandIntent(Class<?> clazz, String method) {
+        return new Intent(clazz.getName() + SUFFIX_COMMAND)
+                .putExtra(EXTRA_METHOD, method);
+    }
+}
diff --git a/tests/src/com/android/launcher3/testcomponent/RequestPinItemActivity.java b/tests/src/com/android/launcher3/testcomponent/RequestPinItemActivity.java
new file mode 100644
index 0000000..8580992
--- /dev/null
+++ b/tests/src/com/android/launcher3/testcomponent/RequestPinItemActivity.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.android.launcher3.testcomponent;
+
+import android.annotation.TargetApi;
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetManager;
+import android.content.ComponentName;
+import android.content.IntentSender;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.widget.RemoteViews;
+
+/**
+ * Sample activity to request pinning an item.
+ */
+@TargetApi(26)
+public class RequestPinItemActivity extends BaseTestingActivity {
+
+    private PendingIntent mCallback = null;
+    private String mShortcutId = "test-id";
+    private int mRemoteViewColor = Color.TRANSPARENT;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        addButton("Pin Shortcut", "pinShortcut");
+        addButton("Pin Widget without config ", "pinWidgetNoConfig");
+        addButton("Pin Widget with config", "pinWidgetWithConfig");
+    }
+
+    public void setCallback(PendingIntent callback) {
+        mCallback = callback;
+    }
+
+    public void setRemoteViewColor(int color) {
+        mRemoteViewColor = color;
+    }
+
+    public void setShortcutId(String id) {
+        mShortcutId = id;
+    }
+
+    public void pinShortcut() {
+        ShortcutManager sm = getSystemService(ShortcutManager.class);
+
+        // Generate icon
+        int r = sm.getIconMaxWidth() / 2;
+        Bitmap icon = Bitmap.createBitmap(r * 2, r * 2, Bitmap.Config.ARGB_8888);
+        Paint p = new Paint();
+        p.setColor(Color.RED);
+        new Canvas(icon).drawCircle(r, r, r, p);
+
+        ShortcutInfo info = new ShortcutInfo.Builder(this, mShortcutId)
+                .setIntent(getPackageManager().getLaunchIntentForPackage(getPackageName()))
+                .setIcon(Icon.createWithBitmap(icon))
+                .setShortLabel("Test shortcut")
+                .build();
+
+        IntentSender callback = mCallback == null ? null : mCallback.getIntentSender();
+        sm.requestPinShortcut(info, callback);
+    }
+
+    public void pinWidgetNoConfig() {
+        requestWidget(new ComponentName(this, AppWidgetNoConfig.class));
+    }
+
+    public void pinWidgetWithConfig() {
+        requestWidget(new ComponentName(this, AppWidgetWithConfig.class));
+    }
+
+    private void requestWidget(ComponentName cn) {
+        Bundle extras = null;
+        if (mRemoteViewColor != Color.TRANSPARENT) {
+            int layoutId = getResources().getIdentifier(
+                    "test_layout_appwidget_view", "layout", getPackageName());
+            RemoteViews views = new RemoteViews(getPackageName(), layoutId);
+            views.setInt(android.R.id.icon, "setBackgroundColor", mRemoteViewColor);
+            extras = new Bundle();
+            extras.putParcelable(AppWidgetManager.EXTRA_APPWIDGET_PREVIEW, views);
+        }
+
+        AppWidgetManager.getInstance(this).requestPinAppWidget(cn, extras, mCallback);
+    }
+}
diff --git a/tests/src/com/android/launcher3/testcomponent/WidgetConfigActivity.java b/tests/src/com/android/launcher3/testcomponent/WidgetConfigActivity.java
new file mode 100644
index 0000000..d76ad04
--- /dev/null
+++ b/tests/src/com/android/launcher3/testcomponent/WidgetConfigActivity.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.android.launcher3.testcomponent;
+
+import android.os.Bundle;
+
+/**
+ * Simple activity for widget configuration
+ */
+public class WidgetConfigActivity extends BaseTestingActivity {
+
+    public static final String SUFFIX_FINISH = "-finish";
+    public static final String EXTRA_CODE = "code";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        addButton("Cancel", "clickCancel");
+        addButton("OK", "clickOK");
+    }
+
+    public void clickCancel() {
+        setResult(RESULT_CANCELED);
+        finish();
+    }
+
+    public void clickOK() {
+        setResult(RESULT_OK);
+        finish();
+    }
+}
diff --git a/tests/src/com/android/launcher3/ui/AllAppsAppLaunchTest.java b/tests/src/com/android/launcher3/ui/AllAppsAppLaunchTest.java
index abe6b95..0ced7cf 100644
--- a/tests/src/com/android/launcher3/ui/AllAppsAppLaunchTest.java
+++ b/tests/src/com/android/launcher3/ui/AllAppsAppLaunchTest.java
@@ -1,13 +1,13 @@
 package com.android.launcher3.ui;
 
+import android.content.pm.LauncherActivityInfo;
+import android.os.Process;
 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 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;
 
@@ -17,14 +17,14 @@
 @LargeTest
 public class AllAppsAppLaunchTest extends LauncherInstrumentationTestCase {
 
-    private LauncherActivityInfoCompat mSettingsApp;
+    private LauncherActivityInfo mSettingsApp;
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
 
         mSettingsApp = LauncherAppsCompat.getInstance(mTargetContext)
-                .getActivityList("com.android.settings", UserHandleCompat.myUserHandle()).get(0);
+                .getActivityList("com.android.settings", Process.myUserHandle()).get(0);
     }
 
     public void testAppLauncher_portrait() throws Exception {
diff --git a/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java b/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java
index 56fc90a..9361750 100644
--- a/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java
+++ b/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java
@@ -1,13 +1,13 @@
 package com.android.launcher3.ui;
 
+import android.content.pm.LauncherActivityInfo;
+import android.os.Process;
 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 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;
 
@@ -17,14 +17,15 @@
 @LargeTest
 public class AllAppsIconToHomeTest extends LauncherInstrumentationTestCase {
 
-    private LauncherActivityInfoCompat mSettingsApp;
+    private LauncherActivityInfo mSettingsApp;
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
+        setDefaultLauncher();
 
         mSettingsApp = LauncherAppsCompat.getInstance(mTargetContext)
-                .getActivityList("com.android.settings", UserHandleCompat.myUserHandle()).get(0);
+                .getActivityList("com.android.settings", Process.myUserHandle()).get(0);
     }
 
     public void testDragIcon_portrait() throws Throwable {
@@ -47,7 +48,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..47b43f5 100644
--- a/tests/src/com/android/launcher3/ui/LauncherInstrumentationTestCase.java
+++ b/tests/src/com/android/launcher3/ui/LauncherInstrumentationTestCase.java
@@ -1,13 +1,31 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
 package com.android.launcher3.ui;
 
-import android.app.SearchManager;
-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.pm.ActivityInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.graphics.Point;
 import android.os.ParcelFileDescriptor;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.support.test.uiautomator.By;
@@ -19,30 +37,39 @@
 import android.test.InstrumentationTestCase;
 import android.view.MotionEvent;
 
-import com.android.launcher3.InvariantDeviceProfile;
 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.MainThreadExecutor;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.testcomponent.AppWidgetNoConfig;
+import com.android.launcher3.testcomponent.AppWidgetWithConfig;
 import com.android.launcher3.util.ManagedProfileHeuristic;
 
+import java.io.BufferedReader;
 import java.io.FileInputStream;
 import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
 import java.util.Locale;
 import java.util.concurrent.Callable;
-import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Base class for all instrumentation tests providing various utility methods.
  */
 public class LauncherInstrumentationTestCase extends InstrumentationTestCase {
 
+    public static final long DEFAULT_ACTIVITY_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
+    public static final long DEFAULT_BROADCAST_TIMEOUT_SECS = 5;
+
     public static final long DEFAULT_UI_TIMEOUT = 3000;
+    public static final long DEFAULT_WORKER_TIMEOUT_SECS = 5;
 
     protected UiDevice mDevice;
     protected Context mTargetContext;
@@ -74,11 +101,14 @@
      * Starts the launcher activity in the target package and returns the Launcher instance.
      */
     protected Launcher startLauncher() {
-        Intent homeIntent = new Intent(Intent.ACTION_MAIN)
+        return (Launcher) getInstrumentation().startActivitySync(getHomeIntent());
+    }
+
+    protected Intent getHomeIntent() {
+        return new Intent(Intent.ACTION_MAIN)
                 .addCategory(Intent.CATEGORY_HOME)
                 .setPackage(mTargetPackage)
                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        return (Launcher) getInstrumentation().startActivitySync(homeIntent);
     }
 
     /**
@@ -89,16 +119,31 @@
         if (mTargetContext.getPackageManager().checkPermission(
                 mTargetPackage, android.Manifest.permission.BIND_APPWIDGET)
                 != PackageManager.PERMISSION_GRANTED) {
-            ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation().executeShellCommand(
-                    "appwidget grantbind --package " + mTargetPackage);
-            // Read the input stream fully.
-            FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
-            while (fis.read() != -1);
-            fis.close();
+            runShellCommand("appwidget grantbind --package " + mTargetPackage);
         }
     }
 
     /**
+     * Sets the target launcher as default launcher.
+     */
+    protected void setDefaultLauncher() throws IOException {
+        ActivityInfo launcher = mTargetContext.getPackageManager()
+                .queryIntentActivities(getHomeIntent(), 0).get(0).activityInfo;
+        runShellCommand("cmd package set-home-activity " +
+                new ComponentName(launcher.packageName, launcher.name).flattenToString());
+    }
+
+    protected void runShellCommand(String command) throws IOException {
+        ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation()
+                .executeShellCommand(command);
+
+        // Read the input stream fully.
+        FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+        while (fis.read() != -1);
+        fis.close();
+    }
+
+    /**
      * Opens all apps and returns the recycler view
      */
     protected UiObject2 openAllApps() {
@@ -141,29 +186,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 +242,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,34 +257,32 @@
                 LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
         LauncherSettings.Settings.call(mTargetContext.getContentResolver(),
                 LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG);
-        LauncherClings.markFirstRunClingDismissed(mTargetContext);
-        ManagedProfileHeuristic.markExistingUsersForNoFolderCreation(mTargetContext);
+        resetLoaderState();
+    }
 
-        runTestOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                // Reset the loader state
-                LauncherAppState.getInstance().getModel().resetLoadedState(true, true);
-            }
-        });
+    protected void resetLoaderState() {
+        try {
+            runTestOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    ManagedProfileHeuristic.markExistingUsersForNoFolderCreation(mTargetContext);
+                    LauncherAppState.getInstance(mTargetContext).getModel().forceReload();
+                }
+            });
+        } catch (Throwable t) {
+            throw new IllegalArgumentException(t);
+        }
     }
 
     /**
      * Runs the callback on the UI thread and returns the result.
      */
     protected <T> T getOnUiThread(final Callable<T> callback) {
-        final AtomicReference<T> result = new AtomicReference<>(null);
         try {
-            runTestOnUiThread(new Runnable() {
-                @Override
-                public void run() {
-                    try {
-                        result.set(callback.call());
-                    } catch (Exception e) { }
-                }
-            });
-        } catch (Throwable t) { }
-        return result.get();
+            return new MainThreadExecutor().submit(callback).get();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
     }
 
     /**
@@ -224,36 +290,14 @@
      * @param hasConfigureScreen if true, a provider with a config screen is returned.
      */
     protected LauncherAppWidgetProviderInfo findWidgetProvider(final boolean hasConfigureScreen) {
-        LauncherAppWidgetProviderInfo info = getOnUiThread(new Callable<LauncherAppWidgetProviderInfo>() {
+        LauncherAppWidgetProviderInfo info =
+                getOnUiThread(new Callable<LauncherAppWidgetProviderInfo>() {
             @Override
             public LauncherAppWidgetProviderInfo call() throws Exception {
-                InvariantDeviceProfile idv =
-                        LauncherAppState.getInstance().getInvariantDeviceProfile();
-
-                ComponentName searchComponent = ((SearchManager) mTargetContext
-                        .getSystemService(Context.SEARCH_SERVICE)).getGlobalSearchActivity();
-                String searchPackage = searchComponent == null
-                        ? null : searchComponent.getPackageName();
-
-                for (AppWidgetProviderInfo info :
-                        AppWidgetManagerCompat.getInstance(mTargetContext).getAllProviders()) {
-                    if ((info.configure != null) ^ hasConfigureScreen) {
-                        continue;
-                    }
-                    // Exclude the widgets in search package, as Launcher already binds them in
-                    // QSB, so they can cause conflicts.
-                    if (info.provider.getPackageName().equals(searchPackage)) {
-                        continue;
-                    }
-                    LauncherAppWidgetProviderInfo widgetInfo = LauncherAppWidgetProviderInfo
-                            .fromProviderInfo(mTargetContext, info);
-                    if (widgetInfo.minSpanX >= idv.numColumns
-                            || widgetInfo.minSpanY >= idv.numRows) {
-                        continue;
-                    }
-                    return widgetInfo;
-                }
-                return null;
+                ComponentName cn = new ComponentName(getInstrumentation().getContext(),
+                        hasConfigureScreen ? AppWidgetWithConfig.class : AppWidgetNoConfig.class);
+                return AppWidgetManagerCompat.getInstance(mTargetContext)
+                        .findProvider(cn, Process.myUserHandle());
             }
         });
         if (info == null) {
@@ -270,4 +314,35 @@
         String name = mTargetContext.getResources().getResourceEntryName(id);
         return By.res(mTargetPackage, name);
     }
+
+
+    /**
+     * Broadcast receiver which blocks until the result is received.
+     */
+    public class BlockingBroadcastReceiver extends BroadcastReceiver {
+
+        private final CountDownLatch latch = new CountDownLatch(1);
+        private Intent mIntent;
+
+        public BlockingBroadcastReceiver(String action) {
+            mTargetContext.registerReceiver(this, new IntentFilter(action));
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            mIntent = intent;
+            latch.countDown();
+        }
+
+        public Intent blockingGetIntent() throws InterruptedException {
+            latch.await(DEFAULT_BROADCAST_TIMEOUT_SECS, TimeUnit.SECONDS);
+            mTargetContext.unregisterReceiver(this);
+            return mIntent;
+        }
+
+        public Intent blockingGetExtraIntent() throws InterruptedException {
+            Intent intent = blockingGetIntent();
+            return intent == null ? null : (Intent) intent.getParcelableExtra(Intent.EXTRA_INTENT);
+        }
+    }
 }
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..3a0b613
--- /dev/null
+++ b/tests/src/com/android/launcher3/ui/ShortcutsLaunchTest.java
@@ -0,0 +1,69 @@
+package com.android.launcher3.ui;
+
+import android.content.pm.LauncherActivityInfo;
+import android.graphics.Point;
+import android.os.Process;
+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.LauncherAppsCompat;
+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 LauncherActivityInfo mSettingsApp;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        setDefaultLauncher();
+
+        mSettingsApp = LauncherAppsCompat.getInstance(mTargetContext)
+                .getActivityList("com.android.settings", Process.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.bubble_text));
+        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..5d86d1e
--- /dev/null
+++ b/tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java
@@ -0,0 +1,75 @@
+package com.android.launcher3.ui;
+
+import android.content.pm.LauncherActivityInfo;
+import android.graphics.Point;
+import android.os.Process;
+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.LauncherAppsCompat;
+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 LauncherActivityInfo mSettingsApp;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        setDefaultLauncher();
+
+        mSettingsApp = LauncherAppsCompat.getInstance(mTargetContext)
+                .getActivityList("com.android.settings", Process.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.bubble_text));
+        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/ui/widget/AddConfigWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
new file mode 100644
index 0000000..0b4e34f
--- /dev/null
+++ b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.android.launcher3.ui.widget;
+
+import android.app.Activity;
+import android.app.Application;
+import android.appwidget.AppWidgetManager;
+import android.content.Intent;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiObject2;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.view.View;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.MainThreadExecutor;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.testcomponent.WidgetConfigActivity;
+import com.android.launcher3.ui.LauncherInstrumentationTestCase;
+import com.android.launcher3.util.Condition;
+import com.android.launcher3.util.SimpleActivityMonitor;
+import com.android.launcher3.util.Wait;
+import com.android.launcher3.widget.WidgetCell;
+
+import java.util.concurrent.Callable;
+
+/**
+ * Test to verify widget configuration is properly shown.
+ */
+@LargeTest
+public class AddConfigWidgetTest extends LauncherInstrumentationTestCase {
+
+    private LauncherAppWidgetProviderInfo mWidgetInfo;
+    private SimpleActivityMonitor mActivityMonitor;
+    private MainThreadExecutor mMainThreadExecutor;
+    private AppWidgetManager mAppWidgetManager;
+
+    private int mWidgetId;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mWidgetInfo = findWidgetProvider(true /* hasConfigureScreen */);
+        mActivityMonitor = new SimpleActivityMonitor();
+        ((Application) getInstrumentation().getTargetContext().getApplicationContext())
+                .registerActivityLifecycleCallbacks(mActivityMonitor);
+        mMainThreadExecutor = new MainThreadExecutor();
+        mAppWidgetManager = AppWidgetManager.getInstance(mTargetContext);
+
+        grantWidgetPermission();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        ((Application) getInstrumentation().getTargetContext().getApplicationContext())
+                .unregisterActivityLifecycleCallbacks(mActivityMonitor);
+        super.tearDown();
+    }
+
+    public void testWidgetConfig() throws Throwable {
+        runTest(false, true);
+    }
+
+    public void testWidgetConfig_rotate() throws Throwable {
+        runTest(true, true);
+    }
+
+    public void testConfigCancelled() throws Throwable {
+        runTest(false, false);
+    }
+
+    public void testConfigCancelled_rotate() throws Throwable {
+        runTest(true, false);
+    }
+
+    /**
+     * @param rotateConfig should the config screen be rotated
+     * @param acceptConfig accept the config activity
+     */
+    private void runTest(boolean rotateConfig, boolean acceptConfig) throws Throwable {
+        lockRotation(true);
+
+        clearHomescreen();
+        startLauncher();
+
+        // Open widget tray and wait for load complete.
+        final UiObject2 widgetContainer = openWidgetsTray();
+        assertTrue(Wait.atMost(Condition.minChildCount(widgetContainer, 2), DEFAULT_UI_TIMEOUT));
+
+        // Drag widget to homescreen
+        WidgetConfigStartupMonitor monitor = new WidgetConfigStartupMonitor();
+        UiObject2 widget = scrollAndFind(widgetContainer, By.clazz(WidgetCell.class)
+                .hasDescendant(By.text(mWidgetInfo.getLabel(mTargetContext.getPackageManager()))));
+        dragToWorkspace(widget, false);
+        // Widget id for which the config activity was opened
+        mWidgetId = monitor.getWidgetId();
+
+        if (rotateConfig) {
+            // Rotate the screen and verify that the config activity is recreated
+            monitor = new WidgetConfigStartupMonitor();
+            lockRotation(false);
+            assertEquals(mWidgetId, monitor.getWidgetId());
+        }
+
+        // Verify that the widget id is valid and bound
+        assertNotNull(mAppWidgetManager.getAppWidgetInfo(mWidgetId));
+
+        setResult(acceptConfig);
+        if (acceptConfig) {
+            assertTrue(Wait.atMost(new WidgetSearchCondition(), DEFAULT_ACTIVITY_TIMEOUT));
+            assertNotNull(mAppWidgetManager.getAppWidgetInfo(mWidgetId));
+        } else {
+            // Verify that the widget id is deleted.
+            assertTrue(Wait.atMost(new Condition() {
+                @Override
+                public boolean isTrue() throws Throwable {
+                    return mAppWidgetManager.getAppWidgetInfo(mWidgetId) == null;
+                }
+            }, DEFAULT_ACTIVITY_TIMEOUT));
+        }
+    }
+
+    private void setResult(boolean success) {
+
+        getInstrumentation().getTargetContext().sendBroadcast(
+                WidgetConfigActivity.getCommandIntent(WidgetConfigActivity.class,
+                        success ? "clickOK" : "clickCancel"));
+    }
+
+    /**
+     * Condition for searching widget id
+     */
+    private class WidgetSearchCondition extends Condition
+            implements Callable<Boolean>, Workspace.ItemOperator {
+
+        @Override
+        public boolean isTrue() throws Throwable {
+            return mMainThreadExecutor.submit(this).get();
+        }
+
+        @Override
+        public boolean evaluate(ItemInfo info, View view) {
+            return info instanceof LauncherAppWidgetInfo &&
+                    ((LauncherAppWidgetInfo) info).providerName.equals(mWidgetInfo.provider) &&
+                    ((LauncherAppWidgetInfo) info).appWidgetId == mWidgetId;
+        }
+
+        @Override
+        public Boolean call() throws Exception {
+            // Find the resumed launcher
+            Launcher launcher = null;
+            for (Activity a : mActivityMonitor.resumed) {
+                if (a instanceof Launcher) {
+                    launcher = (Launcher) a;
+                }
+            }
+            if (launcher == null) {
+                return false;
+            }
+            return launcher.getWorkspace().getFirstMatch(this) != null;
+        }
+    }
+
+    /**
+     * Broadcast receiver for receiving widget config activity status.
+     */
+    private class WidgetConfigStartupMonitor extends BlockingBroadcastReceiver {
+
+        public WidgetConfigStartupMonitor() {
+            super(WidgetConfigActivity.class.getName());
+        }
+
+        public int getWidgetId() throws InterruptedException {
+            Intent intent = blockingGetExtraIntent();
+            assertNotNull(intent);
+            assertEquals(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE, intent.getAction());
+            int widgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
+                    LauncherAppWidgetInfo.NO_ID);
+            assertNotSame(widgetId, LauncherAppWidgetInfo.NO_ID);
+            return widgetId;
+        }
+    }
+}
diff --git a/tests/src/com/android/launcher3/ui/AddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
similarity index 72%
rename from tests/src/com/android/launcher3/ui/AddWidgetTest.java
rename to tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
index a0ca60c..3c92c57 100644
--- a/tests/src/com/android/launcher3/ui/AddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
@@ -1,16 +1,31 @@
-package com.android.launcher3.ui;
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.android.launcher3.ui.widget;
 
 import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.UiObject2;
 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;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.Workspace.ItemOperator;
+import com.android.launcher3.ui.LauncherInstrumentationTestCase;
 import com.android.launcher3.util.Condition;
 import com.android.launcher3.util.Wait;
 import com.android.launcher3.widget.WidgetCell;
@@ -26,6 +41,7 @@
     @Override
     protected void setUp() throws Exception {
         super.setUp();
+        grantWidgetPermission();
 
         widgetInfo = findWidgetProvider(false /* hasConfigureScreen */);
     }
@@ -51,7 +67,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/BindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
similarity index 88%
rename from tests/src/com/android/launcher3/BindWidgetTest.java
rename to tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
index 5c5069f..df2b662 100644
--- a/tests/src/com/android/launcher3/BindWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
@@ -1,6 +1,20 @@
-package com.android.launcher3;
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.android.launcher3.ui.widget;
 
-import android.annotation.TargetApi;
 import android.appwidget.AppWidgetHost;
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -9,15 +23,23 @@
 import android.content.pm.PackageInstaller.SessionParams;
 import android.content.pm.PackageManager;
 import android.database.Cursor;
-import android.os.Build;
 import android.os.Bundle;
 import android.support.test.uiautomator.UiSelector;
 import android.test.suitebuilder.annotation.LargeTest;
 
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppWidgetHostView;
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.PendingAppWidgetHostView;
+import com.android.launcher3.Workspace;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.PackageInstallerCompat;
 import com.android.launcher3.ui.LauncherInstrumentationTestCase;
-import com.android.launcher3.util.ManagedProfileHeuristic;
+import com.android.launcher3.util.ContentWriter;
+import com.android.launcher3.util.LooperExecuter;
 import com.android.launcher3.widget.PendingAddWidgetInfo;
 import com.android.launcher3.widget.WidgetHostViewLoader;
 
@@ -32,7 +54,6 @@
  * Note running these tests will clear the workspace on the device.
  */
 @LargeTest
-@TargetApi(Build.VERSION_CODES.LOLLIPOP)
 public class BindWidgetTest extends LauncherInstrumentationTestCase {
 
     private ContentResolver mResolver;
@@ -218,28 +239,16 @@
         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(mTargetContext));
+        resetLoaderState();
 
-        // Reset loader
-        try {
-            runTestOnUiThread(new Runnable() {
-                @Override
-                public void run() {
-                    LauncherClings.markFirstRunClingDismissed(mTargetContext);
-                    ManagedProfileHeuristic.markExistingUsersForNoFolderCreation(mTargetContext);
-                    LauncherAppState.getInstance().getModel().resetLoadedState(true, true);
-                }
-            });
-        } catch (Throwable t) {
-            throw new IllegalArgumentException(t);
-        }
         // Launch the home activity
         startLauncher();
         // Verify UI
@@ -264,13 +273,13 @@
         item.spanY = info.minSpanY;
         item.minSpanX = info.minSpanX;
         item.minSpanY = info.minSpanY;
-        item.user = mWidgetManager.getUser(info);
+        item.user = info.getUser();
         item.cellX = 0;
         item.cellY = 1;
         item.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
 
         if (bindWidget) {
-            PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(mTargetContext, info);
+            PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(info);
             pendingInfo.spanX = item.spanX;
             pendingInfo.spanY = item.spanY;
             pendingInfo.minSpanX = item.minSpanX;
@@ -330,14 +339,11 @@
     /**
      * Blocks the current thread until all the jobs in the main worker thread are complete.
      */
-    private void waitUntilLoaderIdle() throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-        LauncherModel.sWorker.post(new Runnable() {
-            @Override
-            public void run() {
-                latch.countDown();
-            }
-        });
-        assertTrue(latch.await(5, TimeUnit.SECONDS));
+    private void waitUntilLoaderIdle() throws Exception {
+        new LooperExecuter(LauncherModel.getWorkerLooper())
+                .submit(new Runnable() {
+                    @Override
+                    public void run() { }
+                }).get(DEFAULT_WORKER_TIMEOUT_SECS, TimeUnit.SECONDS);
     }
 }
diff --git a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
new file mode 100644
index 0000000..b798dfa
--- /dev/null
+++ b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.android.launcher3.ui.widget;
+
+import android.app.Activity;
+import android.app.Application;
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetManager;
+import android.content.Intent;
+import android.graphics.Color;
+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.View;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.MainThreadExecutor;
+import com.android.launcher3.R;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.Workspace.ItemOperator;
+import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.testcomponent.AppWidgetNoConfig;
+import com.android.launcher3.testcomponent.AppWidgetWithConfig;
+import com.android.launcher3.testcomponent.RequestPinItemActivity;
+import com.android.launcher3.ui.LauncherInstrumentationTestCase;
+import com.android.launcher3.util.Condition;
+import com.android.launcher3.util.SimpleActivityMonitor;
+import com.android.launcher3.util.Wait;
+import com.android.launcher3.widget.WidgetCell;
+
+import java.util.UUID;
+import java.util.concurrent.Callable;
+
+/**
+ * Test to verify pin item request flow.
+ */
+@LargeTest
+public class RequestPinItemTest  extends LauncherInstrumentationTestCase {
+
+    private SimpleActivityMonitor mActivityMonitor;
+    private MainThreadExecutor mMainThreadExecutor;
+
+    private String mCallbackAction;
+    private String mShortcutId;
+    private int mAppWidgetId;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        grantWidgetPermission();
+        setDefaultLauncher();
+
+        mActivityMonitor = new SimpleActivityMonitor();
+        ((Application) getInstrumentation().getTargetContext().getApplicationContext())
+                .registerActivityLifecycleCallbacks(mActivityMonitor);
+        mMainThreadExecutor = new MainThreadExecutor();
+
+        mCallbackAction = UUID.randomUUID().toString();
+        mShortcutId = UUID.randomUUID().toString();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        ((Application) getInstrumentation().getTargetContext().getApplicationContext())
+                .unregisterActivityLifecycleCallbacks(mActivityMonitor);
+        super.tearDown();
+    }
+
+    public void testPinWidgetNoConfig() throws Throwable {
+        runTest("pinWidgetNoConfig", true, new ItemOperator() {
+            @Override
+            public boolean evaluate(ItemInfo info, View view) {
+                return info instanceof LauncherAppWidgetInfo &&
+                        ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId &&
+                        ((LauncherAppWidgetInfo) info).providerName.getClassName()
+                                .equals(AppWidgetNoConfig.class.getName());
+            }
+        });
+    }
+
+    public void testPinWidgetNoConfig_customPreview() throws Throwable {
+        // Command to set custom preview
+        Intent command =  RequestPinItemActivity.getCommandIntent(
+                RequestPinItemActivity.class, "setRemoteViewColor").putExtra(
+                RequestPinItemActivity.EXTRA_PARAM + "0", Color.RED);
+
+        runTest("pinWidgetNoConfig", true, new ItemOperator() {
+            @Override
+            public boolean evaluate(ItemInfo info, View view) {
+                return info instanceof LauncherAppWidgetInfo &&
+                        ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId &&
+                        ((LauncherAppWidgetInfo) info).providerName.getClassName()
+                                .equals(AppWidgetNoConfig.class.getName());
+            }
+        }, command);
+    }
+
+    public void testPinWidgetWithConfig() throws Throwable {
+        runTest("pinWidgetWithConfig", true, new ItemOperator() {
+            @Override
+            public boolean evaluate(ItemInfo info, View view) {
+                return info instanceof LauncherAppWidgetInfo &&
+                        ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId &&
+                        ((LauncherAppWidgetInfo) info).providerName.getClassName()
+                                .equals(AppWidgetWithConfig.class.getName());
+            }
+        });
+    }
+
+    public void testPinShortcut() throws Throwable {
+        // Command to set the shortcut id
+        Intent command = RequestPinItemActivity.getCommandIntent(
+                RequestPinItemActivity.class, "setShortcutId").putExtra(
+                RequestPinItemActivity.EXTRA_PARAM + "0", mShortcutId);
+
+        runTest("pinShortcut", false, new ItemOperator() {
+            @Override
+            public boolean evaluate(ItemInfo info, View view) {
+                return info instanceof ShortcutInfo &&
+                        info.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT &&
+                        ShortcutKey.fromItemInfo(info).getId().equals(mShortcutId);
+            }
+        }, command);
+    }
+
+    private void runTest(String activityMethod, boolean isWidget, ItemOperator itemMatcher,
+            Intent... commandIntents) throws Throwable {
+        if (!Utilities.isAtLeastO()) {
+            return;
+        }
+        lockRotation(true);
+
+        clearHomescreen();
+        startLauncher();
+
+        // Open all apps and wait for load complete
+        final UiObject2 appsContainer = openAllApps();
+        assertTrue(Wait.atMost(Condition.minChildCount(appsContainer, 2), DEFAULT_UI_TIMEOUT));
+
+        // Open Pin item activity
+        BlockingBroadcastReceiver openMonitor = new BlockingBroadcastReceiver(
+                RequestPinItemActivity.class.getName());
+        scrollAndFind(appsContainer, By.text("Test Pin Item")).click();
+        assertNotNull(openMonitor.blockingGetExtraIntent());
+
+        // Set callback
+        PendingIntent callback = PendingIntent.getBroadcast(mTargetContext, 0,
+                new Intent(mCallbackAction), PendingIntent.FLAG_ONE_SHOT);
+        mTargetContext.sendBroadcast(RequestPinItemActivity.getCommandIntent(
+                RequestPinItemActivity.class, "setCallback").putExtra(
+                RequestPinItemActivity.EXTRA_PARAM + "0", callback));
+
+        for (Intent command : commandIntents) {
+            mTargetContext.sendBroadcast(command);
+        }
+
+        // call the requested method to start the flow
+        mTargetContext.sendBroadcast(RequestPinItemActivity.getCommandIntent(
+                RequestPinItemActivity.class, activityMethod));
+        UiObject2 widgetCell = mDevice.wait(
+                Until.findObject(By.clazz(WidgetCell.class)), DEFAULT_ACTIVITY_TIMEOUT);
+        assertNotNull(widgetCell);
+
+        // Accept confirmation:
+        BlockingBroadcastReceiver resultReceiver = new BlockingBroadcastReceiver(mCallbackAction);
+        mDevice.wait(Until.findObject(By.text(mTargetContext.getString(
+                R.string.place_automatically).toUpperCase())), DEFAULT_UI_TIMEOUT).click();
+        Intent result = resultReceiver.blockingGetIntent();
+        assertNotNull(result);
+        mAppWidgetId = result.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
+        if (isWidget) {
+            assertNotSame(-1, mAppWidgetId);
+        }
+
+        // Go back to home
+        mTargetContext.startActivity(getHomeIntent());
+        assertTrue(Wait.atMost(new ItemSearchCondition(itemMatcher), DEFAULT_ACTIVITY_TIMEOUT));
+    }
+
+    /**
+     * Condition for for an item
+     */
+    private class ItemSearchCondition extends Condition implements Callable<Boolean> {
+
+        private final ItemOperator mOp;
+
+        ItemSearchCondition(ItemOperator op) {
+            mOp = op;
+        }
+
+        @Override
+        public boolean isTrue() throws Throwable {
+            return mMainThreadExecutor.submit(this).get();
+        }
+
+        @Override
+        public Boolean call() throws Exception {
+            // Find the resumed launcher
+            Launcher launcher = null;
+            for (Activity a : mActivityMonitor.resumed) {
+                if (a instanceof Launcher) {
+                    launcher = (Launcher) a;
+                }
+            }
+            if (launcher == null) {
+                return false;
+            }
+            return launcher.getWorkspace().getFirstMatch(mOp) != null;
+        }
+    }
+}
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() {
diff --git a/tests/src/com/android/launcher3/util/SimpleActivityMonitor.java b/tests/src/com/android/launcher3/util/SimpleActivityMonitor.java
new file mode 100644
index 0000000..6154ab6
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/SimpleActivityMonitor.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.android.launcher3.util;
+
+import android.app.Activity;
+import android.app.Application.*;
+import android.os.Bundle;
+
+import java.util.ArrayList;
+
+/**
+ * Simple monitor to keep a list of active activities.
+ */
+public class SimpleActivityMonitor implements ActivityLifecycleCallbacks {
+
+    public final ArrayList<Activity> created = new ArrayList<>();
+    public final ArrayList<Activity> started = new ArrayList<>();
+    public final ArrayList<Activity> resumed = new ArrayList<>();
+
+    @Override
+    public void onActivityCreated(Activity activity, Bundle bundle) {
+        created.add(activity);
+    }
+
+    @Override
+    public void onActivityStarted(Activity activity) {
+        started.add(activity);
+    }
+
+    @Override
+    public void onActivityResumed(Activity activity) {
+        resumed.add(activity);
+    }
+
+    @Override
+    public void onActivityPaused(Activity activity) {
+        resumed.remove(activity);
+    }
+
+    @Override
+    public void onActivityStopped(Activity activity) {
+        started.remove(activity);
+    }
+
+    @Override
+    public void onActivitySaveInstanceState(Activity activity, Bundle bundle) { }
+
+    @Override
+    public void onActivityDestroyed(Activity activity) {
+        created.remove(activity);
+    }
+}