Merge mainline-prod into master

$ git merge -s ours origin/mainline-prod

All commits are from upstream.

Bug: 168176832
Test: presubmit
Change-Id: I6c35a9744127316d1e26d5553a859e2e3fdaf270
diff --git a/Android.bp b/Android.bp
index e132854..4265a44 100644
--- a/Android.bp
+++ b/Android.bp
@@ -19,6 +19,7 @@
         "androidx.test.runner",
         "androidx.test.rules",
         "androidx.test.uiautomator_uiautomator",
+        "androidx.preference_preference",
         "SystemUISharedLib",
     ],
     srcs: [
diff --git a/Android.mk b/Android.mk
index 7805b32..25f5412 100644
--- a/Android.mk
+++ b/Android.mk
@@ -28,6 +28,7 @@
     androidx.recyclerview_recyclerview \
     androidx.dynamicanimation_dynamicanimation \
     androidx.preference_preference \
+    androidx.slice_slice-view \
     iconloader_base
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
@@ -48,7 +49,7 @@
 LOCAL_PROTO_JAVA_OUTPUT_PARAMS := enum_style=java
 
 LOCAL_SDK_VERSION := current
-LOCAL_MIN_SDK_VERSION := 21
+LOCAL_MIN_SDK_VERSION := 26
 LOCAL_MODULE := Launcher3CommonDepsLib
 LOCAL_PRIVILEGED_MODULE := true
 LOCAL_MANIFEST_FILE := AndroidManifest-common.xml
@@ -77,7 +78,7 @@
 LOCAL_PROGUARD_ENABLED := disabled
 
 LOCAL_SDK_VERSION := current
-LOCAL_MIN_SDK_VERSION := 21
+LOCAL_MIN_SDK_VERSION := 26
 LOCAL_PACKAGE_NAME := Launcher3
 LOCAL_PRIVILEGED_MODULE := true
 LOCAL_SYSTEM_EXT_MODULE := true
@@ -108,7 +109,7 @@
 LOCAL_PROGUARD_FLAG_FILES := proguard.flags
 
 LOCAL_SDK_VERSION := current
-LOCAL_MIN_SDK_VERSION := 21
+LOCAL_MIN_SDK_VERSION := 26
 LOCAL_PACKAGE_NAME := Launcher3Go
 LOCAL_PRIVILEGED_MODULE := true
 LOCAL_SYSTEM_EXT_MODULE := true
@@ -149,12 +150,9 @@
 LOCAL_SRC_FILES := \
     $(call all-java-files-under, src) \
     $(call all-java-files-under, quickstep/src) \
-    $(call all-java-files-under, quickstep/recents_ui_overrides/src) \
     $(call all-java-files-under, src_shortcuts_overrides)
 
-LOCAL_RESOURCE_DIR := \
-    $(LOCAL_PATH)/quickstep/res \
-    $(LOCAL_PATH)/quickstep/recents_ui_overrides/res
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/quickstep/res
 LOCAL_PROGUARD_ENABLED := disabled
 
 
@@ -183,9 +181,7 @@
 LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3
 LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3
 
-LOCAL_RESOURCE_DIR := \
-    $(LOCAL_PATH)/quickstep/res \
-    $(LOCAL_PATH)/quickstep/recents_ui_overrides/res
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/quickstep/res
 
 LOCAL_FULL_LIBS_MANIFEST_FILES := \
     $(LOCAL_PATH)/quickstep/AndroidManifest-launcher.xml \
@@ -220,12 +216,10 @@
 LOCAL_SRC_FILES := \
     $(call all-java-files-under, src) \
     $(call all-java-files-under, quickstep/src) \
-    $(call all-java-files-under, quickstep/recents_ui_overrides/src) \
     $(call all-java-files-under, go/src)
 
 LOCAL_RESOURCE_DIR := \
     $(LOCAL_PATH)/quickstep/res \
-    $(LOCAL_PATH)/quickstep/recents_ui_overrides/res \
     $(LOCAL_PATH)/go/res
 
 LOCAL_PROGUARD_FLAG_FILES := proguard.flags
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index dd0fc21..97e3786 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -29,12 +29,6 @@
     at compile time. Note that the components defined in AndroidManifest.xml are also required,
     with some minor changed based on the derivative app.
     -->
-    <permission
-        android:name="com.android.launcher.permission.INSTALL_SHORTCUT"
-        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
-        android:protectionLevel="dangerous"
-        android:label="@string/permlab_install_shortcut"
-        android:description="@string/permdesc_install_shortcut" />
 
     <uses-permission android:name="android.permission.CALL_PHONE" />
     <uses-permission android:name="android.permission.SET_WALLPAPER" />
@@ -45,9 +39,6 @@
     <uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
     <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
-
-    <!-- TODO(b/150802536): Enabled only for ENABLE_FIXED_ROTATION_TRANSFORM feature flag -->
-    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
     
     <!--
     Permissions required for read/write access to the workspace data. These permission name
@@ -82,26 +73,18 @@
         android:restoreAnyVersion="true"
         android:supportsRtl="true" >
 
-        <!-- 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: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" >
+            android:name="com.android.launcher3.SessionCommitReceiver"
+            android:exported="true">
             <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" >
+        <receiver android:name="com.android.launcher3.AppWidgetsRestoredReceiver"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.appwidget.action.APPWIDGET_HOST_RESTORED"/>
             </intent-filter>
@@ -116,7 +99,7 @@
         <service
             android:name="com.android.launcher3.notification.NotificationListener"
             android:label="@string/notification_dots_service_title"
-            android:enabled="@bool/notification_dots_enabled"
+            android:exported="true"
             android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
             <intent-filter>
                 <action android:name="android.service.notification.NotificationListenerService" />
@@ -130,6 +113,7 @@
             android:theme="@style/AppItemActivityTheme"
             android:excludeFromRecents="true"
             android:autoRemoveFromRecents="true"
+            android:exported="true"
             android:label="@string/action_add_to_workspace" >
             <intent-filter>
                 <action android:name="android.content.pm.action.CONFIRM_PIN_SHORTCUT" />
@@ -156,8 +140,7 @@
         <provider
             android:name="com.android.launcher3.graphics.GridOptionsProvider"
             android:authorities="${packageName}.grid_control"
-            android:exported="true"
-            android:enabled="false" />
+            android:exported="true" />
 
         <!--
         The settings activity. To extend point settings_fragment_name to appropriate fragment class
@@ -166,6 +149,7 @@
             android:name="com.android.launcher3.settings.SettingsActivity"
             android:label="@string/settings_button_text"
             android:theme="@style/HomeSettingsTheme"
+            android:exported="true"
             android:autoRemoveFromRecents="true">
             <intent-filter>
                 <action android:name="android.intent.action.APPLICATION_PREFERENCES" />
@@ -188,6 +172,7 @@
             android:name="com.android.launcher3.secondarydisplay.SecondaryDisplayLauncher"
             android:theme="@style/AppTheme"
             android:launchMode="singleTop"
+            android:exported="true"
             android:enabled="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index b031ffb..97bce9c 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -20,7 +20,7 @@
 <manifest
     xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.launcher3">
-    <uses-sdk android:targetSdkVersion="29" android:minSdkVersion="25"/>
+    <uses-sdk android:targetSdkVersion="29" android:minSdkVersion="26"/>
     <!--
     Manifest entries specific to Launcher3. This is merged with AndroidManifest-common.xml.
     Refer comments around specific entries on how to extend individual components.
@@ -49,10 +49,11 @@
             android:stateNotNeeded="true"
             android:windowSoftInputMode="adjustPan"
             android:screenOrientation="unspecified"
-            android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize"
+            android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize|uiMode"
             android:resizeableActivity="true"
             android:resumeWhilePausing="true"
             android:taskAffinity=""
+            android:exported="true"
             android:enabled="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2
deleted file mode 100644
index e69de29..0000000
--- a/MODULE_LICENSE_APACHE2
+++ /dev/null
diff --git a/OWNERS b/OWNERS
index 3069afa..1d6ad8c 100644
--- a/OWNERS
+++ b/OWNERS
@@ -28,6 +28,7 @@
 peanutbutter@google.com
 xuqiu@google.com
 sreyasr@google.com
+thiruram@google.com
 
 per-file FeatureFlags.java, globs = set noparent
 per-file FeatureFlags.java = sunnygoyal@google.com, winsonc@google.com, zakcohen@google.com, mrcasey@google.com, adamcohen@google.com, hyunyoungs@google.com
diff --git a/buglist.txt b/buglist.txt
new file mode 100644
index 0000000..382ccfd
--- /dev/null
+++ b/buglist.txt
@@ -0,0 +1,38 @@
+160759508
+144170434
+144170434
+161801331
+162564471
+160361464
+161901771
+160568387
+162623012
+162012217
+160718310
+160361464
+160718310
+162012217
+160748731
+154951045
+162861289
+149934536
+149934536
+144170434
+144170434
+162454040
+161273376
+149934536
+149934536
+158701272
+162871508
+149934536
+161939759
+154964045
+161536946
+149934536
+161685099
+162812884
+162480567
+162480567
+149934536
+149934536
diff --git a/buglist_unique.txt b/buglist_unique.txt
new file mode 100644
index 0000000..d33f4f1
--- /dev/null
+++ b/buglist_unique.txt
@@ -0,0 +1,24 @@
+144170434
+149934536
+154951045
+154964045
+158701272
+160361464
+160568387
+160718310
+160748731
+160759508
+161273376
+161536946
+161685099
+161801331
+161901771
+161939759
+162012217
+162454040
+162480567
+162564471
+162623012
+162812884
+162861289
+162871508
diff --git a/buglist_with_title.txt b/buglist_with_title.txt
new file mode 100644
index 0000000..aa8b413
--- /dev/null
+++ b/buglist_with_title.txt
@@ -0,0 +1,24 @@
+144170434  twickham  P1        FIXED   Improve Overview ->  Home transition ----
+149934536  twickham  P2        FIXED   Update gesture nav pullback logic ----
+154951045  peanutbutter  P1        FIXED   Odd animation occuring at times when swiping to home ----
+154964045  awickham  P2        FIXED   "Clear all" text is not in the middle of app's window vertically ----
+158701272  twickham  P4        FIXED   Discontinuities when long-swiping to home ----
+160361464  tracyzhou  P2        FIXED   Place launcher above the target app in live tile mode ----
+160568387  twickham  P2        FIXED   Can't get to app switcher by swiping up (motion pause not detected) ----
+160718310  xuqiu     P1        FIXED   With "Select" overview action selected, App icon is missing in other overview apps after orientation change ----
+160748731  sunnygoyal  P2        ASSIGNED  Unify prediction model with Launcher model ----
+160759508  twickham  P2        FIXED   Swipe up cannot back to home screen in overview. ----
+161273376  xuqiu     P2        FIXED   [Overview Actions] Add logging and helpful messages ----
+161536946  twickham  P2        FIXED   Haptics don't indicate snap-to in overview,  ----
+161685099  winsonc   P2        FIXED   Screen still stay at the quick settings/notification when I swipe up with 3 finger to check the all apps. ----
+161801331  hyunyoungs  P2        FIXED   Change AllAppsSearch plugin to support only data fetch ----
+161901771  xuqiu     P1        FIXED   Overlapping layer of highlights with app layout getting darker when keep rotating the device from "Feedback" viewpoint in split screen ----
+161939759  sunnygoyal  P2        FIXED   RD1A: Going to overview in landscape mode clips the screen content ----
+162012217  perumaal  P2        ASSIGNED  Leaked Activity Caused by Gleams ----
+162454040  bookatz   P2        ASSIGNED  Create multiuser test that checks that opening an app works properly ----
+162480567  sfufa     P4        FIXED   Enable Item Decorations for search items ----
+162564471  tracyzhou  P2        FIXED   [Live tile] Handle tapping overview actions in live tile mode ----
+162623012  zakcohen  P1        ASSIGNED  Enable chips flag ----
+162812884  winsonc   P2        ASSIGNED  [R]The color have not changed in some page after turning on the dark theme. ----
+162861289  hyunyoungs  P2        FIXED   Add FocusIndicator support to DEVICE_SEARCH feature in S ----
+162871508  sfufa     P2        ASSIGNED  Introduce support for Hero app section ----
diff --git a/commitlist.txt b/commitlist.txt
new file mode 100644
index 0000000..e8eb105
--- /dev/null
+++ b/commitlist.txt
@@ -0,0 +1,1133 @@
+COMMAND>> git log 23bb2d1a4d632c46ed42b22808c052b19365fcb4..532b962a034762b231c0fbb95e145459897e45d5(B
+commit 532b962a034762b231c0fbb95e145459897e45d5
+Merge: 87be28a67 107fe60f6
+Author: Tony Wickham <twickham@google.com>
+Date:   Sat Aug 15 00:20:36 2020 +0000
+
+    resolve merge conflicts of 107fe60f6ef5b3336ece35122650d9ccc1c69913 to ub-launcher3-master
+    
+    Change-Id: I9a1c52e66edd627fd54cc3cb423fa28a6175f8ac
+
+commit 87be28a67b12c5b12a89155c794c0ddd438e8750
+Merge: 4fedc4e58 d844fe758
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Fri Aug 14 23:47:44 2020 +0000
+
+    Merge "Remove ENABLE_OVERVIEW_ACTIONS flag, defaulting to true" into ub-launcher3-master
+
+commit 4fedc4e583fd5b6edcd4ea9b66d4c32991f04867
+Merge: 4fb5f74bb 6c1a88f17
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Fri Aug 14 23:00:09 2020 +0000
+
+    [automerger skipped] Merge "Track OverviewToHomeAnim with StateManager" into ub-launcher3-rvc-qpr-dev am: 6c1a88f172 -s ours
+    
+    am skip reason: Change-Id I5348565b9e705d8ffba39818dde9efe82b16bb7a with SHA-1 4fb5f74bb4 is in history
+    
+    Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/12352271
+    
+    Change-Id: I2a164ac97082a33bd8e859d6fb3f40d338996304
+
+commit d844fe758c880447bff8708646ecfcd92e98ba06
+Author: Tony Wickham <twickham@google.com>
+Date:   Thu Aug 13 19:33:26 2020 -0700
+
+    Remove ENABLE_OVERVIEW_ACTIONS flag, defaulting to true
+    
+    We no longer support the "peeking" model where the shelf would
+    peek on motion pause from an app and overview would peek on motion
+    pause from home. Thus, removed/inlined the following:
+    - FlingAndHoldTouchController (merged into its sole subclass
+      NoButtonNavbarToOverviewTouchController)
+    - ShelfPeekAnim
+    - OverviewPeekState
+    
+    Change-Id: I066a3ad2636fde4786089c922b896bf1e03361fd
+
+commit 107fe60f6ef5b3336ece35122650d9ccc1c69913
+Author: Tony Wickham <twickham@google.com>
+Date:   Wed Aug 12 12:30:00 2020 -0700
+
+    Ensure we clearState() when going to Overview from home
+    
+    We need to do this before potentially starting another
+    interaction during the animation, or we could end up in
+    an inconsistent state.
+    
+    Fixes: 160759508
+    Change-Id: Ia28dceddcc258679fc0b968f5a83fae5ef3f5acb
+
+commit 6c1a88f1720a102d8a3ef94073754b7cd163d6a8
+Merge: 20983ae1f 9dfcc316c
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Fri Aug 14 22:45:17 2020 +0000
+
+    Merge "Track OverviewToHomeAnim with StateManager" into ub-launcher3-rvc-qpr-dev
+
+commit 9dfcc316c1519978e49462707ba36156d7abb2e5
+Author: Tony Wickham <twickham@google.com>
+Date:   Mon Aug 10 18:33:01 2020 -0700
+
+    Track OverviewToHomeAnim with StateManager
+    
+    This way we mark the the current state as NORMAL at the start of
+    the animation, and cancel it as part of other state transitions.
+    This allows us to interact with launcher (e.g. to go to all apps
+    or pull down the notification shade) during the animation.
+    
+    Also use OverviewToHomeAnim from RecentsView#startHome() to
+    ensure the animation is consistent, e.g. doesn't fade out
+    RecentsView, scrolls to page 1, etc.
+    
+    Bug: 144170434
+    Change-Id: I5348565b9e705d8ffba39818dde9efe82b16bb7a
+    Merged-In: I5348565b9e705d8ffba39818dde9efe82b16bb7a
+
+commit 4fb5f74bb4884794a1a5f08f62092f7f63c2d05b
+Author: Tony Wickham <twickham@google.com>
+Date:   Mon Aug 10 18:33:01 2020 -0700
+
+    Track OverviewToHomeAnim with StateManager
+    
+    This way we mark the the current state as NORMAL at the start of
+    the animation, and cancel it as part of other state transitions.
+    This allows us to interact with launcher (e.g. to go to all apps
+    or pull down the notification shade) during the animation.
+    
+    Also use OverviewToHomeAnim from RecentsView#startHome() to
+    ensure the animation is consistent, e.g. doesn't fade out
+    RecentsView, scrolls to page 1, etc.
+    
+    Bug: 144170434
+    Change-Id: I5348565b9e705d8ffba39818dde9efe82b16bb7a
+
+commit 1ae2937f7527517eafcf79071b4547800c1daad4
+Merge: 5fb83a47f 8cac927bd
+Author: Hyunyoung Song <hyunyoungs@google.com>
+Date:   Fri Aug 14 19:34:50 2020 +0000
+
+    Merge "Hookup existing AllAppsSearchPlugin interface to SearchBarController Bug: 161801331" into ub-launcher3-master
+
+commit 5fb83a47f292df4be2f55380caac65e36048052d
+Author: Sunny Goyal <sunnygoyal@google.com>
+Date:   Mon Aug 10 10:50:36 2020 -0700
+
+    Removing icon recents version
+    
+    Change-Id: Ibdc88715e38590cedf8a7b5174061bb2c6ef9533
+
+commit 8cac927bdb1054de643db86c918bb825f0860177
+Author: Hyunyoung Song <hyunyoungs@google.com>
+Date:   Fri Aug 14 00:11:36 2020 -0700
+
+    Hookup existing AllAppsSearchPlugin interface to SearchBarController
+    Bug: 161801331
+    
+    Change-Id: Ied575f78ad2139c6818ae5a13467b7399b9ab17a
+
+commit 8ad575e1e65c8a73b37abcd89062c22a27f0ad37
+Author: Tracy Zhou <tracyzhou@google.com>
+Date:   Sat Aug 1 16:08:48 2020 -0700
+
+    Handle overview actions in Live Tile mode
+    
+    - Switch to screenshot and finish recents animation when an overview action is selected
+    
+    Fixes: 162564471
+    Test: Manual
+    Change-Id: I3db20619435d079bb39ce4cb37b46ea775416336
+
+commit 9a472fbd7dba66d756631ec98430b1da0fb9f00e
+Merge: a9f955173 81c0cac45
+Author: Tracy Zhou <tracyzhou@google.com>
+Date:   Thu Aug 13 21:29:28 2020 +0000
+
+    Merge "[Live Tile] Place the target app back above Launcher when swipe up end target is home" into ub-launcher3-master
+
+commit a9f955173239fb734da22bd56cd0c2178a72213d
+Merge: 895ece297 a310e9c03
+Author: Andy Wickham <awickham@google.com>
+Date:   Thu Aug 13 19:22:12 2020 +0000
+
+    Merge "Refactors LauncherPreviewRenderer to better support subclassing." into ub-launcher3-master
+
+commit 895ece297d748291fcd7b3b429511729e64a1bb2
+Merge: ba9415162 20983ae1f
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Thu Aug 13 18:57:49 2020 +0000
+
+    Merge "[Overview Actions] Reset the modal task view when rotate from select mode." into ub-launcher3-rvc-qpr-dev am: 20983ae1f8
+    
+    Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/12366024
+    
+    Change-Id: I3fec240a659c5a1c80bf4f78aca1af6ace270e27
+
+commit 20983ae1f8a5896f15cbee61397c554990e6bf58
+Merge: 2ddbe127f ba6fec3ea
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Thu Aug 13 18:42:46 2020 +0000
+
+    Merge "[Overview Actions] Reset the modal task view when rotate from select mode." into ub-launcher3-rvc-qpr-dev
+
+commit ba9415162cf2b3c0f46d4c252e50bafcd853616a
+Merge: 58a1689ce 59d04e6ea
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Thu Aug 13 18:17:41 2020 +0000
+
+    Merge "Use system VelocityTracker instead of forking least squares algorithm" into ub-launcher3-master
+
+commit 58a1689cef9f638467cb4dc9835bb6a92d704ce3
+Merge: 9529b95e0 2ddbe127f
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Thu Aug 13 18:05:50 2020 +0000
+
+    Merge "Overview Actions - enable proactive actions flag." into ub-launcher3-rvc-qpr-dev am: 2ddbe127fb
+    
+    Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/12286410
+    
+    Change-Id: I54898c6a1a47d271c8d18c17d24a5acb6e67fd1e
+
+commit 81c0cac4574915a6bf77955bbb74ecc1cde79c6f
+Author: Tracy Zhou <tracyzhou@google.com>
+Date:   Thu Aug 13 11:01:15 2020 -0700
+
+    [Live Tile] Place the target app back above Launcher when swipe up end target is home
+    
+    Test: manual
+    Bug: 160361464
+    Change-Id: Ic2b332774fe3f25f36f0bfd2b5e6cf66fb1d09dd
+
+commit 2ddbe127fb69bd860fc4176893015b63474c6609
+Merge: f35053ad5 1e414d500
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Thu Aug 13 17:50:30 2020 +0000
+
+    Merge "Overview Actions - enable proactive actions flag." into ub-launcher3-rvc-qpr-dev
+
+commit 59d04e6ea69dc0c81fceab601194d8c0a6238fa0
+Author: Tony Wickham <twickham@google.com>
+Date:   Wed Aug 12 18:05:42 2020 -0700
+
+    Use system VelocityTracker instead of forking least squares algorithm
+    
+    Change-Id: I8e03cdd0942b9037054cd1955bdb6809c89f3ea2
+
+commit a310e9c035c87c4fc5b3d23bbcc70f11ffc48561
+Author: Andy Wickham <awickham@google.com>
+Date:   Mon Aug 10 17:04:40 2020 -0700
+
+    Refactors LauncherPreviewRenderer to better support subclassing.
+    
+    Basically this removes the inner class MainThreadRenderer, which
+    was only created and used for its populate() method in the parent's
+    getRenderedView() method. All methods and members of that subclass
+    are merged into the parent LauncherPreviewRenderer class, and
+    getRenderedView() simply inlines the previous populate() code.
+    
+    Other smaller changes:
+     - Extracted out shouldShowQsb() and shouldShowRealLauncherPreview()
+     - Disables search view and its children to prevent interaction
+    
+    Change-Id: I7d0cce73efbe022c16661a0ad66eefe5cb285641
+
+commit 9529b95e06a6ab91650f373d5a335b6bc090ce77
+Merge: 72385839e f35053ad5
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Thu Aug 13 02:25:50 2020 +0000
+
+    Merge "Store mHistoricTimes as longs intead of floats" into ub-launcher3-rvc-qpr-dev am: f35053ad58
+    
+    Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/12365858
+    
+    Change-Id: I3c21d9d47a525e9c975c6acf38c99a2f1668359c
+
+commit f35053ad588974b4daf37fa19866e416b880360d
+Merge: 30fe240f9 a14567096
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Thu Aug 13 02:10:20 2020 +0000
+
+    Merge "Store mHistoricTimes as longs intead of floats" into ub-launcher3-rvc-qpr-dev
+
+commit 72385839ed9038607e186c198ce31a54c52778a4
+Merge: 635c08095 30fe240f9
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Thu Aug 13 01:01:07 2020 +0000
+
+    Merge "TaskOverlayFactory - remove static factory." into ub-launcher3-rvc-qpr-dev am: 30fe240f99
+    
+    Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/12353636
+    
+    Change-Id: Ic165b6184f4f36b5956493d3880a9d93ad27a0d6
+
+commit 30fe240f99f38dfb81a6075b8ab2b0ed436afb75
+Merge: cbe1fc063 cf8275438
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Thu Aug 13 00:47:34 2020 +0000
+
+    Merge "TaskOverlayFactory - remove static factory." into ub-launcher3-rvc-qpr-dev
+
+commit ba6fec3eaeb2087428c8e33826429d72ad0290cf
+Author: Becky Qiu <xuqiu@google.com>
+Date:   Wed Aug 12 17:17:00 2020 -0700
+
+    [Overview Actions] Reset the modal task view when rotate from select mode.
+    
+    Test: local
+    Bug: 161901771
+    Change-Id: I103e59ad68874db7fcb39556764d315983ac047c
+
+commit 635c08095a9057cce7e25952c0f5d5b12a7903d7
+Merge: 93caa9310 cbe1fc063
+Author: Zak Cohen <zakcohen@google.com>
+Date:   Thu Aug 13 00:12:20 2020 +0000
+
+    Merge "Overscroll plugin - give the plugin its own factory." into ub-launcher3-rvc-qpr-dev am: cbe1fc063d
+    
+    Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/12353635
+    
+    Change-Id: Ia471594443d1b744c5c34f0c9c45e7191bbf7938
+
+commit a14567096922f23d6e03c84fc930e3451e7471c2
+Author: Tony Wickham <twickham@google.com>
+Date:   Wed Aug 12 16:48:33 2020 -0700
+
+    Store mHistoricTimes as longs intead of floats
+    
+    The loss of precision was causing us to miscalculate the age of
+    events, and thus not detecting any pause due to denominator = 0.
+    
+    Test: have a device that hasn't been rebooted for a certain
+    amount of time such that SystemClock#uptimeMillis can't be
+    accurately converted to a float, then try to swipe up and hold
+    
+    Fixes: 160568387
+    Change-Id: Idef112187f34a18feea7e6a0b77258626f9d0ed4
+
+commit cbe1fc063d924b2c68a2de8929687baab06439df
+Merge: 9367f9834 fac874152
+Author: Zak Cohen <zakcohen@google.com>
+Date:   Wed Aug 12 23:58:38 2020 +0000
+
+    Merge "Overscroll plugin - give the plugin its own factory." into ub-launcher3-rvc-qpr-dev
+
+commit 93caa93102df1b84e8b8d66fe7ca63e8950e4a6f
+Merge: 74f218530 9367f9834
+Author: Becky Qiu <xuqiu@google.com>
+Date:   Wed Aug 12 23:43:10 2020 +0000
+
+    [automerger skipped] [Overview Actions] Set launcher state to Overview when rotate in recents. am: 9367f98348 -s ours
+    
+    am skip reason: Change-Id I73f498151d7cc6a9db9d352549124c9d550ae6f2 with SHA-1 cb956ff4f5 is in history
+    
+    Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/12354807
+    
+    Change-Id: I02f43e3ee9a7f0f1730b673be220055d72a07d81
+
+commit 74f218530e069c11b36cf4a2cfcc1b76726aea6b
+Merge: cb956ff4f fd58da6a7
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Wed Aug 12 23:36:27 2020 +0000
+
+    Merge "Wrapping display properties in a wrapper class" into ub-launcher3-master
+
+commit 1e414d500591e88c027bfead4c0e456444dc5407
+Author: Zak Cohen <zakcohen@google.com>
+Date:   Fri Jul 31 15:54:36 2020 -0700
+
+    Overview Actions - enable proactive actions flag.
+    
+    Bug: 162623012
+    Test: local
+    Change-Id: I343ae83601ebdab4dacc9f4611b069f87b5542eb
+
+commit cf82754384a26d2038ba1f13c0ecea57c0d3116d
+Author: Zak Cohen <zakcohen@google.com>
+Date:   Tue Aug 11 16:17:41 2020 -0700
+
+    TaskOverlayFactory - remove static factory.
+    
+    Make the overlay factory a member of the recents view.
+    Before the factory was static. The factory has references to activity
+    objects so was causing leaks.
+    
+    Bug: 162012217
+    Tested: local, factory still works
+    Change-Id: I2283134f3008b630ba2056f07e65ac042957cdbd
+
+commit fd58da6a75b55eb2ef40dd53fc6135636812ac6a
+Author: Sunny Goyal <sunnygoyal@google.com>
+Date:   Tue Aug 11 12:06:49 2020 -0700
+
+    Wrapping display properties in a wrapper class
+    
+    This would allow us to support gestures on multiple-displays
+    
+    Change-Id: I8f426bbdf04520f4189cadce2bde6aab4933c8d9
+
+commit cb956ff4f5adf0c4abb586940d9e89582a70bfd3
+Author: Becky Qiu <xuqiu@google.com>
+Date:   Mon Aug 10 12:44:48 2020 -0700
+
+    [Overview Actions] Set launcher state to Overview when rotate in recents.
+    
+    Test: local
+    Bug: 160718310
+    
+    Change-Id: I73f498151d7cc6a9db9d352549124c9d550ae6f2
+
+commit 73f0c3698fe74bb051e0e65a523a2f750b1ba40d
+Merge: 53116c6cc d6c3315a6
+Author: Tracy Zhou <tracyzhou@google.com>
+Date:   Wed Aug 12 16:53:23 2020 +0000
+
+    Merge "Only draw app below launcher for TaskViewSimulator at swipe up or in Overview" into ub-launcher3-master
+
+commit d6c3315a67980c90712103135bae302b86ee080e
+Author: Tracy Zhou <tracyzhou@google.com>
+Date:   Tue Aug 11 22:46:22 2020 -0700
+
+    Only draw app below launcher for TaskViewSimulator at swipe up or in Overview
+    
+    Bug: 160361464
+    Test: Manual
+    Change-Id: Ic4d912f1656d87f445a8016640c0eb166cace0d5
+
+commit 9367f98348f9db5b5b6840b8003dd62c037d8be8
+Author: Becky Qiu <xuqiu@google.com>
+Date:   Mon Aug 10 12:44:48 2020 -0700
+
+    [Overview Actions] Set launcher state to Overview when rotate in recents.
+    
+    Test: local
+    Bug: 160718310
+    
+    Change-Id: I73f498151d7cc6a9db9d352549124c9d550ae6f2
+    Merged-In: I73f498151d7cc6a9db9d352549124c9d550ae6f2
+
+commit fac87415221447f8f47ef4a88d187b7e175a6296
+Author: Zak Cohen <zakcohen@google.com>
+Date:   Mon Aug 10 15:51:45 2020 -0700
+
+    Overscroll plugin - give the plugin its own factory.
+    
+    Separate out the overscroll plugin from the task overlay.
+    These two classes aren't really related.
+    
+    Bug: 162012217
+    Tested: checked plugin still works
+    Change-Id: Id89eff1ad2ae9766efaaecd8cfc66d6521b9ca8c
+
+commit 53116c6cc84236ac49064efa9988f92b5b558599
+Author: Sunny Goyal <sunnygoyal@google.com>
+Date:   Fri Aug 7 16:32:18 2020 -0700
+
+     Simplifying InstallShortcutReceiver
+    
+    > Removing support for legacy shortcuts (with embedded icon)
+    > Unifying pattern for storing the data in prefs
+    
+    Change-Id: Ife250807c7ce5337969d25444ee23c751bc2a487
+
+commit b313f71615c64c8022f587ef3c4ebd34bfdc6171
+Merge: 762d06136 2104d72b2
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Tue Aug 11 21:44:37 2020 +0000
+
+    Merge "LauncherInstrumentation enables even when non-system user" into ub-launcher3-master
+
+commit 762d06136cc7c91afe8cdfdac5426b58c174edd8
+Author: Sunny Goyal <sunnygoyal@google.com>
+Date:   Wed Jul 29 15:03:46 2020 -0700
+
+    Caching last predictions and loading it with model
+    
+    Adding support for persisting itemInfos on disk. This uses
+    a separate xml file. Unlike prefs, it does not keep the items
+    in memory and is just a wraper over reading/writing a file.
+    
+    Bug: 160748731
+    Change-Id: Iaccab9928ab8f30127fb3c2d630ca8ca83f0bd05
+
+commit d62e797d1834306ec394ebf64f4c84939f033d97
+Author: Sunny Goyal <sunnygoyal@google.com>
+Date:   Tue Aug 11 12:10:36 2020 -0700
+
+    Disabling debug log as the associated bug is closed
+    
+    Bug: 154951045
+    Change-Id: I2e503c6d2f0a9f694ee1f88167924114fe495c2f
+
+commit 1bdb8f0efc73419b114074375126883162f4c838
+Merge: 3003e57d1 66aead7d2
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Tue Aug 11 18:57:13 2020 +0000
+
+    Merge "Removing some obsolete methods and wrapper" into ub-launcher3-master
+
+commit 3003e57d1714ba7016c89e520f11aebe83b71b7d
+Merge: fe9b7e37a 5dd045bec
+Author: Hyunyoung Song <hyunyoungs@google.com>
+Date:   Tue Aug 11 17:56:59 2020 +0000
+
+    Merge "Handle IME selection focus for ENABLE_DEVICE_SEARCH" into ub-launcher3-master
+
+commit 5dd045bec67ca12a3de4b4adc88a92a0166c0f50
+Author: Hyunyoung Song <hyunyoungs@google.com>
+Date:   Tue Aug 4 22:22:49 2020 -0700
+
+    Handle IME selection focus for ENABLE_DEVICE_SEARCH
+    
+    Bug: 162861289
+    Change-Id: I15e4eae09be2aa9f89a5157fd74c95e91e64bc53
+
+commit fe9b7e37a801135f959e03ecf26ee454a24d6f8e
+Merge: b810c10bb e14b55c2a
+Author: Tony Wickham <twickham@google.com>
+Date:   Tue Aug 11 00:37:05 2020 +0000
+
+    [automerger skipped] Merge "Don't reapply window transform if we are already running a window anim" into ub-launcher3-rvc-qpr-dev am: e14b55c2a9 -s ours
+    
+    am skip reason: Change-Id I3fa7f0b2581ca83923a42f37f52850b02c22e995 with SHA-1 c830bef923 is in history
+    
+    Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/12346435
+    
+    Change-Id: I35f6f3572754e49626c7a09b832aecf114413bd7
+
+commit e14b55c2a94a255750bca8b2fa4e068fcbeba0cc
+Merge: b460ff1ef 06513f11c
+Author: Tony Wickham <twickham@google.com>
+Date:   Tue Aug 11 00:19:28 2020 +0000
+
+    Merge "Don't reapply window transform if we are already running a window anim" into ub-launcher3-rvc-qpr-dev
+
+commit b810c10bb90a2bae5adf02b7050dcb595f383707
+Merge: 9fc65cb0b b55f7591e
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Mon Aug 10 23:55:24 2020 +0000
+
+    Merge "Ensure that the task is always clipped to match the tile aspect ratio" into ub-launcher3-master
+
+commit 9fc65cb0b79e6d87098aa72aeaadb425619dd656
+Merge: c830bef92 b460ff1ef
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Mon Aug 10 23:02:20 2020 +0000
+
+    Merge "Fix TaskViewTouchController success progress to match haptic" into ub-launcher3-rvc-qpr-dev am: b460ff1ef1
+    
+    Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/12321076
+    
+    Change-Id: Ic4317b94cca7d760d23da0c1c115d422236586e7
+
+commit b460ff1ef1f7fac734f1d0411fd7284e6c1eea7b
+Merge: 03a4a0cd5 b8b3e957a
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Mon Aug 10 22:46:03 2020 +0000
+
+    Merge "Fix TaskViewTouchController success progress to match haptic" into ub-launcher3-rvc-qpr-dev
+
+commit b55f7591ed2fee28e1c2a4059d19d2f069185cf5
+Author: Sunny Goyal <sunnygoyal@google.com>
+Date:   Mon Aug 10 15:08:36 2020 -0700
+
+    Ensure that the task is always clipped to match the tile aspect ratio
+    
+    Instead of trying to estimate the insets, we clip the task such it is
+    restricted within the task insets when possible.
+    
+    Change-Id: If160115fda07dfd2e01e96d41e4b5dc43a77ac94
+
+commit 06513f11c2304a371a7c79cec371562d65393322
+Author: Tony Wickham <twickham@google.com>
+Date:   Mon Aug 10 15:06:02 2020 -0700
+
+    Don't reapply window transform if we are already running a window anim
+    
+    Bug: 149934536
+    Change-Id: I3fa7f0b2581ca83923a42f37f52850b02c22e995
+    Merged-In: I3fa7f0b2581ca83923a42f37f52850b02c22e995
+
+commit c830bef923200e97ad8d0ef9bf37d0c121d7e56d
+Author: Tony Wickham <twickham@google.com>
+Date:   Mon Aug 10 15:06:02 2020 -0700
+
+    Don't reapply window transform if we are already running a window anim
+    
+    Bug: 149934536
+    Change-Id: I3fa7f0b2581ca83923a42f37f52850b02c22e995
+
+commit cf5aea05b3888ea0adc8a53fad62987405206eeb
+Merge: 2fe0d5f71 03a4a0cd5
+Author: Tony Wickham <twickham@google.com>
+Date:   Mon Aug 10 21:29:00 2020 +0000
+
+    Update overview from home transitions am: 03a4a0cd53
+    
+    Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/12184426
+    
+    Change-Id: Iac33ad0742264a97e754a1cc4336ed3ab727944a
+
+commit 2fe0d5f71ded4537c42d07df1464a71daf27e047
+Merge: a2c6c9737 90751820d
+Author: Tony Wickham <twickham@google.com>
+Date:   Mon Aug 10 21:08:54 2020 +0000
+
+    Merge "Add back code missing from upstream merge" into ub-launcher3-master
+
+commit 03a4a0cd530c6d13f01882e81568ab1544030c12
+Author: Tony Wickham <twickham@google.com>
+Date:   Fri Jul 17 13:06:57 2020 -0700
+
+    Update overview from home transitions
+    
+    For both NoButtonNavbarToOverviewTouchController and
+    NavBarToHomeTouchController:
+    - Have consistent resistance applied such that RecentsView scales
+      down and translates up slightly (but not as much as from an app)
+    - Have consistent animation to home if you fling to that state
+      rather than stay in overview. This is handled by a new class,
+      OverviewToHomeAnim, which consolidates logic from NBTHTC and
+      overrides some interpolators such that RecentsView doesn't fade
+      out or translate downwards during the animation (it just slides
+      off the screen while the home animation plays).
+    
+    Also make overview actions not clickable when alpha == 0, so that
+    you can tap the hotseat/qsb during the transition from home to
+    overview.
+    
+    Bug: 144170434
+    Change-Id: Ic291f285ff2f63c477633c48d4fadb23cf70c28a
+
+commit 90751820d9ef8f09ece7aba656dfe881e3b149dd
+Author: Tony Wickham <twickham@google.com>
+Date:   Mon Aug 10 13:11:43 2020 -0700
+
+    Add back code missing from upstream merge
+    
+    Change-Id: I7c2f08ed1b9c7462feabe1ad793af4ddc55aff1b
+
+commit a2c6c973749e1a67bcdbb33cb316c0ba467e3c6d
+Merge: c0ed292cc e6f9e0eb7
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Mon Aug 10 20:01:06 2020 +0000
+
+    Merge "Revert "Update overview from home transitions"" into ub-launcher3-master
+
+commit e6f9e0eb7ea49b0f2feae80d7a2d57fe9db5e38e
+Author: Tony Wickham <twickham@google.com>
+Date:   Mon Aug 10 19:13:17 2020 +0000
+
+    Revert "Update overview from home transitions"
+    
+    This reverts commit f3bc79718201cf17479a46e9860ea518a900d68f.
+    
+    Reason for revert: updating upstream, need to wait to cherry-pick until that's approved
+    
+    Change-Id: I702286dba66fb4582ab682a5b0b8cd80ccebf346
+
+commit c0ed292ccbaa5b94bd3e54a85f5d7e9c1b2a1229
+Merge: cb4c4dea9 999e08f6f
+Author: Tony Wickham <twickham@google.com>
+Date:   Mon Aug 10 16:32:08 2020 +0000
+
+    Merge "Add swipe up resistance to quick switch from home" into ub-launcher3-rvc-qpr-dev am: 999e08f6ff
+    
+    Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/12174364
+    
+    Change-Id: I6ce2dbb31ec8ed0d340165b2fc9a9f0532081d3e
+
+commit cb4c4dea95157c8bce1561bec6cd38daa57cec9e
+Merge: e54db4fbd 6bd1882ea
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Mon Aug 10 16:31:55 2020 +0000
+
+    Merge "[Overview Actions] Add a logging event for tapping images in select mode." into ub-launcher3-rvc-qpr-dev am: 6bd1882eae
+    
+    Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/12336644
+    
+    Change-Id: I2dba3dda4d6ef8a8857e18708a96ebab9da30f20
+
+commit e54db4fbd4a293ecbe0c959aaae356472f9e8db6
+Merge: d72857950 2ace6a25d
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Mon Aug 10 16:31:44 2020 +0000
+
+    Merge "Fixing cutouts insets not clipped properly during swipeup for rotated activities" into ub-launcher3-rvc-qpr-dev am: 2ace6a25d2
+    
+    Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/12327967
+    
+    Change-Id: I6e3f28e464a8ca63840a53f1b35c86cf377c4ad4
+
+commit d72857950461c1b82ce80f85bf359c050c0f0b0c
+Merge: f3bc79718 1a965c190
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Mon Aug 10 16:28:35 2020 +0000
+
+    Merge "resolve merge conflicts of 3a22956f0a6214285500ac040b4ae565d882cc47 to ub-launcher3-master" into ub-launcher3-master
+
+commit f3bc79718201cf17479a46e9860ea518a900d68f
+Author: Tony Wickham <twickham@google.com>
+Date:   Fri Jul 17 13:06:57 2020 -0700
+
+    Update overview from home transitions
+    
+    For both NoButtonNavbarToOverviewTouchController and
+    NavBarToHomeTouchController:
+    - Have consistent resistance applied such that RecentsView scales
+      down and translates up slightly (but not as much as from an app)
+    - Have consistent animation to home if you fling to that state
+      rather than stay in overview. This is handled by a new class,
+      OverviewToHomeAnim, which consolidates logic from NBTHTC and
+      overrides some interpolators such that RecentsView doesn't fade
+      out or translate downwards during the animation (it just slides
+      off the screen while the home animation plays).
+    
+    Also make overview actions not clickable when alpha == 0, so that
+    you can tap the hotseat/qsb during the transition from home to
+    overview.
+    
+    Bug: 144170434
+    Change-Id: Ic291f285ff2f63c477633c48d4fadb23cf70c28a
+
+commit 1a965c190d07140690602761c497495ad1875d03
+Merge: 70a556da1 3a22956f0
+Author: Tony Wickham <twickham@google.com>
+Date:   Sat Aug 8 19:12:11 2020 +0000
+
+    resolve merge conflicts of 3a22956f0a6214285500ac040b4ae565d882cc47 to ub-launcher3-master
+    
+    Change-Id: I2bb370e63d7206d52acb912d5b55a0f0c5f59c3d
+
+commit 999e08f6ffdcf2aa4eb3697cfd2c6c1488a72710
+Merge: 6bd1882ea d6a1063f2
+Author: Tony Wickham <twickham@google.com>
+Date:   Sat Aug 8 02:18:46 2020 +0000
+
+    Merge "Add swipe up resistance to quick switch from home" into ub-launcher3-rvc-qpr-dev
+
+commit 6bd1882eaec5c0571b78e47b58c524f1fcaae207
+Merge: 2ace6a25d 14e03d1c4
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Sat Aug 8 00:53:33 2020 +0000
+
+    Merge "[Overview Actions] Add a logging event for tapping images in select mode." into ub-launcher3-rvc-qpr-dev
+
+commit 2ace6a25d2835eb6bfd7082456dda1a07e419930
+Merge: 3a22956f0 d00340f7a
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Sat Aug 8 00:00:28 2020 +0000
+
+    Merge "Fixing cutouts insets not clipped properly during swipeup for rotated activities" into ub-launcher3-rvc-qpr-dev
+
+commit 66aead7d2cf426f64d06b573470d0658c858b1b3
+Author: Sunny Goyal <sunnygoyal@google.com>
+Date:   Fri Aug 7 16:56:50 2020 -0700
+
+    Removing some obsolete methods and wrapper
+    
+    Change-Id: I9d3c8d851ad596ba0d5165ab3c0f6661575166fc
+
+commit 3a22956f0a6214285500ac040b4ae565d882cc47
+Merge: ce8b2b518 3af714f05
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Fri Aug 7 23:44:37 2020 +0000
+
+    Merge "Remove overscroll on running task when it's not attached to RecentsView" into ub-launcher3-rvc-qpr-dev
+
+commit 2104d72b281ef4ee9f15aef333bd65a03f8f7eb4
+Author: Adam Bookatz <bookatz@google.com>
+Date:   Wed Jul 29 15:46:16 2020 -0700
+
+    LauncherInstrumentation enables even when non-system user
+    
+    Attempting to run a tapl test while the device is in a secondary user
+    currently fails because the test app doesn't get enabled for
+    this secondary user. We fix that (at least one issue with it) in this cl.
+    
+    Test: make PlatformScenarioTests; adb root && adb install -r -g ${ANDROID_PRODUCT_OUT}/testcases/PlatformScenarioTests/$(get_build_var TARGET_ARCH)/PlatformScenarioTests.apk
+    adb shell am instrument -w -r -e class android.platform.test.scenario.chrome.OpenApp android.platform.test.scenario/androidx.test.runner.AndroidJUnitRunner
+    (Do this while the current user is SYSTEM as well as Guest)
+    
+    Bug: 162454040
+    Change-Id: If6d8e545b41eb20e3fed2935c39069ce97d8b6cd
+
+commit 14e03d1c4d6c68e84f3f4d9867e0757562a78e4a
+Author: Becky Qiu <xuqiu@google.com>
+Date:   Fri Aug 7 15:06:38 2020 -0700
+
+    [Overview Actions] Add a logging event for tapping images in select mode.
+    
+    Event IDs were generated by command line on my workstation by uieventscli.
+    Manually added to StatsLogManager.
+    
+    Test: local
+    Bug: 161273376
+    Change-Id: Iee36c450ddb207f87653a8da2355c720369caa34
+
+commit 70a556da1d02fa32218d8dfcd4f4105f090997d9
+Merge: 87805cd94 ce8b2b518
+Author: Tony Wickham <twickham@google.com>
+Date:   Fri Aug 7 20:22:52 2020 +0000
+
+    Add translation component to swipe up resistance am: ce8b2b5180
+    
+    Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/12174363
+    
+    Change-Id: I18d7be49bb3bfc9f846c14a9138b5daf381e1d56
+
+commit d6a1063f258452b5f34dcad22dc416cc03c16dd4
+Author: Tony Wickham <twickham@google.com>
+Date:   Mon Jul 6 17:26:39 2020 -0700
+
+    Add swipe up resistance to quick switch from home
+    
+    Bug: 149934536
+    Change-Id: I1b355a16d972f94d541231449ef323a41861a3e6
+
+commit ce8b2b51800e42fc61c14f516ec563c3561b7adb
+Author: Tony Wickham <twickham@google.com>
+Date:   Wed Jul 1 17:25:28 2020 -0700
+
+    Add translation component to swipe up resistance
+    
+    Now recents view follows your finger all the way to the top of the
+    screen. Specifically, your finger tracks the bottom of the window
+    until resistance starts (when RecentsView is at 75% scale), then
+    we add translation to compensate for the slower rate of scaling
+    down, such that your finger slips to the top of the window by the
+    time it reaches the top of the screen.
+    
+    Also reset this translation back to 0 in the state handlers.
+    
+    Bug: 149934536
+    Fixes: 158701272
+    Change-Id: Iaee58da758d422f0173c29d002f5c451ce0c1809
+
+commit 87805cd9415b7cfb968c1785bbd9845aa4b9db9e
+Merge: 4ade3fd56 4a53c70c5
+Author: Samuel Fufa <sfufa@google.com>
+Date:   Fri Aug 7 18:11:59 2020 +0000
+
+    Merge "Introduce support for Hero app Section" into ub-launcher3-master
+
+commit 4a53c70c5734f65d210c1b425549224acbe7c642
+Author: Samuel Fufa <sfufa@google.com>
+Date:   Tue Aug 4 14:06:55 2020 -0700
+
+    Introduce support for Hero app Section
+    
+    [Video attached to bug report]
+    
+    Bug: 162871508
+    Test: Manual
+    Change-Id: Ia6f5621d6220f55e6fd5e56530853c267838442c
+
+commit 4ade3fd561e357744c41da715d4dfda65f2fee9e
+Merge: 8ccd877f4 3f20e33af
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Fri Aug 7 00:10:05 2020 +0000
+
+    Merge "Fix adjacent page offset in landscape orientation" into ub-launcher3-rvc-qpr-dev am: 3f20e33af6
+    
+    Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/12327711
+    
+    Change-Id: I8f2da96439372a9f6671469c3c41e4c0a43a4067
+
+commit 3f20e33af652f8ecdd729d3f861bf6d95e3f1fa0
+Merge: 3f8d86811 3b9d5843c
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Thu Aug 6 23:52:04 2020 +0000
+
+    Merge "Fix adjacent page offset in landscape orientation" into ub-launcher3-rvc-qpr-dev
+
+commit 3b9d5843c94d042655d5d43a673cf2738f69c5f9
+Author: Tony Wickham <twickham@google.com>
+Date:   Thu Aug 6 15:04:51 2020 -0700
+
+    Fix adjacent page offset in landscape orientation
+    
+    Before, the adjacent tasks were coming in from the top and bottom
+    of the screen rather than the sides.
+    
+    Bug: 149934536
+    Change-Id: Id6e57dcbc1967d70869df06068d25717de116934
+
+commit d00340f7a24d11db58954298eb979e6c8ac0438f
+Author: Sunny Goyal <sunnygoyal@google.com>
+Date:   Thu Aug 6 14:28:13 2020 -0700
+
+    Fixing cutouts insets not clipped properly during swipeup
+    for rotated activities
+    
+    Bug: 161939759
+    Change-Id: Ia55f938fdc51e2813c205d7d13d5f1bf852c9bb2
+
+commit 8ccd877f424070dbf5bec170722d46cea40bf827
+Merge: 7f28b8814 17d0080cc
+Author: Andy Wickham <awickham@google.com>
+Date:   Thu Aug 6 19:37:35 2020 +0000
+
+    Merge "Adjusts Clear all to be vertically centered with the tasks." into ub-launcher3-master
+
+commit 17d0080cc32ed36df154f52f6cf2b5082e7e3c31
+Author: Andy Wickham <awickham@google.com>
+Date:   Tue Aug 4 19:51:26 2020 -0700
+
+    Adjusts Clear all to be vertically centered with the tasks.
+    
+    I made a sample task where the content of the app was
+    centered, and I added lines to demonstrate the center
+    while in Overview. As you can see, the center of the
+    app content is slightly different from the TaskView
+    in Recents - it seems part of the bottom of the app
+    gets removed when transitioning to Recents (for the
+    rounded corner radius or nav bar?). On Pixel 4, I
+    had to add 46dp of padding to the bottom line for it
+    to appear at the bottom of the screenshot in Recents.
+    
+    Screenshot: https://drive.google.com/file/d/1xzQNXWb0T0UGDhDS9Ov-AduM3nd-pfg8/view?usp=sharing
+    Before: https://drive.google.com/file/d/1ghN9VWP44XjcZkdmI106dy4_jB4Vs4J3/view?usp=sharing
+    
+    Rotating between landscape and portrait:
+    https://drive.google.com/file/d/1OD5aKT9LCE5w950-6AaHfWXU4sLyW3Qe/view?usp=sharing
+    
+    Fixes: 154964045
+    Change-Id: I594057e3b5df58c5a907b6ca14daa700d5011b7b
+
+commit b8b3e957a6049ba0cd3ed48623a1666bae80b25e
+Author: Tony Wickham <twickham@google.com>
+Date:   Wed Aug 5 18:43:53 2020 -0700
+
+    Fix TaskViewTouchController success progress to match haptic
+    
+    We use the interpolated progress of the animation contoller target
+    to determine success, but only the child animation had the
+    interpolator set. Updating the parent to use the same interpolator
+    ensures getInterpolatedProgress() returns the same one used to
+    play the haptic.
+    
+    Fixes: 161536946
+    Change-Id: Ibc2aef67f53efa01f2b185cf03140bad4bb5c421
+
+commit 3af714f052d183aa31f22eee3afeecb94ec552db
+Author: Tony Wickham <twickham@google.com>
+Date:   Wed Aug 5 13:32:37 2020 -0700
+
+    Remove overscroll on running task when it's not attached to RecentsView
+    
+    This allows the running task to follow the finger freely until motion
+    pause is detected (or when trying to quick switch in a direciton that
+    has no tasks, as RecentsView is still attached until you swipe up).
+    
+    Bug: 149934536
+    Change-Id: If68166e962af9f28c56017838f720e15ddb96560
+
+commit 7f28b8814bbd8c5b09068eeba44026ff3da10551
+Merge: e7c1769e3 77ca1e4c4
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Wed Aug 5 19:44:57 2020 +0000
+
+    Merge "Moving data sanitization to the end, as it need not block the loading" into ub-launcher3-master
+
+commit e7c1769e30bfed7caa33816a5cd9d61447d75745
+Merge: c226adb38 f8e9b467a
+Author: Tracy Zhou <tracyzhou@google.com>
+Date:   Wed Aug 5 17:42:43 2020 +0000
+
+    Merge "Revert "Finish recents animation when launching another task in Overview"" into ub-launcher3-master
+
+commit c226adb384d11bf88cf60efdd54d324bd4d1f762
+Merge: b3d7ecc15 3f8d86811
+Author: Winson Chung <winsonc@google.com>
+Date:   Wed Aug 5 06:02:28 2020 +0000
+
+    Defer recreation until resumed am: 3f8d868110
+    
+    Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/12309024
+    
+    Change-Id: I814a6cab9046b82d66426fbe19636087cfd3e2b6
+
+commit b3d7ecc15b3be086b67d2e0b1a8360ab2d2ab74b
+Merge: 8aaafc918 dd15acea8
+Author: Winson Chung <winsonc@google.com>
+Date:   Wed Aug 5 05:49:31 2020 +0000
+
+    Merge "Close system dialogs when opening all apps from system action" into ub-launcher3-master
+
+commit dd15acea87875d6d7a0d388e7ddfb283c8fd2a71
+Author: Winson Chung <winsonc@google.com>
+Date:   Tue Aug 4 15:52:01 2020 -0700
+
+    Close system dialogs when opening all apps from system action
+    
+    Bug: 161685099
+    Change-Id: I67313340102816828a95e8f9e7d8fa19b4ca3c55
+
+commit 8aaafc9186039181ad753020e8c668e7457ce453
+Merge: 60096e990 f66f2f66c
+Author: Jason Chang <jasonsfchang@google.com>
+Date:   Wed Aug 5 04:28:29 2020 +0000
+
+    Merge "Refactoring One-Handed feature flag and others" into ub-launcher3-master
+
+commit 77ca1e4c40a98b048905e021d94eab154a637455
+Author: Sunny Goyal <sunnygoyal@google.com>
+Date:   Tue Aug 4 17:28:46 2020 -0700
+
+    Moving data sanitization to the end, as it need not block
+    the loading
+    
+    Change-Id: If1a966e546127efafd0afd7e3ca771f63a1fa544
+
+commit f8e9b467ae11de1d307f5d77c36ff8002e66255d
+Author: Tracy Zhou <tracyzhou@google.com>
+Date:   Tue Aug 4 21:57:14 2020 +0000
+
+    Revert "Finish recents animation when launching another task in Overview"
+    
+    This reverts commit e62eaa16b360433e35b4e4655e16c312a7bfd110.
+    
+    Reason for revert: No longer necessary
+    
+    Change-Id: I9ed533147ac5546b7da6c9e3a51f5a7e6c8ef8e6
+
+commit 3f8d868110b426e40e09ece8b305fd84c1dde60c
+Author: Winson Chung <winsonc@google.com>
+Date:   Tue Aug 4 14:01:27 2020 -0700
+
+    Defer recreation until resumed
+    
+    - Temporarily work around issue with activity recreation while started
+      but not resumed by deferring recreating the activity on theme
+      change until after launcher has resumed.
+    
+    Bug: 162812884
+    Test: Switch dark mode via QuickSettings, Settings
+    Test: Change system theme
+    Change-Id: Ifbc0d538907a30d6b23176cd67353a81b6f8c617
+
+commit 60096e990aba5dd2daf88a31a78c185df19fe111
+Author: Samuel Fufa <sfufa@google.com>
+Date:   Tue Aug 4 11:19:21 2020 -0700
+
+    Remove mFilteredApps
+    
+    Currently, we are maintaining a list of appInfos to report the number of Accessibility recycler-view items. This could be done by maintaining an int.
+    
+    Bug: 162480567
+    Test: Manual
+    Change-Id: I9de8e1d4ac6e1a674d1e19b591dedad0dd4cc536
+
+commit e8bea3ea7a7a2370f1effc75a674e7510264b999
+Author: Samuel Fufa <sfufa@google.com>
+Date:   Thu Jul 30 02:12:10 2020 -0700
+
+    Allow search results decoration [part 2/3]
+    
+    [Video attached to bug report]
+    
+    Bug: 162480567
+    Test: Manual
+    Change-Id: Iff285abde5b2a3f3f3a63e7318020cfe7572af49
+
+commit f66f2f66cf130d48a0c8897628655fd2310a1358
+Author: Jason Chang <jasonsfchang@google.com>
+Date:   Tue Aug 4 19:56:39 2020 +0800
+
+    Refactoring One-Handed feature flag and others
+    
+    Purify some conditions for reducing redundancy codes.
+    
+    Test: manual
+    Change-Id: I2f39207424f9db3fb6b12bc08bee525f3dfab0aa
+
+commit ab98166c36f2955359ccdf31defe0678f66bdd99
+Author: Raman Tenneti <rtenneti@google.com>
+Date:   Mon Aug 3 12:54:21 2020 -0700
+
+    AOSP/Launcher3 - Update language to comply with Android's inclusive language guidance.
+    
+    See https://source.android.com/setup/contribute/respectful-code for reference
+    
+    BUG=161896447
+    
+    Test: make and Presubmit verify.
+    
+    Change-Id: I44e903046dd4cd9c7c07872fc56a8d552eb09522
+
+commit 12e59e9a66882d67687739cfd5be81e9beae790e
+Merge: cb89edcbc ffc06198f
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Mon Aug 3 23:04:08 2020 +0000
+
+    [automerger skipped] Merge "Rewrite long swipe resistance ("pullback") logic" into ub-launcher3-rvc-qpr-dev am: ffc06198ff -s ours
+    
+    am skip reason: Change-Id Ib0f9da18e10cc9ddf1a2f82ed767f237c89d3a41 with SHA-1 03c38c5c66 is in history
+    
+    Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/12174362
+    
+    Change-Id: Ie0fc8684a14ce7ea9730e7160551adbc2bc4b97c
+
+commit cb89edcbcc8fbbee1ddd074500fe26db01b06789
+Merge: 03c38c5c6 eaf7a9554
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Mon Aug 3 22:24:14 2020 +0000
+
+    Merge "Changing minimum supported Launcher version to 26" into ub-launcher3-master
+
+commit ffc06198ff041bd1496ba59b1a5c12922d630fc1
+Merge: bf8472585 354a436f4
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Mon Aug 3 22:11:30 2020 +0000
+
+    Merge "Rewrite long swipe resistance ("pullback") logic" into ub-launcher3-rvc-qpr-dev
+
+commit 354a436f4e96d2ae12740cb1af16b97c37166321
+Author: Tony Wickham <twickham@google.com>
+Date:   Thu Jun 25 15:51:33 2020 -0700
+
+    Rewrite long swipe resistance ("pullback") logic
+    
+    - Rename "pullback" to "resistance" to reduce confusion.
+    - Remove mDragLengthFactorStartPullback & mDragLengthFactorMaxPullback
+    - Add AnimatorControllerWithResistance, which has 2 controllers, one
+      for the normal shift to overview, then one to apply the resistance
+      when swiping beyond that.
+    - Don't hack animator interpolators/progress; insteaad, allow progress
+      to go > 1 (which will run the separate resistance animator).
+    - Don't start launcher controller separately from window controller;
+      instead, both are controlled by mCurrentShift in updateFinalShift().
+    - The resistance animation logic is shared by both the active window
+      and launcher (RecentsView).
+    
+    Bug: 149934536
+    Change-Id: Ib0f9da18e10cc9ddf1a2f82ed767f237c89d3a41
+    Merged-In: Ib0f9da18e10cc9ddf1a2f82ed767f237c89d3a41
+
+commit 03c38c5c665b08acb8d817bedc3ff78120f879da
+Author: Tony Wickham <twickham@google.com>
+Date:   Thu Jun 25 15:51:33 2020 -0700
+
+    Rewrite long swipe resistance ("pullback") logic
+    
+    - Rename "pullback" to "resistance" to reduce confusion.
+    - Remove mDragLengthFactorStartPullback & mDragLengthFactorMaxPullback
+    - Add AnimatorControllerWithResistance, which has 2 controllers, one
+      for the normal shift to overview, then one to apply the resistance
+      when swiping beyond that.
+    - Don't hack animator interpolators/progress; insteaad, allow progress
+      to go > 1 (which will run the separate resistance animator).
+    - Don't start launcher controller separately from window controller;
+      instead, both are controlled by mCurrentShift in updateFinalShift().
+    - The resistance animation logic is shared by both the active window
+      and launcher (RecentsView).
+    
+    Bug: 149934536
+    Change-Id: Ib0f9da18e10cc9ddf1a2f82ed767f237c89d3a41
+
+commit eaf7a955467a5d189b982ee436a8eefaa30e3006
+Author: Sunny Goyal <sunnygoyal@google.com>
+Date:   Wed Jul 29 16:54:20 2020 -0700
+
+    Changing minimum supported Launcher version to 26
+    
+    Change-Id: I49fcf874430ac53c3246371e179fbd828e14e4da
diff --git a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
index e649ce1..aa3710b 100644
--- a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
+++ b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
@@ -164,6 +164,12 @@
             }
 
             case TestProtocol.REQUEST_GET_TEST_EVENTS: {
+                if (sEvents == null) {
+                    // sEvents can be null if Launcher died and restarted after
+                    // REQUEST_START_EVENT_LOGGING.
+                    return response;
+                }
+
                 synchronized (sEvents) {
                     response.putStringArrayList(
                             TestProtocol.TEST_INFO_RESPONSE_FIELD, new ArrayList<>(sEvents));
diff --git a/go/AndroidManifest.xml b/go/AndroidManifest.xml
index f84a82e..f36439d 100644
--- a/go/AndroidManifest.xml
+++ b/go/AndroidManifest.xml
@@ -46,6 +46,12 @@
             tools:node="replace" >
         </activity>
 
+        <service
+            android:name="com.android.launcher3.notification.NotificationListener"
+            android:label="@string/notification_dots_service_title"
+            android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
+            android:enabled="false"
+            tools:node="replace" />
     </application>
 
 </manifest>
diff --git a/go/res/values/dimens.xml b/go/res/values/dimens.xml
deleted file mode 100644
index f1b1053..0000000
--- a/go/res/values/dimens.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<resources>
-    <!-- Dynamic Grid -->
-    <dimen name="dynamic_grid_hotseat_size">60dp</dimen>
-</resources>
\ No newline at end of file
diff --git a/go/src/com/android/launcher3/model/LoaderResults.java b/go/src/com/android/launcher3/model/LoaderResults.java
index 7130531..5f71061 100644
--- a/go/src/com/android/launcher3/model/LoaderResults.java
+++ b/go/src/com/android/launcher3/model/LoaderResults.java
@@ -20,7 +20,6 @@
 
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.model.BgDataModel.Callbacks;
-import com.android.launcher3.util.LooperExecutor;
 
 /**
  * Helper class to handle results of {@link com.android.launcher3.model.LoaderTask}.
@@ -29,12 +28,7 @@
 
     public LoaderResults(LauncherAppState app, BgDataModel dataModel,
             AllAppsList allAppsList, Callbacks[] callbacks) {
-        this(app, dataModel, allAppsList, callbacks, MAIN_EXECUTOR);
-    }
-
-    public LoaderResults(LauncherAppState app, BgDataModel dataModel,
-            AllAppsList allAppsList, Callbacks[] callbacks, LooperExecutor executor) {
-        super(app, dataModel, allAppsList, callbacks, executor);
+        super(app, dataModel, allAppsList, callbacks, MAIN_EXECUTOR);
     }
 
     @Override
diff --git a/go/src/com/android/launcher3/model/WidgetsModel.java b/go/src/com/android/launcher3/model/WidgetsModel.java
index 3b3dc01..89b3831 100644
--- a/go/src/com/android/launcher3/model/WidgetsModel.java
+++ b/go/src/com/android/launcher3/model/WidgetsModel.java
@@ -41,6 +41,7 @@
 
     // True is the widget support is disabled.
     public static final boolean GO_DISABLE_WIDGETS = true;
+    public static final boolean GO_DISABLE_NOTIFICATION_DOTS = true;
 
     private static final ArrayList<WidgetListRowEntry> EMPTY_WIDGET_LIST = new ArrayList<>();
 
diff --git a/protos/launcher_atom.proto b/protos/launcher_atom.proto
index 5611969..cd229ae 100644
--- a/protos/launcher_atom.proto
+++ b/protos/launcher_atom.proto
@@ -131,6 +131,7 @@
 // Legacy shortcuts and shortcuts handled by ShortcutManager
 message Shortcut {
   optional string shortcut_name = 1;
+  optional string shortcut_id = 2;
 }
 
 // AppWidgets handled by AppWidgetManager
diff --git a/quickstep/AndroidManifest-launcher.xml b/quickstep/AndroidManifest-launcher.xml
index 60afddb..19f85e4 100644
--- a/quickstep/AndroidManifest-launcher.xml
+++ b/quickstep/AndroidManifest-launcher.xml
@@ -49,10 +49,11 @@
             android:stateNotNeeded="true"
             android:windowSoftInputMode="adjustPan"
             android:screenOrientation="unspecified"
-            android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize"
+            android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize|uiMode"
             android:resizeableActivity="true"
             android:resumeWhilePausing="true"
             android:taskAffinity=""
+            android:exported="true"
             android:enabled="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index e49f2ec..5be32a8 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -17,97 +17,91 @@
 ** limitations under the License.
 */
 -->
-<manifest
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    package="com.android.launcher3" >
 
-    <permission
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     xmlns:tools="http://schemas.android.com/tools"
+     package="com.android.launcher3">
+
+     <permission
         android:name="${packageName}.permission.HOTSEAT_EDU"
         android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
         android:protectionLevel="signatureOrSystem" />
 
-    <uses-permission android:name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS" />
-    <uses-permission android:name="android.permission.VIBRATE" />
-    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+    <uses-permission android:name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS"/>
+    <uses-permission android:name="android.permission.VIBRATE"/>
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
     <uses-permission android:name="${packageName}.permission.HOTSEAT_EDU" />
 
-    <application
-        android:backupAgent="com.android.launcher3.LauncherBackupAgent"
-        android:fullBackupOnly="true"
-        android:fullBackupContent="@xml/backupscheme"
-        android:hardwareAccelerated="true"
-        android:icon="@drawable/ic_launcher_home"
-        android:label="@string/derived_app_name"
-        android:theme="@style/AppTheme"
-        android:largeHeap="@bool/config_largeHeap"
-        android:restoreAnyVersion="true"
-        android:supportsRtl="true" >
+    <application android:backupAgent="com.android.launcher3.LauncherBackupAgent"
+         android:fullBackupOnly="true"
+         android:fullBackupContent="@xml/backupscheme"
+         android:hardwareAccelerated="true"
+         android:icon="@drawable/ic_launcher_home"
+         android:label="@string/derived_app_name"
+         android:theme="@style/AppTheme"
+         android:largeHeap="@bool/config_largeHeap"
+         android:restoreAnyVersion="true"
+         android:supportsRtl="true">
 
-        <service
-            android:name="com.android.quickstep.TouchInteractionService"
-            android:permission="android.permission.STATUS_BAR_SERVICE"
-            android:directBootAware="true" >
+        <service android:name="com.android.quickstep.TouchInteractionService"
+             android:permission="android.permission.STATUS_BAR_SERVICE"
+             android:directBootAware="true"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.QUICKSTEP_SERVICE" />
+                <action android:name="android.intent.action.QUICKSTEP_SERVICE"/>
             </intent-filter>
         </service>
 
         <activity android:name="com.android.quickstep.RecentsActivity"
-            android:excludeFromRecents="true"
-            android:launchMode="singleTask"
-            android:clearTaskOnLaunch="true"
-            android:stateNotNeeded="true"
-            android:theme="@style/LauncherTheme"
-            android:screenOrientation="unspecified"
-            android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize"
-            android:resizeableActivity="true"
-            android:resumeWhilePausing="true"
-            android:taskAffinity="" />
+             android:excludeFromRecents="true"
+             android:launchMode="singleTask"
+             android:clearTaskOnLaunch="true"
+             android:stateNotNeeded="true"
+             android:theme="@style/LauncherTheme"
+             android:screenOrientation="unspecified"
+             android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize|uiMode"
+             android:resizeableActivity="true"
+             android:resumeWhilePausing="true"
+             android:taskAffinity=""/>
 
         <!-- Content provider to settings search. The autority should be same as the packageName -->
-        <provider
-            android:name="com.android.quickstep.LauncherSearchIndexablesProvider"
-            android:authorities="${packageName}"
-            android:grantUriPermissions="true"
-            android:multiprocess="true"
-            android:permission="android.permission.READ_SEARCH_INDEXABLES"
-            android:exported="true">
+        <provider android:name="com.android.quickstep.LauncherSearchIndexablesProvider"
+             android:authorities="${packageName}"
+             android:grantUriPermissions="true"
+             android:multiprocess="true"
+             android:permission="android.permission.READ_SEARCH_INDEXABLES"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.content.action.SEARCH_INDEXABLES_PROVIDER" />
+                <action android:name="android.content.action.SEARCH_INDEXABLES_PROVIDER"/>
             </intent-filter>
         </provider>
 
         <!-- FileProvider used for sharing images. -->
-        <provider
-            android:name="androidx.core.content.FileProvider"
-            android:authorities="${packageName}.overview.fileprovider"
-            android:exported="false"
-            android:grantUriPermissions="true">
-            <meta-data
-                android:name="android.support.FILE_PROVIDER_PATHS"
-                android:resource="@xml/overview_file_provider_paths" />
+        <provider android:name="androidx.core.content.FileProvider"
+             android:authorities="${packageName}.overview.fileprovider"
+             android:exported="false"
+             android:grantUriPermissions="true">
+            <meta-data android:name="android.support.FILE_PROVIDER_PATHS"
+                 android:resource="@xml/overview_file_provider_paths"/>
         </provider>
 
-        <service
-            android:name="com.android.launcher3.uioverrides.dynamicui.WallpaperManagerCompatVL$ColorExtractionService"
-            tools:node="remove" />
+        <service android:name="com.android.launcher3.uioverrides.dynamicui.WallpaperManagerCompatVL$ColorExtractionService"
+             tools:node="remove"/>
 
-        <activity
-            android:name="com.android.launcher3.proxy.ProxyActivityStarter"
-            android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen"
-            android:launchMode="singleTask"
-            android:clearTaskOnLaunch="true"
-            android:exported="false" />
+        <activity android:name="com.android.launcher3.proxy.ProxyActivityStarter"
+             android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen"
+             android:launchMode="singleTask"
+             android:clearTaskOnLaunch="true"
+             android:exported="false"/>
 
-        <activity
-            android:name="com.android.quickstep.interaction.GestureSandboxActivity"
-            android:autoRemoveFromRecents="true"
-            android:excludeFromRecents="true"
-            android:screenOrientation="portrait">
+        <activity android:name="com.android.quickstep.interaction.GestureSandboxActivity"
+             android:autoRemoveFromRecents="true"
+             android:excludeFromRecents="true"
+             android:screenOrientation="portrait"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.quickstep.action.GESTURE_SANDBOX" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.quickstep.action.GESTURE_SANDBOX"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
         <activity
@@ -116,7 +110,8 @@
             android:noHistory="true"
             android:launchMode="singleTask"
             android:clearTaskOnLaunch="true"
-            android:permission="${packageName}.permission.HOTSEAT_EDU">
+            android:permission="${packageName}.permission.HOTSEAT_EDU"
+            android:exported="true">
             <intent-filter>
                 <action android:name="com.android.launcher3.action.SHOW_HYBRID_HOTSEAT_EDU"/>
                 <category android:name="android.intent.category.DEFAULT" />
diff --git a/quickstep/recents_ui_overrides/res/values/colors.xml b/quickstep/recents_ui_overrides/res/values/colors.xml
deleted file mode 100644
index f03f118..0000000
--- a/quickstep/recents_ui_overrides/res/values/colors.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 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>
-    <color name="chip_hint_foreground_color">#fff</color>
-    <color name="chip_scrim_start_color">#39000000</color>
-
-    <color name="all_apps_label_text">#61000000</color>
-    <color name="all_apps_label_text_dark">#61FFFFFF</color>
-    <color name="all_apps_prediction_row_separator">#3c000000</color>
-    <color name="all_apps_prediction_row_separator_dark">#3cffffff</color>
-</resources>
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/res/values/dimens.xml b/quickstep/recents_ui_overrides/res/values/dimens.xml
deleted file mode 100644
index 9266b06..0000000
--- a/quickstep/recents_ui_overrides/res/values/dimens.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 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>
-    <dimen name="chip_hint_border_width">1dp</dimen>
-    <dimen name="chip_hint_corner_radius">20dp</dimen>
-    <dimen name="chip_hint_outer_padding">20dp</dimen>
-    <dimen name="chip_hint_start_padding">10dp</dimen>
-    <dimen name="chip_hint_end_padding">12dp</dimen>
-    <dimen name="chip_hint_horizontal_margin">20dp</dimen>
-    <dimen name="chip_hint_vertical_offset">16dp</dimen>
-    <dimen name="chip_hint_elevation">2dp</dimen>
-    <dimen name="chip_icon_size">16dp</dimen>
-    <dimen name="chip_text_height">26dp</dimen>
-    <dimen name="chip_text_top_padding">4dp</dimen>
-    <dimen name="chip_text_start_padding">10dp</dimen>
-    <dimen name="chip_text_size">14sp</dimen>
-
-    <dimen name="all_apps_prediction_row_divider_height">17dp</dimen>
-    <dimen name="all_apps_label_top_padding">16dp</dimen>
-    <dimen name="all_apps_label_bottom_padding">8dp</dimen>
-    <dimen name="all_apps_label_text_size">14sp</dimen>
-
-    <!-- Minimum distance to swipe to trigger accessibility gesture -->
-    <dimen name="accessibility_gesture_min_swipe_distance">80dp</dimen>
-</resources>
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/src/REMOVED.txt b/quickstep/recents_ui_overrides/src/REMOVED.txt
new file mode 100644
index 0000000..c3a3eaf
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/REMOVED.txt
@@ -0,0 +1,2 @@
+Temp file to prevent build breakage.
+Will be removed in followup cl.
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/ComponentKeyMapper.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/ComponentKeyMapper.java
deleted file mode 100644
index d200868..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/ComponentKeyMapper.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/**
- * Copyright (C) 2019 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.appprediction;
-
-import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER;
-
-import com.android.launcher3.allapps.AllAppsStore;
-import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.ItemInfoWithIcon;
-import com.android.launcher3.util.ComponentKey;
-
-public class ComponentKeyMapper {
-
-    protected final ComponentKey componentKey;
-    private final DynamicItemCache mCache;
-
-    public ComponentKeyMapper(ComponentKey key, DynamicItemCache cache) {
-        componentKey = key;
-        mCache = cache;
-    }
-
-    public String getPackage() {
-        return componentKey.componentName.getPackageName();
-    }
-
-    public String getComponentClass() {
-        return componentKey.componentName.getClassName();
-    }
-
-    public ComponentKey getComponentKey() {
-        return componentKey;
-    }
-
-    @Override
-    public String toString() {
-        return componentKey.toString();
-    }
-
-    public ItemInfoWithIcon getApp(AllAppsStore store) {
-        AppInfo item = store.getApp(componentKey);
-        if (item != null) {
-            return item;
-        } else if (getComponentClass().equals(COMPONENT_CLASS_MARKER)) {
-            return mCache.getInstantApp(componentKey.componentName.getPackageName());
-        } else {
-            return mCache.getShortcutInfo(componentKey);
-        }
-    }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java
deleted file mode 100644
index ab96b1340..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java
+++ /dev/null
@@ -1,268 +0,0 @@
-/**
- * Copyright (C) 2019 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.appprediction;
-
-import static android.content.pm.PackageManager.MATCH_INSTANT;
-
-import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ShortcutInfo;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.util.ArrayMap;
-import android.util.Log;
-
-import androidx.annotation.MainThread;
-import androidx.annotation.Nullable;
-import androidx.annotation.UiThread;
-import androidx.annotation.WorkerThread;
-
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.allapps.AllAppsStore;
-import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.shortcuts.ShortcutKey;
-import com.android.launcher3.shortcuts.ShortcutRequest;
-import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.InstantAppResolver;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Utility class which loads and caches predicted items like instant apps and shortcuts, before
- * they can be displayed on the UI
- */
-public class DynamicItemCache {
-
-    private static final String TAG = "DynamicItemCache";
-    private static final boolean DEBUG = false;
-    private static final String DEFAULT_URL = "default-url";
-
-    private static final int BG_MSG_LOAD_SHORTCUTS = 1;
-    private static final int BG_MSG_LOAD_INSTANT_APPS = 2;
-
-    private static final int UI_MSG_UPDATE_SHORTCUTS = 1;
-    private static final int UI_MSG_UPDATE_INSTANT_APPS = 2;
-
-    private final Context mContext;
-    private final Handler mWorker;
-    private final Handler mUiHandler;
-    private final InstantAppResolver mInstantAppResolver;
-    private final Runnable mOnUpdateCallback;
-    private final IconCache mIconCache;
-
-    private final Map<ComponentKey, WorkspaceItemInfo> mShortcuts;
-    private final Map<String, InstantAppItemInfo> mInstantApps;
-
-    public DynamicItemCache(Context context, Runnable onUpdateCallback) {
-        mContext = context;
-        mWorker = new Handler(MODEL_EXECUTOR.getLooper(), this::handleWorkerMessage);
-        mUiHandler = new Handler(Looper.getMainLooper(), this::handleUiMessage);
-        mInstantAppResolver = InstantAppResolver.newInstance(context);
-        mOnUpdateCallback = onUpdateCallback;
-        mIconCache = LauncherAppState.getInstance(mContext).getIconCache();
-
-        mShortcuts = new HashMap<>();
-        mInstantApps = new HashMap<>();
-    }
-
-    public void cacheItems(List<ShortcutKey> shortcutKeys, List<String> pkgNames) {
-        if (!shortcutKeys.isEmpty()) {
-            mWorker.removeMessages(BG_MSG_LOAD_SHORTCUTS);
-            Message.obtain(mWorker, BG_MSG_LOAD_SHORTCUTS, shortcutKeys).sendToTarget();
-        }
-        if (!pkgNames.isEmpty()) {
-            mWorker.removeMessages(BG_MSG_LOAD_INSTANT_APPS);
-            Message.obtain(mWorker, BG_MSG_LOAD_INSTANT_APPS, pkgNames).sendToTarget();
-        }
-    }
-
-    private boolean handleWorkerMessage(Message msg) {
-        switch (msg.what) {
-            case BG_MSG_LOAD_SHORTCUTS: {
-                List<ShortcutKey> shortcutKeys = msg.obj != null ?
-                        (List<ShortcutKey>) msg.obj : Collections.EMPTY_LIST;
-                Map<ShortcutKey, WorkspaceItemInfo> shortcutKeyAndInfos = new ArrayMap<>();
-                for (ShortcutKey shortcutKey : shortcutKeys) {
-                    WorkspaceItemInfo workspaceItemInfo = loadShortcutWorker(shortcutKey);
-                    if (workspaceItemInfo != null) {
-                        shortcutKeyAndInfos.put(shortcutKey, workspaceItemInfo);
-                    }
-                }
-                Message.obtain(mUiHandler, UI_MSG_UPDATE_SHORTCUTS, shortcutKeyAndInfos)
-                        .sendToTarget();
-                return true;
-            }
-            case BG_MSG_LOAD_INSTANT_APPS: {
-                List<String> pkgNames = msg.obj != null ?
-                        (List<String>) msg.obj : Collections.EMPTY_LIST;
-                List<InstantAppItemInfo> instantAppItemInfos = new ArrayList<>();
-                for (String pkgName : pkgNames) {
-                    InstantAppItemInfo instantAppItemInfo = loadInstantApp(pkgName);
-                    if (instantAppItemInfo != null) {
-                        instantAppItemInfos.add(instantAppItemInfo);
-                    }
-                }
-                Message.obtain(mUiHandler, UI_MSG_UPDATE_INSTANT_APPS, instantAppItemInfos)
-                        .sendToTarget();
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    private boolean handleUiMessage(Message msg) {
-        switch (msg.what) {
-            case UI_MSG_UPDATE_SHORTCUTS: {
-                mShortcuts.clear();
-                mShortcuts.putAll((Map<ShortcutKey, WorkspaceItemInfo>) msg.obj);
-                mOnUpdateCallback.run();
-                return true;
-            }
-            case UI_MSG_UPDATE_INSTANT_APPS: {
-                List<InstantAppItemInfo> instantAppItemInfos = (List<InstantAppItemInfo>) msg.obj;
-                mInstantApps.clear();
-                for (InstantAppItemInfo instantAppItemInfo : instantAppItemInfos) {
-                    mInstantApps.put(instantAppItemInfo.getTargetComponent().getPackageName(),
-                            instantAppItemInfo);
-                }
-                mOnUpdateCallback.run();
-                if (DEBUG) {
-                    Log.d(TAG, String.format("Cache size: %d, Cache: %s",
-                            mInstantApps.size(), mInstantApps.toString()));
-                }
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    @WorkerThread
-    private WorkspaceItemInfo loadShortcutWorker(ShortcutKey shortcutKey) {
-        List<ShortcutInfo> details = shortcutKey.buildRequest(mContext).query(ShortcutRequest.ALL);
-        if (!details.isEmpty()) {
-            WorkspaceItemInfo si = new WorkspaceItemInfo(details.get(0), mContext);
-            mIconCache.getShortcutIcon(si, details.get(0));
-            return si;
-        }
-        if (DEBUG) {
-            Log.d(TAG, "No shortcut found: " + shortcutKey.toString());
-        }
-        return null;
-    }
-
-    private InstantAppItemInfo loadInstantApp(String pkgName) {
-        PackageManager pm = mContext.getPackageManager();
-
-        try {
-            ApplicationInfo ai = pm.getApplicationInfo(pkgName, 0);
-            if (!mInstantAppResolver.isInstantApp(ai)) {
-                return null;
-            }
-        } catch (PackageManager.NameNotFoundException e) {
-            return null;
-        }
-
-        String url = retrieveDefaultUrl(pkgName, pm);
-        if (url == null) {
-            Log.w(TAG, "no default-url available for pkg " + pkgName);
-            return null;
-        }
-
-        Intent intent = new Intent(Intent.ACTION_VIEW)
-                .addCategory(Intent.CATEGORY_BROWSABLE)
-                .setData(Uri.parse(url));
-        InstantAppItemInfo info = new InstantAppItemInfo(intent, pkgName);
-        IconCache iconCache = LauncherAppState.getInstance(mContext).getIconCache();
-        iconCache.getTitleAndIcon(info, false);
-        if (info.bitmap.icon == null || iconCache.isDefaultIcon(info.bitmap, info.user)) {
-            return null;
-        }
-        return info;
-    }
-
-    @Nullable
-    public static String retrieveDefaultUrl(String pkgName, PackageManager pm) {
-        Intent mainIntent = new Intent().setAction(Intent.ACTION_MAIN)
-                .addCategory(Intent.CATEGORY_LAUNCHER).setPackage(pkgName);
-        List<ResolveInfo> resolveInfos = pm.queryIntentActivities(
-                mainIntent, MATCH_INSTANT | PackageManager.GET_META_DATA);
-        String url = null;
-        for (ResolveInfo resolveInfo : resolveInfos) {
-            if (resolveInfo.activityInfo.metaData != null
-                    && resolveInfo.activityInfo.metaData.containsKey(DEFAULT_URL)) {
-                url = resolveInfo.activityInfo.metaData.getString(DEFAULT_URL);
-            }
-        }
-        return url;
-    }
-
-    @UiThread
-    public InstantAppItemInfo getInstantApp(String pkgName) {
-        return mInstantApps.get(pkgName);
-    }
-
-    @MainThread
-    public WorkspaceItemInfo getShortcutInfo(ComponentKey key) {
-        return mShortcuts.get(key);
-    }
-
-    /**
-     * requests and caches icons for app targets
-     */
-    public void updateDependencies(List<ComponentKeyMapper> componentKeyMappers,
-            AllAppsStore appsStore, IconCache.ItemInfoUpdateReceiver callback, int itemCount) {
-        List<String> instantAppsToLoad = new ArrayList<>();
-        List<ShortcutKey> shortcutsToLoad = new ArrayList<>();
-        int total = componentKeyMappers.size();
-        for (int i = 0, count = 0; i < total && count < itemCount; i++) {
-            ComponentKeyMapper mapper = componentKeyMappers.get(i);
-            // Update instant apps
-            if (COMPONENT_CLASS_MARKER.equals(mapper.getComponentClass())) {
-                instantAppsToLoad.add(mapper.getPackage());
-                count++;
-            } else if (mapper.getComponentKey() instanceof ShortcutKey) {
-                shortcutsToLoad.add((ShortcutKey) mapper.getComponentKey());
-                count++;
-            } else {
-                // Reload high res icon
-                AppInfo info = (AppInfo) mapper.getApp(appsStore);
-                if (info != null) {
-                    if (info.usingLowResIcon()) {
-                        mIconCache.updateIconInBackground(callback, info);
-                    }
-                    count++;
-                }
-            }
-        }
-        cacheItems(shortcutsToLoad, instantAppsToLoad);
-    }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java
deleted file mode 100644
index 4c7943b..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java
+++ /dev/null
@@ -1,276 +0,0 @@
-/*
- * Copyright (C) 2019 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.appprediction;
-
-import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_GRID;
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-
-import android.annotation.TargetApi;
-import android.app.prediction.AppPredictionContext;
-import android.app.prediction.AppPredictionManager;
-import android.app.prediction.AppPredictor;
-import android.app.prediction.AppTarget;
-import android.app.prediction.AppTargetEvent;
-import android.app.prediction.AppTargetId;
-import android.content.ComponentName;
-import android.content.Context;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.os.UserHandle;
-import android.util.Log;
-
-import androidx.annotation.Nullable;
-import androidx.annotation.UiThread;
-import androidx.annotation.WorkerThread;
-
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.appprediction.PredictionUiStateManager.Client;
-import com.android.launcher3.model.AppLaunchTracker;
-import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
-import com.android.systemui.plugins.AppLaunchEventsPlugin;
-import com.android.systemui.plugins.PluginListener;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Subclass of app tracker which publishes the data to the prediction engine and gets back results.
- */
-@TargetApi(Build.VERSION_CODES.Q)
-public class PredictionAppTracker extends AppLaunchTracker
-        implements PluginListener<AppLaunchEventsPlugin> {
-
-    private static final String TAG = "PredictionAppTracker";
-    private static final boolean DBG = false;
-
-    private static final int MSG_INIT = 0;
-    private static final int MSG_DESTROY = 1;
-    private static final int MSG_LAUNCH = 2;
-    private static final int MSG_PREDICT = 3;
-
-    protected final Context mContext;
-    private final Handler mMessageHandler;
-    private final List<AppLaunchEventsPlugin> mAppLaunchEventsPluginsList;
-
-    // Accessed only on worker thread
-    private AppPredictor mHomeAppPredictor;
-    private AppPredictor mRecentsOverviewPredictor;
-
-    public PredictionAppTracker(Context context) {
-        mContext = context;
-        mMessageHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(), this::handleMessage);
-        InvariantDeviceProfile.INSTANCE.get(mContext).addOnChangeListener(this::onIdpChanged);
-
-        mMessageHandler.sendEmptyMessage(MSG_INIT);
-
-        mAppLaunchEventsPluginsList = new ArrayList<>();
-        PluginManagerWrapper.INSTANCE.get(context)
-                .addPluginListener(this, AppLaunchEventsPlugin.class, true);
-    }
-
-    @UiThread
-    private void onIdpChanged(int changeFlags, InvariantDeviceProfile profile) {
-        if ((changeFlags & CHANGE_FLAG_GRID) != 0) {
-            // Reinitialize everything
-            mMessageHandler.sendEmptyMessage(MSG_INIT);
-        }
-    }
-
-    @WorkerThread
-    private void destroy() {
-        if (mHomeAppPredictor != null) {
-            mHomeAppPredictor.destroy();
-            mHomeAppPredictor = null;
-        }
-        if (mRecentsOverviewPredictor != null) {
-            mRecentsOverviewPredictor.destroy();
-            mRecentsOverviewPredictor = null;
-        }
-    }
-
-    @WorkerThread
-    private AppPredictor createPredictor(Client client, int count) {
-        AppPredictionManager apm = mContext.getSystemService(AppPredictionManager.class);
-
-        if (apm == null) {
-            return null;
-        }
-
-        AppPredictor predictor = apm.createAppPredictionSession(
-                new AppPredictionContext.Builder(mContext)
-                        .setUiSurface(client.id)
-                        .setPredictedTargetCount(count)
-                        .setExtras(getAppPredictionContextExtras(client))
-                        .build());
-        predictor.registerPredictionUpdates(mContext.getMainExecutor(),
-                PredictionUiStateManager.INSTANCE.get(mContext).appPredictorCallback(client));
-        predictor.requestPredictionUpdate();
-        return predictor;
-    }
-
-    /**
-     * Override to add custom extras.
-     */
-    @WorkerThread
-    @Nullable
-    public Bundle getAppPredictionContextExtras(Client client) {
-        return null;
-    }
-
-    @WorkerThread
-    private boolean handleMessage(Message msg) {
-        switch (msg.what) {
-            case MSG_INIT: {
-                // Destroy any existing clients
-                destroy();
-
-                // Initialize the clients
-                int count = InvariantDeviceProfile.INSTANCE.get(mContext).numAllAppsColumns;
-                mHomeAppPredictor = createPredictor(Client.HOME, count);
-                mRecentsOverviewPredictor = createPredictor(Client.OVERVIEW, count);
-                return true;
-            }
-            case MSG_DESTROY: {
-                destroy();
-                return true;
-            }
-            case MSG_LAUNCH: {
-                if (mHomeAppPredictor != null) {
-                    mHomeAppPredictor.notifyAppTargetEvent((AppTargetEvent) msg.obj);
-                }
-                return true;
-            }
-            case MSG_PREDICT: {
-                if (mHomeAppPredictor != null) {
-                    String client = (String) msg.obj;
-                    if (Client.HOME.id.equals(client)) {
-                        mHomeAppPredictor.requestPredictionUpdate();
-                    } else {
-                        mRecentsOverviewPredictor.requestPredictionUpdate();
-                    }
-                }
-                return true;
-            }
-        }
-        return false;
-    }
-
-    @Override
-    @UiThread
-    public void onReturnedToHome() {
-        String client = Client.HOME.id;
-        mMessageHandler.removeMessages(MSG_PREDICT, client);
-        Message.obtain(mMessageHandler, MSG_PREDICT, client).sendToTarget();
-        if (DBG) {
-            Log.d(TAG, String.format("Sent immediate message to update %s", client));
-        }
-
-        // Relay onReturnedToHome to every plugin.
-        mAppLaunchEventsPluginsList.forEach(AppLaunchEventsPlugin::onReturnedToHome);
-    }
-
-    @Override
-    @UiThread
-    public void onStartShortcut(String packageName, String shortcutId, UserHandle user,
-                                String container) {
-        // TODO: Use the full shortcut info
-        AppTarget target = new AppTarget.Builder(
-                new AppTargetId("shortcut:" + shortcutId), packageName, user)
-                .setClassName(shortcutId)
-                .build();
-
-        sendLaunch(target, container);
-
-        // Relay onStartShortcut info to every connected plugin.
-        mAppLaunchEventsPluginsList
-                .forEach(plugin -> plugin.onStartShortcut(
-                        packageName,
-                        shortcutId,
-                        user,
-                        container != null ? container : CONTAINER_DEFAULT)
-        );
-
-    }
-
-    @Override
-    @UiThread
-    public void onStartApp(ComponentName cn, UserHandle user, String container) {
-        if (cn != null) {
-            AppTarget target = new AppTarget.Builder(
-                    new AppTargetId("app:" + cn), cn.getPackageName(), user)
-                    .setClassName(cn.getClassName())
-                    .build();
-            sendLaunch(target, container);
-
-            // Relay onStartApp to every connected plugin.
-            mAppLaunchEventsPluginsList
-                    .forEach(plugin -> plugin.onStartApp(
-                            cn,
-                            user,
-                            container != null ? container : CONTAINER_DEFAULT)
-            );
-        }
-    }
-
-    @Override
-    @UiThread
-    public void onDismissApp(ComponentName cn, UserHandle user, String container) {
-        if (cn == null) return;
-        AppTarget target = new AppTarget.Builder(
-                new AppTargetId("app: " + cn), cn.getPackageName(), user)
-                .setClassName(cn.getClassName())
-                .build();
-        sendDismiss(target, container);
-
-        // Relay onDismissApp to every connected plugin.
-        mAppLaunchEventsPluginsList
-                .forEach(plugin -> plugin.onDismissApp(
-                        cn,
-                        user,
-                        container != null ? container : CONTAINER_DEFAULT)
-        );
-    }
-
-    @UiThread
-    private void sendEvent(AppTarget target, String container, int eventId) {
-        AppTargetEvent event = new AppTargetEvent.Builder(target, eventId)
-                .setLaunchLocation(container == null ? CONTAINER_DEFAULT : container)
-                .build();
-        Message.obtain(mMessageHandler, MSG_LAUNCH, event).sendToTarget();
-    }
-
-    @UiThread
-    private void sendLaunch(AppTarget target, String container) {
-        sendEvent(target, container, AppTargetEvent.ACTION_LAUNCH);
-    }
-
-    @UiThread
-    private void sendDismiss(AppTarget target, String container) {
-        sendEvent(target, container, AppTargetEvent.ACTION_DISMISS);
-    }
-
-    @Override
-    public void onPluginConnected(AppLaunchEventsPlugin appLaunchEventsPlugin, Context context) {
-        mAppLaunchEventsPluginsList.add(appLaunchEventsPlugin);
-    }
-
-    @Override
-    public void onPluginDisconnected(AppLaunchEventsPlugin appLaunchEventsPlugin) {
-        mAppLaunchEventsPluginsList.remove(appLaunchEventsPlugin);
-    }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
deleted file mode 100644
index a0f6b04..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
+++ /dev/null
@@ -1,370 +0,0 @@
-/*
- * Copyright (C) 2019 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.appprediction;
-
-import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
-import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
-import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
-import static com.android.launcher3.LauncherState.BACKGROUND_APP;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-
-import android.app.prediction.AppPredictor;
-import android.app.prediction.AppTarget;
-import android.content.ComponentName;
-import android.content.Context;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.allapps.AllAppsContainerView;
-import com.android.launcher3.allapps.AllAppsStore.OnUpdateListener;
-import com.android.launcher3.hybridhotseat.HotseatPredictionController;
-import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.ItemInfoWithIcon;
-import com.android.launcher3.shortcuts.ShortcutKey;
-import com.android.launcher3.statemanager.StateManager.StateListener;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
-import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.MainThreadInitializedObject;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.OptionalInt;
-import java.util.stream.IntStream;
-
-/**
- * Handler responsible to updating the UI due to predicted apps changes. Operations:
- * 1) Pushes the predicted apps to all-apps. If all-apps is visible, waits until it becomes
- * invisible again before applying the changes. This ensures that the UI does not change abruptly
- * in front of the user, even if an app launched and user pressed back button to return to the
- * all-apps UI again.
- * 2) Prefetch high-res icons for predicted apps. This ensures that we have the icons in memory
- * even if all-apps is not opened as they are shown in search UI as well
- * 3) Load instant app if it is not already in memory. As predictions are persisted on disk,
- * instant app will not be in memory when launcher starts.
- * 4) Maintains the current active client id (for the predictions) and all updates are performed on
- * that client id.
- */
-public class PredictionUiStateManager implements StateListener<LauncherState>,
-        ItemInfoUpdateReceiver, OnIDPChangeListener, OnUpdateListener {
-
-    public static final String LAST_PREDICTION_ENABLED_STATE = "last_prediction_enabled_state";
-
-    // TODO (b/129421797): Update the client constants
-    public enum Client {
-        HOME("home"),
-        OVERVIEW("overview");
-
-        public final String id;
-
-        Client(String id) {
-            this.id = id;
-        }
-    }
-
-    public static final MainThreadInitializedObject<PredictionUiStateManager> INSTANCE =
-            new MainThreadInitializedObject<>(PredictionUiStateManager::new);
-
-    private final Context mContext;
-
-    private final DynamicItemCache mDynamicItemCache;
-    private final List[] mPredictionServicePredictions;
-
-    private int mMaxIconsPerRow;
-    private Client mActiveClient;
-
-    private AllAppsContainerView mAppsView;
-
-    private PredictionState mPendingState;
-    private PredictionState mCurrentState;
-
-    private boolean mGettingValidPredictionResults;
-
-    private PredictionUiStateManager(Context context) {
-        mContext = context;
-
-        mDynamicItemCache = new DynamicItemCache(context, this::onAppsUpdated);
-
-        mActiveClient = Client.HOME;
-
-        InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
-        mMaxIconsPerRow = idp.numColumns;
-
-        idp.addOnChangeListener(this);
-        mPredictionServicePredictions = new List[Client.values().length];
-        for (int i = 0; i < mPredictionServicePredictions.length; i++) {
-            mPredictionServicePredictions[i] = Collections.emptyList();
-        }
-        mGettingValidPredictionResults = Utilities.getDevicePrefs(context)
-                .getBoolean(LAST_PREDICTION_ENABLED_STATE, true);
-
-        // Call this last
-        mCurrentState = parseLastState();
-    }
-
-    @Override
-    public void onIdpChanged(int changeFlags, InvariantDeviceProfile profile) {
-        mMaxIconsPerRow = profile.numColumns;
-    }
-
-    public Client getClient() {
-        return mActiveClient;
-    }
-
-    public void switchClient(Client client) {
-        if (client == mActiveClient) {
-            return;
-        }
-        mActiveClient = client;
-        dispatchOnChange(true);
-    }
-
-    public void setTargetAppsView(AllAppsContainerView appsView) {
-        if (mAppsView != null) {
-            mAppsView.getAppsStore().removeUpdateListener(this);
-        }
-        mAppsView = appsView;
-        if (mAppsView != null) {
-            mAppsView.getAppsStore().addUpdateListener(this);
-        }
-        if (mPendingState != null) {
-            applyState(mPendingState);
-            mPendingState = null;
-        } else {
-            applyState(mCurrentState);
-        }
-        updateDependencies(mCurrentState);
-    }
-
-    @Override
-    public void reapplyItemInfo(ItemInfoWithIcon info) { }
-
-    @Override
-    public void onStateTransitionComplete(LauncherState state) {
-        if (mAppsView == null) {
-            return;
-        }
-        if (mPendingState != null && canApplyPredictions(mPendingState)) {
-            applyState(mPendingState);
-            mPendingState = null;
-        }
-        if (mPendingState == null) {
-            Launcher.getLauncher(mAppsView.getContext()).getStateManager()
-                    .removeStateListener(this);
-        }
-    }
-
-    private void scheduleApplyPredictedApps(PredictionState state) {
-        boolean registerListener = mPendingState == null;
-        mPendingState = state;
-        if (registerListener) {
-            // Add a listener and wait until appsView is invisible again.
-            Launcher.getLauncher(mAppsView.getContext()).getStateManager().addStateListener(this);
-        }
-    }
-
-    private void applyState(PredictionState state) {
-        mCurrentState = state;
-        if (mAppsView != null) {
-            mAppsView.getFloatingHeaderView().findFixedRowByType(PredictionRowView.class)
-                    .setPredictedApps(mCurrentState.apps);
-        }
-    }
-
-    private void updatePredictionStateAfterCallback() {
-        boolean validResults = false;
-        for (List l : mPredictionServicePredictions) {
-            validResults |= l != null && !l.isEmpty();
-        }
-        if (validResults != mGettingValidPredictionResults) {
-            mGettingValidPredictionResults = validResults;
-            Utilities.getDevicePrefs(mContext).edit()
-                    .putBoolean(LAST_PREDICTION_ENABLED_STATE, true)
-                    .apply();
-        }
-        dispatchOnChange(true);
-    }
-
-    public AppPredictor.Callback appPredictorCallback(Client client) {
-        return targets -> {
-            mPredictionServicePredictions[client.ordinal()] = targets;
-            updatePredictionStateAfterCallback();
-        };
-    }
-
-    private void dispatchOnChange(boolean changed) {
-        PredictionState newState = changed
-                ? parseLastState()
-                : mPendingState != null && canApplyPredictions(mPendingState)
-                        ? mPendingState
-                        : mCurrentState;
-        if (changed && mAppsView != null && !canApplyPredictions(newState)) {
-            scheduleApplyPredictedApps(newState);
-        } else {
-            applyState(newState);
-        }
-    }
-
-    private PredictionState parseLastState() {
-        PredictionState state = new PredictionState();
-        state.isEnabled = mGettingValidPredictionResults;
-        if (!state.isEnabled) {
-            state.apps = Collections.EMPTY_LIST;
-            return state;
-        }
-
-        state.apps = new ArrayList<>();
-
-        List<AppTarget> appTargets = mPredictionServicePredictions[mActiveClient.ordinal()];
-        if (!appTargets.isEmpty()) {
-            for (AppTarget appTarget : appTargets) {
-                ComponentKey key;
-                if (appTarget.getShortcutInfo() != null) {
-                    key = ShortcutKey.fromInfo(appTarget.getShortcutInfo());
-                } else {
-                    key = new ComponentKey(new ComponentName(appTarget.getPackageName(),
-                            appTarget.getClassName()), appTarget.getUser());
-                }
-                state.apps.add(new ComponentKeyMapper(key, mDynamicItemCache));
-            }
-        }
-        updateDependencies(state);
-        return state;
-    }
-
-    private void updateDependencies(PredictionState state) {
-        if (!state.isEnabled || mAppsView == null) {
-            return;
-        }
-        mDynamicItemCache.updateDependencies(state.apps, mAppsView.getAppsStore(), this,
-                mMaxIconsPerRow);
-    }
-
-    @Override
-    public void onAppsUpdated() {
-        dispatchOnChange(false);
-    }
-
-    private boolean canApplyPredictions(PredictionState newState) {
-        if (mAppsView == null) {
-            // If there is no apps view, no need to schedule.
-            return true;
-        }
-        Launcher launcher = Launcher.getLauncher(mAppsView.getContext());
-        PredictionRowView predictionRow = mAppsView.getFloatingHeaderView().
-                findFixedRowByType(PredictionRowView.class);
-        if (!predictionRow.isShown() || predictionRow.getAlpha() == 0 ||
-                launcher.isForceInvisible()) {
-            return true;
-        }
-
-        if (mCurrentState.isEnabled != newState.isEnabled
-                || mCurrentState.apps.isEmpty() != newState.apps.isEmpty()) {
-            // If the visibility of the prediction row is changing, apply immediately.
-            return true;
-        }
-
-        if (launcher.getDeviceProfile().isVerticalBarLayout()) {
-            // If we are here & mAppsView.isShown() = true, we are probably in all-apps or mid way
-            return false;
-        }
-        if (!launcher.isInState(OVERVIEW) && !launcher.isInState(BACKGROUND_APP)) {
-            // Just a fallback as we dont need to apply instantly, if we are not in the swipe-up UI
-            return false;
-        }
-
-        // Instead of checking against 1, we should check against (1 + delta), where delta accounts
-        // for the nav-bar height (as app icon can still be visible under the nav-bar). Checking
-        // against 1, keeps the logic simple :)
-        return launcher.getAllAppsController().getProgress() > 1;
-    }
-
-    public PredictionState getCurrentState() {
-        return mCurrentState;
-    }
-
-    /**
-     * Returns ranking info for the app within all apps prediction.
-     * Only applicable when {@link ItemInfo#itemType} is one of the followings:
-     * {@link LauncherSettings.Favorites#ITEM_TYPE_APPLICATION},
-     * {@link LauncherSettings.Favorites#ITEM_TYPE_SHORTCUT},
-     * {@link LauncherSettings.Favorites#ITEM_TYPE_DEEP_SHORTCUT}
-     */
-    public OptionalInt getAllAppsRank(@Nullable ItemInfo itemInfo) {
-        if (itemInfo == null || itemInfo.getTargetComponent() == null || itemInfo.user == null) {
-            return OptionalInt.empty();
-        }
-
-        if (itemInfo.itemType == ITEM_TYPE_APPLICATION
-                || itemInfo.itemType == ITEM_TYPE_SHORTCUT
-                || itemInfo.itemType == ITEM_TYPE_DEEP_SHORTCUT) {
-            ComponentKey key = new ComponentKey(itemInfo.getTargetComponent(),
-                    itemInfo.user);
-            final List<ComponentKeyMapper> apps = getCurrentState().apps;
-            return IntStream.range(0, apps.size())
-                    .filter(index -> key.equals(apps.get(index).getComponentKey()))
-                    .findFirst();
-        }
-
-        return OptionalInt.empty();
-    }
-
-    /**
-     * Fill in predicted_rank field based on app prediction.
-     * Only applicable when {@link ItemInfo#itemType} is one of the followings:
-     * {@link LauncherSettings.Favorites#ITEM_TYPE_APPLICATION},
-     * {@link LauncherSettings.Favorites#ITEM_TYPE_SHORTCUT},
-     * {@link LauncherSettings.Favorites#ITEM_TYPE_DEEP_SHORTCUT}
-     */
-    public static void fillInPredictedRank(
-            @NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target) {
-
-        final PredictionUiStateManager manager = PredictionUiStateManager.INSTANCE.getNoCreate();
-        if (manager == null || itemInfo.getTargetComponent() == null || itemInfo.user == null
-                || (itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
-                && itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT
-                && itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT)) {
-            return;
-        }
-        if (itemInfo.container != LauncherSettings.Favorites.CONTAINER_PREDICTION) {
-            HotseatPredictionController.encodeHotseatLayoutIntoPredictionRank(itemInfo, target);
-            return;
-        }
-
-        final ComponentKey k = new ComponentKey(itemInfo.getTargetComponent(), itemInfo.user);
-        final List<ComponentKeyMapper> predictedApps = manager.getCurrentState().apps;
-        IntStream.range(0, predictedApps.size())
-                .filter((i) -> k.equals(predictedApps.get(i).getComponentKey()))
-                .findFirst()
-                .ifPresent((rank) -> target.predictedRank = 0 - rank);
-    }
-
-    public static class PredictionState {
-
-        public boolean isEnabled;
-        public List<ComponentKeyMapper> apps;
-    }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
deleted file mode 100644
index b94e633..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ /dev/null
@@ -1,723 +0,0 @@
-/*
- * Copyright (C) 2019 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.hybridhotseat;
-
-import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_GRID;
-import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
-import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.hybridhotseat.HotseatEduController.getSettingsIntent;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_RANKED;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.app.prediction.AppPredictionContext;
-import android.app.prediction.AppPredictionManager;
-import android.app.prediction.AppPredictor;
-import android.app.prediction.AppTarget;
-import android.app.prediction.AppTargetEvent;
-import android.content.ComponentName;
-import android.os.Process;
-import android.util.Log;
-import android.view.HapticFeedbackConstants;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.DragSource;
-import com.android.launcher3.DropTarget;
-import com.android.launcher3.Hotseat;
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.allapps.AllAppsStore;
-import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.launcher3.appprediction.ComponentKeyMapper;
-import com.android.launcher3.appprediction.DynamicItemCache;
-import com.android.launcher3.dragndrop.DragController;
-import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
-import com.android.launcher3.logger.LauncherAtom.PredictedHotseatContainer;
-import com.android.launcher3.logging.InstanceId;
-import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.FolderInfo;
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.ItemInfoWithIcon;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.popup.SystemShortcut;
-import com.android.launcher3.shortcuts.ShortcutKey;
-import com.android.launcher3.touch.ItemLongClickListener;
-import com.android.launcher3.uioverrides.PredictedAppIcon;
-import com.android.launcher3.uioverrides.QuickstepLauncher;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
-import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.IntArray;
-import com.android.launcher3.util.OnboardingPrefs;
-import com.android.launcher3.views.ArrowTipView;
-import com.android.launcher3.views.Snackbar;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.OptionalInt;
-import java.util.stream.IntStream;
-
-/**
- * Provides prediction ability for the hotseat. Fills gaps in hotseat with predicted items, allows
- * pinning of predicted apps and manages replacement of predicted apps with user drag.
- */
-public class HotseatPredictionController implements DragController.DragListener,
-        View.OnAttachStateChangeListener, SystemShortcut.Factory<QuickstepLauncher>,
-        InvariantDeviceProfile.OnIDPChangeListener, AllAppsStore.OnUpdateListener,
-        IconCache.ItemInfoUpdateReceiver, DragSource {
-
-    private static final String TAG = "PredictiveHotseat";
-    private static final boolean DEBUG = false;
-
-    private static final String PREDICTION_CLIENT = "hotseat";
-    private DropTarget.DragObject mDragObject;
-    private int mHotSeatItemsCount;
-    private int mPredictedSpotsCount = 0;
-
-    private Launcher mLauncher;
-    private final Hotseat mHotseat;
-
-    private final HotseatRestoreHelper mRestoreHelper;
-
-    private List<ComponentKeyMapper> mComponentKeyMappers = new ArrayList<>();
-
-    private DynamicItemCache mDynamicItemCache;
-
-    private final HotseatPredictionModel mPredictionModel;
-    private AppPredictor mAppPredictor;
-    private AllAppsStore mAllAppsStore;
-    private AnimatorSet mIconRemoveAnimators;
-    private boolean mUIUpdatePaused = false;
-    private boolean mIsDestroyed = false;
-
-
-    private List<PredictedAppIcon.PredictedIconOutlineDrawing> mOutlineDrawings = new ArrayList<>();
-
-    private final View.OnLongClickListener mPredictionLongClickListener = v -> {
-        if (!ItemLongClickListener.canStartDrag(mLauncher)) return false;
-        if (mLauncher.getWorkspace().isSwitchingState()) return false;
-        if (!mLauncher.getOnboardingPrefs().getBoolean(
-                OnboardingPrefs.HOTSEAT_LONGPRESS_TIP_SEEN)) {
-            Snackbar.show(mLauncher, R.string.hotseat_tip_gaps_filled,
-                    R.string.hotseat_prediction_settings, null,
-                    () -> mLauncher.startActivity(getSettingsIntent()));
-            mLauncher.getOnboardingPrefs().markChecked(OnboardingPrefs.HOTSEAT_LONGPRESS_TIP_SEEN);
-            mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
-            return true;
-        }
-        // Start the drag
-        mLauncher.getWorkspace().beginDragShared(v, this, new DragOptions());
-        return true;
-    };
-
-    public HotseatPredictionController(Launcher launcher) {
-        mLauncher = launcher;
-        mHotseat = launcher.getHotseat();
-        mAllAppsStore = mLauncher.getAppsView().getAppsStore();
-        LauncherAppState appState = LauncherAppState.getInstance(launcher);
-        mPredictionModel = (HotseatPredictionModel) appState.getPredictionModel();
-        mAllAppsStore.addUpdateListener(this);
-        mDynamicItemCache = new DynamicItemCache(mLauncher, this::fillGapsWithPrediction);
-        mHotSeatItemsCount = mLauncher.getDeviceProfile().inv.numHotseatIcons;
-        launcher.getDeviceProfile().inv.addOnChangeListener(this);
-        mHotseat.addOnAttachStateChangeListener(this);
-        mRestoreHelper = new HotseatRestoreHelper(mLauncher);
-        if (mHotseat.isAttachedToWindow()) {
-            onViewAttachedToWindow(mHotseat);
-        }
-    }
-
-    /**
-     * Shows appropriate hotseat education based on prediction enabled and migration states.
-     */
-    public void showEdu() {
-        mLauncher.getStateManager().goToState(NORMAL, true, () -> {
-            if (mComponentKeyMappers.isEmpty()) {
-                // launcher has empty predictions set
-                Snackbar.show(mLauncher, R.string.hotsaet_tip_prediction_disabled,
-                        R.string.hotseat_prediction_settings, null,
-                        () -> mLauncher.startActivity(getSettingsIntent()));
-            } else if (getPredictedIcons().size() >= (mHotSeatItemsCount + 1) / 2) {
-                showDiscoveryTip();
-            } else {
-                HotseatEduController eduController = new HotseatEduController(mLauncher,
-                        mRestoreHelper,
-                        this::createPredictor);
-                eduController.setPredictedApps(mapToWorkspaceItemInfo(mComponentKeyMappers));
-                eduController.showEdu();
-            }
-        });
-    }
-
-    /**
-     * Shows educational tip for hotseat if user does not go through Tips app.
-     */
-    private void showDiscoveryTip() {
-        if (getPredictedIcons().isEmpty()) {
-            new ArrowTipView(mLauncher).show(
-                    mLauncher.getString(R.string.hotseat_tip_no_empty_slots), mHotseat.getTop());
-        } else {
-            Snackbar.show(mLauncher, R.string.hotseat_tip_gaps_filled,
-                    R.string.hotseat_prediction_settings, null,
-                    () -> mLauncher.startActivity(getSettingsIntent()));
-        }
-    }
-
-    /**
-     * Returns if hotseat client has predictions
-     */
-    public boolean hasPredictions() {
-        return !mComponentKeyMappers.isEmpty();
-    }
-
-    @Override
-    public void onViewAttachedToWindow(View view) {
-        mLauncher.getDragController().addDragListener(this);
-    }
-
-    @Override
-    public void onViewDetachedFromWindow(View view) {
-        mLauncher.getDragController().removeDragListener(this);
-    }
-
-    private void fillGapsWithPrediction() {
-        fillGapsWithPrediction(false, null);
-    }
-
-    private void fillGapsWithPrediction(boolean animate, Runnable callback) {
-        if (mUIUpdatePaused || mDragObject != null) {
-            return;
-        }
-        List<WorkspaceItemInfo> predictedApps = mapToWorkspaceItemInfo(mComponentKeyMappers);
-        if (mComponentKeyMappers.isEmpty() != predictedApps.isEmpty()) {
-            // Safely ignore update as AppsList is not ready yet. This will called again once
-            // apps are ready (HotseatPredictionController#onAppsUpdated)
-            return;
-        }
-        int predictionIndex = 0;
-        ArrayList<WorkspaceItemInfo> newItems = new ArrayList<>();
-        // make sure predicted icon removal and filling predictions don't step on each other
-        if (mIconRemoveAnimators != null && mIconRemoveAnimators.isRunning()) {
-            mIconRemoveAnimators.addListener(new AnimationSuccessListener() {
-                @Override
-                public void onAnimationSuccess(Animator animator) {
-                    fillGapsWithPrediction(animate, callback);
-                    mIconRemoveAnimators.removeListener(this);
-                }
-            });
-            return;
-        }
-        for (int rank = 0; rank < mHotSeatItemsCount; rank++) {
-            View child = mHotseat.getChildAt(
-                    mHotseat.getCellXFromOrder(rank),
-                    mHotseat.getCellYFromOrder(rank));
-
-            if (child != null && !isPredictedIcon(child)) {
-                continue;
-            }
-            if (predictedApps.size() <= predictionIndex) {
-                // Remove predicted apps from the past
-                if (isPredictedIcon(child)) {
-                    mHotseat.removeView(child);
-                }
-                continue;
-            }
-            WorkspaceItemInfo predictedItem = predictedApps.get(predictionIndex++);
-            if (isPredictedIcon(child) && child.isEnabled()) {
-                PredictedAppIcon icon = (PredictedAppIcon) child;
-                icon.applyFromWorkspaceItem(predictedItem);
-                icon.finishBinding(mPredictionLongClickListener);
-            } else {
-                newItems.add(predictedItem);
-            }
-            preparePredictionInfo(predictedItem, rank);
-        }
-        mPredictedSpotsCount = predictionIndex;
-        bindItems(newItems, animate, callback);
-    }
-
-    private void bindItems(List<WorkspaceItemInfo> itemsToAdd, boolean animate, Runnable callback) {
-        AnimatorSet animationSet = new AnimatorSet();
-        for (WorkspaceItemInfo item : itemsToAdd) {
-            PredictedAppIcon icon = PredictedAppIcon.createIcon(mHotseat, item);
-            mLauncher.getWorkspace().addInScreenFromBind(icon, item);
-            icon.finishBinding(mPredictionLongClickListener);
-            if (animate) {
-                animationSet.play(ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 0.2f, 1));
-            }
-        }
-        if (animate) {
-            if (callback != null) {
-                animationSet.addListener(AnimationSuccessListener.forRunnable(callback));
-            }
-            animationSet.start();
-        } else {
-            if (callback != null) callback.run();
-        }
-    }
-
-    /**
-     * Unregisters callbacks and frees resources
-     */
-    public void destroy() {
-        mIsDestroyed = true;
-        mAllAppsStore.removeUpdateListener(this);
-        mLauncher.getDeviceProfile().inv.removeOnChangeListener(this);
-        mHotseat.removeOnAttachStateChangeListener(this);
-        if (mAppPredictor != null) {
-            mAppPredictor.destroy();
-        }
-    }
-
-    /**
-     * start and pauses predicted apps update on the hotseat
-     */
-    public void setPauseUIUpdate(boolean paused) {
-        mUIUpdatePaused = paused;
-        if (!paused) {
-            fillGapsWithPrediction();
-        }
-    }
-
-    /**
-     * Creates App Predictor with all the current apps pinned on the hotseat
-     */
-    public void createPredictor() {
-        AppPredictionManager apm = mLauncher.getSystemService(AppPredictionManager.class);
-        if (apm == null) {
-            return;
-        }
-        if (mAppPredictor != null) {
-            mAppPredictor.destroy();
-            mAppPredictor = null;
-        }
-        WeakReference<HotseatPredictionController> controllerRef = new WeakReference<>(this);
-
-
-        mPredictionModel.createBundle(bundle -> {
-            if (mIsDestroyed) return;
-            mAppPredictor = apm.createAppPredictionSession(
-                    new AppPredictionContext.Builder(mLauncher)
-                            .setUiSurface(PREDICTION_CLIENT)
-                            .setPredictedTargetCount(mHotSeatItemsCount)
-                            .setExtras(bundle)
-                            .build());
-            mAppPredictor.registerPredictionUpdates(
-                    mLauncher.getApplicationContext().getMainExecutor(),
-                    list -> {
-                        if (controllerRef.get() != null) {
-                            controllerRef.get().setPredictedApps(list);
-                        }
-                    });
-            mAppPredictor.requestPredictionUpdate();
-        });
-        setPauseUIUpdate(false);
-    }
-
-    /**
-     * Create WorkspaceItemInfo objects and binds PredictedAppIcon views for cached predicted items.
-     */
-    public void showCachedItems(List<AppInfo> apps, IntArray ranks) {
-        if (hasPredictions() && mAppPredictor != null) {
-            mAppPredictor.requestPredictionUpdate();
-            fillGapsWithPrediction();
-            return;
-        }
-        int count = Math.min(ranks.size(), apps.size());
-        List<WorkspaceItemInfo> items = new ArrayList<>(count);
-        for (int i = 0; i < count; i++) {
-            WorkspaceItemInfo item = new WorkspaceItemInfo(apps.get(i));
-            ComponentKey componentKey = new ComponentKey(item.getTargetComponent(), item.user);
-            preparePredictionInfo(item, ranks.get(i));
-            items.add(item);
-
-            mComponentKeyMappers.add(new ComponentKeyMapper(componentKey, mDynamicItemCache));
-        }
-        updateDependencies();
-        bindItems(items, false, null);
-    }
-
-    private void setPredictedApps(List<AppTarget> appTargets) {
-        mComponentKeyMappers.clear();
-        if (appTargets.isEmpty()) {
-            mRestoreHelper.restoreBackup();
-        }
-        StringBuilder predictionLog = new StringBuilder("predictedApps: [\n");
-        ArrayList<ComponentKey> componentKeys = new ArrayList<>();
-        for (AppTarget appTarget : appTargets) {
-            ComponentKey key;
-            if (appTarget.getShortcutInfo() != null) {
-                key = ShortcutKey.fromInfo(appTarget.getShortcutInfo());
-            } else {
-                key = new ComponentKey(new ComponentName(appTarget.getPackageName(),
-                        appTarget.getClassName()), appTarget.getUser());
-            }
-            componentKeys.add(key);
-            predictionLog.append(key.toString());
-            predictionLog.append(",rank:");
-            predictionLog.append(appTarget.getRank());
-            predictionLog.append("\n");
-            mComponentKeyMappers.add(new ComponentKeyMapper(key, mDynamicItemCache));
-        }
-        predictionLog.append("]");
-        if (Utilities.IS_DEBUG_DEVICE) {
-            HotseatFileLog.INSTANCE.get(mLauncher).log(TAG, predictionLog.toString());
-        }
-        updateDependencies();
-        fillGapsWithPrediction();
-        mPredictionModel.cachePredictionComponentKeys(componentKeys);
-    }
-
-    private void updateDependencies() {
-        mDynamicItemCache.updateDependencies(mComponentKeyMappers, mAllAppsStore, this,
-                mHotSeatItemsCount);
-    }
-
-    /**
-     * Pins a predicted app icon into place.
-     */
-    public void pinPrediction(ItemInfo info) {
-        PredictedAppIcon icon = (PredictedAppIcon) mHotseat.getChildAt(
-                mHotseat.getCellXFromOrder(info.rank),
-                mHotseat.getCellYFromOrder(info.rank));
-        if (icon == null) {
-            return;
-        }
-        WorkspaceItemInfo workspaceItemInfo = new WorkspaceItemInfo((WorkspaceItemInfo) info);
-        mLauncher.getModelWriter().addItemToDatabase(workspaceItemInfo,
-                LauncherSettings.Favorites.CONTAINER_HOTSEAT, workspaceItemInfo.screenId,
-                workspaceItemInfo.cellX, workspaceItemInfo.cellY);
-        ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 1, 0.8f, 1).start();
-        icon.pin(workspaceItemInfo);
-        AppTarget appTarget = mPredictionModel.getAppTargetFromInfo(workspaceItemInfo);
-        if (appTarget != null) {
-            notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(appTarget,
-                    AppTargetEvent.ACTION_PIN, workspaceItemInfo));
-        }
-    }
-
-    private List<WorkspaceItemInfo> mapToWorkspaceItemInfo(
-            List<ComponentKeyMapper> components) {
-        AllAppsStore allAppsStore = mLauncher.getAppsView().getAppsStore();
-        if (allAppsStore.getApps().length == 0) {
-            return Collections.emptyList();
-        }
-
-        List<WorkspaceItemInfo> predictedApps = new ArrayList<>();
-        for (ComponentKeyMapper mapper : components) {
-            ItemInfoWithIcon info = mapper.getApp(allAppsStore);
-            if (info instanceof AppInfo) {
-                WorkspaceItemInfo predictedApp = new WorkspaceItemInfo((AppInfo) info);
-                predictedApp.container = LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
-                predictedApps.add(predictedApp);
-            } else if (info instanceof WorkspaceItemInfo) {
-                WorkspaceItemInfo predictedApp = new WorkspaceItemInfo((WorkspaceItemInfo) info);
-                predictedApp.container = LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
-                predictedApps.add(predictedApp);
-            } else {
-                if (DEBUG) {
-                    Log.e(TAG, "Predicted app not found: " + mapper);
-                }
-            }
-            // Stop at the number of hotseat items
-            if (predictedApps.size() == mHotSeatItemsCount) {
-                break;
-            }
-        }
-        return predictedApps;
-    }
-
-    private List<PredictedAppIcon> getPredictedIcons() {
-        List<PredictedAppIcon> icons = new ArrayList<>();
-        ViewGroup vg = mHotseat.getShortcutsAndWidgets();
-        for (int i = 0; i < vg.getChildCount(); i++) {
-            View child = vg.getChildAt(i);
-            if (isPredictedIcon(child)) {
-                icons.add((PredictedAppIcon) child);
-            }
-        }
-        return icons;
-    }
-
-    private void removePredictedApps(List<PredictedAppIcon.PredictedIconOutlineDrawing> outlines,
-            ItemInfo draggedInfo) {
-        if (mIconRemoveAnimators != null) {
-            mIconRemoveAnimators.end();
-        }
-        mIconRemoveAnimators = new AnimatorSet();
-        removeOutlineDrawings();
-        for (PredictedAppIcon icon : getPredictedIcons()) {
-            if (!icon.isEnabled()) {
-                continue;
-            }
-            if (icon.getTag().equals(draggedInfo)) {
-                mHotseat.removeView(icon);
-                continue;
-            }
-            int rank = ((WorkspaceItemInfo) icon.getTag()).rank;
-            outlines.add(new PredictedAppIcon.PredictedIconOutlineDrawing(
-                    mHotseat.getCellXFromOrder(rank), mHotseat.getCellYFromOrder(rank), icon));
-            icon.setEnabled(false);
-            ObjectAnimator animator = ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 0);
-            animator.addListener(new AnimationSuccessListener() {
-                @Override
-                public void onAnimationSuccess(Animator animator) {
-                    if (icon.getParent() != null) {
-                        mHotseat.removeView(icon);
-                    }
-                }
-            });
-            mIconRemoveAnimators.play(animator);
-        }
-        mIconRemoveAnimators.start();
-    }
-
-    private void notifyItemAction(AppTargetEvent event) {
-        if (mAppPredictor != null) {
-            mAppPredictor.notifyAppTargetEvent(event);
-        }
-    }
-
-    @Override
-    public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
-        removePredictedApps(mOutlineDrawings, dragObject.dragInfo);
-        mDragObject = dragObject;
-        if (mOutlineDrawings.isEmpty()) return;
-        for (PredictedAppIcon.PredictedIconOutlineDrawing outlineDrawing : mOutlineDrawings) {
-            mHotseat.addDelegatedCellDrawing(outlineDrawing);
-        }
-        mHotseat.invalidate();
-    }
-
-    /**
-     * Unpins pinned app when it's converted into a folder
-     */
-    public void folderCreatedFromWorkspaceItem(ItemInfo itemInfo, FolderInfo folderInfo) {
-        AppTarget folderTarget = mPredictionModel.getAppTargetFromInfo(folderInfo);
-        AppTarget itemTarget = mPredictionModel.getAppTargetFromInfo(itemInfo);
-        if (folderTarget != null && HotseatPredictionModel.isTrackedForPrediction(folderInfo)) {
-            notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(folderTarget,
-                    AppTargetEvent.ACTION_PIN, folderInfo));
-        }
-        // using folder info with isTrackedForPrediction as itemInfo.container is already changed
-        // to folder by this point
-        if (itemTarget != null && HotseatPredictionModel.isTrackedForPrediction(folderInfo)) {
-            notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(itemTarget,
-                    AppTargetEvent.ACTION_UNPIN, folderInfo
-            ));
-        }
-    }
-
-    /**
-     * Pins workspace item created when all folder items are removed but one
-     */
-    public void folderConvertedToWorkspaceItem(ItemInfo itemInfo, FolderInfo folderInfo) {
-        AppTarget folderTarget = mPredictionModel.getAppTargetFromInfo(folderInfo);
-        AppTarget itemTarget = mPredictionModel.getAppTargetFromInfo(itemInfo);
-        if (folderTarget != null && HotseatPredictionModel.isTrackedForPrediction(folderInfo)) {
-            notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(folderTarget,
-                    AppTargetEvent.ACTION_UNPIN, folderInfo));
-        }
-        if (itemTarget != null && HotseatPredictionModel.isTrackedForPrediction(itemInfo)) {
-            notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(itemTarget,
-                    AppTargetEvent.ACTION_PIN, itemInfo));
-        }
-    }
-
-    @Override
-    public void onDragEnd() {
-        if (mDragObject == null) {
-            return;
-        }
-
-        ItemInfo dragInfo = mDragObject.dragInfo;
-        if (mDragObject.isMoved()) {
-            AppTarget appTarget = mPredictionModel.getAppTargetFromInfo(dragInfo);
-            //always send pin event first to prevent AiAi from predicting an item moved within
-            // the same page
-            if (appTarget != null && HotseatPredictionModel.isTrackedForPrediction(dragInfo)) {
-                notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(appTarget,
-                        AppTargetEvent.ACTION_PIN, dragInfo));
-            }
-            if (appTarget != null && HotseatPredictionModel.isTrackedForPrediction(
-                    mDragObject.originalDragInfo)) {
-                notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(appTarget,
-                        AppTargetEvent.ACTION_UNPIN, mDragObject.originalDragInfo));
-            }
-        }
-        mDragObject = null;
-        fillGapsWithPrediction(true, this::removeOutlineDrawings);
-    }
-
-
-    @Nullable
-    @Override
-    public SystemShortcut<QuickstepLauncher> getShortcut(QuickstepLauncher activity,
-            ItemInfo itemInfo) {
-        if (itemInfo.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
-            return null;
-        }
-        return new PinPrediction(activity, itemInfo);
-    }
-
-    private void preparePredictionInfo(WorkspaceItemInfo itemInfo, int rank) {
-        itemInfo.container = LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
-        itemInfo.rank = rank;
-        itemInfo.cellX = mHotseat.getCellXFromOrder(rank);
-        itemInfo.cellY = mHotseat.getCellYFromOrder(rank);
-        itemInfo.screenId = rank;
-    }
-
-    private void removeOutlineDrawings() {
-        if (mOutlineDrawings.isEmpty()) return;
-        for (PredictedAppIcon.PredictedIconOutlineDrawing outlineDrawing : mOutlineDrawings) {
-            mHotseat.removeDelegatedCellDrawing(outlineDrawing);
-        }
-        mHotseat.invalidate();
-        mOutlineDrawings.clear();
-    }
-
-    @Override
-    public void onIdpChanged(int changeFlags, InvariantDeviceProfile profile) {
-        if ((changeFlags & CHANGE_FLAG_GRID) != 0) {
-            this.mHotSeatItemsCount = profile.numHotseatIcons;
-            createPredictor();
-        }
-    }
-
-    @Override
-    public void onAppsUpdated() {
-        fillGapsWithPrediction();
-    }
-
-    @Override
-    public void reapplyItemInfo(ItemInfoWithIcon info) {
-    }
-
-    @Override
-    public void onDropCompleted(View target, DropTarget.DragObject d, boolean success) {
-        //Does nothing
-    }
-
-    @Override
-    public void fillInLogContainerData(ItemInfo childInfo, LauncherLogProto.Target child,
-            ArrayList<LauncherLogProto.Target> parents) {
-        mHotseat.fillInLogContainerData(childInfo, child, parents);
-    }
-
-    /**
-     * Logs rank info based on current list of predicted items
-     */
-    public void logLaunchedAppRankingInfo(@NonNull ItemInfo itemInfo, InstanceId instanceId) {
-        if (Utilities.IS_DEBUG_DEVICE) {
-            final String pkg = itemInfo.getTargetComponent() != null
-                    ? itemInfo.getTargetComponent().getPackageName() : "unknown";
-            HotseatFileLog.INSTANCE.get(mLauncher).log("UserEvent",
-                    "appLaunch: packageName:" + pkg + ",isWorkApp:" + (itemInfo.user != null
-                            && !Process.myUserHandle().equals(itemInfo.user))
-                            + ",launchLocation:" + itemInfo.container);
-        }
-
-        if (itemInfo.getTargetComponent() == null || itemInfo.user == null) {
-            return;
-        }
-
-        final ComponentKey key = new ComponentKey(itemInfo.getTargetComponent(), itemInfo.user);
-
-        final List<ComponentKeyMapper> predictedApps = new ArrayList<>(mComponentKeyMappers);
-        OptionalInt rank = IntStream.range(0, predictedApps.size())
-                .filter(index -> key.equals(predictedApps.get(index).getComponentKey()))
-                .findFirst();
-        if (!rank.isPresent()) {
-            return;
-        }
-
-        int cardinality = 0;
-        for (PredictedAppIcon icon : getPredictedIcons()) {
-            ItemInfo info = (ItemInfo) icon.getTag();
-            cardinality |= 1 << info.screenId;
-        }
-
-        PredictedHotseatContainer.Builder containerBuilder = PredictedHotseatContainer.newBuilder();
-        containerBuilder.setCardinality(cardinality);
-        if (itemInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
-            containerBuilder.setIndex(rank.getAsInt());
-        }
-        mLauncher.getStatsLogManager().logger()
-                .withInstanceId(instanceId)
-                .withRank(rank.getAsInt())
-                .withContainerInfo(ContainerInfo.newBuilder()
-                        .setPredictedHotseatContainer(containerBuilder)
-                        .build())
-                .log(LAUNCHER_HOTSEAT_RANKED);
-    }
-
-    private class PinPrediction extends SystemShortcut<QuickstepLauncher> {
-
-        private PinPrediction(QuickstepLauncher target, ItemInfo itemInfo) {
-            super(R.drawable.ic_pin, R.string.pin_prediction, target,
-                    itemInfo);
-        }
-
-        @Override
-        public void onClick(View view) {
-            dismissTaskMenuView(mTarget);
-            pinPrediction(mItemInfo);
-        }
-    }
-
-    /**
-     * Fill in predicted_rank field based on app prediction.
-     * Only applicable when {@link ItemInfo#itemType} is PREDICTED_HOTSEAT
-     */
-    public static void encodeHotseatLayoutIntoPredictionRank(
-            @NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target) {
-        QuickstepLauncher launcher = QuickstepLauncher.ACTIVITY_TRACKER.getCreatedActivity();
-        if (launcher == null || launcher.getHotseatPredictionController() == null
-                || itemInfo.getTargetComponent() == null) {
-            return;
-        }
-        HotseatPredictionController controller = launcher.getHotseatPredictionController();
-
-        final ComponentKey k = new ComponentKey(itemInfo.getTargetComponent(), itemInfo.user);
-
-        final List<ComponentKeyMapper> predictedApps = controller.mComponentKeyMappers;
-        OptionalInt rank = IntStream.range(0, predictedApps.size())
-                .filter((i) -> k.equals(predictedApps.get(i).getComponentKey()))
-                .findFirst();
-
-        target.predictedRank = 10000 + (controller.mPredictedSpotsCount * 100)
-                + (rank.isPresent() ? rank.getAsInt() + 1 : 0);
-    }
-
-    private static boolean isPredictedIcon(View view) {
-        return view instanceof PredictedAppIcon && view.getTag() instanceof WorkspaceItemInfo
-                && ((WorkspaceItemInfo) view.getTag()).container
-                == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
-    }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java
deleted file mode 100644
index fc9a11b..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2019 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.uioverrides.states;
-
-import com.android.launcher3.Launcher;
-
-public class OverviewPeekState extends OverviewState {
-    private static final float OVERVIEW_OFFSET = 0.7f;
-
-    public OverviewPeekState(int id) {
-        super(id);
-    }
-
-    @Override
-    public float[] getOverviewScaleAndOffset(Launcher launcher) {
-        return new float[] {NO_SCALE, OVERVIEW_OFFSET};
-    }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
deleted file mode 100644
index fac478e..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
+++ /dev/null
@@ -1,279 +0,0 @@
-/*
- * Copyright (C) 2019 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.uioverrides.touchcontrollers;
-
-import static com.android.launcher3.LauncherState.ALL_APPS;
-import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.LauncherState.OVERVIEW_PEEK;
-import static com.android.launcher3.anim.Interpolators.ACCEL;
-import static com.android.launcher3.anim.Interpolators.DEACCEL;
-import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_HEADER_FADE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_SCALE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE;
-import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_PEEK;
-import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
-import static com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory.INDEX_PAUSE_TO_OVERVIEW_ANIM;
-import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewConfiguration;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.states.StateAnimationConfig;
-import com.android.launcher3.states.StateAnimationConfig.AnimationFlags;
-import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
-import com.android.launcher3.util.VibratorWrapper;
-import com.android.quickstep.SystemUiProxy;
-import com.android.quickstep.util.MotionPauseDetector;
-import com.android.quickstep.views.RecentsView;
-
-/**
- * Touch controller which handles swipe and hold to go to Overview
- */
-public class FlingAndHoldTouchController extends PortraitStatesTouchController {
-
-    private static final long PEEK_IN_ANIM_DURATION = 240;
-    private static final long PEEK_OUT_ANIM_DURATION = 100;
-    private static final float MAX_DISPLACEMENT_PERCENT = 0.75f;
-
-    protected final MotionPauseDetector mMotionPauseDetector;
-    private final float mMotionPauseMinDisplacement;
-    private final float mMotionPauseMaxDisplacement;
-
-    private AnimatorSet mPeekAnim;
-
-    public FlingAndHoldTouchController(Launcher l) {
-        super(l, false /* allowDragToOverview */);
-        mMotionPauseDetector = new MotionPauseDetector(l);
-        mMotionPauseMinDisplacement = ViewConfiguration.get(l).getScaledTouchSlop();
-        mMotionPauseMaxDisplacement = getMotionPauseMaxDisplacement();
-    }
-
-    protected float getMotionPauseMaxDisplacement() {
-        return getShiftRange() * MAX_DISPLACEMENT_PERCENT;
-    }
-
-    @Override
-    protected long getAtomicDuration() {
-        return QuickstepAtomicAnimationFactory.ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW;
-    }
-
-    @Override
-    public void onDragStart(boolean start, float startDisplacement) {
-        mMotionPauseDetector.clear();
-
-        super.onDragStart(start, startDisplacement);
-
-        if (handlingOverviewAnim()) {
-            mMotionPauseDetector.setOnMotionPauseListener(this::onMotionPauseChanged);
-        }
-
-        if (mAtomicAnim != null) {
-            mAtomicAnim.cancel();
-        }
-    }
-
-    protected void onMotionPauseChanged(boolean isPaused) {
-        RecentsView recentsView = mLauncher.getOverviewPanel();
-        recentsView.setOverviewStateEnabled(isPaused);
-        if (mPeekAnim != null) {
-            mPeekAnim.cancel();
-        }
-        LauncherState fromState = isPaused ? NORMAL : OVERVIEW_PEEK;
-        LauncherState toState = isPaused ? OVERVIEW_PEEK : NORMAL;
-        long peekDuration = isPaused ? PEEK_IN_ANIM_DURATION : PEEK_OUT_ANIM_DURATION;
-
-        StateAnimationConfig config = new StateAnimationConfig();
-        config.duration = peekDuration;
-        config.animFlags = PLAY_ATOMIC_OVERVIEW_PEEK;
-        mPeekAnim = mLauncher.getStateManager().createAtomicAnimation(
-                fromState, toState, config);
-        mPeekAnim.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mPeekAnim = null;
-            }
-        });
-        mPeekAnim.start();
-        VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC);
-
-        mLauncher.getDragLayer().getScrim().createSysuiMultiplierAnim(isPaused ? 0 : 1)
-                .setDuration(peekDuration).start();
-    }
-
-    /**
-     * @return Whether we are handling the overview animation, rather than
-     * having it as part of the existing animation to the target state.
-     */
-    protected boolean handlingOverviewAnim() {
-        int stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags();
-        return mStartState == NORMAL && (stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0;
-    }
-
-    @Override
-    protected StateAnimationConfig getConfigForStates(
-            LauncherState fromState, LauncherState toState) {
-        if (fromState == NORMAL && toState == ALL_APPS) {
-            StateAnimationConfig builder = new StateAnimationConfig();
-            // Fade in prediction icons quickly, then rest of all apps after reaching overview.
-            float progressToReachOverview = NORMAL.getVerticalProgress(mLauncher)
-                    - OVERVIEW.getVerticalProgress(mLauncher);
-            builder.setInterpolator(ANIM_ALL_APPS_HEADER_FADE, Interpolators.clampToProgress(
-                    ACCEL,
-                    0,
-                    ALL_APPS_CONTENT_FADE_THRESHOLD));
-            builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(
-                    ACCEL,
-                    progressToReachOverview,
-                    progressToReachOverview + ALL_APPS_CONTENT_FADE_THRESHOLD));
-
-            // Get workspace out of the way quickly, to prepare for potential pause.
-            builder.setInterpolator(ANIM_WORKSPACE_SCALE, DEACCEL_3);
-            builder.setInterpolator(ANIM_WORKSPACE_TRANSLATE, DEACCEL_3);
-            builder.setInterpolator(ANIM_WORKSPACE_FADE, DEACCEL_3);
-            return builder;
-        } else if (fromState == ALL_APPS && toState == NORMAL) {
-            StateAnimationConfig builder = new StateAnimationConfig();
-            // Keep all apps/predictions opaque until the very end of the transition.
-            float progressToReachOverview = OVERVIEW.getVerticalProgress(mLauncher);
-            builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(
-                    DEACCEL,
-                    progressToReachOverview - ALL_APPS_CONTENT_FADE_THRESHOLD,
-                    progressToReachOverview));
-            builder.setInterpolator(ANIM_ALL_APPS_HEADER_FADE, Interpolators.clampToProgress(
-                    DEACCEL,
-                    1 - ALL_APPS_CONTENT_FADE_THRESHOLD,
-                    1));
-            return builder;
-        }
-        return super.getConfigForStates(fromState, toState);
-    }
-
-    @Override
-    public boolean onDrag(float displacement, MotionEvent event) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.PAUSE_NOT_DETECTED, "FlingAndHoldTouchController");
-        }
-        float upDisplacement = -displacement;
-        mMotionPauseDetector.setDisallowPause(!handlingOverviewAnim()
-                || upDisplacement < mMotionPauseMinDisplacement
-                || upDisplacement > mMotionPauseMaxDisplacement);
-        mMotionPauseDetector.addPosition(event);
-        return super.onDrag(displacement, event);
-    }
-
-    @Override
-    public void onDragEnd(float velocity) {
-        if (mMotionPauseDetector.isPaused() && handlingOverviewAnim()) {
-            goToOverviewOnDragEnd(velocity);
-        } else {
-            super.onDragEnd(velocity);
-        }
-
-        View searchView = mLauncher.getAppsView().getSearchView();
-        if (searchView instanceof FeedbackHandler) {
-            ((FeedbackHandler) searchView).resetFeedback();
-        }
-        mMotionPauseDetector.clear();
-    }
-
-    protected void goToOverviewOnDragEnd(float velocity) {
-        if (mPeekAnim != null) {
-            mPeekAnim.cancel();
-        }
-
-        Animator overviewAnim = mLauncher.createAtomicAnimationFactory()
-                .createStateElementAnimation(INDEX_PAUSE_TO_OVERVIEW_ANIM);
-        mAtomicAnim = new AnimatorSet();
-        mAtomicAnim.addListener(new AnimationSuccessListener() {
-            @Override
-            public void onAnimationSuccess(Animator animator) {
-                onSwipeInteractionCompleted(OVERVIEW, Touch.SWIPE);
-            }
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                super.onAnimationEnd(animation);
-                if (mCancelled) {
-                    StateAnimationConfig config = new StateAnimationConfig();
-                    config.animFlags = PLAY_ATOMIC_OVERVIEW_PEEK;
-                    config.duration = PEEK_OUT_ANIM_DURATION;
-                    mPeekAnim = mLauncher.getStateManager().createAtomicAnimation(
-                            mFromState, mToState, config);
-                    mPeekAnim.start();
-                }
-                mAtomicAnim = null;
-            }
-        });
-        mAtomicAnim.play(overviewAnim);
-        mAtomicAnim.start();
-    }
-
-    @Override
-    protected void goToTargetState(LauncherState targetState, int logAction) {
-        if (mPeekAnim != null && mPeekAnim.isStarted()) {
-            // Don't jump to the target state until overview is no longer peeking.
-            mPeekAnim.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    FlingAndHoldTouchController.super.goToTargetState(targetState, logAction);
-                }
-            });
-        } else {
-            super.goToTargetState(targetState, logAction);
-        }
-    }
-
-    @Override
-    @AnimationFlags
-    protected int updateAnimComponentsOnReinit(@AnimationFlags int animComponents) {
-        if (handlingOverviewAnim()) {
-            // We don't want the state transition to all apps to animate overview,
-            // as that will cause a jump after our atomic animation.
-            return animComponents | SKIP_OVERVIEW;
-        } else {
-            return animComponents;
-        }
-    }
-
-    /**
-     * Interface for views with feedback animation requiring reset
-     */
-    public interface FeedbackHandler {
-
-        /**
-         * reset searchWidget feedback
-         */
-        void resetFeedback();
-    }
-
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
deleted file mode 100644
index 9316938..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
+++ /dev/null
@@ -1,265 +0,0 @@
-/*
- * Copyright (C) 2020 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.uioverrides.touchcontrollers;
-
-import static com.android.launcher3.LauncherState.HINT_STATE;
-import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
-import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
-import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_PEEK;
-import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
-import android.graphics.PointF;
-import android.util.Log;
-import android.view.MotionEvent;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.launcher3.graphics.OverviewScrim;
-import com.android.launcher3.statemanager.StateManager;
-import com.android.launcher3.states.StateAnimationConfig;
-import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
-import com.android.launcher3.util.VibratorWrapper;
-import com.android.quickstep.util.StaggeredWorkspaceAnim;
-import com.android.quickstep.views.RecentsView;
-
-/**
- * Touch controller which handles swipe and hold from the nav bar to go to Overview. Swiping above
- * the nav bar falls back to go to All Apps. Swiping from the nav bar without holding goes to the
- * first home screen instead of to Overview.
- */
-public class NoButtonNavbarToOverviewTouchController extends FlingAndHoldTouchController {
-
-
-    // How much of the movement to use for translating overview after swipe and hold.
-    private static final float OVERVIEW_MOVEMENT_FACTOR = 0.25f;
-    private static final long TRANSLATION_ANIM_MIN_DURATION_MS = 80;
-    private static final float TRANSLATION_ANIM_VELOCITY_DP_PER_MS = 0.8f;
-
-    private final RecentsView mRecentsView;
-
-    private boolean mDidTouchStartInNavBar;
-    private boolean mReachedOverview;
-    private boolean mIsOverviewRehidden;
-    private boolean mIsHomeStaggeredAnimFinished;
-    // The last recorded displacement before we reached overview.
-    private PointF mStartDisplacement = new PointF();
-
-    // Normal to Hint animation has flag SKIP_OVERVIEW, so we update this scrim with this animator.
-    private ObjectAnimator mNormalToHintOverviewScrimAnimator;
-
-    public NoButtonNavbarToOverviewTouchController(Launcher l) {
-        super(l);
-        mRecentsView = l.getOverviewPanel();
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.PAUSE_NOT_DETECTED, "NoButtonNavbarToOverviewTouchController.ctor");
-        }
-    }
-
-    @Override
-    protected float getMotionPauseMaxDisplacement() {
-        // No need to disallow pause when swiping up all the way up the screen (unlike
-        // FlingAndHoldTouchController where user is probably intending to go to all apps).
-        return Float.MAX_VALUE;
-    }
-
-    @Override
-    protected boolean canInterceptTouch(MotionEvent ev) {
-        mDidTouchStartInNavBar = (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0;
-        return super.canInterceptTouch(ev);
-    }
-
-    @Override
-    protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
-        if (fromState == NORMAL && mDidTouchStartInNavBar) {
-            return HINT_STATE;
-        } else if (fromState == OVERVIEW && isDragTowardPositive) {
-            // Don't allow swiping up to all apps.
-            return OVERVIEW;
-        }
-        return super.getTargetState(fromState, isDragTowardPositive);
-    }
-
-    @Override
-    protected float initCurrentAnimation(int animComponents) {
-        float progressMultiplier = super.initCurrentAnimation(animComponents);
-        if (mToState == HINT_STATE) {
-            // Track the drag across the entire height of the screen.
-            progressMultiplier = -1 / getShiftRange();
-        }
-        return progressMultiplier;
-    }
-
-    @Override
-    public void onDragStart(boolean start, float startDisplacement) {
-        super.onDragStart(start, startDisplacement);
-        if (mFromState == NORMAL && mToState == HINT_STATE) {
-            mNormalToHintOverviewScrimAnimator = ObjectAnimator.ofFloat(
-                    mLauncher.getDragLayer().getOverviewScrim(),
-                    OverviewScrim.SCRIM_PROGRESS,
-                    mFromState.getOverviewScrimAlpha(mLauncher),
-                    mToState.getOverviewScrimAlpha(mLauncher));
-        }
-        mReachedOverview = false;
-    }
-
-    @Override
-    protected void updateProgress(float fraction) {
-        super.updateProgress(fraction);
-        if (mNormalToHintOverviewScrimAnimator != null) {
-            mNormalToHintOverviewScrimAnimator.setCurrentFraction(fraction);
-        }
-    }
-
-    @Override
-    public void onDragEnd(float velocity) {
-        super.onDragEnd(velocity);
-        mNormalToHintOverviewScrimAnimator = null;
-    }
-
-    @Override
-    protected void updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration,
-            LauncherState targetState, float velocity, boolean isFling) {
-        super.updateSwipeCompleteAnimation(animator, expectedDuration, targetState, velocity,
-                isFling);
-        if (targetState == HINT_STATE) {
-            // Normally we compute the duration based on the velocity and distance to the given
-            // state, but since the hint state tracks the entire screen without a clear endpoint, we
-            // need to manually set the duration to a reasonable value.
-            animator.setDuration(HINT_STATE.getTransitionDuration(mLauncher));
-        }
-    }
-
-    @Override
-    protected void onMotionPauseChanged(boolean isPaused) {
-        if (mCurrentAnimation == null) {
-            return;
-        }
-        mNormalToHintOverviewScrimAnimator = null;
-        mCurrentAnimation.dispatchOnCancelWithoutCancelRunnable(() -> {
-            mLauncher.getStateManager().goToState(OVERVIEW, true, () -> {
-                mReachedOverview = true;
-                maybeSwipeInteractionToOverviewComplete();
-            });
-        });
-        VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC);
-    }
-
-    private void maybeSwipeInteractionToOverviewComplete() {
-        if (mReachedOverview && mDetector.isSettlingState()) {
-            onSwipeInteractionCompleted(OVERVIEW, Touch.SWIPE);
-        }
-    }
-
-    // Used if flinging back to home after reaching overview
-    private void maybeSwipeInteractionToHomeComplete() {
-        if (mIsHomeStaggeredAnimFinished && mIsOverviewRehidden) {
-            onSwipeInteractionCompleted(NORMAL, Touch.FLING);
-        }
-    }
-
-    @Override
-    protected boolean handlingOverviewAnim() {
-        return mDidTouchStartInNavBar && super.handlingOverviewAnim();
-    }
-
-    @Override
-    public boolean onDrag(float yDisplacement, float xDisplacement, MotionEvent event) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.PAUSE_NOT_DETECTED, "NoButtonNavbarToOverviewTouchController");
-        }
-        if (mMotionPauseDetector.isPaused()) {
-            if (!mReachedOverview) {
-                mStartDisplacement.set(xDisplacement, yDisplacement);
-            } else {
-                mRecentsView.setTranslationX((xDisplacement - mStartDisplacement.x)
-                        * OVERVIEW_MOVEMENT_FACTOR);
-                mRecentsView.setTranslationY((yDisplacement - mStartDisplacement.y)
-                        * OVERVIEW_MOVEMENT_FACTOR);
-            }
-            // Stay in Overview.
-            return true;
-        }
-        return super.onDrag(yDisplacement, xDisplacement, event);
-    }
-
-    @Override
-    protected void goToOverviewOnDragEnd(float velocity) {
-        float velocityDp = dpiFromPx(velocity);
-        boolean isFling = Math.abs(velocityDp) > 1;
-        StateManager<LauncherState> stateManager = mLauncher.getStateManager();
-        boolean goToHomeInsteadOfOverview = isFling;
-        if (goToHomeInsteadOfOverview) {
-            if (velocity > 0) {
-                stateManager.goToState(NORMAL, true,
-                        () -> onSwipeInteractionCompleted(NORMAL, Touch.FLING));
-            } else {
-                mIsHomeStaggeredAnimFinished = mIsOverviewRehidden = false;
-
-                StaggeredWorkspaceAnim staggeredWorkspaceAnim = new StaggeredWorkspaceAnim(
-                        mLauncher, velocity, false /* animateOverviewScrim */);
-                staggeredWorkspaceAnim.addAnimatorListener(new AnimationSuccessListener() {
-                    @Override
-                    public void onAnimationSuccess(Animator animator) {
-                        mIsHomeStaggeredAnimFinished = true;
-                        maybeSwipeInteractionToHomeComplete();
-                    }
-                }).start();
-
-                // StaggeredWorkspaceAnim doesn't animate overview, so we handle it here.
-                stateManager.cancelAnimation();
-                StateAnimationConfig config = new StateAnimationConfig();
-                config.duration = OVERVIEW.getTransitionDuration(mLauncher);
-                config.animFlags = PLAY_ATOMIC_OVERVIEW_PEEK;
-                AnimatorSet anim = stateManager.createAtomicAnimation(
-                        stateManager.getState(), NORMAL, config);
-                anim.addListener(AnimationSuccessListener.forRunnable(() -> {
-                    mIsOverviewRehidden = true;
-                    maybeSwipeInteractionToHomeComplete();
-                }));
-                anim.start();
-            }
-        }
-        if (mReachedOverview) {
-            float distanceDp = dpiFromPx(Math.max(
-                    Math.abs(mRecentsView.getTranslationX()),
-                    Math.abs(mRecentsView.getTranslationY())));
-            long duration = (long) Math.max(TRANSLATION_ANIM_MIN_DURATION_MS,
-                    distanceDp / TRANSLATION_ANIM_VELOCITY_DP_PER_MS);
-            mRecentsView.animate()
-                    .translationX(0)
-                    .translationY(0)
-                    .setInterpolator(ACCEL_DEACCEL)
-                    .setDuration(duration)
-                    .withEndAction(goToHomeInsteadOfOverview
-                            ? null
-                            : this::maybeSwipeInteractionToOverviewComplete);
-        }
-    }
-
-    private float dpiFromPx(float pixels) {
-        return Utilities.dpiFromPx(pixels, mLauncher.getResources().getDisplayMetrics());
-    }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
deleted file mode 100644
index a63f3a8..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
+++ /dev/null
@@ -1,385 +0,0 @@
-/*
- * Copyright (C) 2019 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.quickstep;
-
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.os.Build;
-import android.util.Log;
-import android.view.MotionEvent;
-
-import androidx.annotation.CallSuper;
-import androidx.annotation.UiThread;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.statemanager.StatefulActivity;
-import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.util.VibratorWrapper;
-import com.android.launcher3.util.WindowBounds;
-import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
-import com.android.quickstep.util.ActiveGestureLog;
-import com.android.quickstep.util.ActivityInitListener;
-import com.android.quickstep.util.RectFSpringAnim;
-import com.android.quickstep.util.SurfaceTransactionApplier;
-import com.android.quickstep.util.TransformParams;
-import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.TaskView;
-import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.shared.system.InputConsumerController;
-import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-
-import java.util.ArrayList;
-import java.util.function.Consumer;
-
-/**
- * Base class for swipe up handler with some utility methods
- */
-@TargetApi(Build.VERSION_CODES.Q)
-public abstract class BaseSwipeUpHandler<T extends StatefulActivity<?>, Q extends RecentsView>
-        extends SwipeUpAnimationLogic implements RecentsAnimationListener {
-
-    private static final String TAG = "BaseSwipeUpHandler";
-
-    protected final BaseActivityInterface<?, T> mActivityInterface;
-    protected final InputConsumerController mInputConsumer;
-
-    protected final ActivityInitListener mActivityInitListener;
-
-    protected RecentsAnimationController mRecentsAnimationController;
-    protected RecentsAnimationTargets mRecentsAnimationTargets;
-
-    // Callbacks to be made once the recents animation starts
-    private final ArrayList<Runnable> mRecentsAnimationStartCallbacks = new ArrayList<>();
-
-    protected T mActivity;
-    protected Q mRecentsView;
-
-    protected Runnable mGestureEndCallback;
-
-    protected MultiStateCallback mStateCallback;
-
-    protected boolean mCanceled;
-
-    private boolean mRecentsViewScrollLinked = false;
-
-    protected BaseSwipeUpHandler(Context context, RecentsAnimationDeviceState deviceState,
-            GestureState gestureState, InputConsumerController inputConsumer) {
-        super(context, deviceState, gestureState, new TransformParams());
-        mActivityInterface = gestureState.getActivityInterface();
-        mActivityInitListener = mActivityInterface.createActivityInitListener(this::onActivityInit);
-        mInputConsumer = inputConsumer;
-    }
-
-    /**
-     * To be called at the end of constructor of subclasses. This calls various methods which can
-     * depend on proper class initialization.
-     */
-    protected void initAfterSubclassConstructor() {
-        initTransitionEndpoints(
-                mTaskViewSimulator.getOrientationState().getLauncherDeviceProfile());
-    }
-
-    protected void performHapticFeedback() {
-        VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC);
-    }
-
-    public Consumer<MotionEvent> getRecentsViewDispatcher(float navbarRotation) {
-        return mRecentsView != null ? mRecentsView.getEventDispatcher(navbarRotation) : null;
-    }
-
-    public void setGestureEndCallback(Runnable gestureEndCallback) {
-        mGestureEndCallback = gestureEndCallback;
-    }
-
-    public abstract Intent getLaunchIntent();
-
-    protected void linkRecentsViewScroll() {
-        SurfaceTransactionApplier.create(mRecentsView, applier -> {
-            mTransformParams.setSyncTransactionApplier(applier);
-            runOnRecentsAnimationStart(() ->
-                    mRecentsAnimationTargets.addReleaseCheck(applier));
-        });
-
-        mRecentsView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
-            if (moveWindowWithRecentsScroll()) {
-                updateFinalShift();
-            }
-        });
-        runOnRecentsAnimationStart(() ->
-                mRecentsView.setRecentsAnimationTargets(mRecentsAnimationController,
-                        mRecentsAnimationTargets));
-        mRecentsViewScrollLinked = true;
-    }
-
-    protected void startNewTask(Consumer<Boolean> resultCallback) {
-        // Launch the task user scrolled to (mRecentsView.getNextPage()).
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            // We finish recents animation inside launchTask() when live tile is enabled.
-            mRecentsView.getNextPageTaskView().launchTask(false /* animate */,
-                    true /* freezeTaskList */);
-        } else {
-            int taskId = mRecentsView.getNextPageTaskView().getTask().key.id;
-            if (!mCanceled) {
-                TaskView nextTask = mRecentsView.getTaskView(taskId);
-                if (nextTask != null) {
-                    mGestureState.updateLastStartedTaskId(taskId);
-                    boolean hasTaskPreviouslyAppeared = mGestureState.getPreviouslyAppearedTaskIds()
-                            .contains(taskId);
-                    nextTask.launchTask(false /* animate */, true /* freezeTaskList */,
-                            success -> {
-                                resultCallback.accept(success);
-                                if (success) {
-                                    if (hasTaskPreviouslyAppeared) {
-                                        onRestartPreviouslyAppearedTask();
-                                    }
-                                } else {
-                                    mActivityInterface.onLaunchTaskFailed();
-                                    nextTask.notifyTaskLaunchFailed(TAG);
-                                    mRecentsAnimationController.finish(true /* toRecents */, null);
-                                }
-                            }, MAIN_EXECUTOR.getHandler());
-                }
-            }
-            mCanceled = false;
-        }
-    }
-
-    /**
-     * Called when we successfully startNewTask() on the task that was previously running. Normally
-     * we call resumeLastTask() when returning to the previously running task, but this handles a
-     * specific edge case: if we switch from A to B, and back to A before B appears, we need to
-     * start A again to ensure it stays on top.
-     */
-    @CallSuper
-    protected void onRestartPreviouslyAppearedTask() {
-        // Finish the controller here, since we won't get onTaskAppeared() for a task that already
-        // appeared.
-        if (mRecentsAnimationController != null) {
-            mRecentsAnimationController.finish(false, null);
-        }
-    }
-
-    /**
-     * Runs the given {@param action} if the recents animation has already started, or queues it to
-     * be run when it is next started.
-     */
-    protected void runOnRecentsAnimationStart(Runnable action) {
-        if (mRecentsAnimationTargets == null) {
-            mRecentsAnimationStartCallbacks.add(action);
-        } else {
-            action.run();
-        }
-    }
-
-    /**
-     * TODO can we remove this now that we don't finish the controller until onTaskAppeared()?
-     * @return whether the recents animation has started and there are valid app targets.
-     */
-    protected boolean hasTargets() {
-        return mRecentsAnimationTargets != null && mRecentsAnimationTargets.hasTargets();
-    }
-
-    @Override
-    public void onRecentsAnimationStart(RecentsAnimationController recentsAnimationController,
-            RecentsAnimationTargets targets) {
-        mRecentsAnimationController = recentsAnimationController;
-        mRecentsAnimationTargets = targets;
-        mTransformParams.setTargetSet(mRecentsAnimationTargets);
-        RemoteAnimationTargetCompat runningTaskTarget = targets.findTask(
-                mGestureState.getRunningTaskId());
-
-        if (runningTaskTarget != null) {
-            mTaskViewSimulator.setPreview(runningTaskTarget);
-        }
-
-        // Only initialize the device profile, if it has not been initialized before, as in some
-        // configurations targets.homeContentInsets may not be correct.
-        if (mActivity == null) {
-            DeviceProfile dp = mTaskViewSimulator.getOrientationState().getLauncherDeviceProfile();
-            if (targets.minimizedHomeBounds != null && runningTaskTarget != null) {
-                Rect overviewStackBounds = mActivityInterface
-                        .getOverviewWindowBounds(targets.minimizedHomeBounds, runningTaskTarget);
-                dp = dp.getMultiWindowProfile(mContext,
-                        new WindowBounds(overviewStackBounds, targets.homeContentInsets));
-            } else {
-                // If we are not in multi-window mode, home insets should be same as system insets.
-                dp = dp.copy(mContext);
-            }
-            dp.updateInsets(targets.homeContentInsets);
-            dp.updateIsSeascape(mContext);
-            initTransitionEndpoints(dp);
-        }
-
-        // Notify when the animation starts
-        if (!mRecentsAnimationStartCallbacks.isEmpty()) {
-            for (Runnable action : new ArrayList<>(mRecentsAnimationStartCallbacks)) {
-                action.run();
-            }
-            mRecentsAnimationStartCallbacks.clear();
-        }
-    }
-
-    @Override
-    public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
-        mRecentsAnimationController = null;
-        mRecentsAnimationTargets = null;
-        if (mRecentsView != null) {
-            mRecentsView.setRecentsAnimationTargets(null, null);
-        }
-    }
-
-    @Override
-    public void onRecentsAnimationFinished(RecentsAnimationController controller) {
-        mRecentsAnimationController = null;
-        mRecentsAnimationTargets = null;
-        if (mRecentsView != null) {
-            mRecentsView.setRecentsAnimationTargets(null, null);
-        }
-    }
-
-    @Override
-    public void onTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {
-        if (mRecentsAnimationController != null) {
-            if (handleTaskAppeared(appearedTaskTarget)) {
-                mRecentsAnimationController.finish(false /* toRecents */,
-                        null /* onFinishComplete */);
-                mActivityInterface.onLaunchTaskSuccess();
-                ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", false);
-            }
-        }
-    }
-
-    /** @return Whether this was the task we were waiting to appear, and thus handled it. */
-    protected abstract boolean handleTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget);
-
-    /**
-     * @return The index of the TaskView in RecentsView whose taskId matches the task that will
-     * resume if we finish the controller.
-     */
-    protected int getLastAppearedTaskIndex() {
-        return mGestureState.getLastAppearedTaskId() != -1
-                ? mRecentsView.getTaskIndexForId(mGestureState.getLastAppearedTaskId())
-                : mRecentsView.getRunningTaskIndex();
-    }
-
-    /**
-     * @return Whether we are continuing a gesture that already landed on a new task,
-     * but before that task appeared.
-     */
-    protected boolean hasStartedNewTask() {
-        return mGestureState.getLastStartedTaskId() != -1;
-    }
-
-    /**
-     * Return true if the window should be translated horizontally if the recents view scrolls
-     */
-    protected abstract boolean moveWindowWithRecentsScroll();
-
-    protected boolean onActivityInit(Boolean alreadyOnHome) {
-        T createdActivity = mActivityInterface.getCreatedActivity();
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.PAUSE_NOT_DETECTED, "BaseSwipeUpHandler.1");
-        }
-        if (createdActivity != null) {
-            if (TestProtocol.sDebugTracing) {
-                Log.d(TestProtocol.PAUSE_NOT_DETECTED, "BaseSwipeUpHandler.2");
-            }
-            initTransitionEndpoints(createdActivity.getDeviceProfile());
-        }
-        return true;
-    }
-
-    /**
-     * Called to create a input proxy for the running task
-     */
-    @UiThread
-    protected abstract InputConsumer createNewInputProxyHandler();
-
-    /**
-     * Called when the value of {@link #mCurrentShift} changes
-     */
-    @UiThread
-    public abstract void updateFinalShift();
-
-    /**
-     * Called when motion pause is detected
-     */
-    public abstract void onMotionPauseChanged(boolean isPaused);
-
-    @UiThread
-    public void onGestureStarted(boolean isLikelyToStartNewTask) { }
-
-    @UiThread
-    public abstract void onGestureCancelled();
-
-    @UiThread
-    public abstract void onGestureEnded(float endVelocity, PointF velocity, PointF downPos);
-
-    public abstract void onConsumerAboutToBeSwitched();
-
-    public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) { }
-
-    /**
-     * Registers a callback to run when the activity is ready.
-     * @param intent The intent that will be used to start the activity if it doesn't exist already.
-     */
-    public void initWhenReady(Intent intent) {
-        // Preload the plan
-        RecentsModel.INSTANCE.get(mContext).getTasks(null);
-
-        mActivityInitListener.register(intent);
-    }
-
-    /**
-     * Applies the transform on the recents animation
-     */
-    protected void applyWindowTransform() {
-        if (mWindowTransitionController != null) {
-            float progress = mCurrentShift.value / mDragLengthFactor;
-            mWindowTransitionController.setPlayFraction(progress);
-        }
-        if (mRecentsAnimationTargets != null) {
-            if (mRecentsViewScrollLinked) {
-                mTaskViewSimulator.setScroll(mRecentsView.getScrollOffset());
-            }
-            mTaskViewSimulator.apply(mTransformParams);
-        }
-    }
-
-    @Override
-    protected RectFSpringAnim createWindowAnimationToHome(float startProgress,
-            HomeAnimationFactory homeAnimationFactory) {
-        RectFSpringAnim anim =
-                super.createWindowAnimationToHome(startProgress, homeAnimationFactory);
-        if (mRecentsAnimationTargets != null) {
-            mRecentsAnimationTargets.addReleaseCheck(anim);
-        }
-        return anim;
-    }
-
-    public interface Factory {
-
-        BaseSwipeUpHandler newHandler(
-                GestureState gestureState, long touchTimeMs, boolean continuingLastGesture);
-    }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/logging/UserEventDispatcherAppPredictionExtension.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/logging/UserEventDispatcherAppPredictionExtension.java
deleted file mode 100644
index e000803..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/logging/UserEventDispatcherAppPredictionExtension.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2018 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.quickstep.logging;
-
-import android.content.Context;
-
-import androidx.annotation.NonNull;
-
-import com.android.launcher3.appprediction.PredictionUiStateManager;
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
-
-import java.util.ArrayList;
-
-/**
- * This class handles AOSP MetricsLogger function calls and logging around
- * quickstep interactions and app launches.
- */
-@SuppressWarnings("unused")
-public class UserEventDispatcherAppPredictionExtension extends UserEventDispatcherExtension {
-
-    public static final int ALL_APPS_PREDICTION_TIPS = 2;
-
-    private static final String TAG = "UserEventDispatcher";
-
-    public UserEventDispatcherAppPredictionExtension(Context context) {
-        super(context);
-    }
-
-    @Override
-    protected void onFillInLogContainerData(
-            @NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target,
-            @NonNull ArrayList<LauncherLogProto.Target> targets) {
-        PredictionUiStateManager.fillInPredictedRank(itemInfo, target);
-    }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java
deleted file mode 100644
index 85006da..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2019 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.quickstep.util;
-
-import static com.android.launcher3.LauncherState.BACKGROUND_APP;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
-import static com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory.INDEX_SHELF_ANIM;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.view.animation.Interpolator;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.uioverrides.states.OverviewState;
-
-/**
- * Animates the shelf between states HIDE, PEEK, and OVERVIEW.
- */
-public class ShelfPeekAnim {
-
-    public static final Interpolator INTERPOLATOR = OVERSHOOT_1_2;
-    public static final long DURATION = 240;
-
-    private final Launcher mLauncher;
-
-    private ShelfAnimState mShelfState;
-    private boolean mIsPeeking;
-
-    public ShelfPeekAnim(Launcher launcher) {
-        mLauncher = launcher;
-    }
-
-    /**
-     * Animates to the given state, canceling the previous animation if it was still running.
-     */
-    public void setShelfState(ShelfAnimState shelfState, Interpolator interpolator, long duration) {
-        if (mShelfState == shelfState || FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()) {
-            return;
-        }
-        mLauncher.getStateManager().cancelStateElementAnimation(INDEX_SHELF_ANIM);
-        mShelfState = shelfState;
-        mIsPeeking = mShelfState == ShelfAnimState.PEEK || mShelfState == ShelfAnimState.HIDE;
-        if (mShelfState == ShelfAnimState.CANCEL) {
-            return;
-        }
-        float shelfHiddenProgress = BACKGROUND_APP.getVerticalProgress(mLauncher);
-        float shelfOverviewProgress = OVERVIEW.getVerticalProgress(mLauncher);
-        // Peek based on default overview progress so we can see hotseat if we're showing
-        // that instead of predictions in overview.
-        float defaultOverviewProgress = OverviewState.getDefaultVerticalProgress(mLauncher);
-        float shelfPeekingProgress = shelfHiddenProgress
-                - (shelfHiddenProgress - defaultOverviewProgress) * 0.25f;
-        float toProgress = mShelfState == ShelfAnimState.HIDE
-                ? shelfHiddenProgress
-                : mShelfState == ShelfAnimState.PEEK
-                        ? shelfPeekingProgress
-                        : shelfOverviewProgress;
-        Animator shelfAnim = mLauncher.getStateManager()
-                .createStateElementAnimation(INDEX_SHELF_ANIM, toProgress);
-        shelfAnim.setInterpolator(interpolator);
-        shelfAnim.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationCancel(Animator animation) {
-                mShelfState = ShelfAnimState.CANCEL;
-            }
-
-            @Override
-            public void onAnimationEnd(Animator animator) {
-                mIsPeeking = mShelfState == ShelfAnimState.PEEK;
-            }
-        });
-        shelfAnim.setDuration(duration).start();
-    }
-
-    /** @return Whether the shelf is currently peeking or animating to or from peeking. */
-    public boolean isPeeking() {
-        return mIsPeeking;
-    }
-
-    /** The various shelf states we can animate to. */
-    public enum ShelfAnimState {
-        HIDE(true), PEEK(true), OVERVIEW(false), CANCEL(false);
-
-        ShelfAnimState(boolean shouldPreformHaptic) {
-            this.shouldPreformHaptic = shouldPreformHaptic;
-        }
-
-        public final boolean shouldPreformHaptic;
-    }
-}
diff --git a/quickstep/recents_ui_overrides/res/drawable/all_apps_edu_circle.xml b/quickstep/res/drawable/all_apps_edu_circle.xml
similarity index 100%
rename from quickstep/recents_ui_overrides/res/drawable/all_apps_edu_circle.xml
rename to quickstep/res/drawable/all_apps_edu_circle.xml
diff --git a/quickstep/res/drawable/bg_circle.xml b/quickstep/res/drawable/bg_circle.xml
new file mode 100644
index 0000000..506177b
--- /dev/null
+++ b/quickstep/res/drawable/bg_circle.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2020 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="oval">
+    <solid android:color="#FFFFFFFF" />
+</shape>
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/res/drawable/chip_hint_background_light.xml b/quickstep/res/drawable/chip_hint_background_light.xml
similarity index 100%
rename from quickstep/recents_ui_overrides/res/drawable/chip_hint_background_light.xml
rename to quickstep/res/drawable/chip_hint_background_light.xml
diff --git a/quickstep/recents_ui_overrides/res/drawable/chip_scrim_gradient.xml b/quickstep/res/drawable/chip_scrim_gradient.xml
similarity index 100%
rename from quickstep/recents_ui_overrides/res/drawable/chip_scrim_gradient.xml
rename to quickstep/res/drawable/chip_scrim_gradient.xml
diff --git a/quickstep/recents_ui_overrides/res/drawable/hotseat_edu_notification_icon.xml b/quickstep/res/drawable/hotseat_edu_notification_icon.xml
similarity index 100%
rename from quickstep/recents_ui_overrides/res/drawable/hotseat_edu_notification_icon.xml
rename to quickstep/res/drawable/hotseat_edu_notification_icon.xml
diff --git a/quickstep/recents_ui_overrides/res/layout/all_apps_edu_view.xml b/quickstep/res/layout/all_apps_edu_view.xml
similarity index 100%
rename from quickstep/recents_ui_overrides/res/layout/all_apps_edu_view.xml
rename to quickstep/res/layout/all_apps_edu_view.xml
diff --git a/quickstep/recents_ui_overrides/res/layout/fallback_recents_activity.xml b/quickstep/res/layout/fallback_recents_activity.xml
similarity index 100%
rename from quickstep/recents_ui_overrides/res/layout/fallback_recents_activity.xml
rename to quickstep/res/layout/fallback_recents_activity.xml
diff --git a/quickstep/recents_ui_overrides/res/layout/floating_header_content.xml b/quickstep/res/layout/floating_header_content.xml
similarity index 100%
rename from quickstep/recents_ui_overrides/res/layout/floating_header_content.xml
rename to quickstep/res/layout/floating_header_content.xml
diff --git a/quickstep/res/layout/gesture_tutorial_fragment.xml b/quickstep/res/layout/gesture_tutorial_fragment.xml
index 459d65f..43bf0ea 100644
--- a/quickstep/res/layout/gesture_tutorial_fragment.xml
+++ b/quickstep/res/layout/gesture_tutorial_fragment.xml
@@ -24,6 +24,14 @@
         android:layout_height="match_parent"
         android:background="@drawable/gesture_tutorial_ripple"/>
 
+    <com.android.launcher3.views.ClipIconView
+        android:id="@+id/gesture_tutorial_fake_icon_view"
+        android:layout_width="20dp"
+        android:layout_height="20dp"
+        android:background="@drawable/bg_circle"
+        android:backgroundTint="@color/gesture_tutorial_fake_task_view_color"
+        android:visibility="invisible" />
+
     <View
         android:id="@+id/gesture_tutorial_fake_task_view"
         android:layout_width="match_parent"
@@ -41,81 +49,81 @@
         android:id="@+id/gesture_tutorial_fragment_close_button"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:padding="18dp"
-        android:layout_marginTop="30dp"
-        android:layout_marginStart="4dp"
         android:layout_alignParentStart="true"
         android:layout_alignParentTop="true"
-        android:background="@android:color/transparent"
+        android:layout_marginStart="4dp"
+        android:layout_marginTop="30dp"
         android:accessibilityTraversalAfter="@id/gesture_tutorial_fragment_titles_container"
+        android:background="@android:color/transparent"
         android:contentDescription="@string/gesture_tutorial_close_button_content_description"
-        android:tint="?android:attr/textColorPrimary"
-        android:src="@drawable/gesture_tutorial_close_button"/>
+        android:padding="18dp"
+        android:src="@drawable/gesture_tutorial_close_button"
+        android:tint="?android:attr/textColorPrimary"/>
 
     <LinearLayout
         android:id="@+id/gesture_tutorial_fragment_titles_container"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_marginTop="70dp"
         android:layout_alignParentTop="true"
+        android:layout_marginTop="70dp"
         android:focusable="true"
         android:gravity="center_horizontal"
         android:orientation="vertical">
 
         <TextView
             android:id="@+id/gesture_tutorial_fragment_title_view"
+            style="@style/TextAppearance.GestureTutorial.Title"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_marginStart="@dimen/gesture_tutorial_title_margin_start_end"
-            android:layout_marginEnd="@dimen/gesture_tutorial_title_margin_start_end"
-            style="@style/TextAppearance.GestureTutorial.Title"/>
+            android:layout_marginEnd="@dimen/gesture_tutorial_title_margin_start_end"/>
 
         <TextView
             android:id="@+id/gesture_tutorial_fragment_subtitle_view"
+            style="@style/TextAppearance.GestureTutorial.Subtitle"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_marginTop="10dp"
             android:layout_marginStart="@dimen/gesture_tutorial_subtitle_margin_start_end"
-            android:layout_marginEnd="@dimen/gesture_tutorial_subtitle_margin_start_end"
-            style="@style/TextAppearance.GestureTutorial.Subtitle"/>
+            android:layout_marginTop="10dp"
+            android:layout_marginEnd="@dimen/gesture_tutorial_subtitle_margin_start_end"/>
     </LinearLayout>
 
     <TextView
         android:id="@+id/gesture_tutorial_fragment_feedback_view"
+        style="@style/TextAppearance.GestureTutorial.Feedback"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_marginBottom="10dp"
-        android:layout_centerHorizontal="true"
         android:layout_above="@id/gesture_tutorial_fragment_action_button"
+        android:layout_centerHorizontal="true"
         android:layout_marginStart="@dimen/gesture_tutorial_feedback_margin_start_end"
         android:layout_marginEnd="@dimen/gesture_tutorial_feedback_margin_start_end"
-        style="@style/TextAppearance.GestureTutorial.Feedback"/>
+        android:layout_marginBottom="10dp"/>
 
     <!-- android:stateListAnimator="@null" removes shadow and normal on click behavior (increase
          of elevation and shadow) which is replaced by ripple effect in android:foreground -->
     <Button
         android:id="@+id/gesture_tutorial_fragment_action_button"
+        style="@style/TextAppearance.GestureTutorial.ButtonLabel"
         android:layout_width="142dp"
         android:layout_height="49dp"
-        android:layout_marginEnd="@dimen/gesture_tutorial_button_margin_start_end"
-        android:layout_marginBottom="48dp"
         android:layout_alignParentEnd="true"
         android:layout_alignParentBottom="true"
-        android:stateListAnimator="@null"
+        android:layout_marginEnd="@dimen/gesture_tutorial_button_margin_start_end"
+        android:layout_marginBottom="48dp"
         android:background="@drawable/gesture_tutorial_action_button_background"
         android:foreground="?android:attr/selectableItemBackgroundBorderless"
-        style="@style/TextAppearance.GestureTutorial.ButtonLabel"/>
+        android:stateListAnimator="@null"/>
 
     <Button
         android:id="@+id/gesture_tutorial_fragment_action_text_button"
+        style="@style/TextAppearance.GestureTutorial.TextButtonLabel"
         android:layout_width="142dp"
         android:layout_height="49dp"
-        android:layout_marginStart="@dimen/gesture_tutorial_button_margin_start_end"
-        android:layout_marginBottom="48dp"
         android:layout_alignParentStart="true"
         android:layout_alignParentBottom="true"
-        android:stateListAnimator="@null"
+        android:layout_marginStart="@dimen/gesture_tutorial_button_margin_start_end"
+        android:layout_marginBottom="48dp"
         android:background="@null"
         android:foreground="?android:attr/selectableItemBackgroundBorderless"
-        style="@style/TextAppearance.GestureTutorial.TextButtonLabel"/>
+        android:stateListAnimator="@null"/>
 </RelativeLayout>
\ No newline at end of file
diff --git a/quickstep/res/layout/overview_actions_container.xml b/quickstep/res/layout/overview_actions_container.xml
index e05688e..258f24a 100644
--- a/quickstep/res/layout/overview_actions_container.xml
+++ b/quickstep/res/layout/overview_actions_container.xml
@@ -17,9 +17,7 @@
 <com.android.quickstep.views.OverviewActionsView xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="@dimen/overview_actions_height"
-    android:layout_gravity="center_horizontal|bottom"
-    android:layout_marginLeft="@dimen/overview_actions_horizontal_margin"
-    android:layout_marginRight="@dimen/overview_actions_horizontal_margin">
+    android:layout_gravity="center_horizontal|bottom">
 
     <LinearLayout
         android:id="@+id/action_buttons"
diff --git a/quickstep/res/layout/overview_clear_all_button.xml b/quickstep/res/layout/overview_clear_all_button.xml
index fc06ba0..34ff91d 100644
--- a/quickstep/res/layout/overview_clear_all_button.xml
+++ b/quickstep/res/layout/overview_clear_all_button.xml
@@ -23,5 +23,4 @@
     android:text="@string/recents_clear_all"
     android:textColor="?attr/workspaceTextColor"
     android:textSize="14sp"
-    android:translationY="@dimen/task_thumbnail_half_top_margin"
-    />
\ No newline at end of file
+    android:translationY="@dimen/task_thumbnail_half_top_margin" />
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/res/layout/overview_panel.xml b/quickstep/res/layout/overview_panel.xml
similarity index 100%
rename from quickstep/recents_ui_overrides/res/layout/overview_panel.xml
rename to quickstep/res/layout/overview_panel.xml
diff --git a/quickstep/recents_ui_overrides/res/layout/predicted_app_icon.xml b/quickstep/res/layout/predicted_app_icon.xml
similarity index 100%
rename from quickstep/recents_ui_overrides/res/layout/predicted_app_icon.xml
rename to quickstep/res/layout/predicted_app_icon.xml
diff --git a/quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml b/quickstep/res/layout/predicted_hotseat_edu.xml
similarity index 100%
rename from quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml
rename to quickstep/res/layout/predicted_hotseat_edu.xml
diff --git a/quickstep/res/values/colors.xml b/quickstep/res/values/colors.xml
index 40da136..449fe10 100644
--- a/quickstep/res/values/colors.xml
+++ b/quickstep/res/values/colors.xml
@@ -16,4 +16,12 @@
 <resources>
     <color name="back_arrow_color_light">#FFFFFFFF</color>
     <color name="back_arrow_color_dark">#99000000</color>
+
+    <color name="chip_hint_foreground_color">#fff</color>
+    <color name="chip_scrim_start_color">#39000000</color>
+
+    <color name="all_apps_label_text">#61000000</color>
+    <color name="all_apps_label_text_dark">#61FFFFFF</color>
+    <color name="all_apps_prediction_row_separator">#3c000000</color>
+    <color name="all_apps_prediction_row_separator_dark">#3cffffff</color>
 </resources>
\ No newline at end of file
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index 0f2955b..9ec303a 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -16,6 +16,8 @@
 <resources>
     <string name="task_overlay_factory_class" translatable="false"/>
 
+    <string name="overscroll_plugin_factory_class" translatable="false" />
+
     <!-- Activities which block home gesture -->
     <string-array name="gesture_blocking_activities" translatable="false">
         <item>com.android.launcher3/com.android.quickstep.interaction.GestureSandboxActivity</item>
@@ -35,4 +37,6 @@
     <integer name="assistant_gesture_corner_deg_threshold">20</integer>
 
     <string name="wellbeing_provider_pkg" translatable="false"/>
+
+    <integer name="max_depth_blur_radius">150</integer>
 </resources>
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 9d70316..313db8c 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -30,7 +30,6 @@
 
     <dimen name="recents_page_spacing">10dp</dimen>
     <dimen name="recents_clear_all_deadzone_vertical_margin">70dp</dimen>
-    <dimen name="overview_peek_distance">96dp</dimen>
 
     <!-- The speed in dp/s at which the user needs to be scrolling in recents such that we start
              loading full resolution screenshots. -->
@@ -76,6 +75,10 @@
     <dimen name="gestures_assistant_drag_threshold">55dp</dimen>
     <dimen name="gestures_assistant_fling_threshold">55dp</dimen>
 
+    <!-- One-Handed Mode -->
+    <!-- Threshold for draging distance to enable one-handed mode -->
+    <dimen name="gestures_onehanded_drag_threshold">20dp</dimen>
+
     <!-- Distance to move elements when swiping up to go home from launcher -->
     <dimen name="home_pullback_distance">28dp</dimen>
 
@@ -98,4 +101,26 @@
     <dimen name="swipe_edu_circle_size">64dp</dimen>
     <dimen name="swipe_edu_width">80dp</dimen>
     <dimen name="swipe_edu_max_height">184dp</dimen>
+
+    <dimen name="chip_hint_border_width">1dp</dimen>
+    <dimen name="chip_hint_corner_radius">20dp</dimen>
+    <dimen name="chip_hint_outer_padding">20dp</dimen>
+    <dimen name="chip_hint_start_padding">10dp</dimen>
+    <dimen name="chip_hint_end_padding">12dp</dimen>
+    <dimen name="chip_hint_horizontal_margin">20dp</dimen>
+    <dimen name="chip_hint_vertical_offset">16dp</dimen>
+    <dimen name="chip_hint_elevation">2dp</dimen>
+    <dimen name="chip_icon_size">16dp</dimen>
+    <dimen name="chip_text_height">26dp</dimen>
+    <dimen name="chip_text_top_padding">4dp</dimen>
+    <dimen name="chip_text_start_padding">10dp</dimen>
+    <dimen name="chip_text_size">14sp</dimen>
+
+    <dimen name="all_apps_prediction_row_divider_height">17dp</dimen>
+    <dimen name="all_apps_label_top_padding">16dp</dimen>
+    <dimen name="all_apps_label_bottom_padding">8dp</dimen>
+    <dimen name="all_apps_label_text_size">14sp</dimen>
+
+    <!-- Minimum distance to swipe to trigger accessibility gesture -->
+    <dimen name="accessibility_gesture_min_swipe_distance">80dp</dimen>
 </resources>
diff --git a/quickstep/recents_ui_overrides/res/values/override.xml b/quickstep/res/values/override.xml
similarity index 86%
rename from quickstep/recents_ui_overrides/res/values/override.xml
rename to quickstep/res/values/override.xml
index 6aa9619..8f4ce43 100644
--- a/quickstep/recents_ui_overrides/res/values/override.xml
+++ b/quickstep/res/values/override.xml
@@ -25,8 +25,8 @@
 
   <string name="main_process_initializer_class" translatable="false">com.android.quickstep.QuickstepProcessInitializer</string>
 
-  <string name="user_event_dispatcher_class" translatable="false">com.android.quickstep.logging.UserEventDispatcherAppPredictionExtension</string>
+  <string name="user_event_dispatcher_class" translatable="false">com.android.quickstep.logging.UserEventDispatcherExtension</string>
 
-  <string name="prediction_model_class" translatable="false">com.android.launcher3.hybridhotseat.HotseatPredictionModel</string>
+  <string name="model_delegate_class" translatable="false">com.android.launcher3.model.QuickstepModelDelegate</string>
+
 </resources>
-
diff --git a/quickstep/robolectric_tests/src/com/android/quickstep/OrientationTouchTransformerTest.java b/quickstep/robolectric_tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
index 22d205a..5cb55ec 100644
--- a/quickstep/robolectric_tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
+++ b/quickstep/robolectric_tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
@@ -32,7 +32,7 @@
 import android.view.Surface;
 
 import com.android.launcher3.ResourceUtils;
-import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.DisplayController;
 
 import org.junit.Before;
 import org.junit.Ignore;
@@ -50,7 +50,7 @@
     private OrientationTouchTransformer mTouchTransformer;
 
     Resources mResources;
-    private DefaultDisplay.Info mInfo;
+    private DisplayController.Info mInfo;
 
 
     @Before
@@ -231,12 +231,12 @@
         assertTrue(mTouchTransformer.touchInValidSwipeRegions(inRegion2.getX(), inRegion2.getY()));
     }
 
-    private DefaultDisplay.Info createDisplayInfo(int rotation) {
+    private DisplayController.Info createDisplayInfo(int rotation) {
         Point p = new Point(SIZE_WIDTH, SIZE_HEIGHT);
         if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
             p = new Point(SIZE_HEIGHT, SIZE_WIDTH);
         }
-        return new DefaultDisplay.Info(0, rotation, 0, p, p, p, null);
+        return new DisplayController.Info(0, rotation, 0, p, p, p, null);
     }
 
     private float generateTouchRegionHeight(int rotation) {
diff --git a/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java b/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java
index 93b64e6..7049af0 100644
--- a/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java
+++ b/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java
@@ -17,6 +17,7 @@
 
 import static com.android.launcher3.util.LauncherUIHelper.doLayout;
 
+import android.app.ActivityManager.RunningTaskInfo;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.Config;
 
@@ -50,7 +51,7 @@
     }
 
     @Test
-    public void testRecets_showCurrentTask() {
+    public void testRecents_showCurrentTask() {
         ActivityController<RecentsActivity> controller =
                 Robolectric.buildActivity(RecentsActivity.class);
 
@@ -58,7 +59,10 @@
         doLayout(activity);
 
         FallbackRecentsView frv = activity.getOverviewPanel();
-        frv.showCurrentTask(22);
+
+        RunningTaskInfo placeholderTask = new RunningTaskInfo();
+        placeholderTask.taskId = 22;
+        frv.showCurrentTask(placeholderTask);
         doLayout(activity);
 
         ThumbnailData thumbnailData = new ThumbnailData();
diff --git a/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java b/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
index a31ba21..5491daa 100644
--- a/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
+++ b/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
@@ -27,7 +27,7 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.shadows.LShadowDisplay;
-import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.DisplayController;
 import com.android.quickstep.LauncherActivityInterface;
 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
 
@@ -144,7 +144,7 @@
                     LauncherActivityInterface.INSTANCE);
             tvs.setDp(mDeviceProfile);
 
-            int launcherRotation = DefaultDisplay.INSTANCE.get(mContext).getInfo().rotation;
+            int launcherRotation = DisplayController.INSTANCE.get(mContext).getInfo().rotation;
             if (mAppRotation < 0) {
                 mAppRotation = launcherRotation;
             }
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 47ce320..68111d2 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -26,12 +26,10 @@
 import android.animation.ValueAnimator;
 import android.content.Intent;
 import android.content.IntentSender;
-import android.content.SharedPreferences;
 import android.os.Bundle;
 import android.os.CancellationSignal;
 
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.hybridhotseat.HotseatPredictionController;
 import com.android.launcher3.model.WellbeingModel;
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.proxy.ProxyActivityStarter;
@@ -40,17 +38,14 @@
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.statemanager.StateManager.StateHandler;
 import com.android.launcher3.uioverrides.RecentsViewStateController;
-import com.android.launcher3.util.OnboardingPrefs;
 import com.android.launcher3.util.UiThreadHelper;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
 import com.android.quickstep.SystemUiProxy;
-import com.android.quickstep.util.QuickstepOnboardingPrefs;
 import com.android.quickstep.util.RemoteAnimationProvider;
 import com.android.quickstep.util.RemoteFadeOutAnimationListener;
-import com.android.quickstep.util.ShelfPeekAnim;
 import com.android.quickstep.views.OverviewActionsView;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -73,10 +68,7 @@
             (context, arg1, arg2) -> SystemUiProxy.INSTANCE.get(context).setBackButtonAlpha(
                     Float.intBitsToFloat(arg1), arg2 != 0);
 
-    private final ShelfPeekAnim mShelfPeekAnim = new ShelfPeekAnim(this);
-
     private OverviewActionsView mActionsView;
-    protected HotseatPredictionController mHotseatPredictionController;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -111,6 +103,13 @@
     }
 
     @Override
+    protected void handleGestureContract(Intent intent) {
+        if (FeatureFlags.SEPARATE_RECENTS_ACTIVITY.get()) {
+            super.handleGestureContract(intent);
+        }
+    }
+
+    @Override
     public void onTrimMemory(int level) {
         super.onTrimMemory(level);
         RecentsModel.INSTANCE.get(this).onTrimMemory(level);
@@ -157,6 +156,12 @@
     @Override
     protected void onDeferredResumed() {
         super.onDeferredResumed();
+        handlePendingActivityRequest();
+    }
+
+    @Override
+    protected void handlePendingActivityRequest() {
+        super.handlePendingActivityRequest();
         if (mPendingActivityRequestCode != -1 && isInState(NORMAL)) {
             // Remove any active ProxyActivityStarter task and send RESULT_CANCELED to Launcher.
             onActivityResult(mPendingActivityRequestCode, RESULT_CANCELED, null);
@@ -183,7 +188,7 @@
     }
 
     private boolean isOverviewActionsEnabled() {
-        return FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(this);
+        return removeShelfFromOverview(this);
     }
 
     public <T extends OverviewActionsView> T getActionsView() {
@@ -212,11 +217,6 @@
     }
 
     @Override
-    protected OnboardingPrefs createOnboardingPrefs(SharedPreferences sharedPrefs) {
-        return new QuickstepOnboardingPrefs(this, sharedPrefs);
-    }
-
-    @Override
     public void useFadeOutAnimationForLauncherStart(CancellationSignal signal) {
         QuickstepAppTransitionManagerImpl appTransitionManager =
                 (QuickstepAppTransitionManagerImpl) getAppTransitionManager();
@@ -304,17 +304,6 @@
                 Stream.of(WellbeingModel.SHORTCUT_FACTORY));
     }
 
-    public ShelfPeekAnim getShelfPeekAnim() {
-        return mShelfPeekAnim;
-    }
-
-    /**
-     * Returns Prediction controller for hybrid hotseat
-     */
-    public HotseatPredictionController getHotseatPredictionController() {
-        return mHotseatPredictionController;
-    }
-
     public void setHintUserWillBeActive() {
         addActivityFlags(ACTIVITY_STATE_USER_WILL_BE_ACTIVE);
     }
diff --git a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
index 02769c6..b35b33c 100644
--- a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
+++ b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
@@ -16,7 +16,7 @@
 package com.android.launcher3;
 
 import static com.android.launcher3.Utilities.postAsyncCallback;
-import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
+import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
 import static com.android.systemui.shared.recents.utilities.Utilities.postAtFrontOfQueueAsynchronously;
 
 import android.animation.Animator;
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
rename to quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index 10f789d..2543148 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
@@ -16,8 +16,6 @@
 
 package com.android.launcher3;
 
-import static android.util.TypedValue.COMPLEX_UNIT_DIP;
-
 import static com.android.launcher3.BaseActivity.INVISIBLE_ALL;
 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_APP_TRANSITIONS;
 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_PENDING_FLAGS;
@@ -47,7 +45,6 @@
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
-import android.annotation.TargetApi;
 import android.app.ActivityOptions;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -57,12 +54,10 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
-import android.os.Build;
 import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.Looper;
 import android.util.Pair;
-import android.util.TypedValue;
 import android.view.View;
 
 import androidx.annotation.NonNull;
@@ -97,7 +92,6 @@
  * {@link LauncherAppTransitionManager} with Quickstep-specific app transitions for launching from
  * home and/or all-apps.  Not used for 3p launchers.
  */
-@TargetApi(Build.VERSION_CODES.O)
 @SuppressWarnings("unused")
 public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTransitionManager
         implements OnDeviceProfileChangeListener {
@@ -879,10 +873,8 @@
                             }
                         });
                     } else {
-                        float velocityDpPerS = DynamicResource.provider(mLauncher)
+                        float velocityPxPerS = DynamicResource.provider(mLauncher)
                                 .getDimension(R.dimen.unlock_staggered_velocity_dp_per_s);
-                        float velocityPxPerS = TypedValue.applyDimension(COMPLEX_UNIT_DIP,
-                                velocityDpPerS, mLauncher.getResources().getDisplayMetrics());
                         anim.play(new StaggeredWorkspaceAnim(mLauncher, velocityPxPerS, false)
                                 .getAnimators());
                     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java b/quickstep/src/com/android/launcher3/appprediction/AllAppsTipView.java
similarity index 91%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java
rename to quickstep/src/com/android/launcher3/appprediction/AllAppsTipView.java
index 8477b10..98bf483 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/AllAppsTipView.java
@@ -19,7 +19,6 @@
 import static com.android.launcher3.AbstractFloatingView.TYPE_DISCOVERY_BOUNCE;
 import static com.android.launcher3.AbstractFloatingView.TYPE_ON_BOARD_POPUP;
 import static com.android.launcher3.LauncherState.ALL_APPS;
-import static com.android.quickstep.logging.UserEventDispatcherExtension.ALL_APPS_PREDICTION_TIPS;
 
 import android.os.UserManager;
 
@@ -31,7 +30,6 @@
 import com.android.launcher3.allapps.FloatingHeaderView;
 import com.android.launcher3.statemanager.StateManager.StateListener;
 import com.android.launcher3.views.ArrowTipView;
-import com.android.systemui.shared.system.LauncherEventUtil;
 
 /**
  * ArrowTip helper aligned just above prediction apps, shown to users that enter all apps for the
@@ -57,8 +55,7 @@
         floatingHeaderView.findFixedRowByType(PredictionRowView.class).getLocationOnScreen(coords);
         ArrowTipView arrowTipView = new ArrowTipView(launcher).setOnClosedCallback(() -> {
             launcher.getSharedPrefs().edit().putBoolean(ALL_APPS_TIP_SEEN, true).apply();
-            launcher.getUserEventDispatcher().logActionTip(LauncherEventUtil.DISMISS,
-                    ALL_APPS_PREDICTION_TIPS);
+            // TODO: add log to WW
         });
         arrowTipView.show(launcher.getString(R.string.all_apps_prediction_tip), coords[1]);
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AppsDividerView.java b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
similarity index 95%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AppsDividerView.java
rename to quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
index 914d9e9..b891120 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AppsDividerView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
@@ -1,4 +1,4 @@
-/**
+/*
  * Copyright (C) 2019 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -42,6 +42,7 @@
 import com.android.launcher3.allapps.FloatingHeaderRow;
 import com.android.launcher3.allapps.FloatingHeaderView;
 import com.android.launcher3.anim.PropertySetter;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.statemanager.StateManager.StateListener;
 import com.android.launcher3.util.Themes;
 
@@ -90,7 +91,8 @@
         mLauncher = Launcher.getLauncher(context);
 
         boolean isMainColorDark = Themes.getAttrBoolean(context, R.attr.isMainColorDark);
-        mPaint.setStrokeWidth(getResources().getDimensionPixelSize(R.dimen.all_apps_divider_height));
+        mPaint.setStrokeWidth(
+                getResources().getDimensionPixelSize(R.dimen.all_apps_divider_height));
 
         mStrokeColor = ContextCompat.getColor(context, isMainColorDark
                 ? R.color.all_apps_prediction_row_separator_dark
@@ -134,7 +136,7 @@
                 if (row == this) {
                     break;
                 } else if (row.shouldDraw()) {
-                    sectionCount ++;
+                    sectionCount++;
                 }
             }
 
@@ -181,6 +183,11 @@
     }
 
     private void updateViewVisibility() {
+        // hide divider since we have item decoration for prediction row
+        if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
+            setVisibility(GONE);
+            return;
+        }
         setVisibility(mDividerType == DividerType.NONE
                 ? GONE
                 : (mIsScrolledOut ? INVISIBLE : VISIBLE));
@@ -303,4 +310,9 @@
     public Class<AppsDividerView> getTypeClass() {
         return AppsDividerView.class;
     }
+
+    @Override
+    public View getFocusedChild() {
+        return null;
+    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/InstantAppItemInfo.java b/quickstep/src/com/android/launcher3/appprediction/InstantAppItemInfo.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/InstantAppItemInfo.java
rename to quickstep/src/com/android/launcher3/appprediction/InstantAppItemInfo.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
similarity index 80%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java
rename to quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
index 44691d3..d3c4c3d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
@@ -30,7 +30,6 @@
 import android.os.Build;
 import android.util.AttributeSet;
 import android.util.IntProperty;
-import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.animation.Interpolator;
@@ -44,10 +43,9 @@
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
-import com.android.launcher3.allapps.AllAppsStore;
+import com.android.launcher3.allapps.AllAppsSectionDecorator;
 import com.android.launcher3.allapps.FloatingHeaderRow;
 import com.android.launcher3.allapps.FloatingHeaderView;
 import com.android.launcher3.anim.AlphaUpdateListener;
@@ -56,8 +54,6 @@
 import com.android.launcher3.keyboard.FocusIndicatorHelper;
 import com.android.launcher3.keyboard.FocusIndicatorHelper.SimpleFocusIndicatorHelper;
 import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider;
-import com.android.launcher3.model.AppLaunchTracker;
-import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -68,15 +64,12 @@
 import com.android.quickstep.AnimatedFloat;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 
 @TargetApi(Build.VERSION_CODES.P)
 public class PredictionRowView extends LinearLayout implements
         LogContainerProvider, OnDeviceProfileChangeListener, FloatingHeaderRow {
 
-    private static final String TAG = "PredictionRowView";
-
     private static final IntProperty<PredictionRowView> TEXT_ALPHA =
             new IntProperty<PredictionRowView>("textAlpha") {
                 @Override
@@ -93,20 +86,15 @@
     private static final Interpolator ALPHA_FACTOR_INTERPOLATOR =
             (t) -> (t < 0.8f) ? 0 : (t - 0.8f) / 0.2f;
 
-    private static final OnClickListener PREDICTION_CLICK_LISTENER =
-            ItemClickHandler.getInstance(AppLaunchTracker.CONTAINER_PREDICTIONS);
-
     private final Launcher mLauncher;
-    private final PredictionUiStateManager mPredictionUiStateManager;
     private int mNumPredictedAppsPerRow;
 
-    // The set of predicted app component names
-    private final List<ComponentKeyMapper> mPredictedAppComponents = new ArrayList<>();
-    // The set of predicted apps resolved from the component names and the current set of apps
-    private final ArrayList<ItemInfoWithIcon> mPredictedApps = new ArrayList<>();
     // Helper to drawing the focus indicator.
     private final FocusIndicatorHelper mFocusHelper;
 
+    // The set of predicted apps resolved from the component names and the current set of apps
+    private final List<WorkspaceItemInfo> mPredictedApps = new ArrayList<>();
+
     private final int mIconTextColor;
     private final int mIconFullTextAlpha;
     private int mIconLastSetTextAlpha;
@@ -124,6 +112,8 @@
 
     private boolean mPredictionsEnabled = false;
 
+    AllAppsSectionDecorator.SectionDecorationHandler mDecorationHandler;
+
     public PredictionRowView(@NonNull Context context) {
         this(context, null);
     }
@@ -138,36 +128,24 @@
         mLauncher = Launcher.getLauncher(context);
         mLauncher.addOnDeviceProfileChangeListener(this);
 
-        mPredictionUiStateManager = PredictionUiStateManager.INSTANCE.get(context);
-
         mIconTextColor = Themes.getAttrColor(context, android.R.attr.textColorSecondary);
         mIconFullTextAlpha = Color.alpha(mIconTextColor);
         mIconCurrentTextAlpha = mIconFullTextAlpha;
 
+        if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
+            mDecorationHandler = new AllAppsSectionDecorator.SectionDecorationHandler(getContext(),
+                    false);
+        }
+
         updateVisibility();
     }
 
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-
-        mPredictionUiStateManager.setTargetAppsView(mLauncher.getAppsView());
-        getAppsStore().registerIconContainer(this);
         AllAppsTipView.scheduleShowIfNeeded(mLauncher);
     }
 
-    private AllAppsStore getAppsStore() {
-        return mLauncher.getAppsView().getAppsStore();
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-
-        mPredictionUiStateManager.setTargetAppsView(null);
-        getAppsStore().unregisterIconContainer(this);
-    }
-
     public void setup(FloatingHeaderView parent, FloatingHeaderRow[] rows, boolean tabsHidden) {
         mParent = parent;
     }
@@ -184,6 +162,15 @@
 
     @Override
     protected void dispatchDraw(Canvas canvas) {
+        if (mDecorationHandler != null) {
+            mDecorationHandler.reset();
+            int childrenCount = getChildCount();
+            for (int i = 0; i < childrenCount; i++) {
+                mDecorationHandler.extendBounds(getChildAt(i));
+            }
+            mDecorationHandler.onDraw(canvas);
+            mDecorationHandler.onFocusDraw(canvas, getFocusedChild());
+        }
         mFocusHelper.draw(canvas);
         super.dispatchDraw(canvas);
     }
@@ -197,7 +184,7 @@
 
     @Override
     public boolean shouldDraw() {
-        return getVisibility() != GONE;
+        return getVisibility() == VISIBLE;
     }
 
     @Override
@@ -209,7 +196,7 @@
      * Returns the predicted apps.
      */
     public List<ItemInfoWithIcon> getPredictedApps() {
-        return mPredictedApps;
+        return new ArrayList<>(mPredictedApps);
     }
 
     /**
@@ -221,12 +208,12 @@
      * If the number of predicted apps is the same as the previous list of predicted apps,
      * we can optimize by swapping them in place.
      */
-    public void setPredictedApps(List<ComponentKeyMapper> apps) {
-        mPredictedAppComponents.clear();
-        mPredictedAppComponents.addAll(apps);
-
+    public void setPredictedApps(List<ItemInfo> items) {
         mPredictedApps.clear();
-        mPredictedApps.addAll(processPredictedAppComponents(mPredictedAppComponents));
+        items.stream()
+                .filter(itemInfo -> itemInfo instanceof WorkspaceItemInfo)
+                .map(itemInfo -> (WorkspaceItemInfo) itemInfo)
+                .forEach(mPredictedApps::add);
         applyPredictionApps();
     }
 
@@ -246,7 +233,7 @@
             while (getChildCount() < mNumPredictedAppsPerRow) {
                 BubbleTextView icon = (BubbleTextView) inflater.inflate(
                         R.layout.all_apps_icon, this, false);
-                icon.setOnClickListener(PREDICTION_CLICK_LISTENER);
+                icon.setOnClickListener(ItemClickHandler.INSTANCE);
                 icon.setOnLongClickListener(ItemLongClickListener.INSTANCE_ALL_APPS);
                 icon.setLongPressTimeoutFactor(1f);
                 icon.setOnFocusChangeListener(mFocusHelper);
@@ -268,11 +255,7 @@
             icon.reset();
             if (predictionCount > i) {
                 icon.setVisibility(View.VISIBLE);
-                if (mPredictedApps.get(i) instanceof AppInfo) {
-                    icon.applyFromApplicationInfo((AppInfo) mPredictedApps.get(i));
-                } else if (mPredictedApps.get(i) instanceof WorkspaceItemInfo) {
-                    icon.applyFromWorkspaceItem((WorkspaceItemInfo) mPredictedApps.get(i));
-                }
+                icon.applyFromWorkspaceItem(mPredictedApps.get(i));
                 icon.setTextColor(iconColor);
             } else {
                 icon.setVisibility(predictionCount == 0 ? GONE : INVISIBLE);
@@ -288,33 +271,6 @@
         mParent.onHeightUpdated();
     }
 
-    private List<ItemInfoWithIcon> processPredictedAppComponents(
-            List<ComponentKeyMapper> components) {
-        if (getAppsStore().getApps().length == 0) {
-            // Apps have not been bound yet.
-            return Collections.emptyList();
-        }
-
-        List<ItemInfoWithIcon> predictedApps = new ArrayList<>();
-        for (ComponentKeyMapper mapper : components) {
-            ItemInfoWithIcon info = mapper.getApp(getAppsStore());
-            if (info != null) {
-                ItemInfoWithIcon predictedApp = info.clone();
-                predictedApp.container = LauncherSettings.Favorites.CONTAINER_PREDICTION;
-                predictedApps.add(predictedApp);
-            } else {
-                if (FeatureFlags.IS_STUDIO_BUILD) {
-                    Log.e(TAG, "Predicted app not found: " + mapper);
-                }
-            }
-            // Stop at the number of predicted apps
-            if (predictedApps.size() == mNumPredictedAppsPerRow) {
-                break;
-            }
-        }
-        return predictedApps;
-    }
-
     @Override
     public void fillInLogContainerData(ItemInfo childInfo, LauncherLogProto.Target child,
             ArrayList<LauncherLogProto.Target> parents) {
@@ -409,4 +365,9 @@
     public Class<PredictionRowView> getTypeClass() {
         return PredictionRowView.class;
     }
+
+    @Override
+    public View getFocusedChild() {
+        return getChildAt(0);
+    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduActivity.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduActivity.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduActivity.java
rename to quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduActivity.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
similarity index 95%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
rename to quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
index 4f95254..4451e7a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
@@ -47,35 +47,27 @@
  */
 public class HotseatEduController {
 
-    public static final String HOTSEAT_EDU_ACTION =
-            "com.android.launcher3.action.SHOW_HYBRID_HOTSEAT_EDU";
     public static final String SETTINGS_ACTION =
             "android.settings.ACTION_CONTENT_SUGGESTIONS_SETTINGS";
 
     private final Launcher mLauncher;
     private final Hotseat mHotseat;
-    private HotseatRestoreHelper mRestoreHelper;
     private List<WorkspaceItemInfo> mPredictedApps;
     private HotseatEduDialog mActiveDialog;
 
     private ArrayList<ItemInfo> mNewItems = new ArrayList<>();
     private IntArray mNewScreens = null;
-    private Runnable mOnOnboardingComplete;
 
-    HotseatEduController(Launcher launcher, HotseatRestoreHelper restoreHelper, Runnable runnable) {
+    HotseatEduController(Launcher launcher) {
         mLauncher = launcher;
         mHotseat = launcher.getHotseat();
-        mRestoreHelper = restoreHelper;
-        mOnOnboardingComplete = runnable;
     }
 
     /**
      * Checks what type of migration should be used and migrates hotseat
      */
     void migrate() {
-        if (mRestoreHelper != null) {
-            mRestoreHelper.createBackup();
-        }
+        HotseatRestoreHelper.createBackup(mLauncher);
         if (FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get()) {
             migrateToFolder();
         } else {
@@ -227,7 +219,7 @@
     }
 
     void finishOnboarding() {
-        mOnOnboardingComplete.run();
+        mLauncher.getModel().onWorkspaceUiChanged();
     }
 
     void showDimissTip() {
@@ -284,4 +276,3 @@
         return new Intent(SETTINGS_ACTION).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
     }
 }
-
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
similarity index 96%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
rename to quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
index 4b8e434..c3677ea 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
@@ -40,7 +40,6 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.uioverrides.PredictedAppIcon;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.views.AbstractSlideInView;
 
 import java.util.List;
@@ -122,16 +121,6 @@
     }
 
     @Override
-    public void logActionCommand(int command) {
-        // Since this is on-boarding popup, it is not a user controlled action.
-    }
-
-    @Override
-    public int getLogContainerType() {
-        return LauncherLogProto.ContainerType.TIP;
-    }
-
-    @Override
     protected boolean isOfType(int type) {
         return (type & TYPE_ON_BOARD_POPUP) != 0;
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatFileLog.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatFileLog.java
similarity index 97%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatFileLog.java
rename to quickstep/src/com/android/launcher3/hybridhotseat/HotseatFileLog.java
index c15a596..20e1edc 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatFileLog.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatFileLog.java
@@ -71,10 +71,9 @@
     }
 
     private PrintWriter getWriter() {
-        String fName = FILE_NAME_PREFIX + (LOG_DAYS % 10);
-        if (fName.equals(mFileName)) return mCurrentWriter;
-
         Calendar cal = Calendar.getInstance();
+        String fName = FILE_NAME_PREFIX + (cal.get(Calendar.DAY_OF_YEAR) % 10);
+        if (fName.equals(mFileName)) return mCurrentWriter;
 
         boolean append = false;
         File logFile = new File(mLogsDir, fName);
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
new file mode 100644
index 0000000..151a113
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -0,0 +1,451 @@
+/*
+ * Copyright (C) 2019 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.hybridhotseat;
+
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.hybridhotseat.HotseatEduController.getSettingsIntent;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_PREDICTION_PINNED;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_RANKED;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.content.ComponentName;
+import android.os.Process;
+import android.view.HapticFeedbackConstants;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.DragSource;
+import com.android.launcher3.DropTarget;
+import com.android.launcher3.Hotseat;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.dragndrop.DragController;
+import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.graphics.DragPreviewProvider;
+import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
+import com.android.launcher3.logger.LauncherAtom.PredictedHotseatContainer;
+import com.android.launcher3.logging.InstanceId;
+import com.android.launcher3.model.BgDataModel.FixedContainerItems;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.popup.SystemShortcut;
+import com.android.launcher3.touch.ItemLongClickListener;
+import com.android.launcher3.uioverrides.PredictedAppIcon;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.util.OnboardingPrefs;
+import com.android.launcher3.views.ArrowTipView;
+import com.android.launcher3.views.Snackbar;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Provides prediction ability for the hotseat. Fills gaps in hotseat with predicted items, allows
+ * pinning of predicted apps and manages replacement of predicted apps with user drag.
+ */
+public class HotseatPredictionController implements DragController.DragListener,
+        SystemShortcut.Factory<QuickstepLauncher>, InvariantDeviceProfile.OnIDPChangeListener,
+        DragSource {
+
+    private int mHotSeatItemsCount;
+
+    private Launcher mLauncher;
+    private final Hotseat mHotseat;
+
+    private List<ItemInfo> mPredictedItems = Collections.emptyList();
+
+    private AnimatorSet mIconRemoveAnimators;
+    private boolean mUIUpdatePaused = false;
+    private boolean mDragInProgress = false;
+
+    private List<PredictedAppIcon.PredictedIconOutlineDrawing> mOutlineDrawings = new ArrayList<>();
+
+    private final View.OnLongClickListener mPredictionLongClickListener = v -> {
+        if (!ItemLongClickListener.canStartDrag(mLauncher)) return false;
+        if (mLauncher.getWorkspace().isSwitchingState()) return false;
+        if (!mLauncher.getOnboardingPrefs().getBoolean(
+                OnboardingPrefs.HOTSEAT_LONGPRESS_TIP_SEEN)) {
+            Snackbar.show(mLauncher, R.string.hotseat_tip_gaps_filled,
+                    R.string.hotseat_prediction_settings, null,
+                    () -> mLauncher.startActivity(getSettingsIntent()));
+            mLauncher.getOnboardingPrefs().markChecked(OnboardingPrefs.HOTSEAT_LONGPRESS_TIP_SEEN);
+            mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+            return true;
+        }
+
+        // Start the drag
+        // Use a new itemInfo so that the original predicted item is stable
+        WorkspaceItemInfo dragItem = new WorkspaceItemInfo((WorkspaceItemInfo) v.getTag());
+        v.setVisibility(View.INVISIBLE);
+        mLauncher.getWorkspace().beginDragShared(
+                v, null, this, dragItem, new DragPreviewProvider(v), new DragOptions());
+        return true;
+    };
+
+    public HotseatPredictionController(Launcher launcher) {
+        mLauncher = launcher;
+        mHotseat = launcher.getHotseat();
+        mHotSeatItemsCount = mLauncher.getDeviceProfile().inv.numHotseatIcons;
+        mLauncher.getDragController().addDragListener(this);
+
+        launcher.getDeviceProfile().inv.addOnChangeListener(this);
+    }
+
+    /**
+     * Shows appropriate hotseat education based on prediction enabled and migration states.
+     */
+    public void showEdu() {
+        mLauncher.getStateManager().goToState(NORMAL, true, () -> {
+            if (mPredictedItems.isEmpty()) {
+                // launcher has empty predictions set
+                Snackbar.show(mLauncher, R.string.hotsaet_tip_prediction_disabled,
+                        R.string.hotseat_prediction_settings, null,
+                        () -> mLauncher.startActivity(getSettingsIntent()));
+            } else if (getPredictedIcons().size() >= (mHotSeatItemsCount + 1) / 2) {
+                showDiscoveryTip();
+            } else {
+                HotseatEduController eduController = new HotseatEduController(mLauncher);
+                eduController.setPredictedApps(mPredictedItems.stream()
+                        .map(i -> (WorkspaceItemInfo) i)
+                        .collect(Collectors.toList()));
+                eduController.showEdu();
+            }
+        });
+    }
+
+    /**
+     * Shows educational tip for hotseat if user does not go through Tips app.
+     */
+    private void showDiscoveryTip() {
+        if (getPredictedIcons().isEmpty()) {
+            new ArrowTipView(mLauncher).show(
+                    mLauncher.getString(R.string.hotseat_tip_no_empty_slots), mHotseat.getTop());
+        } else {
+            Snackbar.show(mLauncher, R.string.hotseat_tip_gaps_filled,
+                    R.string.hotseat_prediction_settings, null,
+                    () -> mLauncher.startActivity(getSettingsIntent()));
+        }
+    }
+
+    /**
+     * Returns if hotseat client has predictions
+     */
+    public boolean hasPredictions() {
+        return !mPredictedItems.isEmpty();
+    }
+
+    private void fillGapsWithPrediction() {
+        fillGapsWithPrediction(false, null);
+    }
+
+    private void fillGapsWithPrediction(boolean animate, Runnable callback) {
+        if (mUIUpdatePaused || mDragInProgress) {
+            return;
+        }
+
+        int predictionIndex = 0;
+        ArrayList<WorkspaceItemInfo> newItems = new ArrayList<>();
+        // make sure predicted icon removal and filling predictions don't step on each other
+        if (mIconRemoveAnimators != null && mIconRemoveAnimators.isRunning()) {
+            mIconRemoveAnimators.addListener(new AnimationSuccessListener() {
+                @Override
+                public void onAnimationSuccess(Animator animator) {
+                    fillGapsWithPrediction(animate, callback);
+                    mIconRemoveAnimators.removeListener(this);
+                }
+            });
+            return;
+        }
+        for (int rank = 0; rank < mHotSeatItemsCount; rank++) {
+            View child = mHotseat.getChildAt(
+                    mHotseat.getCellXFromOrder(rank),
+                    mHotseat.getCellYFromOrder(rank));
+
+            if (child != null && !isPredictedIcon(child)) {
+                continue;
+            }
+            if (mPredictedItems.size() <= predictionIndex) {
+                // Remove predicted apps from the past
+                if (isPredictedIcon(child)) {
+                    mHotseat.removeView(child);
+                }
+                continue;
+            }
+            WorkspaceItemInfo predictedItem =
+                    (WorkspaceItemInfo) mPredictedItems.get(predictionIndex++);
+            if (isPredictedIcon(child) && child.isEnabled()) {
+                PredictedAppIcon icon = (PredictedAppIcon) child;
+                icon.applyFromWorkspaceItem(predictedItem);
+                icon.finishBinding(mPredictionLongClickListener);
+            } else {
+                newItems.add(predictedItem);
+            }
+            preparePredictionInfo(predictedItem, rank);
+        }
+        bindItems(newItems, animate, callback);
+    }
+
+    private void bindItems(List<WorkspaceItemInfo> itemsToAdd, boolean animate, Runnable callback) {
+        AnimatorSet animationSet = new AnimatorSet();
+        for (WorkspaceItemInfo item : itemsToAdd) {
+            PredictedAppIcon icon = PredictedAppIcon.createIcon(mHotseat, item);
+            mLauncher.getWorkspace().addInScreenFromBind(icon, item);
+            icon.finishBinding(mPredictionLongClickListener);
+            if (animate) {
+                animationSet.play(ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 0.2f, 1));
+            }
+        }
+        if (animate) {
+            if (callback != null) {
+                animationSet.addListener(AnimationSuccessListener.forRunnable(callback));
+            }
+            animationSet.start();
+        } else {
+            if (callback != null) callback.run();
+        }
+    }
+
+    /**
+     * Unregisters callbacks and frees resources
+     */
+    public void destroy() {
+        mLauncher.getDeviceProfile().inv.removeOnChangeListener(this);
+    }
+
+    /**
+     * start and pauses predicted apps update on the hotseat
+     */
+    public void setPauseUIUpdate(boolean paused) {
+        mUIUpdatePaused = paused;
+        if (!paused) {
+            fillGapsWithPrediction();
+        }
+    }
+
+    /**
+     * Sets or updates the predicted items
+     */
+    public void setPredictedItems(FixedContainerItems items) {
+        mPredictedItems = items.items;
+        if (mPredictedItems.isEmpty()) {
+            HotseatRestoreHelper.restoreBackup(mLauncher);
+        }
+        fillGapsWithPrediction();
+    }
+
+    /**
+     * Pins a predicted app icon into place.
+     */
+    public void pinPrediction(ItemInfo info) {
+        PredictedAppIcon icon = (PredictedAppIcon) mHotseat.getChildAt(
+                mHotseat.getCellXFromOrder(info.rank),
+                mHotseat.getCellYFromOrder(info.rank));
+        if (icon == null) {
+            return;
+        }
+        WorkspaceItemInfo workspaceItemInfo = new WorkspaceItemInfo((WorkspaceItemInfo) info);
+        mLauncher.getModelWriter().addItemToDatabase(workspaceItemInfo,
+                LauncherSettings.Favorites.CONTAINER_HOTSEAT, workspaceItemInfo.screenId,
+                workspaceItemInfo.cellX, workspaceItemInfo.cellY);
+        ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 1, 0.8f, 1).start();
+        icon.pin(workspaceItemInfo);
+        mLauncher.getStatsLogManager().logger()
+                .withItemInfo(workspaceItemInfo)
+                .log(LAUNCHER_HOTSEAT_PREDICTION_PINNED);
+    }
+
+    private List<PredictedAppIcon> getPredictedIcons() {
+        List<PredictedAppIcon> icons = new ArrayList<>();
+        ViewGroup vg = mHotseat.getShortcutsAndWidgets();
+        for (int i = 0; i < vg.getChildCount(); i++) {
+            View child = vg.getChildAt(i);
+            if (isPredictedIcon(child)) {
+                icons.add((PredictedAppIcon) child);
+            }
+        }
+        return icons;
+    }
+
+    private void removePredictedApps(List<PredictedAppIcon.PredictedIconOutlineDrawing> outlines,
+            DropTarget.DragObject dragObject) {
+        if (mIconRemoveAnimators != null) {
+            mIconRemoveAnimators.end();
+        }
+        mIconRemoveAnimators = new AnimatorSet();
+        removeOutlineDrawings();
+        for (PredictedAppIcon icon : getPredictedIcons()) {
+            if (!icon.isEnabled()) {
+                continue;
+            }
+            if (dragObject.dragSource == this && icon.equals(dragObject.originalView)) {
+                mHotseat.removeView(icon);
+                continue;
+            }
+            int rank = ((WorkspaceItemInfo) icon.getTag()).rank;
+            outlines.add(new PredictedAppIcon.PredictedIconOutlineDrawing(
+                    mHotseat.getCellXFromOrder(rank), mHotseat.getCellYFromOrder(rank), icon));
+            icon.setEnabled(false);
+            ObjectAnimator animator = ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 0);
+            animator.addListener(new AnimationSuccessListener() {
+                @Override
+                public void onAnimationSuccess(Animator animator) {
+                    if (icon.getParent() != null) {
+                        mHotseat.removeView(icon);
+                    }
+                }
+            });
+            mIconRemoveAnimators.play(animator);
+        }
+        mIconRemoveAnimators.start();
+    }
+
+    @Override
+    public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
+        removePredictedApps(mOutlineDrawings, dragObject);
+        if (mOutlineDrawings.isEmpty()) return;
+        for (PredictedAppIcon.PredictedIconOutlineDrawing outlineDrawing : mOutlineDrawings) {
+            mHotseat.addDelegatedCellDrawing(outlineDrawing);
+        }
+        mDragInProgress = true;
+        mHotseat.invalidate();
+    }
+
+    @Override
+    public void onDragEnd() {
+        mDragInProgress = false;
+        fillGapsWithPrediction(true, this::removeOutlineDrawings);
+    }
+
+    @Nullable
+    @Override
+    public SystemShortcut<QuickstepLauncher> getShortcut(QuickstepLauncher activity,
+            ItemInfo itemInfo) {
+        if (itemInfo.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
+            return null;
+        }
+        return new PinPrediction(activity, itemInfo);
+    }
+
+    private void preparePredictionInfo(WorkspaceItemInfo itemInfo, int rank) {
+        itemInfo.container = LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
+        itemInfo.rank = rank;
+        itemInfo.cellX = mHotseat.getCellXFromOrder(rank);
+        itemInfo.cellY = mHotseat.getCellYFromOrder(rank);
+        itemInfo.screenId = rank;
+    }
+
+    private void removeOutlineDrawings() {
+        if (mOutlineDrawings.isEmpty()) return;
+        for (PredictedAppIcon.PredictedIconOutlineDrawing outlineDrawing : mOutlineDrawings) {
+            mHotseat.removeDelegatedCellDrawing(outlineDrawing);
+        }
+        mHotseat.invalidate();
+        mOutlineDrawings.clear();
+    }
+
+    @Override
+    public void onIdpChanged(int changeFlags, InvariantDeviceProfile profile) {
+        this.mHotSeatItemsCount = profile.numHotseatIcons;
+    }
+
+    @Override
+    public void onDropCompleted(View target, DropTarget.DragObject d, boolean success) {
+        //Does nothing
+    }
+
+    /**
+     * Logs rank info based on current list of predicted items
+     */
+    public void logLaunchedAppRankingInfo(@NonNull ItemInfo itemInfo, InstanceId instanceId) {
+        if (Utilities.IS_DEBUG_DEVICE) {
+            final String pkg = itemInfo.getTargetComponent() != null
+                    ? itemInfo.getTargetComponent().getPackageName() : "unknown";
+            HotseatFileLog.INSTANCE.get(mLauncher).log("UserEvent",
+                    "appLaunch: packageName:" + pkg + ",isWorkApp:" + (itemInfo.user != null
+                            && !Process.myUserHandle().equals(itemInfo.user))
+                            + ",launchLocation:" + itemInfo.container);
+        }
+
+
+        ComponentName targetCN = itemInfo.getTargetComponent();
+        if (targetCN == null) {
+            return;
+        }
+        int rank = -1;
+        for (int i = mPredictedItems.size() - 1; i >= 0; i--) {
+            ItemInfo info = mPredictedItems.get(i);
+            if (targetCN.equals(info.getTargetComponent()) && itemInfo.user.equals(info.user)) {
+                rank = i;
+                break;
+            }
+        }
+        if (rank < 0) {
+            return;
+        }
+
+        int cardinality = 0;
+        for (PredictedAppIcon icon : getPredictedIcons()) {
+            ItemInfo info = (ItemInfo) icon.getTag();
+            cardinality |= 1 << info.screenId;
+        }
+
+        PredictedHotseatContainer.Builder containerBuilder = PredictedHotseatContainer.newBuilder();
+        containerBuilder.setCardinality(cardinality);
+        if (itemInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
+            containerBuilder.setIndex(rank);
+        }
+        mLauncher.getStatsLogManager().logger()
+                .withInstanceId(instanceId)
+                .withRank(rank)
+                .withContainerInfo(ContainerInfo.newBuilder()
+                        .setPredictedHotseatContainer(containerBuilder)
+                        .build())
+                .log(LAUNCHER_HOTSEAT_RANKED);
+    }
+
+    private class PinPrediction extends SystemShortcut<QuickstepLauncher> {
+
+        private PinPrediction(QuickstepLauncher target, ItemInfo itemInfo) {
+            super(R.drawable.ic_pin, R.string.pin_prediction, target,
+                    itemInfo);
+        }
+
+        @Override
+        public void onClick(View view) {
+            dismissTaskMenuView(mTarget);
+            pinPrediction(mItemInfo);
+        }
+    }
+
+    private static boolean isPredictedIcon(View view) {
+        return view instanceof PredictedAppIcon && view.getTag() instanceof WorkspaceItemInfo
+                && ((WorkspaceItemInfo) view.getTag()).container
+                == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java
similarity index 66%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java
rename to quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java
index 5a038d2..8f31c22 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java
@@ -15,7 +15,7 @@
  */
 package com.android.launcher3.hybridhotseat;
 
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
 
 import android.app.prediction.AppTarget;
 import android.app.prediction.AppTargetEvent;
@@ -24,13 +24,10 @@
 import android.content.Context;
 import android.os.Bundle;
 
-import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.Workspace;
-import com.android.launcher3.model.AllAppsList;
-import com.android.launcher3.model.BaseModelUpdateTask;
 import com.android.launcher3.model.BgDataModel;
-import com.android.launcher3.model.PredictionModel;
+import com.android.launcher3.model.BgDataModel.FixedContainerItems;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -38,55 +35,48 @@
 
 import java.util.ArrayList;
 import java.util.Locale;
-import java.util.function.Consumer;
 
 /**
  * Model helper for app predictions in workspace
  */
-public class HotseatPredictionModel extends PredictionModel {
+public class HotseatPredictionModel {
     private static final String APP_LOCATION_HOTSEAT = "hotseat";
     private static final String APP_LOCATION_WORKSPACE = "workspace";
 
     private static final String BUNDLE_KEY_PIN_EVENTS = "pin_events";
     private static final String BUNDLE_KEY_CURRENT_ITEMS = "current_items";
 
-
-    public HotseatPredictionModel(Context context) { }
-
     /**
-     * Creates and returns bundle using workspace items and cached items
+     * Creates and returns bundle using workspace items
      */
-    public void createBundle(Consumer<Bundle> cb) {
-        LauncherAppState appState = LauncherAppState.getInstance(mContext);
-        appState.getModel().enqueueModelUpdateTask(new BaseModelUpdateTask() {
-            @Override
-            public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
-                Bundle bundle = new Bundle();
-                ArrayList<AppTargetEvent> events = new ArrayList<>();
-                ArrayList<ItemInfo> workspaceItems = new ArrayList<>(dataModel.workspaceItems);
-                workspaceItems.addAll(dataModel.appWidgets);
-                for (ItemInfo item : workspaceItems) {
-                    AppTarget target = getAppTargetFromInfo(item);
-                    if (target != null && !isTrackedForPrediction(item)) continue;
-                    events.add(wrapAppTargetWithLocation(target, AppTargetEvent.ACTION_PIN, item));
-                }
-                ArrayList<AppTarget> currentTargets = new ArrayList<>();
-                for (ItemInfo itemInfo : dataModel.cachedPredictedItems) {
-                    AppTarget target = getAppTargetFromInfo(itemInfo);
-                    if (target != null) currentTargets.add(target);
-                }
-                bundle.putParcelableArrayList(BUNDLE_KEY_PIN_EVENTS, events);
-                bundle.putParcelableArrayList(BUNDLE_KEY_CURRENT_ITEMS, currentTargets);
-                MAIN_EXECUTOR.execute(() -> cb.accept(bundle));
+    public static Bundle convertDataModelToAppTargetBundle(Context context, BgDataModel dataModel) {
+        Bundle bundle = new Bundle();
+        ArrayList<AppTargetEvent> events = new ArrayList<>();
+        ArrayList<ItemInfo> workspaceItems = new ArrayList<>(dataModel.workspaceItems);
+        workspaceItems.addAll(dataModel.appWidgets);
+        for (ItemInfo item : workspaceItems) {
+            AppTarget target = getAppTargetFromInfo(context, item);
+            if (target != null && !isTrackedForPrediction(item)) continue;
+            events.add(wrapAppTargetWithLocation(target, AppTargetEvent.ACTION_PIN, item));
+        }
+        ArrayList<AppTarget> currentTargets = new ArrayList<>();
+        FixedContainerItems hotseatItems = dataModel.extraItems.get(CONTAINER_HOTSEAT_PREDICTION);
+        if (hotseatItems != null) {
+            for (ItemInfo itemInfo : hotseatItems.items) {
+                AppTarget target = getAppTargetFromInfo(context, itemInfo);
+                if (target != null) currentTargets.add(target);
             }
-        });
+        }
+        bundle.putParcelableArrayList(BUNDLE_KEY_PIN_EVENTS, events);
+        bundle.putParcelableArrayList(BUNDLE_KEY_CURRENT_ITEMS, currentTargets);
+        return bundle;
     }
 
     /**
      * Creates and returns for {@link AppTarget} object given an {@link ItemInfo}. Returns null
      * if item is not supported prediction
      */
-    public AppTarget getAppTargetFromInfo(ItemInfo info) {
+    public static AppTarget getAppTargetFromInfo(Context context, ItemInfo info) {
         if (info == null) return null;
         if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
                 && info instanceof LauncherAppWidgetInfo
@@ -107,17 +97,17 @@
                     shortcutKey.componentName.getPackageName(), shortcutKey.user).build();
         } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
             return new AppTarget.Builder(new AppTargetId("folder:" + info.id),
-                    mContext.getPackageName(), info.user).build();
+                    context.getPackageName(), info.user).build();
         }
         return null;
     }
 
-
     /**
      * Creates and returns {@link AppTargetEvent} from an {@link AppTarget}, action, and item
      * location using {@link ItemInfo}
      */
-    public AppTargetEvent wrapAppTargetWithLocation(AppTarget target, int action, ItemInfo info) {
+    public static AppTargetEvent wrapAppTargetWithLocation(
+            AppTarget target, int action, ItemInfo info) {
         String location = String.format(Locale.ENGLISH, "%s/%d/[%d,%d]/[%d,%d]",
                 info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
                         ? APP_LOCATION_HOTSEAT : APP_LOCATION_WORKSPACE,
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java
similarity index 79%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java
rename to quickstep/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java
index 8c1db4e..90f762e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java
@@ -19,8 +19,10 @@
 import static com.android.launcher3.provider.LauncherDbUtils.tableExists;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
+import android.content.Context;
+
 import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.model.GridBackupTable;
 import com.android.launcher3.provider.LauncherDbUtils;
@@ -29,30 +31,24 @@
  * A helper class to manage migration revert restoration for hybrid hotseat
  */
 public class HotseatRestoreHelper {
-    private final Launcher mLauncher;
-    private boolean mBackupRestored = false;
-
-    HotseatRestoreHelper(Launcher context) {
-        mLauncher = context;
-    }
 
     /**
      * Creates a snapshot backup of Favorite table for future restoration use.
      */
-    public void createBackup() {
+    public static void createBackup(Context context) {
         MODEL_EXECUTOR.execute(() -> {
             try (LauncherDbUtils.SQLiteTransaction transaction = (LauncherDbUtils.SQLiteTransaction)
                     LauncherSettings.Settings.call(
-                            mLauncher.getContentResolver(),
+                            context.getContentResolver(),
                             LauncherSettings.Settings.METHOD_NEW_TRANSACTION)
                             .getBinder(LauncherSettings.Settings.EXTRA_VALUE)) {
-                InvariantDeviceProfile idp = mLauncher.getDeviceProfile().inv;
-                GridBackupTable backupTable = new GridBackupTable(mLauncher,
+                InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
+                GridBackupTable backupTable = new GridBackupTable(context,
                         transaction.getDb(), idp.numHotseatIcons, idp.numColumns,
                         idp.numRows);
                 backupTable.createCustomBackupTable(HYBRID_HOTSEAT_BACKUP_TABLE);
                 transaction.commit();
-                LauncherSettings.Settings.call(mLauncher.getContentResolver(),
+                LauncherSettings.Settings.call(context.getContentResolver(),
                         LauncherSettings.Settings.METHOD_REFRESH_HOTSEAT_RESTORE_TABLE);
             }
         });
@@ -61,25 +57,23 @@
     /**
      * Finds and restores a previously saved snapshow of Favorites table
      */
-    public void restoreBackup() {
-        if (mBackupRestored) return;
+    public static void restoreBackup(Context context) {
         MODEL_EXECUTOR.execute(() -> {
             try (LauncherDbUtils.SQLiteTransaction transaction = (LauncherDbUtils.SQLiteTransaction)
                     LauncherSettings.Settings.call(
-                            mLauncher.getContentResolver(),
+                            context.getContentResolver(),
                             LauncherSettings.Settings.METHOD_NEW_TRANSACTION)
                             .getBinder(LauncherSettings.Settings.EXTRA_VALUE)) {
                 if (!tableExists(transaction.getDb(), HYBRID_HOTSEAT_BACKUP_TABLE)) {
                     return;
                 }
-                InvariantDeviceProfile idp = mLauncher.getDeviceProfile().inv;
-                GridBackupTable backupTable = new GridBackupTable(mLauncher,
+                InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
+                GridBackupTable backupTable = new GridBackupTable(context,
                         transaction.getDb(), idp.numHotseatIcons, idp.numColumns,
                         idp.numRows);
                 backupTable.restoreFromCustomBackupTable(HYBRID_HOTSEAT_BACKUP_TABLE, true);
                 transaction.commit();
-                mBackupRestored = true;
-                mLauncher.getModel().forceReload();
+                LauncherAppState.getInstance(context).getModel().forceReload();
             }
         });
     }
diff --git a/quickstep/src/com/android/launcher3/model/AppEventProducer.java b/quickstep/src/com/android/launcher3/model/AppEventProducer.java
new file mode 100644
index 0000000..364a321
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/model/AppEventProducer.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2019 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 static android.app.prediction.AppTargetEvent.ACTION_DISMISS;
+import static android.app.prediction.AppTargetEvent.ACTION_LAUNCH;
+import static android.app.prediction.AppTargetEvent.ACTION_PIN;
+import static android.app.prediction.AppTargetEvent.ACTION_UNPIN;
+
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_CONVERTED_TO_ICON;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_PREDICTION_PINNED;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DRAG_STARTED;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROPPED_ON_DONT_SUGGEST;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROPPED_ON_REMOVE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROP_FOLDER_CREATED;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_LEFT;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_RIGHT;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_SWIPE_DOWN;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
+import android.annotation.TargetApi;
+import android.app.prediction.AppTarget;
+import android.app.prediction.AppTargetEvent;
+import android.app.prediction.AppTargetId;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Process;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.text.TextUtils;
+
+import androidx.annotation.AnyThread;
+import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
+
+import com.android.launcher3.logger.LauncherAtom;
+import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
+import com.android.launcher3.logger.LauncherAtom.FolderContainer;
+import com.android.launcher3.logger.LauncherAtom.HotseatContainer;
+import com.android.launcher3.logger.LauncherAtom.WorkspaceContainer;
+import com.android.launcher3.logging.StatsLogManager.EventEnum;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.pm.UserCache;
+import com.android.quickstep.logging.StatsLogCompatManager.StatsLogConsumer;
+
+import java.util.Locale;
+import java.util.function.ObjIntConsumer;
+import java.util.function.Predicate;
+
+/**
+ * Utility class to track stats log and emit corresponding app events
+ */
+@TargetApi(Build.VERSION_CODES.R)
+public class AppEventProducer implements StatsLogConsumer {
+
+    private static final int MSG_LAUNCH = 0;
+
+    private final Context mContext;
+    private final Handler mMessageHandler;
+    private final ObjIntConsumer<AppTargetEvent> mCallback;
+
+    private LauncherAtom.ItemInfo mLastDragItem;
+
+    public AppEventProducer(Context context, ObjIntConsumer<AppTargetEvent> callback) {
+        mContext = context;
+        mMessageHandler = new Handler(MODEL_EXECUTOR.getLooper(), this::handleMessage);
+        mCallback = callback;
+    }
+
+    @WorkerThread
+    private boolean handleMessage(Message msg) {
+        switch (msg.what) {
+            case MSG_LAUNCH: {
+                mCallback.accept((AppTargetEvent) msg.obj, msg.arg1);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @AnyThread
+    private void sendEvent(LauncherAtom.ItemInfo atomInfo, int eventId, int targetPredictor) {
+        sendEvent(toAppTarget(atomInfo), atomInfo, eventId, targetPredictor);
+    }
+
+    @AnyThread
+    private void sendEvent(AppTarget target, LauncherAtom.ItemInfo locationInfo, int eventId,
+            int targetPredictor) {
+        if (target != null) {
+            AppTargetEvent event = new AppTargetEvent.Builder(target, eventId)
+                    .setLaunchLocation(getContainer(locationInfo))
+                    .build();
+            mMessageHandler.obtainMessage(MSG_LAUNCH, targetPredictor, 0, event).sendToTarget();
+        }
+    }
+
+    @Override
+    public void consume(EventEnum event, LauncherAtom.ItemInfo atomInfo) {
+        if (event == LAUNCHER_APP_LAUNCH_TAP
+                || event == LAUNCHER_TASK_LAUNCH_SWIPE_DOWN
+                || event == LAUNCHER_TASK_LAUNCH_TAP
+                || event == LAUNCHER_QUICKSWITCH_RIGHT
+                || event == LAUNCHER_QUICKSWITCH_LEFT) {
+            sendEvent(atomInfo, ACTION_LAUNCH, CONTAINER_PREDICTION);
+        } else if (event == LAUNCHER_ITEM_DROPPED_ON_DONT_SUGGEST) {
+            sendEvent(atomInfo, ACTION_DISMISS, CONTAINER_PREDICTION);
+        } else if (event == LAUNCHER_ITEM_DRAG_STARTED) {
+            mLastDragItem = atomInfo;
+        } else if (event == LAUNCHER_ITEM_DROP_COMPLETED) {
+            if (mLastDragItem == null) {
+                return;
+            }
+            if (isTrackedForHotseatPrediction(atomInfo)) {
+                sendEvent(atomInfo, ACTION_PIN, CONTAINER_HOTSEAT_PREDICTION);
+            }
+            if (isTrackedForHotseatPrediction(mLastDragItem)) {
+                sendEvent(mLastDragItem, ACTION_UNPIN, CONTAINER_HOTSEAT_PREDICTION);
+            }
+            mLastDragItem = null;
+        } else if (event == LAUNCHER_ITEM_DROP_FOLDER_CREATED) {
+            if (isTrackedForHotseatPrediction(atomInfo)) {
+                sendEvent(createTempFolderTarget(), atomInfo, ACTION_PIN,
+                        CONTAINER_HOTSEAT_PREDICTION);
+                sendEvent(atomInfo, ACTION_UNPIN, CONTAINER_HOTSEAT_PREDICTION);
+            }
+        } else if (event == LAUNCHER_FOLDER_CONVERTED_TO_ICON) {
+            if (isTrackedForHotseatPrediction(atomInfo)) {
+                sendEvent(createTempFolderTarget(), atomInfo, ACTION_UNPIN,
+                        CONTAINER_HOTSEAT_PREDICTION);
+                sendEvent(atomInfo, ACTION_PIN, CONTAINER_HOTSEAT_PREDICTION);
+            }
+        } else if (event == LAUNCHER_ITEM_DROPPED_ON_REMOVE) {
+            if (mLastDragItem != null && isTrackedForHotseatPrediction(mLastDragItem)) {
+                sendEvent(mLastDragItem, ACTION_UNPIN, CONTAINER_HOTSEAT_PREDICTION);
+            }
+        } else if (event == LAUNCHER_HOTSEAT_PREDICTION_PINNED) {
+            if (isTrackedForHotseatPrediction(atomInfo)) {
+                sendEvent(atomInfo, ACTION_PIN, CONTAINER_HOTSEAT_PREDICTION);
+            }
+        }
+    }
+
+    @Nullable
+    private AppTarget toAppTarget(LauncherAtom.ItemInfo info) {
+        UserHandle userHandle = Process.myUserHandle();
+        if (info.getIsWork()) {
+            userHandle = UserCache.INSTANCE.get(mContext).getUserProfiles().stream()
+                    .filter(((Predicate<UserHandle>) userHandle::equals).negate())
+                    .findAny()
+                    .orElse(null);
+        }
+        if (userHandle == null) {
+            return null;
+        }
+        ComponentName cn = null;
+        String id = null;
+
+        switch (info.getItemCase()) {
+            case APPLICATION: {
+                LauncherAtom.Application app = info.getApplication();
+                if ((cn = parseNullable(app.getComponentName())) != null) {
+                    id = "app:" + cn.getPackageName();
+                }
+                break;
+            }
+            case SHORTCUT: {
+                LauncherAtom.Shortcut si = info.getShortcut();
+                if (!TextUtils.isEmpty(si.getShortcutId())
+                        && (cn = parseNullable(si.getShortcutName())) != null) {
+                    id = "shortcut:" + si.getShortcutId();
+                }
+                break;
+            }
+            case WIDGET: {
+                LauncherAtom.Widget widget = info.getWidget();
+                if ((cn = parseNullable(widget.getComponentName())) != null) {
+                    id = "widget:" + cn.getPackageName();
+                }
+                break;
+            }
+            case TASK: {
+                LauncherAtom.Task task = info.getTask();
+                if ((cn = parseNullable(task.getComponentName())) != null) {
+                    id = "app:" + cn.getPackageName();
+                }
+                break;
+            }
+            case FOLDER_ICON:
+                return createTempFolderTarget();
+        }
+        if (id != null && cn != null) {
+            return new AppTarget.Builder(new AppTargetId(id), cn.getPackageName(), userHandle)
+                    .setClassName(cn.getClassName())
+                    .build();
+        }
+        return null;
+    }
+
+    private AppTarget createTempFolderTarget() {
+        return new AppTarget.Builder(new AppTargetId("folder:" + SystemClock.uptimeMillis()),
+                mContext.getPackageName(), Process.myUserHandle())
+                .build();
+    }
+
+    private String getContainer(LauncherAtom.ItemInfo info) {
+        ContainerInfo ci = info.getContainerInfo();
+        switch (ci.getContainerCase()) {
+            case WORKSPACE: {
+                // In case the item type is not widgets, the spaceX and spanY default to 1.
+                int spanX = info.getWidget().getSpanX();
+                int spanY = info.getWidget().getSpanY();
+                return getWorkspaceContainerString(ci.getWorkspace(), spanX, spanY);
+            }
+            case HOTSEAT: {
+                return getHotseatContainerString(ci.getHotseat());
+            }
+            case TASK_SWITCHER_CONTAINER: {
+                return "task-switcher";
+            }
+            case ALL_APPS_CONTAINER: {
+                return "all-apps";
+            }
+            case SEARCH_RESULT_CONTAINER: {
+                return "search-results";
+            }
+            case PREDICTED_HOTSEAT_CONTAINER: {
+                return "predictions/hotseat";
+            }
+            case PREDICTION_CONTAINER: {
+                return "predictions";
+            }
+            case FOLDER: {
+                FolderContainer fc = ci.getFolder();
+                switch (fc.getParentContainerCase()) {
+                    case WORKSPACE:
+                        return "folder/" + getWorkspaceContainerString(fc.getWorkspace(), 1, 1);
+                    case HOTSEAT:
+                        return "folder/" + getHotseatContainerString(fc.getHotseat());
+                }
+                return "folder";
+            }
+        }
+        return "";
+    }
+
+    private static String getWorkspaceContainerString(WorkspaceContainer wc, int spanX, int spanY) {
+        return String.format(Locale.ENGLISH, "workspace/%d/[%d,%d]/[%d,%d]",
+                wc.getPageIndex(), wc.getGridX(), wc.getGridY(), spanX, spanY);
+    }
+
+    private static String getHotseatContainerString(HotseatContainer hc) {
+        return String.format(Locale.ENGLISH, "hotseat/%1$d/[%1$d,0]/[1,1]", hc.getIndex());
+    }
+
+    private static ComponentName parseNullable(String componentNameString) {
+        return TextUtils.isEmpty(componentNameString)
+                ? null : ComponentName.unflattenFromString(componentNameString);
+    }
+
+    /**
+     * Helper method to determine if {@link ItemInfo} should be tracked and reported to predictors
+     */
+    private static boolean isTrackedForHotseatPrediction(LauncherAtom.ItemInfo info) {
+        ContainerInfo ci = info.getContainerInfo();
+        switch (ci.getContainerCase()) {
+            case HOTSEAT:
+                return true;
+            case WORKSPACE:
+                return ci.getWorkspace().getPageIndex() == 0;
+            default:
+                return false;
+        }
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java b/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java
new file mode 100644
index 0000000..b0fba3d
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2020 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 static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+import static com.android.launcher3.model.QuickstepModelDelegate.LAST_PREDICTION_ENABLED_STATE;
+import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER;
+
+import android.app.prediction.AppTarget;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
+import android.os.UserHandle;
+
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.model.BgDataModel.FixedContainerItems;
+import com.android.launcher3.model.QuickstepModelDelegate.PredictorState;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Task to update model as a result of predicted apps update
+ */
+public class PredictionUpdateTask extends BaseModelUpdateTask {
+
+    private final List<AppTarget> mTargets;
+    private final PredictorState mPredictorState;
+
+    PredictionUpdateTask(PredictorState predictorState, List<AppTarget> targets) {
+        mPredictorState = predictorState;
+        mTargets = targets;
+    }
+
+    @Override
+    public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+        Context context = app.getContext();
+
+        // TODO: remove this
+        Utilities.getDevicePrefs(context).edit()
+                .putBoolean(LAST_PREDICTION_ENABLED_STATE, !mTargets.isEmpty()).apply();
+
+        FixedContainerItems fci = mPredictorState.items;
+        Set<UserHandle> usersForChangedShortcuts = new HashSet<>(fci.items.stream()
+                .filter(info -> info.itemType == ITEM_TYPE_DEEP_SHORTCUT)
+                .map(info -> info.user)
+                .collect(Collectors.toSet()));
+        fci.items.clear();
+
+        for (AppTarget target : mTargets) {
+            WorkspaceItemInfo itemInfo;
+            ShortcutInfo si = target.getShortcutInfo();
+            if (si != null) {
+                usersForChangedShortcuts.add(si.getUserHandle());
+                itemInfo = new WorkspaceItemInfo(si, context);
+                app.getIconCache().getShortcutIcon(itemInfo, si);
+            } else {
+                String className = target.getClassName();
+                if (COMPONENT_CLASS_MARKER.equals(className)) {
+                    // TODO: Implement this
+                    continue;
+                }
+                ComponentName cn = new ComponentName(target.getPackageName(), className);
+                UserHandle user = target.getUser();
+                itemInfo = apps.data.stream()
+                        .filter(info -> user.equals(info.user) && cn.equals(info.componentName))
+                        .map(ai -> {
+                            app.getIconCache().getTitleAndIcon(ai, false);
+                            return ai.makeWorkspaceItem();
+                        })
+                        .findAny()
+                        .orElseGet(() -> {
+                            LauncherActivityInfo lai = context.getSystemService(LauncherApps.class)
+                                    .resolveActivity(AppInfo.makeLaunchIntent(cn), user);
+                            if (lai == null) {
+                                return null;
+                            }
+                            AppInfo ai = new AppInfo(context, lai, user);
+                            app.getIconCache().getTitleAndIcon(ai, lai, false);
+                            return ai.makeWorkspaceItem();
+                        });
+
+                if (itemInfo == null) {
+                    continue;
+                }
+            }
+
+            itemInfo.container = fci.containerId;
+            fci.items.add(itemInfo);
+        }
+
+        bindExtraContainerItems(fci);
+        usersForChangedShortcuts.forEach(
+                u -> dataModel.updateShortcutPinnedState(app.getContext(), u));
+
+        // Save to disk
+        mPredictorState.storage.write(context, fci.items);
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
new file mode 100644
index 0000000..be57dec
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2020 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 static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_GRID;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+import static com.android.launcher3.hybridhotseat.HotseatPredictionModel.convertDataModelToAppTargetBundle;
+
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionManager;
+import android.app.prediction.AppPredictor;
+import android.app.prediction.AppTarget;
+import android.app.prediction.AppTargetEvent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
+import android.os.UserHandle;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.model.BgDataModel.FixedContainerItems;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.util.Executors;
+import com.android.launcher3.util.PersistedItemArray;
+import com.android.quickstep.logging.StatsLogCompatManager;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.IntStream;
+
+/**
+ * Model delegate which loads prediction items
+ */
+public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChangeListener {
+
+    public static final String LAST_PREDICTION_ENABLED_STATE = "last_prediction_enabled_state";
+
+    private final PredictorState mAllAppsState =
+            new PredictorState(CONTAINER_PREDICTION, "all_apps_predictions");
+    private final PredictorState mHotseatState =
+            new PredictorState(CONTAINER_HOTSEAT_PREDICTION, "hotseat_predictions");
+
+    private final InvariantDeviceProfile mIDP;
+    private final AppEventProducer mAppEventProducer;
+
+    private boolean mActive = false;
+
+    public QuickstepModelDelegate(Context context) {
+        mAppEventProducer = new AppEventProducer(context, this::onAppTargetEvent);
+
+        mIDP = InvariantDeviceProfile.INSTANCE.get(context);
+        mIDP.addOnChangeListener(this);
+        StatsLogCompatManager.LOGS_CONSUMER.add(mAppEventProducer);
+    }
+
+    @Override
+    public void loadItems(UserManagerState ums, Map<ShortcutKey, ShortcutInfo> pinnedShortcuts) {
+        // TODO: Implement caching and preloading
+        super.loadItems(ums, pinnedShortcuts);
+
+        WorkspaceItemFactory allAppsFactory =
+                new WorkspaceItemFactory(mApp, ums, pinnedShortcuts, mIDP.numAllAppsColumns);
+        mAllAppsState.items.setItems(
+                mAllAppsState.storage.read(mApp.getContext(), allAppsFactory, ums.allUsers::get));
+        mDataModel.extraItems.put(CONTAINER_PREDICTION, mAllAppsState.items);
+
+        WorkspaceItemFactory hotseatFactory =
+                new WorkspaceItemFactory(mApp, ums, pinnedShortcuts, mIDP.numHotseatIcons);
+        mHotseatState.items.setItems(
+                mHotseatState.storage.read(mApp.getContext(), hotseatFactory, ums.allUsers::get));
+        mDataModel.extraItems.put(CONTAINER_HOTSEAT_PREDICTION, mHotseatState.items);
+        mActive = true;
+    }
+
+    @Override
+    public void workspaceLoadComplete() {
+        super.workspaceLoadComplete();
+        recreatePredictors();
+    }
+
+    @Override
+    public void validateData() {
+        super.validateData();
+        if (mAllAppsState.predictor != null) {
+            mAllAppsState.predictor.requestPredictionUpdate();
+        }
+    }
+
+    @Override
+    public void destroy() {
+        super.destroy();
+        mActive = false;
+        StatsLogCompatManager.LOGS_CONSUMER.remove(mAppEventProducer);
+
+        destroyPredictors();
+        mIDP.removeOnChangeListener(this);
+    }
+
+    private void destroyPredictors() {
+        mAllAppsState.destroyPredictor();
+        mHotseatState.destroyPredictor();
+    }
+
+    @WorkerThread
+    private void recreatePredictors() {
+        destroyPredictors();
+        if (!mActive) {
+            return;
+        }
+        Context context = mApp.getContext();
+        AppPredictionManager apm = context.getSystemService(AppPredictionManager.class);
+        if (apm == null) {
+            return;
+        }
+
+        registerPredictor(mAllAppsState, apm.createAppPredictionSession(
+                new AppPredictionContext.Builder(context)
+                        .setUiSurface("home")
+                        .setPredictedTargetCount(mIDP.numAllAppsColumns)
+                        .build()));
+
+        // TODO: get bundle
+        registerPredictor(mHotseatState, apm.createAppPredictionSession(
+                new AppPredictionContext.Builder(context)
+                        .setUiSurface("hotseat")
+                        .setPredictedTargetCount(mIDP.numHotseatIcons)
+                        .setExtras(convertDataModelToAppTargetBundle(context, mDataModel))
+                        .build()));
+
+    }
+
+    private void registerPredictor(PredictorState state, AppPredictor predictor) {
+        state.predictor = predictor;
+        state.predictor.registerPredictionUpdates(
+                Executors.MODEL_EXECUTOR, t -> handleUpdate(state, t));
+        state.predictor.requestPredictionUpdate();
+    }
+
+    private void handleUpdate(PredictorState state, List<AppTarget> targets) {
+        if (state.setTargets(targets)) {
+            // No diff, skip
+            return;
+        }
+        mApp.getModel().enqueueModelUpdateTask(new PredictionUpdateTask(state, targets));
+    }
+
+    @Override
+    public void onIdpChanged(int changeFlags, InvariantDeviceProfile profile) {
+        if ((changeFlags & CHANGE_FLAG_GRID) != 0) {
+            // Reinitialize everything
+            Executors.MODEL_EXECUTOR.execute(this::recreatePredictors);
+        }
+    }
+
+    private void onAppTargetEvent(AppTargetEvent event, int client) {
+        PredictorState state = client == CONTAINER_PREDICTION ? mAllAppsState : mHotseatState;
+        if (state.predictor != null) {
+            state.predictor.notifyAppTargetEvent(event);
+        }
+    }
+
+    static class PredictorState {
+
+        public final FixedContainerItems items;
+        public final PersistedItemArray storage;
+        public AppPredictor predictor;
+
+        private List<AppTarget> mLastTargets;
+
+        PredictorState(int container, String storageName) {
+            items = new FixedContainerItems(container);
+            storage = new PersistedItemArray(storageName);
+            mLastTargets = Collections.emptyList();
+        }
+
+        public void destroyPredictor() {
+            if (predictor != null) {
+                predictor.destroy();
+                predictor = null;
+            }
+        }
+
+        /**
+         * Sets the new targets and returns true if it was different than before.
+         */
+        boolean setTargets(List<AppTarget> newTargets) {
+            List<AppTarget> oldTargets = mLastTargets;
+            mLastTargets = newTargets;
+
+            int size = oldTargets.size();
+            return size == newTargets.size() && IntStream.range(0, size)
+                    .allMatch(i -> areAppTargetsSame(oldTargets.get(i), newTargets.get(i)));
+        }
+    }
+
+    /**
+     * Compares two targets for the properties which we care about
+     */
+    private static boolean areAppTargetsSame(AppTarget t1, AppTarget t2) {
+        if (!Objects.equals(t1.getPackageName(), t2.getPackageName())
+                || !Objects.equals(t1.getUser(), t2.getUser())
+                || !Objects.equals(t1.getClassName(), t2.getClassName())) {
+            return false;
+        }
+
+        ShortcutInfo s1 = t1.getShortcutInfo();
+        ShortcutInfo s2 = t2.getShortcutInfo();
+        if (s1 != null) {
+            if (s2 == null || !Objects.equals(s1.getId(), s2.getId())) {
+                return false;
+            }
+        } else if (s2 != null) {
+            return false;
+        }
+        return true;
+    }
+
+    private static class WorkspaceItemFactory implements PersistedItemArray.ItemFactory {
+
+        private final LauncherAppState mAppState;
+        private final UserManagerState mUMS;
+        private final Map<ShortcutKey, ShortcutInfo> mPinnedShortcuts;
+        private final int mMaxCount;
+
+        private int mReadCount = 0;
+
+        protected WorkspaceItemFactory(LauncherAppState appState, UserManagerState ums,
+                Map<ShortcutKey, ShortcutInfo> pinnedShortcuts, int maxCount) {
+            mAppState = appState;
+            mUMS = ums;
+            mPinnedShortcuts = pinnedShortcuts;
+            mMaxCount = maxCount;
+        }
+
+        @Nullable
+        @Override
+        public ItemInfo createInfo(int itemType, UserHandle user, Intent intent) {
+            if (mReadCount >= mMaxCount) {
+                return null;
+            }
+            switch (itemType) {
+                case ITEM_TYPE_APPLICATION: {
+                    LauncherActivityInfo lai = mAppState.getContext()
+                            .getSystemService(LauncherApps.class)
+                            .resolveActivity(intent, user);
+                    if (lai == null) {
+                        return null;
+                    }
+                    AppInfo info = new AppInfo(lai, user, mUMS.isUserQuiet(user));
+                    mAppState.getIconCache().getTitleAndIcon(info, lai, false);
+                    mReadCount++;
+                    return info.makeWorkspaceItem();
+                }
+                case ITEM_TYPE_DEEP_SHORTCUT: {
+                    ShortcutKey key = ShortcutKey.fromIntent(intent, user);
+                    if (key == null) {
+                        return null;
+                    }
+                    ShortcutInfo si = mPinnedShortcuts.get(key);
+                    if (si == null) {
+                        return null;
+                    }
+                    WorkspaceItemInfo wii = new WorkspaceItemInfo(si, mAppState.getContext());
+                    mAppState.getIconCache().getShortcutIcon(wii, si);
+                    mReadCount++;
+                    return wii;
+                }
+            }
+            return null;
+        }
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/model/WellbeingModel.java b/quickstep/src/com/android/launcher3/model/WellbeingModel.java
index f42b124..810f4e3 100644
--- a/quickstep/src/com/android/launcher3/model/WellbeingModel.java
+++ b/quickstep/src/com/android/launcher3/model/WellbeingModel.java
@@ -43,9 +43,15 @@
 import android.util.Log;
 
 import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.WorkerThread;
 
 import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherProvider;
+import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.popup.RemoteActionShortcut;
 import com.android.launcher3.popup.SystemShortcut;
@@ -57,6 +63,7 @@
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.function.Consumer;
 
 /**
  * Data model for digital wellbeing status of apps.
@@ -71,13 +78,21 @@
     private static final int MSG_PACKAGE_REMOVED = 2;
     private static final int MSG_FULL_REFRESH = 3;
 
+    private static final int UNKNOWN_MINIMAL_DEVICE_STATE = 0;
+    private static final int IN_MINIMAL_DEVICE = 2;
+
     // Welbeing contract
+    private static final String PATH_ACTIONS = "actions";
+    private static final String PATH_MINIMAL_DEVICE = "minimal_device";
+    private static final String METHOD_GET_MINIMAL_DEVICE_CONFIG = "get_minimal_device_config";
     private static final String METHOD_GET_ACTIONS = "get_actions";
     private static final String EXTRA_ACTIONS = "actions";
     private static final String EXTRA_ACTION = "action";
     private static final String EXTRA_MAX_NUM_ACTIONS_SHOWN = "max_num_actions_shown";
     private static final String EXTRA_PACKAGES = "packages";
     private static final String EXTRA_SUCCESS = "success";
+    private static final String EXTRA_MINIMAL_DEVICE_STATE = "minimal_device_state";
+    private static final String DB_NAME_MINIMAL_DEVICE = "minimal.db";
 
     public static final MainThreadInitializedObject<WellbeingModel> INSTANCE =
             new MainThreadInitializedObject<>(WellbeingModel::new);
@@ -104,15 +119,23 @@
         mContentObserver = new ContentObserver(MAIN_EXECUTOR.getHandler()) {
             @Override
             public void onChange(boolean selfChange, Uri uri) {
-                // Wellbeing reports that app actions have changed.
                 if (DEBUG || mIsInTest) {
-                    Log.d(TAG, "ContentObserver.onChange() called with: selfChange = [" + selfChange
-                            + "], uri = [" + uri + "]");
+                    Log.d(TAG, "ContentObserver.onChange() called with: selfChange = ["
+                            + selfChange + "], uri = [" + uri + "]");
                 }
                 Preconditions.assertUIThread();
-                updateWellbeingData();
+
+                if (uri.getPath().contains(PATH_ACTIONS)) {
+                    // Wellbeing reports that app actions have changed.
+                    updateWellbeingData();
+                } else if (uri.getPath().contains(PATH_MINIMAL_DEVICE)) {
+                    // Wellbeing reports that minimal device state or config is changed.
+                    updateLauncherModel(context);
+                }
             }
         };
+        FeatureFlags.ENABLE_MINIMAL_DEVICE.addChangeListener(mContext, () ->
+                updateLauncherModel(context));
 
         if (!TextUtils.isEmpty(mWellbeingProviderPkg)) {
             context.registerReceiver(
@@ -146,10 +169,13 @@
     private void restartObserver() {
         final ContentResolver resolver = mContext.getContentResolver();
         resolver.unregisterContentObserver(mContentObserver);
-        Uri actionsUri = apiBuilder().path("actions").build();
+        Uri actionsUri = apiBuilder().path(PATH_ACTIONS).build();
+        Uri minimalDeviceUri = apiBuilder().path(PATH_MINIMAL_DEVICE).build();
         try {
             resolver.registerContentObserver(
                     actionsUri, true /* notifyForDescendants */, mContentObserver);
+            resolver.registerContentObserver(
+                    minimalDeviceUri, true /* notifyForDescendants */, mContentObserver);
         } catch (Exception e) {
             Log.e(TAG, "Failed to register content observer for " + actionsUri + ": " + e);
             if (mIsInTest) throw new RuntimeException(e);
@@ -191,12 +217,69 @@
         mWorkerHandler.sendEmptyMessage(MSG_FULL_REFRESH);
     }
 
+    private void updateLauncherModel(@NonNull final Context context) {
+        if (!FeatureFlags.ENABLE_MINIMAL_DEVICE.get()) {
+            reloadLauncherInNormalMode(context);
+            return;
+        }
+        runWithMinimalDeviceConfigs((bundle) -> {
+            if (bundle.getInt(EXTRA_MINIMAL_DEVICE_STATE, UNKNOWN_MINIMAL_DEVICE_STATE)
+                    == IN_MINIMAL_DEVICE) {
+                reloadLauncherInMinimalMode(context);
+            } else {
+                reloadLauncherInNormalMode(context);
+            }
+        });
+    }
+
+    private void reloadLauncherInNormalMode(@NonNull final Context context) {
+        LauncherSettings.Settings.call(context.getContentResolver(),
+                LauncherSettings.Settings.METHOD_SWITCH_DATABASE,
+                InvariantDeviceProfile.INSTANCE.get(context).dbFile);
+    }
+
+    private void reloadLauncherInMinimalMode(@NonNull final Context context) {
+        final Bundle extras = new Bundle();
+        extras.putString(LauncherProvider.KEY_LAYOUT_PROVIDER_AUTHORITY,
+                mWellbeingProviderPkg + ".api");
+        LauncherSettings.Settings.call(context.getContentResolver(),
+                LauncherSettings.Settings.METHOD_SWITCH_DATABASE,
+                DB_NAME_MINIMAL_DEVICE, extras);
+    }
+
     private Uri.Builder apiBuilder() {
         return new Uri.Builder()
                 .scheme(SCHEME_CONTENT)
                 .authority(mWellbeingProviderPkg + ".api");
     }
 
+    /**
+     * Fetch most up-to-date minimal device config.
+     */
+    @WorkerThread
+    private void runWithMinimalDeviceConfigs(Consumer<Bundle> consumer) {
+        if (!FeatureFlags.ENABLE_MINIMAL_DEVICE.get()) {
+            return;
+        }
+        if (DEBUG || mIsInTest) {
+            Log.d(TAG, "runWithMinimalDeviceConfigs() called");
+        }
+        Preconditions.assertNonUiThread();
+
+        final Uri contentUri = apiBuilder().build();
+        final Bundle remoteBundle;
+        try (ContentProviderClient client = mContext.getContentResolver()
+                .acquireUnstableContentProviderClient(contentUri)) {
+            remoteBundle = client.call(
+                    METHOD_GET_MINIMAL_DEVICE_CONFIG, null /* args */, null /* extras */);
+            consumer.accept(remoteBundle);
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to retrieve data from " + contentUri + ": " + e);
+            if (mIsInTest) throw new RuntimeException(e);
+        }
+        if (DEBUG || mIsInTest) Log.i(TAG, "runWithMinimalDeviceConfigs(): finished");
+    }
+
     private boolean updateActions(String... packageNames) {
         if (packageNames.length == 0) {
             return true;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index ec3a490..aad7e17 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -16,19 +16,22 @@
 
 package com.android.launcher3.uioverrides;
 
-import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.graphics.OverviewScrim.SCRIM_MULTIPLIER;
 import static com.android.launcher3.graphics.Scrim.SCRIM_PROGRESS;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_MODAL;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCRIM_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
 import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_PEEK;
 import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_SCALE;
 import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
 import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
+import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
+import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
 
 import android.util.FloatProperty;
 
@@ -61,12 +64,14 @@
     @Override
     public void setState(@NonNull LauncherState state) {
         float[] scaleAndOffset = state.getOverviewScaleAndOffset(mLauncher);
-        SCALE_PROPERTY.set(mRecentsView, scaleAndOffset[0]);
+        RECENTS_SCALE_PROPERTY.set(mRecentsView, scaleAndOffset[0]);
         ADJACENT_PAGE_OFFSET.set(mRecentsView, scaleAndOffset[1]);
+        TASK_SECONDARY_TRANSLATION.set(mRecentsView, 0f);
 
         getContentAlphaProperty().set(mRecentsView, state.overviewUi ? 1f : 0);
         OverviewScrim scrim = mLauncher.getDragLayer().getOverviewScrim();
         SCRIM_PROGRESS.set(scrim, state.getOverviewScrimAlpha(mLauncher));
+        SCRIM_MULTIPLIER.set(scrim, 1f);
         getTaskModalnessProperty().set(mRecentsView, state.getOverviewModalness());
     }
 
@@ -93,16 +98,20 @@
     void setStateWithAnimationInternal(@NonNull final LauncherState toState,
             @NonNull StateAnimationConfig config, @NonNull PendingAnimation setter) {
         float[] scaleAndOffset = toState.getOverviewScaleAndOffset(mLauncher);
-        setter.setFloat(mRecentsView, SCALE_PROPERTY, scaleAndOffset[0],
+        setter.setFloat(mRecentsView, RECENTS_SCALE_PROPERTY, scaleAndOffset[0],
                 config.getInterpolator(ANIM_OVERVIEW_SCALE, LINEAR));
         setter.setFloat(mRecentsView, ADJACENT_PAGE_OFFSET, scaleAndOffset[1],
                 config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_X, LINEAR));
+        setter.setFloat(mRecentsView, TASK_SECONDARY_TRANSLATION, 0f,
+                config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, LINEAR));
 
         setter.setFloat(mRecentsView, getContentAlphaProperty(), toState.overviewUi ? 1 : 0,
                 config.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT));
         OverviewScrim scrim = mLauncher.getDragLayer().getOverviewScrim();
         setter.setFloat(scrim, SCRIM_PROGRESS, toState.getOverviewScrimAlpha(mLauncher),
                 config.getInterpolator(ANIM_OVERVIEW_SCRIM_FADE, LINEAR));
+        setter.setFloat(scrim, SCRIM_MULTIPLIER, 1f,
+                config.getInterpolator(ANIM_OVERVIEW_SCRIM_FADE, LINEAR));
 
         setter.setFloat(
                 mRecentsView, getTaskModalnessProperty(),
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
rename to quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIconInflater.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIconInflater.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIconInflater.java
rename to quickstep/src/com/android/launcher3/uioverrides/PredictedAppIconInflater.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
similarity index 80%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
rename to quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 9a03d92..a284f5d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -17,6 +17,9 @@
 
 import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
 
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
@@ -27,36 +30,33 @@
 import static com.android.launcher3.testing.TestProtocol.QUICK_SWITCH_STATE_ORDINAL;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
+import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
 
 import android.content.Intent;
+import android.content.SharedPreferences;
 import android.content.res.Configuration;
-import android.os.Bundle;
 import android.util.Log;
 import android.view.View;
 
-import androidx.annotation.Nullable;
-
 import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.allapps.DiscoveryBounce;
 import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.appprediction.PredictionUiStateManager;
+import com.android.launcher3.appprediction.PredictionRowView;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.folder.Folder;
 import com.android.launcher3.hybridhotseat.HotseatPredictionController;
 import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.logging.StatsLogManager.StatsLogger;
-import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.BgDataModel.FixedContainerItems;
 import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory;
-import com.android.launcher3.uioverrides.touchcontrollers.FlingAndHoldTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.LandscapeEdgeSwipeController;
 import com.android.launcher3.uioverrides.touchcontrollers.NavBarToHomeTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.NoButtonNavbarToOverviewTouchController;
@@ -67,21 +67,22 @@
 import com.android.launcher3.uioverrides.touchcontrollers.StatusBarTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.TransposedQuickSwitchTouchController;
-import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.OnboardingPrefs;
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.util.UiThreadHelper;
 import com.android.launcher3.util.UiThreadHelper.AsyncCommand;
 import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.util.QuickstepOnboardingPrefs;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.List;
-import java.util.OptionalInt;
+import java.util.Objects;
 import java.util.stream.Stream;
 
 public class QuickstepLauncher extends BaseQuickstepLauncher {
@@ -93,13 +94,8 @@
     public static final AsyncCommand SET_SHELF_HEIGHT = (context, arg1, arg2) ->
             SystemUiProxy.INSTANCE.get(context).setShelfHeight(arg1 != 0, arg2);
 
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        if (mHotseatPredictionController != null) {
-            mHotseatPredictionController.createPredictor();
-        }
-    }
+    private FixedContainerItems mAllAppsPredictions;
+    private HotseatPredictionController mHotseatPredictionController;
 
     @Override
     protected void setupViews() {
@@ -113,8 +109,23 @@
     protected void logAppLaunch(ItemInfo info, InstanceId instanceId) {
         StatsLogger logger = getStatsLogManager()
                 .logger().withItemInfo(info).withInstanceId(instanceId);
-        OptionalInt allAppsRank = PredictionUiStateManager.INSTANCE.get(this).getAllAppsRank(info);
-        allAppsRank.ifPresent(logger::withRank);
+
+        if (mAllAppsPredictions != null
+                && (info.itemType == ITEM_TYPE_APPLICATION
+                || info.itemType == ITEM_TYPE_SHORTCUT
+                || info.itemType == ITEM_TYPE_DEEP_SHORTCUT)) {
+            int count = mAllAppsPredictions.items.size();
+            for (int i = 0; i < count; i++) {
+                ItemInfo targetInfo = mAllAppsPredictions.items.get(i);
+                if (targetInfo.itemType == info.itemType
+                        && targetInfo.user.equals(info.user)
+                        && Objects.equals(targetInfo.getIntent(), info.getIntent())) {
+                    logger.withRank(i);
+                    break;
+                }
+
+            }
+        }
         logger.log(LAUNCHER_APP_LAUNCH_TAP);
 
         if (mHotseatPredictionController != null) {
@@ -122,6 +133,18 @@
         }
     }
 
+    /**
+     * Returns Prediction controller for hybrid hotseat
+     */
+    public HotseatPredictionController getHotseatPredictionController() {
+        return mHotseatPredictionController;
+    }
+
+    @Override
+    protected OnboardingPrefs createOnboardingPrefs(SharedPreferences sharedPrefs) {
+        return new QuickstepOnboardingPrefs(this, sharedPrefs);
+    }
+
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
@@ -129,12 +152,11 @@
     }
 
     @Override
-    public boolean startActivitySafely(View v, Intent intent, ItemInfo item,
-            @Nullable String sourceContainer) {
+    public boolean startActivitySafely(View v, Intent intent, ItemInfo item) {
         if (mHotseatPredictionController != null) {
             mHotseatPredictionController.setPauseUIUpdate(true);
         }
-        return super.startActivitySafely(v, intent, item, sourceContainer);
+        return super.startActivitySafely(v, intent, item);
     }
 
     @Override
@@ -152,19 +174,10 @@
     }
 
     @Override
-    public void folderCreatedFromItem(Folder folder, WorkspaceItemInfo itemInfo) {
-        super.folderCreatedFromItem(folder, itemInfo);
-        if (mHotseatPredictionController != null) {
-            mHotseatPredictionController.folderCreatedFromWorkspaceItem(itemInfo, folder.getInfo());
-        }
-    }
-
-    @Override
-    public void folderConvertedToItem(Folder folder, WorkspaceItemInfo itemInfo) {
-        super.folderConvertedToItem(folder, itemInfo);
-        if (mHotseatPredictionController != null) {
-            mHotseatPredictionController.folderConvertedToWorkspaceItem(itemInfo, folder.getInfo());
-        }
+    protected void showAllAppsFromIntent(boolean alreadyOnHome) {
+        ActivityManagerWrapper.getInstance().closeSystemWindows(
+            CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY);
+        super.showAllAppsFromIntent(alreadyOnHome);
     }
 
     @Override
@@ -196,10 +209,14 @@
     }
 
     @Override
-    public void bindPredictedItems(List<AppInfo> appInfos, IntArray ranks) {
-        super.bindPredictedItems(appInfos, ranks);
-        if (mHotseatPredictionController != null) {
-            mHotseatPredictionController.showCachedItems(appInfos, ranks);
+    public void bindExtraContainerItems(FixedContainerItems item) {
+        if (item.containerId == Favorites.CONTAINER_PREDICTION) {
+            mAllAppsPredictions = item;
+            getAppsView().getFloatingHeaderView().findFixedRowByType(PredictionRowView.class)
+                    .setPredictedApps(item.items);
+        } else if (item.containerId == Favorites.CONTAINER_HOTSEAT_PREDICTION
+                && mHotseatPredictionController != null) {
+            mHotseatPredictionController.setPredictedItems(item);
         }
     }
 
@@ -219,10 +236,8 @@
         switch (state.ordinal) {
             case HINT_STATE_ORDINAL: {
                 Workspace workspace = getWorkspace();
-                boolean willMoveScreens = workspace.getNextPage() != Workspace.DEFAULT_PAGE;
-                getStateManager().goToState(NORMAL, true,
-                        willMoveScreens ? null : getScrimView()::startDragHandleEducationAnim);
-                if (willMoveScreens) {
+                getStateManager().goToState(NORMAL);
+                if (workspace.getNextPage() != Workspace.DEFAULT_PAGE) {
                     workspace.post(workspace::moveToDefaultScreen);
                 }
                 break;
@@ -272,14 +287,7 @@
             if (TestProtocol.sDebugTracing) {
                 Log.d(TestProtocol.PAUSE_NOT_DETECTED, "createTouchControllers.2");
             }
-            if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()) {
-                if (TestProtocol.sDebugTracing) {
-                    Log.d(TestProtocol.PAUSE_NOT_DETECTED, "createTouchControllers.3");
-                }
-                list.add(new NoButtonNavbarToOverviewTouchController(this));
-            } else {
-                list.add(new FlingAndHoldTouchController(this));
-            }
+            list.add(new NoButtonNavbarToOverviewTouchController(this));
         } else {
             if (getDeviceProfile().isVerticalBarLayout()) {
                 list.add(new OverviewToAllAppsTouchController(this));
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
similarity index 88%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
rename to quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 085b9b3..5ccc1e8 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -17,6 +17,7 @@
 
 import static com.android.launcher3.LauncherState.OVERVIEW_BUTTONS;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_ACTIONS_FADE;
 import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
 import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
 import static com.android.quickstep.views.RecentsView.TASK_MODALNESS;
@@ -57,7 +58,7 @@
             mRecentsView.updateEmptyMessage();
             mRecentsView.resetTaskVisuals();
         }
-        setAlphas(PropertySetter.NO_ANIM_PROPERTY_SETTER, state);
+        setAlphas(PropertySetter.NO_ANIM_PROPERTY_SETTER, new StateAnimationConfig(), state);
         mRecentsView.setFullscreenProgress(state.getOverviewFullscreenProgress());
     }
 
@@ -75,17 +76,19 @@
                     AnimationSuccessListener.forRunnable(mRecentsView::resetTaskVisuals));
         }
 
-        setAlphas(builder, toState);
+        setAlphas(builder, config, toState);
         builder.setFloat(mRecentsView, FULLSCREEN_PROGRESS,
                 toState.getOverviewFullscreenProgress(), LINEAR);
     }
 
-    private void setAlphas(PropertySetter propertySetter, LauncherState state) {
+    private void setAlphas(PropertySetter propertySetter, StateAnimationConfig config,
+            LauncherState state) {
         float buttonAlpha = (state.getVisibleElements(mLauncher) & OVERVIEW_BUTTONS) != 0 ? 1 : 0;
         propertySetter.setFloat(mRecentsView.getClearAllButton(), ClearAllButton.VISIBILITY_ALPHA,
                 buttonAlpha, LINEAR);
         propertySetter.setFloat(mLauncher.getActionsView().getVisibilityAlpha(),
-                MultiValueAlpha.VALUE, buttonAlpha, LINEAR);
+                MultiValueAlpha.VALUE, buttonAlpha, config.getInterpolator(
+                        ANIM_OVERVIEW_ACTIONS_FADE, LINEAR));
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
index e7cd393..bce73cd 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -16,8 +16,7 @@
 package com.android.launcher3.uioverrides.states;
 
 import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
-import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
+import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
 
 import android.content.Context;
 
@@ -25,7 +24,6 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.allapps.AllAppsContainerView;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import com.android.quickstep.SysUINavigationMode;
 
 /**
  * Definition for AllApps state
@@ -65,13 +63,7 @@
     public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) {
         ScaleAndTranslation scaleAndTranslation = LauncherState.OVERVIEW
                 .getWorkspaceScaleAndTranslation(launcher);
-        if (SysUINavigationMode.getMode(launcher) == NO_BUTTON && !ENABLE_OVERVIEW_ACTIONS.get()) {
-            float normalScale = 1;
-            // Scale down halfway to where we'd be in overview, to prepare for a potential pause.
-            scaleAndTranslation.scale = (scaleAndTranslation.scale + normalScale) / 2;
-        } else {
-            scaleAndTranslation.scale = 1;
-        }
+        scaleAndTranslation.scale = 1;
         return scaleAndTranslation;
     }
 
@@ -92,7 +84,8 @@
 
     @Override
     public float[] getOverviewScaleAndOffset(Launcher launcher) {
-        return new float[] {0.9f, 0};
+        float offset = removeShelfFromOverview(launcher) ? 1 : 0;
+        return new float[] {0.9f, offset};
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
rename to quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
rename to quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
similarity index 90%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
rename to quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index d174bfd..bbe7821 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -16,8 +16,6 @@
 package com.android.launcher3.uioverrides.states;
 
 import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
-import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
 import static com.android.quickstep.SysUINavigationMode.hideShelfInTwoButtonLandscape;
 import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
@@ -31,7 +29,6 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
 import com.android.launcher3.Workspace;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.util.LayoutUtils;
@@ -65,7 +62,7 @@
     public int getTransitionDuration(Context context) {
         // In no-button mode, overview comes in all the way from the left, so give it more time.
         boolean isNoButtonMode = SysUINavigationMode.INSTANCE.get(context).getMode() == NO_BUTTON;
-        return isNoButtonMode && ENABLE_OVERVIEW_ACTIONS.get() ? 380 : 250;
+        return isNoButtonMode ? 380 : 250;
     }
 
     @Override
@@ -108,8 +105,7 @@
 
     @Override
     public ScaleAndTranslation getQsbScaleAndTranslation(Launcher launcher) {
-        if (this == OVERVIEW && ENABLE_OVERVIEW_ACTIONS.get()
-                && removeShelfFromOverview(launcher)) {
+        if (this == OVERVIEW && removeShelfFromOverview(launcher)) {
             // Treat the QSB as part of the hotseat so they move together.
             return getHotseatScaleAndTranslation(launcher);
         }
@@ -129,7 +125,7 @@
     @Override
     public int getVisibleElements(Launcher launcher) {
         RecentsView recentsView = launcher.getOverviewPanel();
-        if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(launcher) ||
+        if (removeShelfFromOverview(launcher) ||
                 hideShelfInTwoButtonLandscape(launcher, recentsView.getPagedOrientationHandler())) {
             return OVERVIEW_BUTTONS;
         } else if (launcher.getDeviceProfile().isVerticalBarLayout()) {
@@ -179,8 +175,6 @@
     public void onBackPressed(Launcher launcher) {
         TaskView taskView = launcher.<RecentsView>getOverviewPanel().getRunningTaskView();
         if (taskView != null) {
-            launcher.getUserEventDispatcher().logActionCommand(Action.Command.BACK,
-                    newContainerTarget(ContainerType.OVERVIEW));
             taskView.launchTask(true);
         } else {
             super.onBackPressed(launcher);
@@ -191,10 +185,6 @@
         return new BackgroundAppState(id);
     }
 
-    public static OverviewState newPeekState(int id) {
-        return new OverviewPeekState(id);
-    }
-
     public static OverviewState newSwitchState(int id) {
         return new QuickSwitchState(id);
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
rename to quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
similarity index 86%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
rename to quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
index a0af797..94af134 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
@@ -17,33 +17,29 @@
 
 import static android.view.View.VISIBLE;
 
-import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 import static com.android.launcher3.LauncherState.HINT_STATE;
 import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.LauncherState.OVERVIEW_PEEK;
 import static com.android.launcher3.WorkspaceStateTransitionAnimation.getSpringScaleAnimator;
 import static com.android.launcher3.anim.Interpolators.ACCEL;
+import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
-import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
 import static com.android.launcher3.anim.Interpolators.INSTANT;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7;
 import static com.android.launcher3.anim.Interpolators.clampToProgress;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_DEPTH;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_SCALE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_TRANSLATE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCRIM_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
@@ -52,6 +48,7 @@
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE;
 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
 import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
+import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
@@ -163,10 +160,15 @@
             config.setInterpolator(ANIM_WORKSPACE_FADE, ACCEL);
             config.setInterpolator(ANIM_ALL_APPS_FADE, ACCEL);
             config.setInterpolator(ANIM_OVERVIEW_SCALE, clampToProgress(ACCEL, 0, 0.9f));
-            config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCEL);
-            config.setInterpolator(ANIM_OVERVIEW_FADE, DEACCEL_1_7);
-            Workspace workspace = mActivity.getWorkspace();
+            config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCEL_DEACCEL);
 
+            if (SysUINavigationMode.getMode(mActivity) == NO_BUTTON) {
+                config.setInterpolator(ANIM_OVERVIEW_FADE, FINAL_FRAME);
+            } else {
+                config.setInterpolator(ANIM_OVERVIEW_FADE, DEACCEL_1_7);
+            }
+
+            Workspace workspace = mActivity.getWorkspace();
             // Start from a higher workspace scale, but only if we're invisible so we don't jump.
             boolean isWorkspaceVisible = workspace.getVisibility() == VISIBLE;
             if (isWorkspaceVisible) {
@@ -184,48 +186,39 @@
             if (!isHotseatVisible) {
                 hotseat.setScaleX(0.92f);
                 hotseat.setScaleY(0.92f);
-                if (ENABLE_OVERVIEW_ACTIONS.get()) {
-                    AllAppsContainerView qsbContainer = mActivity.getAppsView();
-                    View qsb = qsbContainer.getSearchView();
-                    boolean qsbVisible = qsb.getVisibility() == VISIBLE && qsb.getAlpha() > 0;
-                    if (!qsbVisible) {
-                        qsbContainer.setScaleX(0.92f);
-                        qsbContainer.setScaleY(0.92f);
-                    }
+                AllAppsContainerView qsbContainer = mActivity.getAppsView();
+                View qsb = qsbContainer.getSearchView();
+                boolean qsbVisible = qsb.getVisibility() == VISIBLE && qsb.getAlpha() > 0;
+                if (!qsbVisible) {
+                    qsbContainer.setScaleX(0.92f);
+                    qsbContainer.setScaleY(0.92f);
                 }
             }
-        } else if (toState == NORMAL && fromState == OVERVIEW_PEEK) {
-            // Keep fully visible until the very end (when overview is offscreen) to make invisible.
-            config.setInterpolator(ANIM_OVERVIEW_FADE, FINAL_FRAME);
-        } else if (toState == OVERVIEW_PEEK && fromState == NORMAL) {
-            config.setInterpolator(ANIM_OVERVIEW_FADE, INSTANT);
-            config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_7);
-            config.setInterpolator(ANIM_OVERVIEW_SCRIM_FADE, FAST_OUT_SLOW_IN);
         } else if ((fromState == NORMAL || fromState == HINT_STATE) && toState == OVERVIEW) {
             if (SysUINavigationMode.getMode(mActivity) == NO_BUTTON) {
                 config.setInterpolator(ANIM_WORKSPACE_SCALE,
                         fromState == NORMAL ? ACCEL : OVERSHOOT_1_2);
                 config.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCEL);
+                config.setInterpolator(ANIM_OVERVIEW_FADE, INSTANT);
             } else {
                 config.setInterpolator(ANIM_WORKSPACE_SCALE, OVERSHOOT_1_2);
+                config.setInterpolator(ANIM_OVERVIEW_FADE, OVERSHOOT_1_2);
 
                 // Scale up the recents, if it is not coming from the side
                 RecentsView overview = mActivity.getOverviewPanel();
                 if (overview.getVisibility() != VISIBLE || overview.getContentAlpha() == 0) {
-                    SCALE_PROPERTY.set(overview, RECENTS_PREPARE_SCALE);
+                    RECENTS_SCALE_PROPERTY.set(overview, RECENTS_PREPARE_SCALE);
                 }
             }
             config.setInterpolator(ANIM_WORKSPACE_FADE, OVERSHOOT_1_2);
             config.setInterpolator(ANIM_ALL_APPS_FADE, OVERSHOOT_1_2);
             config.setInterpolator(ANIM_OVERVIEW_SCALE, OVERSHOOT_1_2);
             config.setInterpolator(ANIM_DEPTH, OVERSHOOT_1_2);
-            Interpolator translationInterpolator = ENABLE_OVERVIEW_ACTIONS.get()
-                    && removeShelfFromOverview(mActivity)
+            Interpolator translationInterpolator = removeShelfFromOverview(mActivity)
                     ? OVERSHOOT_1_2
                     : OVERSHOOT_1_7;
             config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, translationInterpolator);
             config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, translationInterpolator);
-            config.setInterpolator(ANIM_OVERVIEW_FADE, OVERSHOOT_1_2);
         } else if (fromState == HINT_STATE && toState == NORMAL) {
             config.setInterpolator(ANIM_DEPTH, DEACCEL_3);
             if (mHintToNormalDuration == -1) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
similarity index 90%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
rename to quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index c1a585e..d574294 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -25,7 +25,6 @@
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
 import static com.android.launcher3.touch.AbstractStateChangeTouchController.SUCCESS_TRANSITION_PROGRESS;
-import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 
 import android.animation.ValueAnimator;
@@ -45,6 +44,7 @@
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.graphics.OverviewScrim;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.testing.TestProtocol;
@@ -52,7 +52,9 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.util.TouchController;
+import com.android.quickstep.util.AnimatorControllerWithResistance;
 import com.android.quickstep.util.AssistantUtilities;
+import com.android.quickstep.util.OverviewToHomeAnim;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 
@@ -63,6 +65,8 @@
         SingleAxisSwipeDetector.Listener {
 
     private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL_3;
+    // The min amount of overview scrim we keep during the transition.
+    private static final float OVERVIEW_TO_HOME_SCRIM_MULTIPLIER = 0.5f;
 
     private final Launcher mLauncher;
     private final SingleAxisSwipeDetector mSwipeDetector;
@@ -156,11 +160,15 @@
         final PendingAnimation builder = new PendingAnimation(accuracy);
         if (mStartState.overviewUi) {
             RecentsView recentsView = mLauncher.getOverviewPanel();
-            builder.setFloat(recentsView, ADJACENT_PAGE_OFFSET,
-                    -mPullbackDistance / recentsView.getPageOffsetScale(), PULLBACK_INTERPOLATOR);
+            AnimatorControllerWithResistance.createRecentsResistanceFromOverviewAnim(mLauncher,
+                    builder);
+
+            builder.setFloat(mLauncher.getDragLayer().getOverviewScrim(),
+                    OverviewScrim.SCRIM_MULTIPLIER, OVERVIEW_TO_HOME_SCRIM_MULTIPLIER,
+                    PULLBACK_INTERPOLATOR);
+
             if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-                builder.addOnFrameCallback(
-                        () -> recentsView.redrawLiveTile(false /* mightNeedToRefill */));
+                builder.addOnFrameCallback(recentsView::redrawLiveTile);
             }
         } else if (mStartState == ALL_APPS) {
             AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
@@ -212,15 +220,20 @@
                 recentsView.switchToScreenshot(null,
                         () -> recentsView.finishRecentsAnimation(true /* toRecents */, null));
             }
-            mLauncher.getStateManager().goToState(mEndState, true,
-                    () -> onSwipeInteractionCompleted(mEndState));
+            if (mStartState.overviewUi) {
+                new OverviewToHomeAnim(mLauncher, () -> onSwipeInteractionCompleted(mEndState))
+                        .animateWithVelocity(velocity);
+            } else {
+                mLauncher.getStateManager().goToState(mEndState, true,
+                        () -> onSwipeInteractionCompleted(mEndState));
+            }
             if (mStartState != mEndState) {
-                logStateChange(mStartState.containerType, logAction);
+                // TODO: add to WW log
             }
             AbstractFloatingView topOpenView = AbstractFloatingView.getTopOpenView(mLauncher);
             if (topOpenView != null) {
                 AbstractFloatingView.closeAllOpenViews(mLauncher);
-                logStateChange(topOpenView.getLogContainerType(), logAction);
+                // TODO: add to WW log
             }
             ActivityManagerWrapper.getInstance()
                     .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
new file mode 100644
index 0000000..591d3ca
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -0,0 +1,334 @@
+/*
+ * Copyright (C) 2020 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.uioverrides.touchcontrollers;
+
+import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.HINT_STATE;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
+import static com.android.launcher3.anim.Interpolators.ACCEL;
+import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
+import static com.android.launcher3.anim.Interpolators.DEACCEL;
+import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_HEADER_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_SCALE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE;
+import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
+
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.graphics.PointF;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.graphics.OverviewScrim;
+import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
+import com.android.launcher3.util.VibratorWrapper;
+import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.util.AnimatorControllerWithResistance;
+import com.android.quickstep.util.MotionPauseDetector;
+import com.android.quickstep.util.OverviewToHomeAnim;
+import com.android.quickstep.views.RecentsView;
+
+/**
+ * Touch controller which handles swipe and hold from the nav bar to go to Overview. Swiping above
+ * the nav bar falls back to go to All Apps. Swiping from the nav bar without holding goes to the
+ * first home screen instead of to Overview.
+ */
+public class NoButtonNavbarToOverviewTouchController extends PortraitStatesTouchController {
+
+
+    // How much of the movement to use for translating overview after swipe and hold.
+    private static final float OVERVIEW_MOVEMENT_FACTOR = 0.25f;
+    private static final long TRANSLATION_ANIM_MIN_DURATION_MS = 80;
+    private static final float TRANSLATION_ANIM_VELOCITY_DP_PER_MS = 0.8f;
+
+    private final RecentsView mRecentsView;
+    private final MotionPauseDetector mMotionPauseDetector;
+    private final float mMotionPauseMinDisplacement;
+
+    private boolean mDidTouchStartInNavBar;
+    private boolean mReachedOverview;
+    // The last recorded displacement before we reached overview.
+    private PointF mStartDisplacement = new PointF();
+    private float mStartY;
+    private AnimatorPlaybackController mOverviewResistYAnim;
+
+    // Normal to Hint animation has flag SKIP_OVERVIEW, so we update this scrim with this animator.
+    private ObjectAnimator mNormalToHintOverviewScrimAnimator;
+
+    public NoButtonNavbarToOverviewTouchController(Launcher l) {
+        super(l, false /* allowDragToOverview */);
+        mRecentsView = l.getOverviewPanel();
+        mMotionPauseDetector = new MotionPauseDetector(l);
+        mMotionPauseMinDisplacement = ViewConfiguration.get(l).getScaledTouchSlop();
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.PAUSE_NOT_DETECTED, "NoButtonNavbarToOverviewTouchController.ctor");
+        }
+    }
+
+    @Override
+    protected boolean canInterceptTouch(MotionEvent ev) {
+        mDidTouchStartInNavBar = (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0;
+        return super.canInterceptTouch(ev);
+    }
+
+    @Override
+    protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
+        if (fromState == NORMAL && mDidTouchStartInNavBar) {
+            return HINT_STATE;
+        } else if (fromState == OVERVIEW && isDragTowardPositive) {
+            // Don't allow swiping up to all apps.
+            return OVERVIEW;
+        }
+        return super.getTargetState(fromState, isDragTowardPositive);
+    }
+
+    @Override
+    protected float initCurrentAnimation(int animComponents) {
+        float progressMultiplier = super.initCurrentAnimation(animComponents);
+        if (mToState == HINT_STATE) {
+            // Track the drag across the entire height of the screen.
+            progressMultiplier = -1 / getShiftRange();
+        }
+        return progressMultiplier;
+    }
+
+    @Override
+    public void onDragStart(boolean start, float startDisplacement) {
+        super.onDragStart(start, startDisplacement);
+
+        mMotionPauseDetector.clear();
+
+        if (handlingOverviewAnim()) {
+            mMotionPauseDetector.setOnMotionPauseListener(this::onMotionPauseChanged);
+        }
+
+        if (mFromState == NORMAL && mToState == HINT_STATE) {
+            mNormalToHintOverviewScrimAnimator = ObjectAnimator.ofFloat(
+                    mLauncher.getDragLayer().getOverviewScrim(),
+                    OverviewScrim.SCRIM_PROGRESS,
+                    mFromState.getOverviewScrimAlpha(mLauncher),
+                    mToState.getOverviewScrimAlpha(mLauncher));
+        }
+        mReachedOverview = false;
+        mOverviewResistYAnim = null;
+    }
+
+    @Override
+    protected void updateProgress(float fraction) {
+        super.updateProgress(fraction);
+        if (mNormalToHintOverviewScrimAnimator != null) {
+            mNormalToHintOverviewScrimAnimator.setCurrentFraction(fraction);
+        }
+    }
+
+    @Override
+    public void onDragEnd(float velocity) {
+        if (mMotionPauseDetector.isPaused() && handlingOverviewAnim()) {
+            goToOverviewOrHomeOnDragEnd(velocity);
+        } else {
+            super.onDragEnd(velocity);
+        }
+
+        View searchView = mLauncher.getAppsView().getSearchView();
+        if (searchView instanceof FeedbackHandler) {
+            ((FeedbackHandler) searchView).resetFeedback();
+        }
+
+        mMotionPauseDetector.clear();
+        mNormalToHintOverviewScrimAnimator = null;
+        if (mLauncher.isInState(OVERVIEW)) {
+            // Normally we would cleanup the state based on mCurrentAnimation, but since we stop
+            // using that when we pause to go to Overview, we need to clean up ourselves.
+            clearState();
+        }
+    }
+
+    @Override
+    protected void updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration,
+            LauncherState targetState, float velocity, boolean isFling) {
+        super.updateSwipeCompleteAnimation(animator, expectedDuration, targetState, velocity,
+                isFling);
+        if (targetState == HINT_STATE) {
+            // Normally we compute the duration based on the velocity and distance to the given
+            // state, but since the hint state tracks the entire screen without a clear endpoint, we
+            // need to manually set the duration to a reasonable value.
+            animator.setDuration(HINT_STATE.getTransitionDuration(mLauncher));
+        }
+    }
+
+    private void onMotionPauseChanged(boolean isPaused) {
+        if (mCurrentAnimation == null) {
+            return;
+        }
+        mNormalToHintOverviewScrimAnimator = null;
+        mCurrentAnimation.dispatchOnCancelWithoutCancelRunnable(() -> {
+            mLauncher.getStateManager().goToState(OVERVIEW, true, () -> {
+                mOverviewResistYAnim = AnimatorControllerWithResistance
+                        .createRecentsResistanceFromOverviewAnim(mLauncher, null)
+                        .createPlaybackController();
+                mReachedOverview = true;
+                maybeSwipeInteractionToOverviewComplete();
+            });
+        });
+        VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC);
+    }
+
+    private void maybeSwipeInteractionToOverviewComplete() {
+        if (mReachedOverview && mDetector.isSettlingState()) {
+            onSwipeInteractionCompleted(OVERVIEW, Touch.SWIPE);
+        }
+    }
+
+    private boolean handlingOverviewAnim() {
+        int stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags();
+        return mDidTouchStartInNavBar && mStartState == NORMAL
+                && (stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0;
+    }
+
+    @Override
+    public boolean onDrag(float yDisplacement, float xDisplacement, MotionEvent event) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.PAUSE_NOT_DETECTED, "NoButtonNavbarToOverviewTouchController");
+        }
+        if (mMotionPauseDetector.isPaused()) {
+            if (!mReachedOverview) {
+                mStartDisplacement.set(xDisplacement, yDisplacement);
+                mStartY = event.getY();
+            } else {
+                mRecentsView.setTranslationX((xDisplacement - mStartDisplacement.x)
+                        * OVERVIEW_MOVEMENT_FACTOR);
+                float yProgress = (mStartDisplacement.y - yDisplacement) / mStartY;
+                if (yProgress > 0 && mOverviewResistYAnim != null) {
+                    mOverviewResistYAnim.setPlayFraction(yProgress);
+                } else {
+                    mRecentsView.setTranslationY((yDisplacement - mStartDisplacement.y)
+                            * OVERVIEW_MOVEMENT_FACTOR);
+                }
+            }
+            // Stay in Overview.
+            return true;
+        }
+
+        float upDisplacement = -yDisplacement;
+        mMotionPauseDetector.setDisallowPause(!handlingOverviewAnim()
+                || upDisplacement < mMotionPauseMinDisplacement);
+        mMotionPauseDetector.addPosition(event);
+
+        return super.onDrag(yDisplacement, xDisplacement, event);
+    }
+
+    private void goToOverviewOrHomeOnDragEnd(float velocity) {
+        float velocityDp = dpiFromPx(velocity);
+        boolean isFling = Math.abs(velocityDp) > 1;
+        boolean goToHomeInsteadOfOverview = isFling;
+        if (goToHomeInsteadOfOverview) {
+            new OverviewToHomeAnim(mLauncher, ()-> onSwipeInteractionCompleted(NORMAL, Touch.FLING))
+                    .animateWithVelocity(velocity);
+        }
+        if (mReachedOverview) {
+            float distanceDp = dpiFromPx(Math.max(
+                    Math.abs(mRecentsView.getTranslationX()),
+                    Math.abs(mRecentsView.getTranslationY())));
+            long duration = (long) Math.max(TRANSLATION_ANIM_MIN_DURATION_MS,
+                    distanceDp / TRANSLATION_ANIM_VELOCITY_DP_PER_MS);
+            mRecentsView.animate()
+                    .translationX(0)
+                    .translationY(0)
+                    .setInterpolator(ACCEL_DEACCEL)
+                    .setDuration(duration)
+                    .withEndAction(goToHomeInsteadOfOverview
+                            ? null
+                            : this::maybeSwipeInteractionToOverviewComplete);
+            if (!goToHomeInsteadOfOverview) {
+                // Return to normal properties for the overview state.
+                StateAnimationConfig config = new StateAnimationConfig();
+                config.duration = duration;
+                LauncherState state = mLauncher.getStateManager().getState();
+                mLauncher.getStateManager().createAtomicAnimation(state, state, config).start();
+            }
+        }
+    }
+
+    private float dpiFromPx(float pixels) {
+        return Utilities.dpiFromPx(pixels, mLauncher.getResources().getDisplayMetrics());
+    }
+
+    @Override
+    protected StateAnimationConfig getConfigForStates(
+            LauncherState fromState, LauncherState toState) {
+        if (fromState == NORMAL && toState == ALL_APPS) {
+            StateAnimationConfig builder = new StateAnimationConfig();
+            // Fade in prediction icons quickly, then rest of all apps after reaching overview.
+            float progressToReachOverview = NORMAL.getVerticalProgress(mLauncher)
+                    - OVERVIEW.getVerticalProgress(mLauncher);
+            builder.setInterpolator(ANIM_ALL_APPS_HEADER_FADE, Interpolators.clampToProgress(
+                    ACCEL,
+                    0,
+                    ALL_APPS_CONTENT_FADE_THRESHOLD));
+            builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(
+                    ACCEL,
+                    progressToReachOverview,
+                    progressToReachOverview + ALL_APPS_CONTENT_FADE_THRESHOLD));
+
+            // Get workspace out of the way quickly, to prepare for potential pause.
+            builder.setInterpolator(ANIM_WORKSPACE_SCALE, DEACCEL_3);
+            builder.setInterpolator(ANIM_WORKSPACE_TRANSLATE, DEACCEL_3);
+            builder.setInterpolator(ANIM_WORKSPACE_FADE, DEACCEL_3);
+            return builder;
+        } else if (fromState == ALL_APPS && toState == NORMAL) {
+            StateAnimationConfig builder = new StateAnimationConfig();
+            // Keep all apps/predictions opaque until the very end of the transition.
+            float progressToReachOverview = OVERVIEW.getVerticalProgress(mLauncher);
+            builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(
+                    DEACCEL,
+                    progressToReachOverview - ALL_APPS_CONTENT_FADE_THRESHOLD,
+                    progressToReachOverview));
+            builder.setInterpolator(ANIM_ALL_APPS_HEADER_FADE, Interpolators.clampToProgress(
+                    DEACCEL,
+                    1 - ALL_APPS_CONTENT_FADE_THRESHOLD,
+                    1));
+            return builder;
+        }
+        return super.getConfigForStates(fromState, toState);
+    }
+
+    /**
+     * Interface for views with feedback animation requiring reset
+     */
+    public interface FeedbackHandler {
+
+        /**
+         * reset searchWidget feedback
+         */
+        void resetFeedback();
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
similarity index 85%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
rename to quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 1b439d1..4b0642f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -15,19 +15,15 @@
  */
 package com.android.launcher3.uioverrides.touchcontrollers;
 
-import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
-import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.LauncherState.OVERVIEW_BUTTONS;
 import static com.android.launcher3.LauncherState.QUICK_SWITCH;
 import static com.android.launcher3.anim.AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD;
 import static com.android.launcher3.anim.Interpolators.ACCEL_0_75;
-import static com.android.launcher3.anim.Interpolators.DEACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_5;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
-import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_UNKNOWN_SWIPEDOWN;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_UNKNOWN_SWIPEUP;
@@ -40,13 +36,12 @@
 import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_RIGHT;
 import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_UP;
 import static com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory.INDEX_PAUSE_TO_OVERVIEW_ANIM;
-import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
+import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
 import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
-import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.CANCEL;
-import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.HIDE;
-import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.PEEK;
 import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
 import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
+import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
+import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
 
 import android.animation.Animator;
@@ -60,10 +55,8 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.graphics.OverviewScrim;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.states.StateAnimationConfig;
@@ -74,11 +67,11 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.util.VibratorWrapper;
+import com.android.quickstep.AnimatedFloat;
 import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.util.AnimatorControllerWithResistance;
 import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.util.MotionPauseDetector;
-import com.android.quickstep.util.ShelfPeekAnim;
-import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState;
 import com.android.quickstep.util.StaggeredWorkspaceAnim;
 import com.android.quickstep.views.LauncherRecentsView;
 
@@ -90,16 +83,16 @@
         BothAxesSwipeDetector.Listener, MotionPauseDetector.OnMotionPauseListener {
 
     /** The minimum progress of the scale/translationY animation until drag end. */
-    private static final float Y_ANIM_MIN_PROGRESS = 0.15f;
+    private static final float Y_ANIM_MIN_PROGRESS = 0.25f;
     private static final Interpolator FADE_OUT_INTERPOLATOR = DEACCEL_5;
     private static final Interpolator TRANSLATE_OUT_INTERPOLATOR = ACCEL_0_75;
-    private static final Interpolator SCALE_DOWN_INTERPOLATOR = DEACCEL;
+    private static final Interpolator SCALE_DOWN_INTERPOLATOR = LINEAR;
 
     private final BaseQuickstepLauncher mLauncher;
     private final BothAxesSwipeDetector mSwipeDetector;
-    private final ShelfPeekAnim mShelfPeekAnim;
     private final float mXRange;
     private final float mYRange;
+    private final float mMaxYProgress;
     private final MotionPauseDetector mMotionPauseDetector;
     private final float mMotionPauseMinDisplacement;
     private final LauncherRecentsView mRecentsView;
@@ -113,16 +106,16 @@
     // and the other two to set overview properties based on x and y progress.
     private AnimatorPlaybackController mNonOverviewAnim;
     private AnimatorPlaybackController mXOverviewAnim;
-    private AnimatorPlaybackController mYOverviewAnim;
+    private AnimatedFloat mYOverviewAnim;
 
     public NoButtonQuickSwitchTouchController(BaseQuickstepLauncher launcher) {
         mLauncher = launcher;
         mSwipeDetector = new BothAxesSwipeDetector(mLauncher, this);
-        mShelfPeekAnim = mLauncher.getShelfPeekAnim();
         mRecentsView = mLauncher.getOverviewPanel();
         mXRange = mLauncher.getDeviceProfile().widthPx / 2f;
         mYRange = LayoutUtils.getShelfTrackingDistance(
             mLauncher, mLauncher.getDeviceProfile(), mRecentsView.getPagedOrientationHandler());
+        mMaxYProgress = mLauncher.getDeviceProfile().heightPx / mYRange;
         mMotionPauseDetector = new MotionPauseDetector(mLauncher);
         mMotionPauseMinDisplacement = mLauncher.getResources().getDimension(
                 R.dimen.motion_pause_detector_min_displacement_from_app);
@@ -187,25 +180,6 @@
     @Override
     public void onMotionPauseChanged(boolean isPaused) {
         VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC);
-
-        if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()) {
-            return;
-        }
-
-        ShelfAnimState shelfState = isPaused ? PEEK : HIDE;
-        if (shelfState == PEEK) {
-            // Some shelf elements (e.g. qsb) were hidden, but we need them visible when peeking.
-            AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
-            allAppsController.setAlphas(
-                    NORMAL, new StateAnimationConfig(), NO_ANIM_PROPERTY_SETTER);
-
-            if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
-                // Hotseat was hidden, but we need it visible when peeking.
-                mLauncher.getHotseat().setAlpha(1);
-            }
-        }
-        mShelfPeekAnim.setShelfState(shelfState, ShelfPeekAnim.INTERPOLATOR,
-                ShelfPeekAnim.DURATION);
     }
 
     private void setupAnimators() {
@@ -244,7 +218,7 @@
         final LauncherState toState = OVERVIEW;
 
         // Set RecentView's initial properties.
-        SCALE_PROPERTY.set(mRecentsView, fromState.getOverviewScaleAndOffset(mLauncher)[0]);
+        RECENTS_SCALE_PROPERTY.set(mRecentsView, fromState.getOverviewScaleAndOffset(mLauncher)[0]);
         ADJACENT_PAGE_OFFSET.set(mRecentsView, 1f);
         mRecentsView.setContentAlpha(1);
         mRecentsView.setFullscreenProgress(fromState.getOverviewFullscreenProgress());
@@ -266,11 +240,22 @@
         //   - RecentsView scale
         //   - RecentsView fullscreenProgress
         PendingAnimation yAnim = new PendingAnimation((long) (mYRange * 2));
-        yAnim.setFloat(mRecentsView, SCALE_PROPERTY, scaleAndOffset[0], SCALE_DOWN_INTERPOLATOR);
+        yAnim.setFloat(mRecentsView, RECENTS_SCALE_PROPERTY, scaleAndOffset[0],
+                SCALE_DOWN_INTERPOLATOR);
         yAnim.setFloat(mRecentsView, FULLSCREEN_PROGRESS,
                 toState.getOverviewFullscreenProgress(), SCALE_DOWN_INTERPOLATOR);
-        mYOverviewAnim = yAnim.createPlaybackController();
-        mYOverviewAnim.dispatchOnStart();
+        AnimatorPlaybackController yNormalController = yAnim.createPlaybackController();
+        AnimatorControllerWithResistance yAnimWithResistance = AnimatorControllerWithResistance
+                .createForRecents(yNormalController, mLauncher,
+                        mRecentsView.getPagedViewOrientedState(), mLauncher.getDeviceProfile(),
+                        mRecentsView, RECENTS_SCALE_PROPERTY, mRecentsView,
+                        TASK_SECONDARY_TRANSLATION);
+        mYOverviewAnim = new AnimatedFloat(() -> {
+            if (mYOverviewAnim != null) {
+                yAnimWithResistance.setProgress(mYOverviewAnim.value, mMaxYProgress);
+            }
+        });
+        yNormalController.dispatchOnStart();
     }
 
     @Override
@@ -286,10 +271,6 @@
         mIsHomeScreenVisible = FADE_OUT_INTERPOLATOR.getInterpolation(xProgress)
                 <= 1 - ALPHA_CUTOFF_THRESHOLD;
 
-        if (wasHomeScreenVisible && !mIsHomeScreenVisible) {
-            // Get the shelf all the way offscreen so it pops up when we decide to peek it.
-            mShelfPeekAnim.setShelfState(HIDE, LINEAR, 0);
-        }
 
         // Only allow motion pause if the home screen is invisible, since some
         // home screen elements will appear in the shelf on motion pause.
@@ -297,16 +278,11 @@
                 || -displacement.y < mMotionPauseMinDisplacement);
         mMotionPauseDetector.addPosition(ev);
 
-        if (mIsHomeScreenVisible) {
-            // Cancel the shelf anim so it doesn't clobber mNonOverviewAnim.
-            mShelfPeekAnim.setShelfState(CANCEL, LINEAR, 0);
-        }
-
         if (mXOverviewAnim != null) {
             mXOverviewAnim.setPlayFraction(xProgress);
         }
         if (mYOverviewAnim != null) {
-            mYOverviewAnim.setPlayFraction(yProgress);
+            mYOverviewAnim.updateValue(yProgress);
         }
         return true;
     }
@@ -353,9 +329,11 @@
         } else if (verticalFling) {
             targetState = velocity.y > 0 ? QUICK_SWITCH : NORMAL;
         } else {
-            // If user isn't flinging, just snap to the closest state based on x progress.
+            // If user isn't flinging, just snap to the closest state.
             boolean passedHorizontalThreshold = mXOverviewAnim.getInterpolatedProgress() > 0.5f;
-            targetState = passedHorizontalThreshold ? QUICK_SWITCH : NORMAL;
+            boolean passedVerticalThreshold = mYOverviewAnim.value > 1f;
+            targetState = passedHorizontalThreshold && !passedVerticalThreshold
+                    ? QUICK_SWITCH : NORMAL;
         }
 
         // Animate the various components to the target state.
@@ -374,9 +352,9 @@
 
         boolean flingUpToNormal = verticalFling && velocity.y < 0 && targetState == NORMAL;
 
-        float yProgress = mYOverviewAnim.getProgressFraction();
+        float yProgress = mYOverviewAnim.value;
         float startYProgress = Utilities.boundToRange(yProgress
-                - velocity.y * getSingleFrameMs(mLauncher) / mYRange, 0f, 1f);
+                - velocity.y * getSingleFrameMs(mLauncher) / mYRange, 0f, mMaxYProgress);
         final float endYProgress;
         if (flingUpToNormal) {
             endYProgress = 1;
@@ -386,12 +364,11 @@
         } else {
             endYProgress = 0;
         }
-        long yDuration = BaseSwipeDetector.calculateDuration(velocity.y,
-                Math.abs(endYProgress - startYProgress));
-        ValueAnimator yOverviewAnim = mYOverviewAnim.getAnimationPlayer();
-        yOverviewAnim.setFloatValues(startYProgress, endYProgress);
+        float yDistanceToCover = Math.abs(endYProgress - startYProgress) * mYRange;
+        long yDuration = (long) (yDistanceToCover / Math.max(1f, Math.abs(velocity.y)));
+        ValueAnimator yOverviewAnim = mYOverviewAnim.animateToValue(startYProgress, endYProgress);
         yOverviewAnim.setDuration(yDuration);
-        mYOverviewAnim.dispatchOnStart();
+        mYOverviewAnim.updateValue(startYProgress);
 
         ValueAnimator nonOverviewAnim = mNonOverviewAnim.getAnimationPlayer();
         if (flingUpToNormal && !mIsHomeScreenVisible) {
@@ -456,9 +433,8 @@
             mXOverviewAnim.getAnimationPlayer().cancel();
         }
         if (mYOverviewAnim != null) {
-            mYOverviewAnim.getAnimationPlayer().cancel();
+            mYOverviewAnim.cancelAnimation();
         }
-        mShelfPeekAnim.setShelfState(ShelfAnimState.CANCEL, LINEAR, 0);
         mMotionPauseDetector.clear();
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/OverviewToAllAppsTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/OverviewToAllAppsTouchController.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/OverviewToAllAppsTouchController.java
rename to quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/OverviewToAllAppsTouchController.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
rename to quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index 20ee61d..1208c6c 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -24,7 +24,6 @@
 import static com.android.launcher3.anim.Interpolators.ACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
 import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
@@ -142,6 +141,10 @@
                 Log.d(TestProtocol.OVERIEW_NOT_ALLAPPS,
                         "PortraitStatesTouchController.getTargetState 1");
             }
+            if (removeShelfFromOverview(mLauncher)) {
+                // Don't allow swiping down to overview.
+                return NORMAL;
+            }
             return TouchInteractionService.isConnected() ?
                     mLauncher.getStateManager().getLastState() : NORMAL;
         } else if (fromState == OVERVIEW) {
@@ -150,7 +153,7 @@
                         "PortraitStatesTouchController.getTargetState 2");
             }
             LauncherState positiveDragTarget = ALL_APPS;
-            if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(mLauncher)) {
+            if (removeShelfFromOverview(mLauncher)) {
                 // Don't allow swiping up to all apps.
                 positiveDragTarget = OVERVIEW;
             }
@@ -245,7 +248,7 @@
 
         final StateAnimationConfig config = totalShift == 0 ? new StateAnimationConfig()
                 : getConfigForStates(mFromState, mToState);
-        config.animFlags = updateAnimComponentsOnReinit(animFlags);
+        config.animFlags = animFlags;
         config.duration = maxAccuracy;
 
         cancelPendingAnim();
@@ -279,14 +282,6 @@
         return 1 / totalShift;
     }
 
-    /**
-     * Give subclasses the chance to update the animation when we re-initialize towards a new state.
-     */
-    @AnimationFlags
-    protected int updateAnimComponentsOnReinit(@AnimationFlags int animComponents) {
-        return animComponents;
-    }
-
     private void cancelPendingAnim() {
         if (mPendingAnimation != null) {
             mPendingAnimation.finish(false, Touch.SWIPE);
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
rename to quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
similarity index 91%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
rename to quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index 0ee5d04..df6194d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -16,7 +16,6 @@
 package com.android.launcher3.uioverrides.touchcontrollers;
 
 import static com.android.launcher3.AbstractFloatingView.TYPE_ACCESSIBLE;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_BOTH;
 import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_NEGATIVE;
 import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_POSITIVE;
@@ -25,6 +24,7 @@
 import android.animation.AnimatorListenerAdapter;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.animation.Interpolator;
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BaseDraggingActivity;
@@ -82,7 +82,15 @@
         mDetector = new SingleAxisSwipeDetector(activity, this, dir);
     }
 
-    private boolean canInterceptTouch() {
+    private boolean canInterceptTouch(MotionEvent ev) {
+        if ((ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) != 0) {
+            // Don't intercept swipes on the nav bar, as user might be trying to go home
+            // during a task dismiss animation.
+            if (mCurrentAnimation != null) {
+                mCurrentAnimation.getAnimationPlayer().end();
+            }
+            return false;
+        }
         if (mCurrentAnimation != null) {
             mCurrentAnimation.forceFinishIfCloseToEnd();
         }
@@ -118,7 +126,7 @@
             clearState();
         }
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-            mNoIntercept = !canInterceptTouch();
+            mNoIntercept = !canInterceptTouch(ev);
             if (mNoIntercept) {
                 return false;
             }
@@ -206,14 +214,19 @@
         long maxDuration = 2 * secondaryLayerDimension;
         int verticalFactor = orientationHandler.getTaskDragDisplacementFactor(mIsRtl);
         int secondaryTaskDimension = orientationHandler.getSecondaryDimension(mTaskBeingDragged);
+        // The interpolator controlling the most prominent visual movement. We use this to determine
+        // whether we passed SUCCESS_TRANSITION_PROGRESS.
+        final Interpolator currentInterpolator;
         if (goingUp) {
+            currentInterpolator = Interpolators.LINEAR;
             mPendingAnimation = mRecentsView.createTaskDismissAnimation(mTaskBeingDragged,
                     true /* animateTaskView */, true /* removeTask */, maxDuration);
 
             mEndDisplacement = -secondaryTaskDimension;
         } else {
+            currentInterpolator = Interpolators.ZOOM_IN;
             mPendingAnimation = mRecentsView.createTaskLaunchAnimation(
-                    mTaskBeingDragged, maxDuration, Interpolators.ZOOM_IN);
+                    mTaskBeingDragged, maxDuration, currentInterpolator);
 
             // Since the thumbnail is what is filling the screen, based the end displacement on it.
             View thumbnailView = mTaskBeingDragged.getThumbnail();
@@ -228,6 +241,9 @@
         }
         mCurrentAnimation = mPendingAnimation.createPlaybackController()
                 .setOnCancelRunnable(this::clearState);
+        // Setting this interpolator doesn't affect the visual motion, but is used to determine
+        // whether we successfully reached the target state in onDragEnd().
+        mCurrentAnimation.getTarget().setInterpolator(currentInterpolator);
         onUserControlledAnimationCreated(mCurrentAnimation);
         mCurrentAnimation.getTarget().addListener(this);
         mCurrentAnimation.dispatchOnStart();
@@ -262,11 +278,6 @@
         mCurrentAnimation.setPlayFraction(Utilities.boundToRange(
                 totalDisplacement * mProgressMultiplier, 0, 1));
 
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            if (mRecentsView.getCurrentPage() != 0 || isGoingUp) {
-                mRecentsView.redrawLiveTile(true);
-            }
-        }
         return true;
     }
 
@@ -297,13 +308,6 @@
         }
 
         mCurrentAnimation.setEndAction(() -> onCurrentAnimationEnd(goingToEnd, logAction));
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            mCurrentAnimation.getAnimationPlayer().addUpdateListener(valueAnimator -> {
-                if (mRecentsView.getCurrentPage() != 0 || mCurrentAnimationIsGoingUp) {
-                    mRecentsView.redrawLiveTile(true);
-                }
-            });
-        }
         mCurrentAnimation.startWithVelocity(mActivity, goingToEnd,
                 velocity, mEndDisplacement, animationDuration);
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TransposedQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TransposedQuickSwitchTouchController.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TransposedQuickSwitchTouchController.java
rename to quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TransposedQuickSwitchTouchController.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
similarity index 77%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java
rename to quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 6c4c5d3..aaa2720 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -15,21 +15,23 @@
  */
 package com.android.quickstep;
 
+import static android.widget.Toast.LENGTH_SHORT;
+
 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER;
 import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_GESTURE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_LEFT;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_RIGHT;
-import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
+import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
+import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
 import static com.android.quickstep.GestureState.GestureEndTarget.HOME;
 import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
 import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK;
@@ -38,28 +40,29 @@
 import static com.android.quickstep.GestureState.STATE_END_TARGET_SET;
 import static com.android.quickstep.GestureState.STATE_RECENTS_SCROLLING_FINISHED;
 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
-import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
-import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.HIDE;
-import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.PEEK;
 import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
 
 import android.animation.Animator;
-import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.PointF;
+import android.graphics.Rect;
 import android.os.Build;
 import android.os.SystemClock;
+import android.util.Log;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnApplyWindowInsetsListener;
 import android.view.ViewTreeObserver.OnDrawListener;
 import android.view.WindowInsets;
 import android.view.animation.Interpolator;
+import android.widget.Toast;
 
+import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 
 import com.android.launcher3.AbstractFloatingView;
@@ -67,22 +70,28 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.logging.StatsLogManager.StatsLogger;
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.util.TraceHelper;
+import com.android.launcher3.util.VibratorWrapper;
+import com.android.launcher3.util.WindowBounds;
 import com.android.quickstep.BaseActivityInterface.AnimationFactory;
 import com.android.quickstep.GestureState.GestureEndTarget;
 import com.android.quickstep.inputconsumers.OverviewInputConsumer;
 import com.android.quickstep.util.ActiveGestureLog;
+import com.android.quickstep.util.ActivityInitListener;
+import com.android.quickstep.util.AnimatorControllerWithResistance;
+import com.android.quickstep.util.InputConsumerProxy;
 import com.android.quickstep.util.RectFSpringAnim;
-import com.android.quickstep.util.ShelfPeekAnim;
-import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState;
+import com.android.quickstep.util.SurfaceTransactionApplier;
+import com.android.quickstep.util.TransformParams;
 import com.android.quickstep.views.LiveTileOverlay;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
@@ -94,17 +103,34 @@
 import com.android.systemui.shared.system.TaskInfoCompat;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 
+import java.util.ArrayList;
+import java.util.function.Consumer;
+
 /**
  * Handles the navigation gestures when Launcher is the default home activity.
- * TODO: Merge this with BaseSwipeUpHandler
  */
-@TargetApi(Build.VERSION_CODES.O)
-public abstract class BaseSwipeUpHandlerV2<T extends StatefulActivity<?>, Q extends RecentsView>
-        extends BaseSwipeUpHandler<T, Q> implements OnApplyWindowInsetsListener {
-    private static final String TAG = BaseSwipeUpHandlerV2.class.getSimpleName();
+@TargetApi(Build.VERSION_CODES.R)
+public abstract class AbsSwipeUpHandler<T extends StatefulActivity<?>, Q extends RecentsView>
+        extends SwipeUpAnimationLogic implements OnApplyWindowInsetsListener,
+        RecentsAnimationCallbacks.RecentsAnimationListener {
+    private static final String TAG = "AbsSwipeUpHandler";
 
     private static final String[] STATE_NAMES = DEBUG_STATES ? new String[16] : null;
 
+    protected final BaseActivityInterface<?, T> mActivityInterface;
+    protected final InputConsumerProxy mInputConsumerProxy;
+    protected final ActivityInitListener mActivityInitListener;
+    // Callbacks to be made once the recents animation starts
+    private final ArrayList<Runnable> mRecentsAnimationStartCallbacks = new ArrayList<>();
+    protected RecentsAnimationController mRecentsAnimationController;
+    protected RecentsAnimationTargets mRecentsAnimationTargets;
+    protected T mActivity;
+    protected Q mRecentsView;
+    protected Runnable mGestureEndCallback;
+    protected MultiStateCallback mStateCallback;
+    protected boolean mCanceled;
+    private boolean mRecentsViewScrollLinked = false;
+
     private static int getFlagForIndex(int index, String name) {
         if (DEBUG_STATES) {
             STATE_NAMES[index] = name;
@@ -174,15 +200,14 @@
 
     // Either RectFSpringAnim (if animating home) or ObjectAnimator (from mCurrentShift) otherwise
     private RunningWindowAnim mRunningWindowAnim;
-    private boolean mIsShelfPeeking;
+    private boolean mIsMotionPaused;
 
     private boolean mContinuingLastGesture;
 
     private ThumbnailData mTaskSnapshot;
 
     // Used to control launcher components throughout the swipe gesture.
-    private AnimatorPlaybackController mLauncherTransitionController;
-    private boolean mHasLauncherTransitionControllerStarted;
+    private AnimatorControllerWithResistance mLauncherTransitionController;
 
     private AnimationFactory mAnimationFactory = (t) -> { };
 
@@ -200,11 +225,15 @@
 
     private final Runnable mOnDeferredActivityLaunch = this::onDeferredActivityLaunch;
 
-    public BaseSwipeUpHandlerV2(Context context, RecentsAnimationDeviceState deviceState,
+    public AbsSwipeUpHandler(Context context, RecentsAnimationDeviceState deviceState,
             TaskAnimationManager taskAnimationManager, GestureState gestureState,
             long touchTimeMs, boolean continuingLastGesture,
             InputConsumerController inputConsumer) {
-        super(context, deviceState, gestureState, inputConsumer);
+        super(context, deviceState, gestureState, new TransformParams());
+        mActivityInterface = gestureState.getActivityInterface();
+        mActivityInitListener = mActivityInterface.createActivityInitListener(this::onActivityInit);
+        mInputConsumerProxy =
+                new InputConsumerProxy(inputConsumer, this::createNewInputProxyHandler);
         mTaskAnimationManager = taskAnimationManager;
         mTouchTimeMs = touchTimeMs;
         mContinuingLastGesture = continuingLastGesture;
@@ -266,10 +295,6 @@
         mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED | STATE_RESUME_LAST_TASK,
                 this::notifyTransitionCancelled);
 
-        mGestureState.runOnceAtState(STATE_END_TARGET_SET,
-                () -> mDeviceState.onEndTargetCalculated(mGestureState.getEndTarget(),
-                        mActivityInterface));
-
         if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             mStateCallback.addChangeListener(STATE_APP_CONTROLLER_RECEIVED | STATE_LAUNCHER_PRESENT
                             | STATE_SCREENSHOT_VIEW_SHOWN | STATE_CAPTURE_SCREENSHOT,
@@ -277,9 +302,17 @@
         }
     }
 
-    @Override
     protected boolean onActivityInit(Boolean alreadyOnHome) {
-        super.onActivityInit(alreadyOnHome);
+        T createdActivity = mActivityInterface.getCreatedActivity();
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.PAUSE_NOT_DETECTED, "BaseSwipeUpHandler.1");
+        }
+        if (createdActivity != null) {
+            if (TestProtocol.sDebugTracing) {
+                Log.d(TestProtocol.PAUSE_NOT_DETECTED, "BaseSwipeUpHandler.2");
+            }
+            initTransitionEndpoints(createdActivity.getDeviceProfile());
+        }
         final T activity = mActivityInterface.getCreatedActivity();
         if (mActivity == activity) {
             return true;
@@ -313,18 +346,14 @@
         }
 
         setupRecentsViewUi();
-
-        if (mDeviceState.getNavMode() == TWO_BUTTONS) {
-            // If the device is in two button mode, swiping up will show overview with predictions
-            // so we need to kick off switching to the overview predictions as soon as possible
-            mActivityInterface.updateOverviewPredictionState();
-        }
         linkRecentsViewScroll();
 
         return true;
     }
 
-    @Override
+    /**
+     * Return true if the window should be translated horizontally if the recents view scrolls
+     */
     protected boolean moveWindowWithRecentsScroll() {
         return mGestureState.getEndTarget() != HOME;
     }
@@ -337,7 +366,7 @@
         if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
             return;
         }
-        mTaskViewSimulator.setRecentsConfiguration(mActivity.getResources().getConfiguration());
+        mTaskViewSimulator.setRecentsRotation(mActivity.getDisplay().getRotation());
 
         // If we've already ended the gesture and are going home, don't prepare recents UI,
         // as that will set the state as BACKGROUND_APP, overriding the animation to NORMAL.
@@ -400,6 +429,11 @@
         mGestureState.getActivityInterface().setOnDeferredActivityLaunchCallback(
                 mOnDeferredActivityLaunch);
 
+        mGestureState.runOnceAtState(STATE_END_TARGET_SET,
+                () -> mDeviceState.getRotationTouchHelper().
+                        onEndTargetCalculated(mGestureState.getEndTarget(),
+                                mActivityInterface));
+
         notifyGestureStartedAsync();
     }
 
@@ -423,7 +457,7 @@
     }
 
     protected void notifyGestureAnimationStartToRecents() {
-        mRecentsView.onGestureAnimationStart(mGestureState.getRunningTaskId());
+        mRecentsView.onGestureAnimationStart(mGestureState.getRunningTask());
     }
 
     private void launcherFrameDrawn() {
@@ -448,15 +482,13 @@
                 .getHighResLoadingState().setVisible(true);
     }
 
-    @Override
+    /**
+     * Called when motion pause is detected
+     */
     public void onMotionPauseChanged(boolean isPaused) {
-        setShelfState(isPaused ? PEEK : HIDE, ShelfPeekAnim.INTERPOLATOR, ShelfPeekAnim.DURATION);
-
-        if (mDeviceState.isFullyGesturalNavMode() && isPaused) {
-            // In fully gestural nav mode, switch to overview predictions once the user has paused
-            // (this is a no-op if the predictions are already in that state)
-            mActivityInterface.updateOverviewPredictionState();
-        }
+        mIsMotionPaused = isPaused;
+        maybeUpdateRecentsAttachedState();
+        performHapticFeedback();
     }
 
     public void maybeUpdateRecentsAttachedState() {
@@ -487,12 +519,25 @@
             // The window is going away so make sure recents is always visible in this case.
             recentsAttachedToAppWindow = true;
         } else {
-            recentsAttachedToAppWindow = mIsShelfPeeking || mIsLikelyToStartNewTask;
+            recentsAttachedToAppWindow = mIsMotionPaused || mIsLikelyToStartNewTask;
         }
         mAnimationFactory.setRecentsAttachedToAppWindow(recentsAttachedToAppWindow, animate);
+
+        // Reapply window transform throughout the attach animation, as the animation affects how
+        // much the window is bound by overscroll (vs moving freely).
+        if (animate) {
+            ValueAnimator reapplyWindowTransformAnim = ValueAnimator.ofFloat(0, 1);
+            reapplyWindowTransformAnim.addUpdateListener(anim -> {
+                if (mRunningWindowAnim == null) {
+                    applyWindowTransform();
+                }
+            });
+            reapplyWindowTransformAnim.setDuration(RECENTS_ATTACH_DURATION).start();
+        } else {
+            applyWindowTransform();
+        }
     }
 
-    @Override
     public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) {
         setIsLikelyToStartNewTask(isLikelyToStartNewTask, true /* animate */);
     }
@@ -504,19 +549,6 @@
         }
     }
 
-    @UiThread
-    public void setShelfState(ShelfAnimState shelfState, Interpolator interpolator, long duration) {
-        mAnimationFactory.setShelfState(shelfState, interpolator, duration);
-        boolean wasShelfPeeking = mIsShelfPeeking;
-        mIsShelfPeeking = shelfState == PEEK;
-        if (mIsShelfPeeking != wasShelfPeeking) {
-            maybeUpdateRecentsAttachedState();
-        }
-        if (shelfState.shouldPreformHaptic) {
-            performHapticFeedback();
-        }
-    }
-
     private void buildAnimationController() {
         if (!canCreateNewOrUpdateExistingLauncherTransitionController()) {
             return;
@@ -527,11 +559,11 @@
 
     /**
      * We don't want to change mLauncherTransitionController if mGestureState.getEndTarget() == HOME
-     * (it has its own animation) or if we're already animating the current controller.
+     * (it has its own animation).
      * @return Whether we can create the launcher controller or update its progress.
      */
     private boolean canCreateNewOrUpdateExistingLauncherTransitionController() {
-        return mGestureState.getEndTarget() != HOME && !mHasLauncherTransitionControllerStarted;
+        return mGestureState.getEndTarget() != HOME;
     }
 
     @Override
@@ -541,28 +573,22 @@
         return result;
     }
 
-    private void onAnimatorPlaybackControllerCreated(AnimatorPlaybackController anim) {
+    private void onAnimatorPlaybackControllerCreated(AnimatorControllerWithResistance anim) {
         mLauncherTransitionController = anim;
-        mLauncherTransitionController.dispatchSetInterpolator(t -> t * mDragLengthFactor);
-        mLauncherTransitionController.dispatchOnStart();
+        mLauncherTransitionController.getNormalController().dispatchOnStart();
         updateLauncherTransitionProgress();
     }
 
-    @Override
     public Intent getLaunchIntent() {
         return mGestureState.getOverviewIntent();
     }
 
+    /**
+     * Called when the value of {@link #mCurrentShift} changes
+     */
+    @UiThread
     @Override
     public void updateFinalShift() {
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            if (mRecentsAnimationTargets != null) {
-                LiveTileOverlay.INSTANCE.update(
-                        mTaskViewSimulator.getCurrentCropRect(),
-                        mTaskViewSimulator.getCurrentCornerRadius());
-            }
-        }
-
         final boolean passed = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW;
         if (passed != mPassedOverviewThreshold) {
             mPassedOverviewThreshold = passed;
@@ -573,6 +599,14 @@
 
         updateSysUiFlags(mCurrentShift.value);
         applyWindowTransform();
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            if (mRecentsAnimationTargets != null) {
+                LiveTileOverlay.INSTANCE.update(
+                        mTaskViewSimulator.getCurrentRect(),
+                        mTaskViewSimulator.getCurrentCornerRadius());
+            }
+        }
+
         updateLauncherTransitionProgress();
     }
 
@@ -581,10 +615,7 @@
                 || !canCreateNewOrUpdateExistingLauncherTransitionController()) {
             return;
         }
-        // Normalize the progress to 0 to 1, as the animation controller will clamp it to that
-        // anyway. The controller mimics the drag length factor by applying it to its interpolators.
-        float progress = mCurrentShift.value / mDragLengthFactor;
-        mLauncherTransitionController.setPlayFraction(progress);
+        mLauncherTransitionController.setProgress(mCurrentShift.value, mDragLengthFactor);
     }
 
     /**
@@ -613,7 +644,41 @@
     public void onRecentsAnimationStart(RecentsAnimationController controller,
             RecentsAnimationTargets targets) {
         ActiveGestureLog.INSTANCE.addLog("startRecentsAnimationCallback", targets.apps.length);
-        super.onRecentsAnimationStart(controller, targets);
+        mRecentsAnimationController = controller;
+        mRecentsAnimationTargets = targets;
+        mTransformParams.setTargetSet(mRecentsAnimationTargets);
+        RemoteAnimationTargetCompat runningTaskTarget = targets.findTask(
+                mGestureState.getRunningTaskId());
+
+        if (runningTaskTarget != null) {
+            mTaskViewSimulator.setPreview(runningTaskTarget);
+        }
+
+        // Only initialize the device profile, if it has not been initialized before, as in some
+        // configurations targets.homeContentInsets may not be correct.
+        if (mActivity == null) {
+            DeviceProfile dp = mTaskViewSimulator.getOrientationState().getLauncherDeviceProfile();
+            if (targets.minimizedHomeBounds != null && runningTaskTarget != null) {
+                Rect overviewStackBounds = mActivityInterface
+                        .getOverviewWindowBounds(targets.minimizedHomeBounds, runningTaskTarget);
+                dp = dp.getMultiWindowProfile(mContext,
+                        new WindowBounds(overviewStackBounds, targets.homeContentInsets));
+            } else {
+                // If we are not in multi-window mode, home insets should be same as system insets.
+                dp = dp.copy(mContext);
+            }
+            dp.updateInsets(targets.homeContentInsets);
+            dp.updateIsSeascape(mContext);
+            initTransitionEndpoints(dp);
+        }
+
+        // Notify when the animation starts
+        if (!mRecentsAnimationStartCallbacks.isEmpty()) {
+            for (Runnable action : new ArrayList<>(mRecentsAnimationStartCallbacks)) {
+                action.run();
+            }
+            mRecentsAnimationStartCallbacks.clear();
+        }
 
         // Only add the callback to enable the input consumer after we actually have the controller
         mStateCallback.runOnceAtState(STATE_APP_CONTROLLER_RECEIVED | STATE_GESTURE_STARTED,
@@ -630,10 +695,14 @@
         mStateCallback.setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
 
         // Defer clearing the controller and the targets until after we've updated the state
-        super.onRecentsAnimationCanceled(thumbnailData);
+        mRecentsAnimationController = null;
+        mRecentsAnimationTargets = null;
+        if (mRecentsView != null) {
+            mRecentsView.setRecentsAnimationTargets(null, null);
+        }
     }
 
-    @Override
+    @UiThread
     public void onGestureStarted(boolean isLikelyToStartNewTask) {
         notifyGestureStartedAsync();
         setIsLikelyToStartNewTask(isLikelyToStartNewTask, false /* animate */);
@@ -657,7 +726,7 @@
     /**
      * Called as a result on ACTION_CANCEL to return the UI to the start state.
      */
-    @Override
+    @UiThread
     public void onGestureCancelled() {
         updateDisplacement(0);
         mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED);
@@ -670,7 +739,7 @@
      * @param velocity The x and y components of the velocity when the gesture ends.
      * @param downPos The x and y value of where the gesture started.
      */
-    @Override
+    @UiThread
     public void onGestureEnded(float endVelocity, PointF velocity, PointF downPos) {
         float flingThreshold = mContext.getResources()
                 .getDimension(R.dimen.quickstep_fling_threshold_velocity);
@@ -688,7 +757,10 @@
         handleNormalGestureEnd(endVelocity, isFling, velocity, false /* isCancel */);
     }
 
-    @Override
+    /**
+     * Called to create a input proxy for the running task
+     */
+    @UiThread
     protected InputConsumer createNewInputProxyHandler() {
         endRunningWindowAnim(mGestureState.getEndTarget() == HOME /* cancel */);
         endLauncherTransitionController();
@@ -729,7 +801,7 @@
         ActiveGestureLog.INSTANCE.addLog("onSettledOnEndTarget " + mGestureState.getEndTarget());
     }
 
-    @Override
+    /** @return Whether this was the task we were waiting to appear, and thus handled it. */
     protected boolean handleTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {
         if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
             return false;
@@ -764,7 +836,7 @@
             if (isCancel) {
                 endTarget = LAST_TASK;
             } else if (mDeviceState.isFullyGesturalNavMode()) {
-                if (mIsShelfPeeking) {
+                if (mIsMotionPaused) {
                     endTarget = RECENTS;
                 } else if (goingToNewTask) {
                     endTarget = NEW_TASK;
@@ -786,7 +858,7 @@
 
             if (mDeviceState.isFullyGesturalNavMode() && isSwipeUp && !willGoToNewTaskOnSwipeUp) {
                 endTarget = HOME;
-            } else if (mDeviceState.isFullyGesturalNavMode() && isSwipeUp && !mIsShelfPeeking) {
+            } else if (mDeviceState.isFullyGesturalNavMode() && isSwipeUp && !mIsMotionPaused) {
                 // If swiping at a diagonal, base end target on the faster velocity.
                 endTarget = NEW_TASK;
             } else if (isSwipeUp) {
@@ -850,13 +922,10 @@
             }
         }
 
-        if (endTarget.isLauncher && mRecentsAnimationController != null) {
-            mRecentsAnimationController.enableInputProxy(mInputConsumer,
-                    this::createNewInputProxyHandler);
+        if (endTarget.isLauncher) {
+            mInputConsumerProxy.enable();
         }
-
         if (endTarget == HOME) {
-            setShelfState(ShelfAnimState.CANCEL, LINEAR, 0);
             duration = Math.max(MIN_OVERSHOOT_DURATION, duration);
         } else if (endTarget == RECENTS) {
             LiveTileOverlay.INSTANCE.startIconAnimation();
@@ -872,9 +941,6 @@
                 }
                 duration = Math.max(duration, mRecentsView.getScroller().getDuration());
             }
-            if (mDeviceState.isFullyGesturalNavMode()) {
-                setShelfState(ShelfAnimState.OVERVIEW, interpolator, duration);
-            }
         }
 
         // Let RecentsView handle the scrolling to the task, which we launch in startNewTask()
@@ -889,22 +955,7 @@
         animateToProgress(startShift, endShift, duration, interpolator, endTarget, velocityPxPerMs);
     }
 
-    private void doLogGesture(GestureEndTarget endTarget) {
-        DeviceProfile dp = mDp;
-        if (dp == null || mDownPos == null) {
-            // We probably never received an animation controller, skip logging.
-            return;
-        }
-
-        int pageIndex = endTarget == LAST_TASK
-                ? LOG_NO_OP_PAGE_INDEX
-                : mRecentsView.getNextPage();
-        UserEventDispatcher.newInstance(mContext).logStateChangeAction(
-                mLogAction, mLogDirection,
-                (int) mDownPos.x, (int) mDownPos.y,
-                ContainerType.NAVBAR, ContainerType.APP,
-                endTarget.containerType,
-                pageIndex);
+    private void doLogGesture(GestureEndTarget endTarget, @Nullable TaskView targetTask) {
         StatsLogManager.EventEnum event;
         switch (endTarget) {
             case HOME:
@@ -922,10 +973,29 @@
             default:
                 event = IGNORE;
         }
-        StatsLogManager.newInstance(mContext).logger()
+        StatsLogger logger = StatsLogManager.newInstance(mContext).logger()
                 .withSrcState(LAUNCHER_STATE_BACKGROUND)
-                .withDstState(StatsLogManager.containerTypeToAtomState(endTarget.containerType))
-                .log(event);
+                .withDstState(StatsLogManager.containerTypeToAtomState(endTarget.containerType));
+        if (targetTask != null) {
+            logger.withItemInfo(targetTask.getItemInfo());
+        }
+        logger.log(event);
+
+
+        DeviceProfile dp = mDp;
+        if (dp == null || mDownPos == null) {
+            // We probably never received an animation controller, skip logging.
+            return;
+        }
+        int pageIndex = endTarget == LAST_TASK
+                ? LOG_NO_OP_PAGE_INDEX
+                : mRecentsView.getNextPage();
+        UserEventDispatcher.newInstance(mContext).logStateChangeAction(
+                mLogAction, mLogDirection,
+                (int) mDownPos.x, (int) mDownPos.y,
+                ContainerType.NAVBAR, ContainerType.APP,
+                endTarget.containerType,
+                pageIndex);
     }
 
     /** Animates to the given progress, where 0 is the current app and 1 is overview. */
@@ -969,6 +1039,7 @@
         }
 
         if (mGestureState.getEndTarget() == HOME) {
+            mTaskViewSimulator.setDrawsBelowRecents(false);
             HomeAnimationFactory homeAnimFactory = createHomeAnimationFactory(duration);
             RectFSpringAnim windowAnim = createWindowAnimationToHome(start, homeAnimFactory);
             windowAnim.addAnimatorListener(new AnimationSuccessListener() {
@@ -1029,31 +1100,6 @@
             windowAnim.start();
             mRunningWindowAnim = RunningWindowAnim.wrap(windowAnim);
         }
-        // Always play the entire launcher animation when going home, since it is separate from
-        // the animation that has been controlled thus far.
-        if (mGestureState.getEndTarget() == HOME) {
-            start = 0;
-        }
-
-        // We want to use the same interpolator as the window, but need to adjust it to
-        // interpolate over the remaining progress (end - start).
-        TimeInterpolator adjustedInterpolator = Interpolators.mapToProgress(
-                interpolator, start, end);
-        if (mLauncherTransitionController == null) {
-            return;
-        }
-        if (start == end || duration <= 0) {
-            mLauncherTransitionController.dispatchSetInterpolator(t -> end);
-        } else {
-            mLauncherTransitionController.dispatchSetInterpolator(adjustedInterpolator);
-        }
-        mLauncherTransitionController.getAnimationPlayer().setDuration(Math.max(0, duration));
-
-        if (UNSTABLE_SPRINGS.get()) {
-            mLauncherTransitionController.dispatchOnStart();
-        }
-        mLauncherTransitionController.getAnimationPlayer().start();
-        mHasLauncherTransitionControllerStarted = true;
     }
 
     private void computeRecentsScrollIfInvisible() {
@@ -1104,10 +1150,12 @@
                 mActivityInterface.onSwipeUpToHomeComplete(mDeviceState);
             }
         });
+        if (mRecentsAnimationTargets != null) {
+            mRecentsAnimationTargets.addReleaseCheck(anim);
+        }
         return anim;
     }
 
-    @Override
     public void onConsumerAboutToBeSwitched() {
         if (mActivity != null) {
             // In the off chance that the gesture ends before Launcher is started, we should clear
@@ -1130,21 +1178,13 @@
     private void resumeLastTask() {
         mRecentsAnimationController.finish(false /* toRecents */, null);
         ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", false);
-        doLogGesture(LAST_TASK);
+        doLogGesture(LAST_TASK, null);
         reset();
     }
 
     @UiThread
     private void startNewTask() {
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            mRecentsAnimationController.finish(true /* toRecents */, this::startNewTaskInternal);
-        } else {
-            startNewTaskInternal();
-        }
-    }
-
-    @UiThread
-    private void startNewTaskInternal() {
+        TaskView taskToLaunch = mRecentsView == null ? null : mRecentsView.getNextPageTaskView();
         startNewTask(success -> {
             if (!success) {
                 reset();
@@ -1153,13 +1193,23 @@
                 endLauncherTransitionController();
                 updateSysUiFlags(1 /* windowProgress == overview */);
             }
-            doLogGesture(NEW_TASK);
+            doLogGesture(NEW_TASK, taskToLaunch);
         });
     }
 
-    @Override
+    /**
+     * Called when we successfully startNewTask() on the task that was previously running. Normally
+     * we call resumeLastTask() when returning to the previously running task, but this handles a
+     * specific edge case: if we switch from A to B, and back to A before B appears, we need to
+     * start A again to ensure it stays on top.
+     */
+    @androidx.annotation.CallSuper
     protected void onRestartPreviouslyAppearedTask() {
-        super.onRestartPreviouslyAppearedTask();
+        // Finish the controller here, since we won't get onTaskAppeared() for a task that already
+        // appeared.
+        if (mRecentsAnimationController != null) {
+            mRecentsAnimationController.finish(false, null);
+        }
         reset();
     }
 
@@ -1174,13 +1224,10 @@
     private void cancelCurrentAnimation() {
         mCanceled = true;
         mCurrentShift.cancelAnimation();
-        if (mLauncherTransitionController != null && mLauncherTransitionController
-                .getAnimationPlayer().isStarted()) {
-            mLauncherTransitionController.getAnimationPlayer().cancel();
-        }
     }
 
     private void invalidateHandler() {
+        mInputConsumerProxy.destroy();
         endRunningWindowAnim(false /* cancel */);
 
         if (mGestureEndCallback != null) {
@@ -1200,9 +1247,11 @@
     }
 
     private void endLauncherTransitionController() {
-        setShelfState(ShelfAnimState.CANCEL, LINEAR, 0);
         if (mLauncherTransitionController != null) {
-            mLauncherTransitionController.getAnimationPlayer().end();
+            // End the animation, but stay at the same visual progress.
+            mLauncherTransitionController.getNormalController().dispatchSetInterpolator(
+                    t -> Utilities.boundToRange(mCurrentShift.value, 0, 1));
+            mLauncherTransitionController.getNormalController().getAnimationPlayer().end();
             mLauncherTransitionController = null;
         }
     }
@@ -1263,7 +1312,7 @@
                     // new thumbnail
                     finishTransitionPosted = ViewUtils.postDraw(taskView,
                             () -> mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED),
-                                    this::isCanceled);
+                            this::isCanceled);
                 }
             }
             if (!finishTransitionPosted) {
@@ -1298,7 +1347,7 @@
                     () -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
         }
         ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", true);
-        doLogGesture(HOME);
+        doLogGesture(HOME, mRecentsView == null ? null : mRecentsView.getCurrentPageTaskView());
     }
 
     protected abstract void finishRecentsControllerToHome(Runnable callback);
@@ -1313,7 +1362,7 @@
         mRecentsView.onSwipeUpAnimationSuccess();
 
         SystemUiProxy.INSTANCE.get(mContext).onOverviewShown(false, TAG);
-        doLogGesture(RECENTS);
+        doLogGesture(RECENTS, mRecentsView.getCurrentPageTaskView());
         reset();
     }
 
@@ -1332,4 +1381,171 @@
         return app.isNotInRecents
                 || app.activityType == ACTIVITY_TYPE_HOME;
     }
+
+    /**
+     * To be called at the end of constructor of subclasses. This calls various methods which can
+     * depend on proper class initialization.
+     */
+    protected void initAfterSubclassConstructor() {
+        initTransitionEndpoints(
+                mTaskViewSimulator.getOrientationState().getLauncherDeviceProfile());
+    }
+
+    protected void performHapticFeedback() {
+        VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC);
+    }
+
+    public Consumer<MotionEvent> getRecentsViewDispatcher(float navbarRotation) {
+        return mRecentsView != null ? mRecentsView.getEventDispatcher(navbarRotation) : null;
+    }
+
+    public void setGestureEndCallback(Runnable gestureEndCallback) {
+        mGestureEndCallback = gestureEndCallback;
+    }
+
+    protected void linkRecentsViewScroll() {
+        SurfaceTransactionApplier.create(mRecentsView, applier -> {
+            mTransformParams.setSyncTransactionApplier(applier);
+            runOnRecentsAnimationStart(() ->
+                    mRecentsAnimationTargets.addReleaseCheck(applier));
+        });
+
+        mRecentsView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
+            if (moveWindowWithRecentsScroll()) {
+                updateFinalShift();
+            }
+        });
+        runOnRecentsAnimationStart(() ->
+                mRecentsView.setRecentsAnimationTargets(mRecentsAnimationController,
+                        mRecentsAnimationTargets));
+        mRecentsViewScrollLinked = true;
+    }
+
+    protected void startNewTask(Consumer<Boolean> resultCallback) {
+        // Launch the task user scrolled to (mRecentsView.getNextPage()).
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            // We finish recents animation inside launchTask() when live tile is enabled.
+            mRecentsView.getNextPageTaskView().launchTask(false /* animate */,
+                    true /* freezeTaskList */);
+        } else {
+            if (!mCanceled) {
+                TaskView nextTask = mRecentsView.getNextPageTaskView();
+                if (nextTask != null) {
+                    int taskId = nextTask.getTask().key.id;
+                    mGestureState.updateLastStartedTaskId(taskId);
+                    boolean hasTaskPreviouslyAppeared = mGestureState.getPreviouslyAppearedTaskIds()
+                            .contains(taskId);
+                    nextTask.launchTask(false /* animate */, true /* freezeTaskList */,
+                            success -> {
+                                resultCallback.accept(success);
+                                if (success) {
+                                    if (hasTaskPreviouslyAppeared) {
+                                        onRestartPreviouslyAppearedTask();
+                                    }
+                                } else {
+                                    mActivityInterface.onLaunchTaskFailed();
+                                    nextTask.notifyTaskLaunchFailed(TAG);
+                                    mRecentsAnimationController.finish(true /* toRecents */, null);
+                                }
+                            }, MAIN_EXECUTOR.getHandler());
+                } else {
+                    mActivityInterface.onLaunchTaskFailed();
+                    Toast.makeText(mContext, R.string.activity_not_available, LENGTH_SHORT).show();
+                    mRecentsAnimationController.finish(true /* toRecents */, null);
+                }
+            }
+            mCanceled = false;
+        }
+    }
+
+    /**
+     * Runs the given {@param action} if the recents animation has already started, or queues it to
+     * be run when it is next started.
+     */
+    protected void runOnRecentsAnimationStart(Runnable action) {
+        if (mRecentsAnimationTargets == null) {
+            mRecentsAnimationStartCallbacks.add(action);
+        } else {
+            action.run();
+        }
+    }
+
+    /**
+     * TODO can we remove this now that we don't finish the controller until onTaskAppeared()?
+     * @return whether the recents animation has started and there are valid app targets.
+     */
+    protected boolean hasTargets() {
+        return mRecentsAnimationTargets != null && mRecentsAnimationTargets.hasTargets();
+    }
+
+    @Override
+    public void onRecentsAnimationFinished(RecentsAnimationController controller) {
+        mRecentsAnimationController = null;
+        mRecentsAnimationTargets = null;
+        if (mRecentsView != null) {
+            mRecentsView.setRecentsAnimationTargets(null, null);
+        }
+    }
+
+    @Override
+    public void onTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {
+        if (mRecentsAnimationController != null) {
+            if (handleTaskAppeared(appearedTaskTarget)) {
+                mRecentsAnimationController.finish(false /* toRecents */,
+                        null /* onFinishComplete */);
+                mActivityInterface.onLaunchTaskSuccess();
+                ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", false);
+            }
+        }
+    }
+
+    /**
+     * @return The index of the TaskView in RecentsView whose taskId matches the task that will
+     * resume if we finish the controller.
+     */
+    protected int getLastAppearedTaskIndex() {
+        return mGestureState.getLastAppearedTaskId() != -1
+                ? mRecentsView.getTaskIndexForId(mGestureState.getLastAppearedTaskId())
+                : mRecentsView.getRunningTaskIndex();
+    }
+
+    /**
+     * @return Whether we are continuing a gesture that already landed on a new task,
+     * but before that task appeared.
+     */
+    protected boolean hasStartedNewTask() {
+        return mGestureState.getLastStartedTaskId() != -1;
+    }
+
+    /**
+     * Registers a callback to run when the activity is ready.
+     * @param intent The intent that will be used to start the activity if it doesn't exist already.
+     */
+    public void initWhenReady(Intent intent) {
+        // Preload the plan
+        RecentsModel.INSTANCE.get(mContext).getTasks(null);
+
+        mActivityInitListener.register(intent);
+    }
+
+    /**
+     * Applies the transform on the recents animation
+     */
+    protected void applyWindowTransform() {
+        if (mWindowTransitionController != null) {
+            mWindowTransitionController.setProgress(mCurrentShift.value, mDragLengthFactor);
+        }
+        if (mRecentsAnimationTargets != null) {
+            if (mRecentsViewScrollLinked) {
+                mTaskViewSimulator.setScroll(mRecentsView.getScrollOffset());
+            }
+            mTaskViewSimulator.apply(mTransformParams);
+        }
+    }
+
+    public interface Factory {
+
+        AbsSwipeUpHandler<StatefulActivity<?>, RecentsView> newHandler(
+                GestureState gestureState, long touchTimeMs, boolean continuingLastGesture);
+    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
similarity index 92%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
rename to quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
index de83caf..55f5424 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
+++ b/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
@@ -24,6 +24,7 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
+import android.app.ActivityManager.RunningTaskInfo;
 import android.util.Log;
 import android.view.animation.Interpolator;
 
@@ -52,17 +53,17 @@
 
     private final BaseActivityInterface<?, T> mActivityInterface;
     // The id of the currently running task that is transitioning to overview.
-    private final int mTargetTaskId;
+    private final RunningTaskInfo mTargetTask;
     private final RecentsAnimationDeviceState mDeviceState;
 
     private T mActivity;
     private RecentsView mRecentsView;
 
     AppToOverviewAnimationProvider(
-            BaseActivityInterface<?, T> activityInterface, int targetTaskId,
+            BaseActivityInterface<?, T> activityInterface, RunningTaskInfo targetTask,
             RecentsAnimationDeviceState deviceState) {
         mActivityInterface = activityInterface;
-        mTargetTaskId = targetTaskId;
+        mTargetTask = targetTask;
         mDeviceState = deviceState;
     }
 
@@ -73,13 +74,13 @@
      * @param wasVisible true if it was visible before
      */
     boolean onActivityReady(T activity, Boolean wasVisible) {
-        activity.<RecentsView>getOverviewPanel().showCurrentTask(mTargetTaskId);
+        activity.<RecentsView>getOverviewPanel().showCurrentTask(mTargetTask);
         AbstractFloatingView.closeAllOpenViews(activity, wasVisible);
         BaseActivityInterface.AnimationFactory factory = mActivityInterface.prepareRecentsUI(
                 mDeviceState,
                 wasVisible, (controller) -> {
-                    controller.dispatchOnStart();
-                    controller.getAnimationPlayer().end();
+                    controller.getNormalController().dispatchOnStart();
+                    controller.getNormalController().getAnimationPlayer().end();
                 });
         factory.createActivityInterface(RECENTS_LAUNCH_DURATION);
         factory.setRecentsAttachedToAppWindow(true, false);
@@ -122,7 +123,8 @@
                 wallpaperTargets, MODE_CLOSING);
 
         // Use the top closing app to determine the insets for the animation
-        RemoteAnimationTargetCompat runningTaskTarget = targets.findTask(mTargetTaskId);
+        RemoteAnimationTargetCompat runningTaskTarget = mTargetTask == null ? null
+                : targets.findTask(mTargetTask.taskId);
         if (runningTaskTarget == null) {
             Log.e(TAG, "No closing app");
             return pa.buildAnim();
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 2b698bd..8b108ac 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -15,12 +15,10 @@
  */
 package com.android.quickstep;
 
-import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.anim.Interpolators.ACCEL_2;
 import static com.android.launcher3.anim.Interpolators.INSTANT;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
-import static com.android.quickstep.BaseSwipeUpHandlerV2.RECENTS_ATTACH_DURATION;
+import static com.android.quickstep.AbsSwipeUpHandler.RECENTS_ATTACH_DURATION;
 import static com.android.quickstep.SysUINavigationMode.getMode;
 import static com.android.quickstep.SysUINavigationMode.hideShelfInTwoButtonLandscape;
 import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
@@ -28,6 +26,8 @@
 import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_TRANSLATE_X_ANIM;
 import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
 import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
+import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
+import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
 
 import android.animation.Animator;
 import android.annotation.TargetApi;
@@ -36,7 +36,6 @@
 import android.graphics.Rect;
 import android.os.Build;
 import android.view.MotionEvent;
-import android.view.animation.Interpolator;
 
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
@@ -52,7 +51,7 @@
 import com.android.launcher3.util.WindowBounds;
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.util.ActivityInitListener;
-import com.android.quickstep.util.ShelfPeekAnim;
+import com.android.quickstep.util.AnimatorControllerWithResistance;
 import com.android.quickstep.util.SplitScreenBounds;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -106,7 +105,7 @@
     public abstract void onAssistantVisibilityChanged(float visibility);
 
     public abstract AnimationFactory prepareRecentsUI(RecentsAnimationDeviceState deviceState,
-            boolean activityVisible, Consumer<AnimatorPlaybackController> callback);
+            boolean activityVisible, Consumer<AnimatorControllerWithResistance> callback);
 
     public abstract ActivityInitListener createActivityInitListener(
             Predicate<Boolean> onInitListener);
@@ -150,17 +149,10 @@
         return deviceState.isInDeferredGestureRegion(ev);
     }
 
-    public abstract void onExitOverview(RecentsAnimationDeviceState deviceState,
+    public abstract void onExitOverview(RotationTouchHelper deviceState,
             Runnable exitRunnable);
 
     /**
-     * Updates the prediction state to the overview state.
-     */
-    public void updateOverviewPredictionState() {
-        // By public overview predictions are not supported
-    }
-
-    /**
      * Used for containerType in {@link com.android.launcher3.logging.UserEventDispatcher}
      */
     public abstract int getContainerType();
@@ -304,9 +296,6 @@
 
         default void onTransitionCancelled() { }
 
-        default void setShelfState(ShelfPeekAnim.ShelfAnimState animState,
-                Interpolator interpolator, long duration) { }
-
         /**
          * @param attached Whether to show RecentsView alongside the app window. If false, recents
          *                 will be hidden by some property we can animate, e.g. alpha.
@@ -319,11 +308,11 @@
 
         protected final ACTIVITY_TYPE mActivity;
         private final STATE_TYPE mStartState;
-        private final Consumer<AnimatorPlaybackController> mCallback;
+        private final Consumer<AnimatorControllerWithResistance> mCallback;
 
         private boolean mIsAttachedToWindow;
 
-        DefaultAnimationFactory(Consumer<AnimatorPlaybackController> callback) {
+        DefaultAnimationFactory(Consumer<AnimatorControllerWithResistance> callback) {
             mCallback = callback;
 
             mActivity = getCreatedActivity();
@@ -351,7 +340,14 @@
             controller.setEndAction(() -> mActivity.getStateManager().goToState(
                     controller.getInterpolatedProgress() > 0.5 ? mOverviewState : mBackgroundState,
                     false));
-            mCallback.accept(controller);
+
+            RecentsView recentsView = mActivity.getOverviewPanel();
+            AnimatorControllerWithResistance controllerWithResistance =
+                    AnimatorControllerWithResistance.createForRecents(controller, mActivity,
+                            recentsView.getPagedViewOrientedState(), mActivity.getDeviceProfile(),
+                            recentsView, RECENTS_SCALE_PROPERTY, recentsView,
+                            TASK_SECONDARY_TRANSLATION);
+            mCallback.accept(controllerWithResistance);
 
             // Creating the activity controller animation sometimes reapplies the launcher state
             // (because we set the animation as the current state animation), so we reapply the
@@ -400,13 +396,13 @@
         protected void createBackgroundToOverviewAnim(ACTIVITY_TYPE activity, PendingAnimation pa) {
             //  Scale down recents from being full screen to being in overview.
             RecentsView recentsView = activity.getOverviewPanel();
-            pa.addFloat(recentsView, SCALE_PROPERTY,
+            pa.addFloat(recentsView, RECENTS_SCALE_PROPERTY,
                     recentsView.getMaxScaleForFullScreen(), 1, LINEAR);
             pa.addFloat(recentsView, FULLSCREEN_PROGRESS, 1, 0, LINEAR);
         }
     }
 
     protected static boolean showOverviewActions(Context context) {
-        return ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(context);
+        return removeShelfFromOverview(context);
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
similarity index 95%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
rename to quickstep/src/com/android/quickstep/FallbackActivityInterface.java
index 33b9cde..3898f0b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
@@ -27,11 +27,11 @@
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
-import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.quickstep.fallback.RecentsState;
 import com.android.quickstep.util.ActivityInitListener;
+import com.android.quickstep.util.AnimatorControllerWithResistance;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
@@ -84,7 +84,7 @@
     /** 6 */
     @Override
     public AnimationFactory prepareRecentsUI(RecentsAnimationDeviceState deviceState,
-            boolean activityVisible, Consumer<AnimatorPlaybackController> callback) {
+            boolean activityVisible, Consumer<AnimatorControllerWithResistance> callback) {
         DefaultAnimationFactory factory = new DefaultAnimationFactory(callback);
         factory.initUI();
         return factory;
@@ -140,7 +140,7 @@
     }
 
     @Override
-    public void onExitOverview(RecentsAnimationDeviceState deviceState, Runnable exitRunnable) {
+    public void onExitOverview(RotationTouchHelper deviceState, Runnable exitRunnable) {
         // no-op, fake landscape not supported for 3P
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
similarity index 63%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
rename to quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
index fc7a119..ffb05df 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -15,14 +15,35 @@
  */
 package com.android.quickstep;
 
+import static android.content.Intent.EXTRA_COMPONENT_NAME;
+import static android.content.Intent.EXTRA_USER;
+
+import static com.android.launcher3.GestureNavContract.EXTRA_GESTURE_CONTRACT;
+import static com.android.launcher3.GestureNavContract.EXTRA_ICON_POSITION;
+import static com.android.launcher3.GestureNavContract.EXTRA_ICON_SURFACE;
+import static com.android.launcher3.GestureNavContract.EXTRA_REMOTE_CALLBACK;
 import static com.android.launcher3.anim.Interpolators.ACCEL;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
 
 import android.animation.ObjectAnimator;
+import android.annotation.TargetApi;
 import android.app.ActivityOptions;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.ParcelUuid;
+import android.os.UserHandle;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.SurfaceControl.Transaction;
 
 import androidx.annotation.NonNull;
 
@@ -32,18 +53,32 @@
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.anim.SpringAnimationBuilder;
 import com.android.quickstep.fallback.FallbackRecentsView;
+import com.android.quickstep.util.RectFSpringAnim;
 import com.android.quickstep.util.TransformParams;
 import com.android.quickstep.util.TransformParams.BuilderProxy;
+import com.android.systemui.shared.recents.model.Task.TaskKey;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
 
+import java.lang.ref.WeakReference;
+import java.util.UUID;
+import java.util.function.Consumer;
+
 /**
  * Handles the navigation gestures when a 3rd party launcher is the default home activity.
  */
+@TargetApi(Build.VERSION_CODES.R)
 public class FallbackSwipeHandler extends
-        BaseSwipeUpHandlerV2<RecentsActivity, FallbackRecentsView> {
+        AbsSwipeUpHandler<RecentsActivity, FallbackRecentsView> {
+
+    /**
+     * Message used for receiving gesture nav contract information. We use a static messenger to
+     * avoid leaking too make binders in case the receiving launcher does not handle the contract
+     * properly.
+     */
+    private static StaticMessageReceiver sMessageReceiver = null;
 
     private FallbackHomeAnimationFactory mActiveAnimationFactory;
     private final boolean mRunningOverHome;
@@ -89,7 +124,9 @@
     protected HomeAnimationFactory createHomeAnimationFactory(long duration) {
         mActiveAnimationFactory = new FallbackHomeAnimationFactory(duration);
         ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
-        mContext.startActivity(new Intent(mGestureState.getHomeIntent()), options.toBundle());
+        Intent intent = new Intent(mGestureState.getHomeIntent());
+        mActiveAnimationFactory.addGestureContract(intent);
+        mContext.startActivity(intent, options.toBundle());
         return mActiveAnimationFactory;
     }
 
@@ -130,17 +167,20 @@
     }
 
     private class FallbackHomeAnimationFactory extends HomeAnimationFactory {
-
+        private final Rect mTempRect = new Rect();
         private final TransformParams mHomeAlphaParams = new TransformParams();
         private final AnimatedFloat mHomeAlpha;
 
         private final AnimatedFloat mVerticalShiftForScale = new AnimatedFloat();
-
         private final AnimatedFloat mRecentsAlpha = new AnimatedFloat();
 
+        private final RectF mTargetRect = new RectF();
+        private SurfaceControl mSurfaceControl;
+
         private final long mDuration;
+
+        private RectFSpringAnim mSpringAnim;
         FallbackHomeAnimationFactory(long duration) {
-            super(null);
             mDuration = duration;
 
             if (mRunningOverHome) {
@@ -162,6 +202,15 @@
                     this::updateRecentsActivityTransformDuringHomeAnim);
         }
 
+        @NonNull
+        @Override
+        public RectF getWindowTargetRect() {
+            if (mTargetRect.isEmpty()) {
+                mTargetRect.set(super.getWindowTargetRect());
+            }
+            return mTargetRect;
+        }
+
         private void updateRecentsActivityTransformDuringHomeAnim(SurfaceParams.Builder builder,
                 RemoteAnimationTargetCompat app, TransformParams params) {
             builder.withAlpha(mRecentsAlpha.value);
@@ -218,5 +267,87 @@
                         .start();
             }
         }
+
+        @Override
+        public void setAnimation(RectFSpringAnim anim) {
+            mSpringAnim = anim;
+        }
+
+        private void onMessageReceived(Message msg) {
+            try {
+                Bundle data = msg.getData();
+                RectF position = data.getParcelable(EXTRA_ICON_POSITION);
+                if (!position.isEmpty()) {
+                    mSurfaceControl = data.getParcelable(EXTRA_ICON_SURFACE);
+                    mTargetRect.set(position);
+                    if (mSpringAnim != null) {
+                        mSpringAnim.onTargetPositionChanged();
+                    }
+                }
+            } catch (Exception e) {
+                // Ignore
+            }
+        }
+
+        @Override
+        public void update(RectF currentRect, float progress, float radius) {
+            if (mSurfaceControl != null) {
+                currentRect.roundOut(mTempRect);
+                Transaction t = new Transaction();
+                t.setGeometry(mSurfaceControl, null, mTempRect, Surface.ROTATION_0);
+                t.apply();
+            }
+        }
+
+        private void addGestureContract(Intent intent) {
+            if (mRunningOverHome || mGestureState.getRunningTask() == null) {
+                return;
+            }
+
+            TaskKey key = new TaskKey(mGestureState.getRunningTask());
+            if (key.getComponent() != null) {
+                if (sMessageReceiver == null) {
+                    sMessageReceiver = new StaticMessageReceiver();
+                }
+
+                Bundle gestureNavContract = new Bundle();
+                gestureNavContract.putParcelable(EXTRA_COMPONENT_NAME, key.getComponent());
+                gestureNavContract.putParcelable(EXTRA_USER, UserHandle.of(key.userId));
+                gestureNavContract.putParcelable(EXTRA_REMOTE_CALLBACK,
+                        sMessageReceiver.newCallback(this::onMessageReceived));
+                intent.putExtra(EXTRA_GESTURE_CONTRACT, gestureNavContract);
+            }
+        }
+    }
+
+    private static class StaticMessageReceiver implements Handler.Callback {
+
+        private final Messenger mMessenger =
+                new Messenger(new Handler(Looper.getMainLooper(), this));
+
+        private ParcelUuid mCurrentUID = new ParcelUuid(UUID.randomUUID());
+        private WeakReference<Consumer<Message>> mCurrentCallback = new WeakReference<>(null);
+
+        public Message newCallback(Consumer<Message> callback) {
+            mCurrentUID = new ParcelUuid(UUID.randomUUID());
+            mCurrentCallback = new WeakReference<>(callback);
+
+            Message msg = Message.obtain();
+            msg.replyTo = mMessenger;
+            msg.obj = mCurrentUID;
+            return msg;
+        }
+
+        @Override
+        public boolean handleMessage(@NonNull Message message) {
+            if (mCurrentUID.equals(message.obj)) {
+                Consumer<Message> consumer = mCurrentCallback.get();
+                if (consumer != null) {
+                    consumer.accept(message);
+                    return true;
+                }
+            }
+            return false;
+        }
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/ImageActionsApi.java b/quickstep/src/com/android/quickstep/ImageActionsApi.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/ImageActionsApi.java
rename to quickstep/src/com/android/quickstep/ImageActionsApi.java
diff --git a/quickstep/src/com/android/quickstep/InputConsumer.java b/quickstep/src/com/android/quickstep/InputConsumer.java
index ec720d5..67711c0 100644
--- a/quickstep/src/com/android/quickstep/InputConsumer.java
+++ b/quickstep/src/com/android/quickstep/InputConsumer.java
@@ -35,6 +35,7 @@
     int TYPE_RESET_GESTURE = 1 << 8;
     int TYPE_OVERSCROLL = 1 << 9;
     int TYPE_SYSUI_OVERLAY = 1 << 10;
+    int TYPE_ONE_HANDED = 1 << 11;
 
     String[] NAMES = new String[] {
            "TYPE_NO_OP",                    // 0
@@ -47,7 +48,8 @@
             "TYPE_OVERVIEW_WITHOUT_FOCUS",  // 7
             "TYPE_RESET_GESTURE",           // 8
             "TYPE_OVERSCROLL",              // 9
-            "TYPE_SYSUI_OVERLAY"         // 10
+            "TYPE_SYSUI_OVERLAY",           // 10
+            "TYPE_ONE_HANDED",              // 11
     };
 
     InputConsumer NO_OP = () -> TYPE_NO_OP;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
similarity index 90%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
rename to quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index edefbe1..036d473 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -27,7 +27,6 @@
 import android.content.res.Resources;
 import android.graphics.Rect;
 import android.util.Log;
-import android.view.animation.Interpolator;
 
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
@@ -39,9 +38,7 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.DiscoveryBounce;
-import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.appprediction.PredictionUiStateManager;
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.statehandlers.DepthController.ClampedDepthProperty;
 import com.android.launcher3.statemanager.StateManager;
@@ -50,8 +47,8 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.util.ActivityInitListener;
+import com.android.quickstep.util.AnimatorControllerWithResistance;
 import com.android.quickstep.util.LayoutUtils;
-import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.plugins.shared.LauncherOverlayManager;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -105,7 +102,7 @@
         // recents, we assume the first task is invisible, making translation off by one task.
         launcher.getStateManager().reapplyState();
         launcher.getRootView().setForceHideBackArrow(false);
-        notifyRecentsOfOrientation(deviceState);
+        notifyRecentsOfOrientation(deviceState.getRotationTouchHelper());
     }
 
     @Override
@@ -119,16 +116,10 @@
 
     @Override
     public AnimationFactory prepareRecentsUI(RecentsAnimationDeviceState deviceState,
-            boolean activityVisible, Consumer<AnimatorPlaybackController> callback) {
-        notifyRecentsOfOrientation(deviceState);
+            boolean activityVisible, Consumer<AnimatorControllerWithResistance> callback) {
+        notifyRecentsOfOrientation(deviceState.getRotationTouchHelper());
         DefaultAnimationFactory factory = new DefaultAnimationFactory(callback) {
             @Override
-            public void setShelfState(ShelfAnimState shelfState, Interpolator interpolator,
-                    long duration) {
-                mActivity.getShelfPeekAnim().setShelfState(shelfState, interpolator, duration);
-            }
-
-            @Override
             protected void createBackgroundToOverviewAnim(BaseQuickstepLauncher activity,
                     PendingAnimation pa) {
                 super.createBackgroundToOverviewAnim(activity, pa);
@@ -228,7 +219,7 @@
 
 
     @Override
-    public void onExitOverview(RecentsAnimationDeviceState deviceState, Runnable exitRunnable) {
+    public void onExitOverview(RotationTouchHelper deviceState, Runnable exitRunnable) {
         final StateManager<LauncherState> stateManager = getCreatedActivity().getStateManager();
         stateManager.addStateListener(
                 new StateManager.StateListener<LauncherState>() {
@@ -244,11 +235,11 @@
                 });
     }
 
-    private void notifyRecentsOfOrientation(RecentsAnimationDeviceState deviceState) {
+    private void notifyRecentsOfOrientation(RotationTouchHelper rotationTouchHelper) {
         // reset layout on swipe to home
         RecentsView recentsView = getCreatedActivity().getOverviewPanel();
-        recentsView.setLayoutRotation(deviceState.getCurrentActiveRotation(),
-                deviceState.getDisplayRotation());
+        recentsView.setLayoutRotation(rotationTouchHelper.getCurrentActiveRotation(),
+                rotationTouchHelper.getDisplayRotation());
     }
 
     @Override
@@ -262,16 +253,6 @@
     }
 
     @Override
-    public void updateOverviewPredictionState() {
-        Launcher launcher = getCreatedActivity();
-        if (launcher == null) {
-            return;
-        }
-        PredictionUiStateManager.INSTANCE.get(launcher).switchClient(
-                PredictionUiStateManager.Client.OVERVIEW);
-    }
-
-    @Override
     public int getContainerType() {
         final Launcher launcher = getVisibleLauncher();
         return launcher != null ? launcher.getStateManager().getState().containerType
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
similarity index 61%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandlerV2.java
rename to quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index fa7d268..4411455 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -16,6 +16,7 @@
 package com.android.quickstep;
 
 import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
 
 import android.animation.AnimatorSet;
 import android.content.Context;
@@ -28,6 +29,7 @@
 import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.views.FloatingIconView;
+import com.android.quickstep.util.RectFSpringAnim;
 import com.android.quickstep.util.StaggeredWorkspaceAnim;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
@@ -37,7 +39,7 @@
  * Temporary class to allow easier refactoring
  */
 public class LauncherSwipeHandlerV2 extends
-        BaseSwipeUpHandlerV2<BaseQuickstepLauncher, RecentsView> {
+        AbsSwipeUpHandler<BaseQuickstepLauncher, RecentsView> {
 
     public LauncherSwipeHandlerV2(Context context, RecentsAnimationDeviceState deviceState,
             TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs,
@@ -72,36 +74,39 @@
             mActivity.getRootView().setForceHideBackArrow(true);
             mActivity.setHintUserWillBeActive();
 
-            homeAnimFactory = new HomeAnimationFactory(floatingIconView) {
-
-                @Override
-                public RectF getWindowTargetRect() {
-                    if (canUseWorkspaceView) {
+            if (canUseWorkspaceView) {
+                // We want the window alpha to be 0 once this threshold is met, so that the
+                // FolderIconView can be seen morphing into the icon shape.
+                float windowAlphaThreshold = 1f - SHAPE_PROGRESS_DURATION;
+                homeAnimFactory = new LauncherHomeAnimationFactory() {
+                    @Override
+                    public RectF getWindowTargetRect() {
                         return iconLocation;
-                    } else {
-                        return super.getWindowTargetRect();
                     }
-                }
 
-                @NonNull
-                @Override
-                public AnimatorPlaybackController createActivityAnimationToHome() {
-                    // Return an empty APC here since we have an non-user controlled animation
-                    // to home.
-                    long accuracy = 2 * Math.max(mDp.widthPx, mDp.heightPx);
-                    return mActivity.getStateManager().createAnimationToNewWorkspace(
-                            NORMAL, accuracy, 0 /* animComponents */);
-                }
+                    @Override
+                    public void setAnimation(RectFSpringAnim anim) {
+                        anim.addAnimatorListener(floatingIconView);
+                        floatingIconView.setOnTargetChangeListener(anim::onTargetPositionChanged);
+                        floatingIconView.setFastFinishRunnable(anim::end);
+                    }
 
-                @Override
-                public void playAtomicAnimation(float velocity) {
-                    new StaggeredWorkspaceAnim(mActivity, velocity,
-                            true /* animateOverviewScrim */).start();
-                }
-            };
+                    @Override
+                    public void update(RectF currentRect, float progress, float radius) {
+                        floatingIconView.update(currentRect, 1f, progress, windowAlphaThreshold,
+                                radius, false);
+                    }
 
+                    @Override
+                    public void onCancel() {
+                        floatingIconView.fastFinish();
+                    }
+                };
+            } else {
+                homeAnimFactory = new LauncherHomeAnimationFactory();
+            }
         } else {
-            homeAnimFactory = new HomeAnimationFactory(null) {
+            homeAnimFactory = new HomeAnimationFactory() {
                 @Override
                 public AnimatorPlaybackController createActivityAnimationToHome() {
                     return AnimatorPlaybackController.wrap(new AnimatorSet(), duration);
@@ -118,4 +123,22 @@
         mRecentsAnimationController.finish(
                 true /* toRecents */, callback, true /* sendUserLeaveHint */);
     }
+
+    private class LauncherHomeAnimationFactory extends HomeAnimationFactory {
+        @NonNull
+        @Override
+        public AnimatorPlaybackController createActivityAnimationToHome() {
+            // Return an empty APC here since we have an non-user controlled animation
+            // to home.
+            long accuracy = 2 * Math.max(mDp.widthPx, mDp.heightPx);
+            return mActivity.getStateManager().createAnimationToNewWorkspace(
+                    NORMAL, accuracy, 0 /* animComponents */);
+        }
+
+        @Override
+        public void playAtomicAnimation(float velocity) {
+            new StaggeredWorkspaceAnim(mActivity, velocity,
+                    true /* animateOverviewScrim */).start();
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
index 1081548..eb33f98 100644
--- a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
+++ b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
@@ -37,7 +37,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.DisplayController.Info;
 
 import java.io.PrintWriter;
 
@@ -63,7 +63,9 @@
     private SparseArray<OrientationRectF> mSwipeTouchRegions = new SparseArray<>(MAX_ORIENTATIONS);
     private final RectF mAssistantLeftRegion = new RectF();
     private final RectF mAssistantRightRegion = new RectF();
+    private final RectF mOneHandedModeRegion = new RectF();
     private int mCurrentDisplayRotation;
+    private int mNavBarGesturalHeight;
     private boolean mEnableMultipleRegions;
     private Resources mResources;
     private OrientationRectF mLastRectTouched;
@@ -83,7 +85,7 @@
      * QUICKSTEP_ROTATION_UNINITIALIZED, then user has not tapped on an active nav region.
      * Otherwise it will be the rotation of the display when the user first interacted with the
      * active nav bar region.
-     * The "session" ends when {@link #enableMultipleRegions(boolean, DefaultDisplay.Info)} is
+     * The "session" ends when {@link #enableMultipleRegions(boolean, Info)} is
      * called - usually from a timeout or if user starts interacting w/ the foreground app.
      *
      * This is different than {@link #mLastRectTouched} as it can get reset by the system whereas
@@ -103,18 +105,33 @@
         mResources = resources;
         mMode = mode;
         mContractInfo = contractInfo;
+        mNavBarGesturalHeight = getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE);
     }
 
-    void setNavigationMode(SysUINavigationMode.Mode newMode, DefaultDisplay.Info info) {
+    private void refreshTouchRegion(Info info, Resources newRes) {
+        // Swipe touch regions are independent of nav mode, so we have to clear them explicitly
+        // here to avoid, for ex, a nav region for 2-button rotation 0 being used for 3-button mode
+        // It tries to cache and reuse swipe regions whenever possible based only on rotation
+        mResources = newRes;
+        mSwipeTouchRegions.clear();
+        resetSwipeRegions(info);
+    }
+
+    void setNavigationMode(SysUINavigationMode.Mode newMode, Info info,
+            Resources newRes) {
         if (mMode == newMode) {
             return;
         }
         this.mMode = newMode;
-        // Swipe touch regions are independent of nav mode, so we have to clear them explicitly
-        // here to avoid, for ex, a nav region for 2-button rotation 0 being used for 3-button mode
-        // It tries to cache and reuse swipe regions whenever possible based only on rotation
-        mSwipeTouchRegions.clear();
-        resetSwipeRegions(info);
+        refreshTouchRegion(info, newRes);
+    }
+
+    void setGesturalHeight(int newGesturalHeight, Info info, Resources newRes) {
+        if (mNavBarGesturalHeight == newGesturalHeight) {
+            return;
+        }
+        mNavBarGesturalHeight = newGesturalHeight;
+        refreshTouchRegion(info, newRes);
     }
 
     /**
@@ -123,9 +140,9 @@
      * alongside other regions.
      * Ok to call multiple times
      *
-     * @see #enableMultipleRegions(boolean, DefaultDisplay.Info)
+     * @see #enableMultipleRegions(boolean, Info)
      */
-    void createOrAddTouchRegion(DefaultDisplay.Info info) {
+    void createOrAddTouchRegion(Info info) {
         mCurrentDisplayRotation = info.rotation;
         if (mQuickStepStartingRotation > QUICKSTEP_ROTATION_UNINITIALIZED
                 && mCurrentDisplayRotation == mQuickStepStartingRotation) {
@@ -153,7 +170,7 @@
      * @param enableMultipleRegions Set to true to start tracking multiple nav bar regions
      * @param info The current displayInfo which will be the start of the quickswitch gesture
      */
-    void enableMultipleRegions(boolean enableMultipleRegions, DefaultDisplay.Info info) {
+    void enableMultipleRegions(boolean enableMultipleRegions, Info info) {
         mEnableMultipleRegions = enableMultipleRegions &&
                 mMode != SysUINavigationMode.Mode.TWO_BUTTONS;
         if (mEnableMultipleRegions) {
@@ -174,7 +191,7 @@
      *
      * @param displayInfo The display whos rotation will be used as the current active rotation
      */
-    void setSingleActiveRegion(DefaultDisplay.Info displayInfo) {
+    void setSingleActiveRegion(Info displayInfo) {
         mActiveTouchRotation = displayInfo.rotation;
         resetSwipeRegions(displayInfo);
     }
@@ -185,7 +202,7 @@
      * To be called whenever we want to stop tracking more than one swipe region.
      * Ok to call multiple times.
      */
-    private void resetSwipeRegions(DefaultDisplay.Info region) {
+    private void resetSwipeRegions(Info region) {
         if (DEBUG) {
             Log.d(TAG, "clearing all regions except rotation: " + mCurrentDisplayRotation);
         }
@@ -209,17 +226,17 @@
         }
     }
 
-    private OrientationRectF createRegionForDisplay(DefaultDisplay.Info display) {
+    private OrientationRectF createRegionForDisplay(Info display) {
         if (DEBUG) {
             Log.d(TAG, "creating rotation region for: " + mCurrentDisplayRotation);
         }
 
         Point size = display.realSize;
         int rotation = display.rotation;
+        int touchHeight = mNavBarGesturalHeight;
         OrientationRectF orientationRectF =
                 new OrientationRectF(0, 0, size.x, size.y, rotation);
         if (mMode == SysUINavigationMode.Mode.NO_BUTTON) {
-            int touchHeight = getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE);
             orientationRectF.top = orientationRectF.bottom - touchHeight;
             updateAssistantRegions(orientationRectF);
         } else {
@@ -235,10 +252,11 @@
                             + getNavbarSize(ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE);
                     break;
                 default:
-                    orientationRectF.top = orientationRectF.bottom
-                            - getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE);
+                    orientationRectF.top = orientationRectF.bottom - touchHeight;
             }
         }
+        // One handed gestural only active on portrait mode
+        mOneHandedModeRegion.set(0, orientationRectF.bottom - touchHeight, size.x, size.y);
 
         return orientationRectF;
     }
@@ -264,6 +282,10 @@
 
     }
 
+    boolean touchInOneHandedModeRegion(MotionEvent ev) {
+        return mOneHandedModeRegion.contains(ev.getX(), ev.getY());
+    }
+
     private int getNavbarSize(String resName) {
         return ResourceUtils.getNavbarSize(resName, mResources);
     }
@@ -352,9 +374,11 @@
         StringBuilder regions = new StringBuilder("  currentTouchableRotations=");
         for(int i = 0; i < mSwipeTouchRegions.size(); i++) {
             OrientationRectF rectF = mSwipeTouchRegions.get(mSwipeTouchRegions.keyAt(i));
-            regions.append(rectF.mRotation).append(" ");
+            regions.append(rectF).append(" ");
         }
         pw.println(regions.toString());
+        pw.println("  mNavBarGesturalHeight=" + mNavBarGesturalHeight);
+        pw.println("  mOneHandedModeRegion=" + mOneHandedModeRegion);
     }
 
     private class OrientationRectF extends RectF {
diff --git a/quickstep/src/com/android/quickstep/OverscrollPluginFactory.java b/quickstep/src/com/android/quickstep/OverscrollPluginFactory.java
new file mode 100644
index 0000000..4c261ab
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/OverscrollPluginFactory.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 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.quickstep;
+
+import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
+
+import com.android.launcher3.R;
+import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.ResourceBasedOverride;
+import com.android.systemui.plugins.OverscrollPlugin;
+
+/**
+ * Resource overrideable factory for forcing a local overscroll plugin.
+ * Override {@link R.string#overscroll_plugin_factory_class} to set a different class.
+ */
+public class OverscrollPluginFactory implements ResourceBasedOverride {
+    public static final MainThreadInitializedObject<OverscrollPluginFactory> INSTANCE = forOverride(
+            OverscrollPluginFactory.class,
+            R.string.overscroll_plugin_factory_class);
+
+    /**
+     * Get the plugin that is defined locally in launcher, as opposed to a dynamic side loaded one.
+     */
+    public OverscrollPlugin getLocalOverscrollPlugin() {
+        return null;
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
similarity index 93%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
rename to quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 434a929..365cff5 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -29,8 +29,6 @@
 
 import androidx.annotation.BinderThread;
 
-import com.android.launcher3.appprediction.PredictionUiStateManager;
-import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.quickstep.util.ActivityInitListener;
@@ -88,12 +86,6 @@
         MAIN_EXECUTOR.execute(new HideRecentsCommand());
     }
 
-    @BinderThread
-    public void onTip(int actionType, int viewType) {
-        MAIN_EXECUTOR.execute(() ->
-                UserEventDispatcher.newInstance(mContext).logActionTip(actionType, viewType));
-    }
-
     private class ShowRecentsCommand extends RecentsActivityCommand {
 
         private final boolean mTriggeredFromAltTab;
@@ -165,7 +157,7 @@
             mActivityInterface = mOverviewComponentObserver.getActivityInterface();
             mCreateTime = SystemClock.elapsedRealtime();
             mAnimationProvider = new AppToOverviewAnimationProvider<>(mActivityInterface,
-                    RecentsModel.getRunningTaskId(), mDeviceState);
+                    ActivityManagerWrapper.getInstance().getRunningTask(), mDeviceState);
 
             // Preload the plan
             mRecentsModel.getTasks(null);
@@ -227,10 +219,6 @@
                         LauncherLogProto.ContainerType.TASKSWITCHER);
                 mUserEventLogged = true;
             }
-
-            // Switch prediction client to overview
-            PredictionUiStateManager.INSTANCE.get(activity).switchClient(
-                    PredictionUiStateManager.Client.OVERVIEW);
             return mAnimationProvider.onActivityReady(activity, wasVisible);
         }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
similarity index 95%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
rename to quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 6f4d34c..e4b8ce2 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -53,9 +53,9 @@
                         Bundle::putInt, PortraitStatesTouchController::getHotseatTop);
             }
 
-            case TestProtocol.REQUEST_OVERVIEW_ACTIONS_ENABLED: {
+            case TestProtocol.REQUEST_OVERVIEW_SHARE_ENABLED: {
                 response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD,
-                        FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get());
+                        FeatureFlags.ENABLE_OVERVIEW_SHARE.get());
                 return response;
             }
         }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
rename to quickstep/src/com/android/quickstep/RecentsActivity.java
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index 4e9aa61..51f5e5d 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -15,49 +15,30 @@
  */
 package com.android.quickstep;
 
-import static android.view.MotionEvent.ACTION_CANCEL;
-import static android.view.MotionEvent.ACTION_DOWN;
-import static android.view.MotionEvent.ACTION_UP;
-
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 
-import android.os.SystemClock;
-import android.util.Log;
-import android.view.InputEvent;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-
 import androidx.annotation.NonNull;
 import androidx.annotation.UiThread;
 
 import com.android.launcher3.util.Preconditions;
 import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
 import java.util.function.Consumer;
-import java.util.function.Supplier;
 
 /**
  * Wrapper around RecentsAnimationControllerCompat to help with some synchronization
  */
 public class RecentsAnimationController {
 
-    private static final String TAG = "RecentsAnimationController";
-
     private final RecentsAnimationControllerCompat mController;
     private final Consumer<RecentsAnimationController> mOnFinishedListener;
     private final boolean mAllowMinimizeSplitScreen;
 
-    private InputConsumerController mInputConsumerController;
-    private Supplier<InputConsumer> mInputProxySupplier;
-    private InputConsumer mInputConsumer;
     private boolean mUseLauncherSysBarFlags = false;
     private boolean mSplitScreenMinimized = false;
-    private boolean mTouchInProgress;
-    private boolean mDisableInputProxyPending;
 
     public RecentsAnimationController(RecentsAnimationControllerCompat controller,
             boolean allowMinimizeSplitScreen,
@@ -136,12 +117,12 @@
 
     @UiThread
     public void finishAnimationToHome() {
-        finishAndDisableInputProxy(true /* toRecents */, null, false /* sendUserLeaveHint */);
+        finishController(true /* toRecents */, null, false /* sendUserLeaveHint */);
     }
 
     @UiThread
     public void finishAnimationToApp() {
-        finishAndDisableInputProxy(false /* toRecents */, null, false /* sendUserLeaveHint */);
+        finishController(false /* toRecents */, null, false /* sendUserLeaveHint */);
     }
 
     /** See {@link #finish(boolean, Runnable, boolean)} */
@@ -160,18 +141,6 @@
     @UiThread
     public void finish(boolean toRecents, Runnable onFinishComplete, boolean sendUserLeaveHint) {
         Preconditions.assertUIThread();
-        if (toRecents && mTouchInProgress) {
-            // Finish the controller as requested, but don't disable input proxy yet.
-            mDisableInputProxyPending = true;
-            finishController(toRecents, onFinishComplete, sendUserLeaveHint);
-        } else {
-            finishAndDisableInputProxy(toRecents, onFinishComplete, sendUserLeaveHint);
-        }
-    }
-
-    private void finishAndDisableInputProxy(boolean toRecents, Runnable onFinishComplete,
-            boolean sendUserLeaveHint) {
-        disableInputProxy();
         finishController(toRecents, onFinishComplete, sendUserLeaveHint);
     }
 
@@ -179,7 +148,6 @@
     public void finishController(boolean toRecents, Runnable callback, boolean sendUserLeaveHint) {
         mOnFinishedListener.accept(this);
         UI_HELPER_EXECUTOR.execute(() -> {
-            mController.setInputConsumerEnabled(false);
             mController.finish(toRecents, sendUserLeaveHint);
             if (callback != null) {
                 MAIN_EXECUTOR.execute(callback);
@@ -197,75 +165,8 @@
         });
     }
 
-    public void enableInputProxy(InputConsumerController inputConsumerController,
-            Supplier<InputConsumer> inputProxySupplier) {
-        mInputProxySupplier = inputProxySupplier;
-        mInputConsumerController = inputConsumerController;
-        mInputConsumerController.setInputListener(this::onInputConsumerEvent);
-    }
-
     /** @return wrapper controller. */
     public RecentsAnimationControllerCompat getController() {
         return mController;
     }
-
-    private void disableInputProxy() {
-        if (mInputConsumer != null && mTouchInProgress) {
-            long now = SystemClock.uptimeMillis();
-            MotionEvent dummyCancel = MotionEvent.obtain(now,  now, ACTION_CANCEL, 0, 0, 0);
-            mInputConsumer.onMotionEvent(dummyCancel);
-            dummyCancel.recycle();
-        }
-        if (mInputConsumerController != null) {
-            mInputConsumerController.setInputListener(null);
-        }
-        mInputProxySupplier = null;
-    }
-
-    private boolean onInputConsumerEvent(InputEvent ev) {
-        if (ev instanceof MotionEvent) {
-            onInputConsumerMotionEvent((MotionEvent) ev);
-        } else if (ev instanceof KeyEvent) {
-            if (mInputConsumer == null) {
-                mInputConsumer = mInputProxySupplier.get();
-            }
-            mInputConsumer.onKeyEvent((KeyEvent) ev);
-            return true;
-        }
-        return false;
-    }
-
-    private boolean onInputConsumerMotionEvent(MotionEvent ev) {
-        int action = ev.getAction();
-
-        // Just to be safe, verify that ACTION_DOWN comes before any other action,
-        // and ignore any ACTION_DOWN after the first one (though that should not happen).
-        if (!mTouchInProgress && action != ACTION_DOWN) {
-            Log.w(TAG, "Received non-down motion before down motion: " + action);
-            return false;
-        }
-        if (mTouchInProgress && action == ACTION_DOWN) {
-            Log.w(TAG, "Received down motion while touch was already in progress");
-            return false;
-        }
-
-        if (action == ACTION_DOWN) {
-            mTouchInProgress = true;
-            if (mInputConsumer == null) {
-                mInputConsumer = mInputProxySupplier.get();
-            }
-        } else if (action == ACTION_CANCEL || action == ACTION_UP) {
-            // Finish any pending actions
-            mTouchInProgress = false;
-            if (mDisableInputProxyPending) {
-                mDisableInputProxyPending = false;
-                disableInputProxy();
-            }
-        }
-        if (mInputConsumer != null) {
-            mInputConsumer.onMotionEvent(ev);
-        }
-
-        return true;
-    }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 0a70bd6..e9fc423 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -16,11 +16,9 @@
 package com.android.quickstep;
 
 import static android.content.Intent.ACTION_USER_UNLOCKED;
-import static android.view.Surface.ROTATION_0;
 
-import static com.android.launcher3.util.DefaultDisplay.CHANGE_ALL;
-import static com.android.launcher3.util.DefaultDisplay.CHANGE_FRAME_DELAY;
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.launcher3.util.DisplayController.DisplayHolder.CHANGE_ALL;
+import static com.android.launcher3.util.DisplayController.DisplayHolder.CHANGE_FRAME_DELAY;
 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
 import static com.android.quickstep.SysUINavigationMode.Mode.THREE_BUTTONS;
 import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
@@ -31,6 +29,7 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ONE_HANDED_ACTIVE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
@@ -45,27 +44,30 @@
 import android.content.res.Resources;
 import android.graphics.Region;
 import android.os.Process;
+import android.os.SystemProperties;
 import android.os.UserManager;
 import android.provider.Settings;
 import android.text.TextUtils;
+import android.util.DisplayMetrics;
 import android.view.MotionEvent;
-import android.view.OrientationEventListener;
+import android.view.Surface;
 
 import androidx.annotation.BinderThread;
 
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.DisplayController.DisplayHolder;
+import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
+import com.android.launcher3.util.DisplayController.Info;
 import com.android.launcher3.util.SecureSettingsObserver;
 import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
+import com.android.quickstep.SysUINavigationMode.OneHandedModeChangeListener;
 import com.android.quickstep.util.NavBarPosition;
-import com.android.quickstep.util.RecentsOrientedState;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
 import com.android.systemui.shared.system.SystemGestureExclusionListenerCompat;
-import com.android.systemui.shared.system.TaskStackChangeListener;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -77,13 +79,16 @@
  */
 public class RecentsAnimationDeviceState implements
         NavigationModeChangeListener,
-        DefaultDisplay.DisplayInfoChangeListener {
+        DisplayInfoChangeListener,
+        OneHandedModeChangeListener {
+
+    static final String SUPPORT_ONE_HANDED_MODE = "ro.support_one_handed_mode";
 
     private final Context mContext;
     private final SysUINavigationMode mSysUiNavMode;
-    private final DefaultDisplay mDefaultDisplay;
+    private final DisplayHolder mDisplayHolder;
     private final int mDisplayId;
-    private int mDisplayRotation;
+    private final RotationTouchHelper mRotationTouchHelper;
 
     private final ArrayList<Runnable> mOnDestroyActions = new ArrayList<>();
 
@@ -94,6 +99,9 @@
     private final Region mDeferredGestureRegion = new Region();
     private boolean mAssistantAvailable;
     private float mAssistantVisibility;
+    private boolean mIsOneHandedModeEnabled;
+    private boolean mIsSwipeToNotificationEnabled;
+    private final boolean mIsOneHandedModeSupported;
 
     private boolean mIsUserUnlocked;
     private final ArrayList<Runnable> mUserUnlockedActions = new ArrayList<>();
@@ -107,85 +115,26 @@
         }
     };
 
-    private TaskStackChangeListener mFrozenTaskListener = new TaskStackChangeListener() {
-        @Override
-        public void onRecentTaskListFrozenChanged(boolean frozen) {
-            mTaskListFrozen = frozen;
-            if (frozen || mInOverview) {
-                return;
-            }
-            enableMultipleRegions(false);
-        }
-
-        @Override
-        public void onActivityRotation(int displayId) {
-            super.onActivityRotation(displayId);
-            // This always gets called before onDisplayInfoChanged() so we know how to process
-            // the rotation in that method. This is done to avoid having a race condition between
-            // the sensor readings and onDisplayInfoChanged() call
-            if (displayId != mDisplayId) {
-                return;
-            }
-
-            mPrioritizeDeviceRotation = true;
-            if (mInOverview) {
-                // reset, launcher must be rotating
-                mExitOverviewRunnable.run();
-            }
-        }
-    };
-
-    private Runnable mExitOverviewRunnable = new Runnable() {
-        @Override
-        public void run() {
-            mInOverview = false;
-            enableMultipleRegions(false);
-        }
-    };
-
-    private OrientationTouchTransformer mOrientationTouchTransformer;
-    /**
-     * Used to listen for when the device rotates into the orientation of the current
-     * foreground app. For example, if a user quickswitches from a portrait to a fixed landscape
-     * app and then rotates rotates the device to match that orientation, this triggers calls to
-     * sysui to adjust the navbar.
-     */
-    private OrientationEventListener mOrientationListener;
-    private int mSensorRotation = ROTATION_0;
-    /**
-     * This is the configuration of the foreground app or the app that will be in the foreground
-     * once a quickstep gesture finishes.
-     */
-    private int mCurrentAppRotation = -1;
-    /**
-     * This flag is set to true when the device physically changes orientations. When true,
-     * we will always report the current rotation of the foreground app whenever the display
-     * changes, as it would indicate the user's intention to rotate the foreground app.
-     */
-    private boolean mPrioritizeDeviceRotation = false;
-
     private Region mExclusionRegion;
     private SystemGestureExclusionListenerCompat mExclusionListener;
 
     private final List<ComponentName> mGestureBlockedActivities;
-    private Runnable mOnDestroyFrozenTaskRunnable;
-    /**
-     * Set to true when user swipes to recents. In recents, we ignore the state of the recents
-     * task list being frozen or not to allow the user to keep interacting with nav bar rotation
-     * they went into recents with as opposed to defaulting to the default display rotation.
-     * TODO: (b/156984037) For when user rotates after entering overview
-     */
-    private boolean mInOverview;
-    private boolean mTaskListFrozen;
 
     private boolean mIsUserSetupComplete;
 
     public RecentsAnimationDeviceState(Context context) {
+        this(context, DisplayController.getDefaultDisplay(context));
+    }
+
+    public RecentsAnimationDeviceState(Context context, DisplayHolder displayHolder) {
         mContext = context;
+        mDisplayHolder = displayHolder;
         mSysUiNavMode = SysUINavigationMode.INSTANCE.get(context);
-        mDefaultDisplay = DefaultDisplay.INSTANCE.get(context);
-        mDisplayId = mDefaultDisplay.getInfo().id;
-        runOnDestroy(() -> mDefaultDisplay.removeChangeListener(this));
+        mDisplayId = mDisplayHolder.getInfo().id;
+        mIsOneHandedModeSupported = SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false);
+        runOnDestroy(() -> mDisplayHolder.removeChangeListener(this));
+        mRotationTouchHelper = new RotationTouchHelper(context, mDisplayHolder);
+        runOnDestroy(mRotationTouchHelper::destroy);
 
         // Register for user unlocked if necessary
         mIsUserUnlocked = context.getSystemService(UserManager.class)
@@ -207,10 +156,6 @@
         };
         runOnDestroy(mExclusionListener::unregister);
 
-        Resources resources = mContext.getResources();
-        mOrientationTouchTransformer = new OrientationTouchTransformer(resources, mMode,
-                () -> QuickStepContract.getWindowCornerRadius(resources));
-
         // Register for navigation mode changes
         onNavigationModeChanged(mSysUiNavMode.addModeChangeListener(this));
         runOnDestroy(() -> mSysUiNavMode.removeModeChangeListener(this));
@@ -231,6 +176,24 @@
             }
         }
 
+        if (mIsOneHandedModeSupported) {
+            SecureSettingsObserver oneHandedEnabledObserver =
+                    SecureSettingsObserver.newOneHandedSettingsObserver(
+                            mContext, enabled -> mIsOneHandedModeEnabled = enabled);
+            oneHandedEnabledObserver.register();
+            oneHandedEnabledObserver.dispatchOnChange();
+            runOnDestroy(oneHandedEnabledObserver::unregister);
+        } else {
+            mIsOneHandedModeEnabled = false;
+        }
+
+        SecureSettingsObserver swipeBottomEnabledObserver =
+                SecureSettingsObserver.newSwipeToNotificationSettingsObserver(
+                        mContext, enabled -> mIsSwipeToNotificationEnabled = enabled);
+        swipeBottomEnabledObserver.register();
+        swipeBottomEnabledObserver.dispatchOnChange();
+        runOnDestroy(swipeBottomEnabledObserver::unregister);
+
         SecureSettingsObserver userSetupObserver = new SecureSettingsObserver(
                 context.getContentResolver(),
                 e -> mIsUserSetupComplete = e,
@@ -241,38 +204,6 @@
             userSetupObserver.register();
             runOnDestroy(userSetupObserver::unregister);
         }
-
-        mOrientationListener = new OrientationEventListener(context) {
-            @Override
-            public void onOrientationChanged(int degrees) {
-                int newRotation = RecentsOrientedState.getRotationForUserDegreesRotated(degrees,
-                        mSensorRotation);
-                if (newRotation == mSensorRotation) {
-                    return;
-                }
-
-                mSensorRotation = newRotation;
-                mPrioritizeDeviceRotation = true;
-
-                if (newRotation == mCurrentAppRotation) {
-                    // When user rotates device to the orientation of the foreground app after
-                    // quickstepping
-                    toggleSecondaryNavBarsForRotation();
-                }
-            }
-        };
-    }
-
-    private void setupOrientationSwipeHandler() {
-        ActivityManagerWrapper.getInstance().registerTaskStackListener(mFrozenTaskListener);
-        mOnDestroyFrozenTaskRunnable = () -> ActivityManagerWrapper.getInstance()
-                .unregisterTaskStackListener(mFrozenTaskListener);
-        runOnDestroy(mOnDestroyFrozenTaskRunnable);
-    }
-
-    private void destroyOrientationSwipeHandlerCallback() {
-        ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mFrozenTaskListener);
-        mOnDestroyActions.remove(mOnDestroyFrozenTaskRunnable);
     }
 
     private void runOnDestroy(Runnable action) {
@@ -297,11 +228,20 @@
         runOnDestroy(() -> mSysUiNavMode.removeModeChangeListener(listener));
     }
 
+    /**
+     * Adds a listener for the one handed mode change,
+     * guaranteed to be called after the device state's mode has changed.
+     */
+    public void addOneHandedModeChangedCallback(OneHandedModeChangeListener listener) {
+        listener.onOneHandedModeChanged(mSysUiNavMode.addOneHandedOverlayChangeListener(listener));
+        runOnDestroy(() -> mSysUiNavMode.removeOneHandedOverlayChangeListener(listener));
+    }
+
     @Override
     public void onNavigationModeChanged(SysUINavigationMode.Mode newMode) {
-        mDefaultDisplay.removeChangeListener(this);
-        mDefaultDisplay.addChangeListener(this);
-        onDisplayInfoChanged(mDefaultDisplay.getInfo(), CHANGE_ALL);
+        mDisplayHolder.removeChangeListener(this);
+        mDisplayHolder.addChangeListener(this);
+        onDisplayInfoChanged(mDisplayHolder.getInfo(), CHANGE_ALL);
 
         if (newMode == NO_BUTTON) {
             mExclusionListener.register();
@@ -309,47 +249,26 @@
             mExclusionListener.unregister();
         }
 
-        mNavBarPosition = new NavBarPosition(newMode, mDefaultDisplay.getInfo());
-
-        mOrientationTouchTransformer.setNavigationMode(newMode, mDefaultDisplay.getInfo());
-        if (!mMode.hasGestures && newMode.hasGestures) {
-            setupOrientationSwipeHandler();
-        } else if (mMode.hasGestures && !newMode.hasGestures){
-            destroyOrientationSwipeHandlerCallback();
-        }
-
+        mNavBarPosition = new NavBarPosition(newMode, mDisplayHolder.getInfo());
         mMode = newMode;
     }
 
     @Override
-    public void onDisplayInfoChanged(DefaultDisplay.Info info, int flags) {
+    public void onDisplayInfoChanged(Info info, int flags) {
         if (info.id != getDisplayId() || flags == CHANGE_FRAME_DELAY) {
             // ignore displays that aren't running launcher and frame refresh rate changes
             return;
         }
 
-        mDisplayRotation = info.rotation;
-
         if (!mMode.hasGestures) {
             return;
         }
         mNavBarPosition = new NavBarPosition(mMode, info);
-        updateGestureTouchRegions();
-        mOrientationTouchTransformer.createOrAddTouchRegion(info);
-        mCurrentAppRotation = mDisplayRotation;
+    }
 
-        /* Update nav bars on the following:
-         * a) if this is coming from an activity rotation OR
-         *   aa) we launch an app in the orientation that user is already in
-         * b) We're not in overview, since overview will always be portrait (w/o home rotation)
-         * c) We're actively in quickswitch mode
-         */
-        if ((mPrioritizeDeviceRotation
-                || mCurrentAppRotation == mSensorRotation) // switch to an app of orientation user is in
-                && !mInOverview
-                && mTaskListFrozen) {
-            toggleSecondaryNavBarsForRotation();
-        }
+    @Override
+    public void onOneHandedModeChanged(int newGesturalHeight) {
+        mRotationTouchHelper.setGesturalHeight(newGesturalHeight);
     }
 
     /**
@@ -464,7 +383,7 @@
      */
     public boolean canStartSystemGesture() {
         boolean canStartWithNavHidden = (mSystemUiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) == 0
-                || mTaskListFrozen;
+                || mRotationTouchHelper.isTaskListFrozen();
         return canStartWithNavHidden
                 && (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) == 0
                 && (mSystemUiStateFlags & SYSUI_STATE_QUICK_SETTINGS_EXPANDED) == 0
@@ -537,30 +456,10 @@
     }
 
     /**
-     * Updates the regions for detecting the swipe up/quickswitch and assistant gestures.
+     * @return whether one-handed mode is enabled and active
      */
-    public void updateGestureTouchRegions() {
-        if (!mMode.hasGestures) {
-            return;
-        }
-
-        mOrientationTouchTransformer.createOrAddTouchRegion(mDefaultDisplay.getInfo());
-    }
-
-    /**
-     * @return whether the coordinates of the {@param event} is in the swipe up gesture region.
-     */
-    public boolean isInSwipeUpTouchRegion(MotionEvent event) {
-        return mOrientationTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY());
-    }
-
-    /**
-     * @return whether the coordinates of the {@param event} with the given {@param pointerIndex}
-     *         is in the swipe up gesture region.
-     */
-    public boolean isInSwipeUpTouchRegion(MotionEvent event, int pointerIndex) {
-        return mOrientationTouchTransformer.touchInValidSwipeRegions(event.getX(pointerIndex),
-                event.getY(pointerIndex));
+    public boolean isOneHandedModeActive() {
+        return (mSystemUiStateFlags & SYSUI_STATE_ONE_HANDED_ACTIVE) != 0;
     }
 
     /**
@@ -620,101 +519,42 @@
     public boolean canTriggerAssistantAction(MotionEvent ev, ActivityManager.RunningTaskInfo task) {
         return mAssistantAvailable
                 && !QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags)
-                && mOrientationTouchTransformer.touchInAssistantRegion(ev)
+                && mRotationTouchHelper.touchInAssistantRegion(ev)
                 && !isLockToAppActive()
                 && !isGestureBlockedActivity(task);
     }
 
     /**
-     * *May* apply a transform on the motion event if it lies in the nav bar region for another
-     * orientation that is currently being tracked as a part of quickstep
+     * One handed gestural in quickstep only active on NO_BUTTON, TWO_BUTTONS, and portrait mode
+     *
+     * @param ev The touch screen motion event.
+     * @return whether the given motion event can trigger the one handed mode.
      */
-    void setOrientationTransformIfNeeded(MotionEvent event) {
-        // negative coordinates bug b/143901881
-        if (event.getX() < 0 || event.getY() < 0) {
-            event.setLocation(Math.max(0, event.getX()), Math.max(0, event.getY()));
+    public boolean canTriggerOneHandedAction(MotionEvent ev) {
+        if (!mIsOneHandedModeSupported) {
+            return false;
         }
-        mOrientationTouchTransformer.transform(event);
-    }
 
-    private void enableMultipleRegions(boolean enable) {
-        mOrientationTouchTransformer.enableMultipleRegions(enable, mDefaultDisplay.getInfo());
-        notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getQuickStepStartingRotation());
-        if (enable && !mInOverview && !TestProtocol.sDisableSensorRotation) {
-            // Clear any previous state from sensor manager
-            mSensorRotation = mCurrentAppRotation;
-            mOrientationListener.enable();
-        } else {
-            mOrientationListener.disable();
+        if (mIsOneHandedModeEnabled || mIsSwipeToNotificationEnabled) {
+            final Info displayInfo = mDisplayHolder.getInfo();
+            return (mRotationTouchHelper.touchInOneHandedModeRegion(ev)
+                && displayInfo.rotation != Surface.ROTATION_90
+                && displayInfo.rotation != Surface.ROTATION_270
+                && displayInfo.metrics.densityDpi < DisplayMetrics.DENSITY_600);
         }
+        return false;
     }
 
-    public void onStartGesture() {
-        if (mTaskListFrozen) {
-            // Prioritize whatever nav bar user touches once in quickstep
-            // This case is specifically when user changes what nav bar they are using mid
-            // quickswitch session before tasks list is unfrozen
-            notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getCurrentActiveRotation());
-        }
+    public boolean isOneHandedModeEnabled() {
+        return mIsOneHandedModeEnabled;
     }
 
-    void onEndTargetCalculated(GestureState.GestureEndTarget endTarget,
-            BaseActivityInterface activityInterface) {
-        if (endTarget == GestureState.GestureEndTarget.RECENTS) {
-            mInOverview = true;
-            if (!mTaskListFrozen) {
-                // If we're in landscape w/o ever quickswitching, show the navbar in landscape
-                enableMultipleRegions(true);
-            }
-            activityInterface.onExitOverview(this, mExitOverviewRunnable);
-        } else if (endTarget == GestureState.GestureEndTarget.HOME) {
-            enableMultipleRegions(false);
-        } else if (endTarget == GestureState.GestureEndTarget.NEW_TASK) {
-            if (mOrientationTouchTransformer.getQuickStepStartingRotation() == -1) {
-                // First gesture to start quickswitch
-                enableMultipleRegions(true);
-            } else {
-                notifySysuiOfCurrentRotation(
-                        mOrientationTouchTransformer.getCurrentActiveRotation());
-            }
-
-            // A new gesture is starting, reset the current device rotation
-            // This is done under the assumption that the user won't rotate the phone and then
-            // quickswitch in the old orientation.
-            mPrioritizeDeviceRotation = false;
-        } else if (endTarget == GestureState.GestureEndTarget.LAST_TASK) {
-            if (!mTaskListFrozen) {
-                // touched nav bar but didn't go anywhere and not quickswitching, do nothing
-                return;
-            }
-            notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getCurrentActiveRotation());
-        }
+    public boolean isSwipeToNotificationEnabled() {
+        return mIsSwipeToNotificationEnabled;
     }
 
-    private void notifySysuiOfCurrentRotation(int rotation) {
-        UI_HELPER_EXECUTOR.execute(() -> SystemUiProxy.INSTANCE.get(mContext)
-                .onQuickSwitchToNewTask(rotation));
-    }
-
-    /**
-     * Disables/Enables multiple nav bars on {@link OrientationTouchTransformer} and then
-     * notifies system UI of the primary rotation the user is interacting with
-     */
-    private void toggleSecondaryNavBarsForRotation() {
-        mOrientationTouchTransformer.setSingleActiveRegion(mDefaultDisplay.getInfo());
-        notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getCurrentActiveRotation());
-    }
-
-    public int getCurrentActiveRotation() {
-        if (!mMode.hasGestures) {
-            // touch rotation should always match that of display for 3 button
-            return mDisplayRotation;
-        }
-        return mOrientationTouchTransformer.getCurrentActiveRotation();
-    }
-
-    public int getDisplayRotation() {
-        return mDisplayRotation;
+    public RotationTouchHelper getRotationTouchHelper() {
+        return mRotationTouchHelper;
     }
 
     public void dump(PrintWriter pw) {
@@ -726,9 +566,9 @@
         pw.println("  assistantAvailable=" + mAssistantAvailable);
         pw.println("  assistantDisabled="
                 + QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags));
-        pw.println("  currentActiveRotation=" + getCurrentActiveRotation());
-        pw.println("  displayRotation=" + getDisplayRotation());
         pw.println("  isUserUnlocked=" + mIsUserUnlocked);
-        mOrientationTouchTransformer.dump(pw);
+        pw.println("  isOneHandedModeEnabled=" + mIsOneHandedModeEnabled);
+        pw.println("  isSwipeToNotificationEnabled=" + mIsSwipeToNotificationEnabled);
+        mRotationTouchHelper.dump(pw);
     }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 517501a..6c302ae 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -93,15 +93,6 @@
     }
 
     /**
-     * @return The task id of the running task, or -1 if there is no current running task.
-     */
-    public static int getRunningTaskId() {
-        ActivityManager.RunningTaskInfo runningTask =
-                ActivityManagerWrapper.getInstance().getRunningTask();
-        return runningTask != null ? runningTask.id : -1;
-    }
-
-    /**
      * @return Whether the provided {@param changeId} is the latest recent tasks list id.
      */
     public boolean isTaskListValid(int changeId) {
@@ -140,7 +131,9 @@
         }
 
         // Keep the cache up to date with the latest thumbnails
-        int runningTaskId = RecentsModel.getRunningTaskId();
+        ActivityManager.RunningTaskInfo runningTask =
+                ActivityManagerWrapper.getInstance().getRunningTask();
+        int runningTaskId = runningTask != null ? runningTask.id : -1;
         mTaskList.getTaskKeys(mThumbnailCache.getCacheSize(), tasks -> {
             for (Task task : tasks) {
                 if (task.key.id == runningTaskId) {
@@ -167,9 +160,9 @@
 
     @Override
     public void onTaskRemoved(int taskId) {
-        Task.TaskKey dummyKey = new Task.TaskKey(taskId, 0, null, null, 0, 0);
-        mThumbnailCache.remove(dummyKey);
-        mIconCache.onTaskRemoved(dummyKey);
+        Task.TaskKey stubKey = new Task.TaskKey(taskId, 0, null, null, 0, 0);
+        mThumbnailCache.remove(stubKey);
+        mIconCache.onTaskRemoved(stubKey);
     }
 
     public void onTrimMemory(int level) {
diff --git a/quickstep/src/com/android/quickstep/RotationTouchHelper.java b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
new file mode 100644
index 0000000..2cf3212
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
@@ -0,0 +1,377 @@
+/*
+ * Copyright (C) 2020 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.quickstep;
+
+import static android.view.Surface.ROTATION_0;
+
+import static com.android.launcher3.util.DisplayController.DisplayHolder.CHANGE_ALL;
+import static com.android.launcher3.util.DisplayController.DisplayHolder.CHANGE_FRAME_DELAY;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.quickstep.SysUINavigationMode.Mode.THREE_BUTTONS;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.view.MotionEvent;
+import android.view.OrientationEventListener;
+
+import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.util.DisplayController.DisplayHolder;
+import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
+import com.android.launcher3.util.DisplayController.Info;
+import com.android.quickstep.util.RecentsOrientedState;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.shared.system.TaskStackChangeListener;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+public class RotationTouchHelper implements
+        SysUINavigationMode.NavigationModeChangeListener,
+        DisplayInfoChangeListener {
+
+    private final OrientationTouchTransformer mOrientationTouchTransformer;
+    private final DisplayHolder mDisplayHolder;
+    private final SysUINavigationMode mSysUiNavMode;
+    private final int mDisplayId;
+    private int mDisplayRotation;
+
+    private final ArrayList<Runnable> mOnDestroyActions = new ArrayList<>();
+
+    private SysUINavigationMode.Mode mMode = THREE_BUTTONS;
+
+    private TaskStackChangeListener mFrozenTaskListener = new TaskStackChangeListener() {
+        @Override
+        public void onRecentTaskListFrozenChanged(boolean frozen) {
+            mTaskListFrozen = frozen;
+            if (frozen || mInOverview) {
+                return;
+            }
+            enableMultipleRegions(false);
+        }
+
+        @Override
+        public void onActivityRotation(int displayId) {
+            super.onActivityRotation(displayId);
+            // This always gets called before onDisplayInfoChanged() so we know how to process
+            // the rotation in that method. This is done to avoid having a race condition between
+            // the sensor readings and onDisplayInfoChanged() call
+            if (displayId != mDisplayId) {
+                return;
+            }
+
+            mPrioritizeDeviceRotation = true;
+            if (mInOverview) {
+                // reset, launcher must be rotating
+                mExitOverviewRunnable.run();
+            }
+        }
+    };
+
+    private Runnable mExitOverviewRunnable = new Runnable() {
+        @Override
+        public void run() {
+            mInOverview = false;
+            enableMultipleRegions(false);
+        }
+    };
+
+    /**
+     * Used to listen for when the device rotates into the orientation of the current foreground
+     * app. For example, if a user quickswitches from a portrait to a fixed landscape app and then
+     * rotates rotates the device to match that orientation, this triggers calls to sysui to adjust
+     * the navbar.
+     */
+    private OrientationEventListener mOrientationListener;
+    private int mSensorRotation = ROTATION_0;
+    /**
+     * This is the configuration of the foreground app or the app that will be in the foreground
+     * once a quickstep gesture finishes.
+     */
+    private int mCurrentAppRotation = -1;
+    /**
+     * This flag is set to true when the device physically changes orientations. When true, we will
+     * always report the current rotation of the foreground app whenever the display changes, as it
+     * would indicate the user's intention to rotate the foreground app.
+     */
+    private boolean mPrioritizeDeviceRotation = false;
+    private Runnable mOnDestroyFrozenTaskRunnable;
+    /**
+     * Set to true when user swipes to recents. In recents, we ignore the state of the recents
+     * task list being frozen or not to allow the user to keep interacting with nav bar rotation
+     * they went into recents with as opposed to defaulting to the default display rotation.
+     * TODO: (b/156984037) For when user rotates after entering overview
+     */
+    private boolean mInOverview;
+    private boolean mTaskListFrozen;
+
+
+    private final Context mContext;
+
+    public RotationTouchHelper(Context context, DisplayHolder displayHolder) {
+        mContext = context;
+        mDisplayHolder = displayHolder;
+        Resources resources = mContext.getResources();
+        mSysUiNavMode = SysUINavigationMode.INSTANCE.get(context);
+        mDisplayId = mDisplayHolder.getInfo().id;
+
+        mOrientationTouchTransformer = new OrientationTouchTransformer(resources, mMode,
+                () -> QuickStepContract.getWindowCornerRadius(resources));
+
+        // Register for navigation mode changes
+        onNavigationModeChanged(mSysUiNavMode.addModeChangeListener(this));
+        runOnDestroy(() -> mSysUiNavMode.removeModeChangeListener(this));
+
+        mOrientationListener = new OrientationEventListener(context) {
+            @Override
+            public void onOrientationChanged(int degrees) {
+                int newRotation = RecentsOrientedState.getRotationForUserDegreesRotated(degrees,
+                        mSensorRotation);
+                if (newRotation == mSensorRotation) {
+                    return;
+                }
+
+                mSensorRotation = newRotation;
+                mPrioritizeDeviceRotation = true;
+
+                if (newRotation == mCurrentAppRotation) {
+                    // When user rotates device to the orientation of the foreground app after
+                    // quickstepping
+                    toggleSecondaryNavBarsForRotation();
+                }
+            }
+        };
+    }
+
+    private void setupOrientationSwipeHandler() {
+        ActivityManagerWrapper.getInstance().registerTaskStackListener(mFrozenTaskListener);
+        mOnDestroyFrozenTaskRunnable = () -> ActivityManagerWrapper.getInstance()
+                .unregisterTaskStackListener(mFrozenTaskListener);
+        runOnDestroy(mOnDestroyFrozenTaskRunnable);
+    }
+
+    private void destroyOrientationSwipeHandlerCallback() {
+        ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mFrozenTaskListener);
+        mOnDestroyActions.remove(mOnDestroyFrozenTaskRunnable);
+    }
+
+    private void runOnDestroy(Runnable action) {
+        mOnDestroyActions.add(action);
+    }
+
+    /**
+     * Cleans up all the registered listeners and receivers.
+     */
+    public void destroy() {
+        for (Runnable r : mOnDestroyActions) {
+            r.run();
+        }
+    }
+
+    public boolean isTaskListFrozen() {
+        return mTaskListFrozen;
+    }
+
+    public boolean touchInAssistantRegion(MotionEvent ev) {
+        return mOrientationTouchTransformer.touchInAssistantRegion(ev);
+    }
+
+    public boolean touchInOneHandedModeRegion(MotionEvent ev) {
+        return mOrientationTouchTransformer.touchInOneHandedModeRegion(ev);
+    }
+
+    /**
+     * Updates the regions for detecting the swipe up/quickswitch and assistant gestures.
+     */
+    public void updateGestureTouchRegions() {
+        if (!mMode.hasGestures) {
+            return;
+        }
+
+        mOrientationTouchTransformer.createOrAddTouchRegion(mDisplayHolder.getInfo());
+    }
+
+    /**
+     * @return whether the coordinates of the {@param event} is in the swipe up gesture region.
+     */
+    public boolean isInSwipeUpTouchRegion(MotionEvent event) {
+        return mOrientationTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY());
+    }
+
+    /**
+     * @return whether the coordinates of the {@param event} with the given {@param pointerIndex}
+     *         is in the swipe up gesture region.
+     */
+    public boolean isInSwipeUpTouchRegion(MotionEvent event, int pointerIndex) {
+        return mOrientationTouchTransformer.touchInValidSwipeRegions(event.getX(pointerIndex),
+                event.getY(pointerIndex));
+    }
+
+
+    @Override
+    public void onNavigationModeChanged(SysUINavigationMode.Mode newMode) {
+        mDisplayHolder.removeChangeListener(this);
+        mDisplayHolder.addChangeListener(this);
+        onDisplayInfoChanged(mDisplayHolder.getInfo(), CHANGE_ALL);
+
+        mOrientationTouchTransformer.setNavigationMode(newMode, mDisplayHolder.getInfo(),
+            mContext.getResources());
+        if (!mMode.hasGestures && newMode.hasGestures) {
+            setupOrientationSwipeHandler();
+        } else if (mMode.hasGestures && !newMode.hasGestures){
+            destroyOrientationSwipeHandlerCallback();
+        }
+
+        mMode = newMode;
+    }
+
+    public int getDisplayRotation() {
+        return mDisplayRotation;
+    }
+
+    @Override
+    public void onDisplayInfoChanged(Info info, int flags) {
+        if (info.id != mDisplayId|| flags == CHANGE_FRAME_DELAY) {
+            // ignore displays that aren't running launcher and frame refresh rate changes
+            return;
+        }
+
+        mDisplayRotation = info.rotation;
+
+        if (!mMode.hasGestures) {
+            return;
+        }
+        updateGestureTouchRegions();
+        mOrientationTouchTransformer.createOrAddTouchRegion(info);
+        mCurrentAppRotation = mDisplayRotation;
+
+        /* Update nav bars on the following:
+         * a) if this is coming from an activity rotation OR
+         *   aa) we launch an app in the orientation that user is already in
+         * b) We're not in overview, since overview will always be portrait (w/o home rotation)
+         * c) We're actively in quickswitch mode
+         */
+        if ((mPrioritizeDeviceRotation
+                || mCurrentAppRotation == mSensorRotation) // switch to an app of orientation user is in
+                && !mInOverview
+                && mTaskListFrozen) {
+            toggleSecondaryNavBarsForRotation();
+        }
+    }
+
+    /**
+     * Sets the gestural height.
+     */
+    void setGesturalHeight(int newGesturalHeight) {
+        mOrientationTouchTransformer.setGesturalHeight(newGesturalHeight, mDisplayHolder.getInfo(),
+            mContext.getResources());
+    }
+
+    /**
+     * *May* apply a transform on the motion event if it lies in the nav bar region for another
+     * orientation that is currently being tracked as a part of quickstep
+     */
+    void setOrientationTransformIfNeeded(MotionEvent event) {
+        // negative coordinates bug b/143901881
+        if (event.getX() < 0 || event.getY() < 0) {
+            event.setLocation(Math.max(0, event.getX()), Math.max(0, event.getY()));
+        }
+        mOrientationTouchTransformer.transform(event);
+    }
+
+    private void enableMultipleRegions(boolean enable) {
+        mOrientationTouchTransformer.enableMultipleRegions(enable, mDisplayHolder.getInfo());
+        notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getQuickStepStartingRotation());
+        if (enable && !mInOverview && !TestProtocol.sDisableSensorRotation) {
+            // Clear any previous state from sensor manager
+            mSensorRotation = mCurrentAppRotation;
+            mOrientationListener.enable();
+        } else {
+            mOrientationListener.disable();
+        }
+    }
+
+    public void onStartGesture() {
+        if (mTaskListFrozen) {
+            // Prioritize whatever nav bar user touches once in quickstep
+            // This case is specifically when user changes what nav bar they are using mid
+            // quickswitch session before tasks list is unfrozen
+            notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getCurrentActiveRotation());
+        }
+    }
+
+    void onEndTargetCalculated(GestureState.GestureEndTarget endTarget,
+            BaseActivityInterface activityInterface) {
+        if (endTarget == GestureState.GestureEndTarget.RECENTS) {
+            mInOverview = true;
+            if (!mTaskListFrozen) {
+                // If we're in landscape w/o ever quickswitching, show the navbar in landscape
+                enableMultipleRegions(true);
+            }
+            activityInterface.onExitOverview(this, mExitOverviewRunnable);
+        } else if (endTarget == GestureState.GestureEndTarget.HOME) {
+            enableMultipleRegions(false);
+        } else if (endTarget == GestureState.GestureEndTarget.NEW_TASK) {
+            if (mOrientationTouchTransformer.getQuickStepStartingRotation() == -1) {
+                // First gesture to start quickswitch
+                enableMultipleRegions(true);
+            } else {
+                notifySysuiOfCurrentRotation(
+                        mOrientationTouchTransformer.getCurrentActiveRotation());
+            }
+
+            // A new gesture is starting, reset the current device rotation
+            // This is done under the assumption that the user won't rotate the phone and then
+            // quickswitch in the old orientation.
+            mPrioritizeDeviceRotation = false;
+        } else if (endTarget == GestureState.GestureEndTarget.LAST_TASK) {
+            if (!mTaskListFrozen) {
+                // touched nav bar but didn't go anywhere and not quickswitching, do nothing
+                return;
+            }
+            notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getCurrentActiveRotation());
+        }
+    }
+
+    private void notifySysuiOfCurrentRotation(int rotation) {
+        UI_HELPER_EXECUTOR.execute(() -> SystemUiProxy.INSTANCE.get(mContext)
+                .onQuickSwitchToNewTask(rotation));
+    }
+
+    /**
+     * Disables/Enables multiple nav bars on {@link OrientationTouchTransformer} and then
+     * notifies system UI of the primary rotation the user is interacting with
+     */
+    private void toggleSecondaryNavBarsForRotation() {
+        mOrientationTouchTransformer.setSingleActiveRegion(mDisplayHolder.getInfo());
+        notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getCurrentActiveRotation());
+    }
+
+    public int getCurrentActiveRotation() {
+        if (!mMode.hasGestures) {
+            // touch rotation should always match that of display for 3 button
+            return mDisplayRotation;
+        }
+        return mOrientationTouchTransformer.getCurrentActiveRotation();
+    }
+
+    public void dump(PrintWriter pw) {
+        pw.println("RotationTouchHelper:");
+        pw.println("  currentActiveRotation=" + getCurrentActiveRotation());
+        pw.println("  displayRotation=" + getDisplayRotation());
+        mOrientationTouchTransformer.dump(pw);
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
similarity index 78%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeUpAnimationLogic.java
rename to quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
index dc8f1c5..b6eaa1c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -16,8 +16,7 @@
 package com.android.quickstep;
 
 import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
-import static com.android.launcher3.anim.Interpolators.DEACCEL;
-import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
 
 import android.animation.Animator;
 import android.content.Context;
@@ -25,10 +24,8 @@
 import android.graphics.Matrix.ScaleToFit;
 import android.graphics.Rect;
 import android.graphics.RectF;
-import android.view.animation.Interpolator;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 
 import com.android.launcher3.DeviceProfile;
@@ -37,7 +34,7 @@
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.touch.PagedOrientationHandler;
-import com.android.launcher3.views.FloatingIconView;
+import com.android.quickstep.util.AnimatorControllerWithResistance;
 import com.android.quickstep.util.RectFSpringAnim;
 import com.android.quickstep.util.TaskViewSimulator;
 import com.android.quickstep.util.TransformParams;
@@ -48,7 +45,6 @@
 public abstract class SwipeUpAnimationLogic {
 
     protected static final Rect TEMP_RECT = new Rect();
-    private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL;
 
     protected DeviceProfile mDp;
 
@@ -69,12 +65,8 @@
     protected int mTransitionDragLength;
     // How much further we can drag past recents, as a factor of mTransitionDragLength.
     protected float mDragLengthFactor = 1;
-    // Start resisting when swiping past this factor of mTransitionDragLength.
-    private float mDragLengthFactorStartPullback = 1f;
-    // This is how far down we can scale down, where 0f is full screen and 1f is recents.
-    private float mDragLengthFactorMaxPullback = 1f;
 
-    protected AnimatorPlaybackController mWindowTransitionController;
+    protected AnimatorControllerWithResistance mWindowTransitionController;
 
     public SwipeUpAnimationLogic(Context context, RecentsAnimationDeviceState deviceState,
             GestureState gestureState, TransformParams transformParams) {
@@ -85,7 +77,9 @@
         mTransformParams = transformParams;
 
         mTaskViewSimulator.setLayoutRotation(
-                mDeviceState.getCurrentActiveRotation(), mDeviceState.getDisplayRotation());
+                mDeviceState.getRotationTouchHelper().getCurrentActiveRotation(),
+                mDeviceState.getRotationTouchHelper().getDisplayRotation());
+        mTaskViewSimulator.setDrawsBelowRecents(true);
     }
 
     protected void initTransitionEndpoints(DeviceProfile dp) {
@@ -99,19 +93,17 @@
         if (mDeviceState.isFullyGesturalNavMode()) {
             // We can drag all the way to the top of the screen.
             mDragLengthFactor = (float) dp.heightPx / mTransitionDragLength;
-
-            float startScale = mTaskViewSimulator.getFullScreenScale();
-            // Start pulling back when RecentsView scale is 0.75f, and let it go down to 0.5f.
-            mDragLengthFactorStartPullback = (0.75f - startScale) / (1 - startScale);
-            mDragLengthFactorMaxPullback = (0.5f - startScale) / (1 - startScale);
         } else {
-            mDragLengthFactor = 1;
-            mDragLengthFactorStartPullback = mDragLengthFactorMaxPullback = 1;
+            mDragLengthFactor = 1 + AnimatorControllerWithResistance.TWO_BUTTON_EXTRA_DRAG_FACTOR;
         }
 
         PendingAnimation pa = new PendingAnimation(mTransitionDragLength * 2);
-        mTaskViewSimulator.addAppToOverviewAnim(pa, t -> t * mDragLengthFactor);
-        mWindowTransitionController = pa.createPlaybackController();
+        mTaskViewSimulator.addAppToOverviewAnim(pa, LINEAR);
+        AnimatorPlaybackController normalController = pa.createPlaybackController();
+        mWindowTransitionController = AnimatorControllerWithResistance.createForRecents(
+                normalController, mContext, mTaskViewSimulator.getOrientationState(),
+                mDp, mTaskViewSimulator.recentsViewScale, AnimatedFloat.VALUE,
+                mTaskViewSimulator.recentsViewSecondaryTranslation, AnimatedFloat.VALUE);
     }
 
     @UiThread
@@ -124,13 +116,6 @@
         } else {
             float translation = Math.max(displacement, 0);
             shift = mTransitionDragLength == 0 ? 0 : translation / mTransitionDragLength;
-            if (shift > mDragLengthFactorStartPullback) {
-                float pullbackProgress = Utilities.getProgress(shift,
-                        mDragLengthFactorStartPullback, mDragLengthFactor);
-                pullbackProgress = PULLBACK_INTERPOLATOR.getInterpolation(pullbackProgress);
-                shift = mDragLengthFactorStartPullback + pullbackProgress
-                        * (mDragLengthFactorMaxPullback - mDragLengthFactorStartPullback);
-            }
         }
 
         mCurrentShift.updateValue(shift);
@@ -148,12 +133,6 @@
 
     protected abstract class HomeAnimationFactory {
 
-        public FloatingIconView mIconView;
-
-        public HomeAnimationFactory(@Nullable FloatingIconView iconView) {
-            mIconView = iconView;
-        }
-
         public @NonNull RectF getWindowTargetRect() {
             PagedOrientationHandler orientationHandler = getOrientationHandler();
             DeviceProfile dp = mDp;
@@ -174,6 +153,12 @@
         public void playAtomicAnimation(float velocity) {
             // No-op
         }
+
+        public void setAnimation(RectFSpringAnim anim) { }
+
+        public void update(RectF currentRect, float progress, float radius) { }
+
+        public void onCancel() { }
     }
 
     /**
@@ -184,10 +169,8 @@
     protected RectFSpringAnim createWindowAnimationToHome(float startProgress,
             HomeAnimationFactory homeAnimationFactory) {
         final RectF targetRect = homeAnimationFactory.getWindowTargetRect();
-        final FloatingIconView fiv = homeAnimationFactory.mIconView;
-        final boolean isFloatingIconView = fiv != null;
 
-        mWindowTransitionController.setPlayFraction(startProgress / mDragLengthFactor);
+        mCurrentShift.updateValue(startProgress);
         mTaskViewSimulator.apply(mTransformParams.setProgress(startProgress));
         RectF cropRectF = new RectF(mTaskViewSimulator.getCurrentCropRect());
 
@@ -203,11 +186,7 @@
         windowToHomePositionMap.mapRect(startRect);
 
         RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect, mContext);
-        if (isFloatingIconView) {
-            anim.addAnimatorListener(fiv);
-            fiv.setOnTargetChangeListener(anim::onTargetPositionChanged);
-            fiv.setFastFinishRunnable(anim::end);
-        }
+        homeAnimationFactory.setAnimation(anim);
 
         SpringAnimationRunner runner = new SpringAnimationRunner(
                 homeAnimationFactory, cropRectF, homeToWindowPositionMap);
@@ -242,32 +221,27 @@
 
         final RectF mWindowCurrentRect = new RectF();
         final Matrix mHomeToWindowPositionMap;
+        final HomeAnimationFactory mAnimationFactory;
 
-        final FloatingIconView mFIV;
         final AnimatorPlaybackController mHomeAnim;
         final RectF mCropRectF;
 
         final float mStartRadius;
         final float mEndRadius;
-        final float mWindowAlphaThreshold;
 
         SpringAnimationRunner(HomeAnimationFactory factory, RectF cropRectF,
                 Matrix homeToWindowPositionMap) {
+            mAnimationFactory = factory;
             mHomeAnim = factory.createActivityAnimationToHome();
             mCropRectF = cropRectF;
             mHomeToWindowPositionMap = homeToWindowPositionMap;
 
             cropRectF.roundOut(mCropRect);
-            mFIV = factory.mIconView;
 
             // End on a "round-enough" radius so that the shape reveal doesn't have to do too much
             // rounding at the end of the animation.
             mStartRadius = mTaskViewSimulator.getCurrentCornerRadius();
             mEndRadius = cropRectF.width() / 2f;
-
-            // We want the window alpha to be 0 once this threshold is met, so that the
-            // FolderIconView can be seen morphing into the icon shape.
-            mWindowAlphaThreshold = mFIV != null ? 1f - SHAPE_PROGRESS_DURATION : 1f;
         }
 
         @Override
@@ -282,10 +256,7 @@
                     .setCornerRadius(cornerRadius);
 
             mTransformParams.applySurfaceParams(mTransformParams.createSurfaceParams(this));
-            if (mFIV != null) {
-                mFIV.update(currentRect, 1f, progress,
-                        mWindowAlphaThreshold, mMatrix.mapRadius(cornerRadius), false);
-            }
+            mAnimationFactory.update(currentRect, progress, mMatrix.mapRadius(cornerRadius));
         }
 
         @Override
@@ -298,9 +269,7 @@
 
         @Override
         public void onCancel() {
-            if (mFIV != null) {
-                mFIV.fastFinish();
-            }
+            mAnimationFactory.onCancel();
         }
 
         @Override
diff --git a/quickstep/src/com/android/quickstep/SysUINavigationMode.java b/quickstep/src/com/android/quickstep/SysUINavigationMode.java
index 05ce2a2..6b50218 100644
--- a/quickstep/src/com/android/quickstep/SysUINavigationMode.java
+++ b/quickstep/src/com/android/quickstep/SysUINavigationMode.java
@@ -16,14 +16,19 @@
 
 package com.android.quickstep;
 
+import static com.android.launcher3.ResourceUtils.INVALID_RESOURCE_HANDLE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NAVIGATION_MODE_2_BUTTON;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NAVIGATION_MODE_3_BUTTON;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NAVIGATION_MODE_GESTURE_BUTTON;
 import static com.android.launcher3.util.PackageManagerHelper.getPackageFilter;
 
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.res.Resources;
 import android.util.Log;
 
+import com.android.launcher3.ResourceUtils;
+import com.android.launcher3.logging.StatsLogManager.LauncherEvent;
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.util.MainThreadInitializedObject;
 
@@ -37,16 +42,18 @@
 public class SysUINavigationMode {
 
     public enum Mode {
-        THREE_BUTTONS(false, 0),
-        TWO_BUTTONS(true, 1),
-        NO_BUTTON(true, 2);
+        THREE_BUTTONS(false, 0, LAUNCHER_NAVIGATION_MODE_3_BUTTON),
+        TWO_BUTTONS(true, 1, LAUNCHER_NAVIGATION_MODE_2_BUTTON),
+        NO_BUTTON(true, 2, LAUNCHER_NAVIGATION_MODE_GESTURE_BUTTON);
 
         public final boolean hasGestures;
         public final int resValue;
+        public final LauncherEvent launcherEvent;
 
-        Mode(boolean hasGestures, int resValue) {
+        Mode(boolean hasGestures, int resValue, LauncherEvent launcherEvent) {
             this.hasGestures = hasGestures;
             this.resValue = resValue;
+            this.launcherEvent = launcherEvent;
         }
     }
 
@@ -58,15 +65,19 @@
             new MainThreadInitializedObject<>(SysUINavigationMode::new);
 
     private static final String TAG = "SysUINavigationMode";
-
     private static final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED";
     private static final String NAV_BAR_INTERACTION_MODE_RES_NAME =
             "config_navBarInteractionMode";
+    private static final String TARGET_OVERLAY_PACKAGE = "android";
 
     private final Context mContext;
     private Mode mMode;
 
+    private int mNavBarGesturalHeight;
+
     private final List<NavigationModeChangeListener> mChangeListeners = new ArrayList<>();
+    private final List<OneHandedModeChangeListener> mOneHandedOverlayChangeListeners =
+            new ArrayList<>();
 
     public SysUINavigationMode(Context context) {
         mContext = context;
@@ -76,8 +87,9 @@
             @Override
             public void onReceive(Context context, Intent intent) {
                 updateMode();
+                updateGesturalHeight();
             }
-        }, getPackageFilter("android", ACTION_OVERLAY_CHANGED));
+        }, getPackageFilter(TARGET_OVERLAY_PACKAGE, ACTION_OVERLAY_CHANGED));
     }
 
     /** Updates navigation mode when needed. */
@@ -89,9 +101,35 @@
         }
     }
 
+    private void updateGesturalHeight() {
+        int newGesturalHeight = ResourceUtils.getDimenByName(
+                ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, mContext.getResources(),
+                INVALID_RESOURCE_HANDLE);
+
+        if (newGesturalHeight == INVALID_RESOURCE_HANDLE) {
+            Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?");
+            return;
+        }
+
+        if (mNavBarGesturalHeight != newGesturalHeight) {
+            mNavBarGesturalHeight = newGesturalHeight;
+            dispatchOneHandedOverlayChange();
+        }
+    }
+
     private void initializeMode() {
-        int modeInt = getSystemIntegerRes(mContext, NAV_BAR_INTERACTION_MODE_RES_NAME);
-        for(Mode m : Mode.values()) {
+        int modeInt = ResourceUtils.getIntegerByName(NAV_BAR_INTERACTION_MODE_RES_NAME,
+                mContext.getResources(), INVALID_RESOURCE_HANDLE);
+        mNavBarGesturalHeight = ResourceUtils.getDimenByName(
+                ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, mContext.getResources(),
+                INVALID_RESOURCE_HANDLE);
+
+        if (modeInt == INVALID_RESOURCE_HANDLE) {
+            Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?");
+            return;
+        }
+
+        for (Mode m : Mode.values()) {
             if (m.resValue == modeInt) {
                 mMode = m;
             }
@@ -104,6 +142,12 @@
         }
     }
 
+    private void dispatchOneHandedOverlayChange() {
+        for (OneHandedModeChangeListener listener : mOneHandedOverlayChangeListeners) {
+            listener.onOneHandedModeChanged(mNavBarGesturalHeight);
+        }
+    }
+
     public Mode addModeChangeListener(NavigationModeChangeListener listener) {
         mChangeListeners.add(listener);
         return mMode;
@@ -113,20 +157,17 @@
         mChangeListeners.remove(listener);
     }
 
-    public Mode getMode() {
-        return mMode;
+    public int addOneHandedOverlayChangeListener(OneHandedModeChangeListener listener) {
+        mOneHandedOverlayChangeListeners.add(listener);
+        return mNavBarGesturalHeight;
     }
 
-    private static int getSystemIntegerRes(Context context, String resName) {
-        Resources res = context.getResources();
-        int resId = res.getIdentifier(resName, "integer", "android");
+    public void removeOneHandedOverlayChangeListener(OneHandedModeChangeListener listener) {
+        mOneHandedOverlayChangeListeners.remove(listener);
+    }
 
-        if (resId != 0) {
-            return res.getInteger(resId);
-        } else {
-            Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?");
-            return -1;
-        }
+    public Mode getMode() {
+        return mMode;
     }
 
     /** @return Whether we can remove the shelf from overview. */
@@ -144,10 +185,14 @@
     public void dump(PrintWriter pw) {
         pw.println("SysUINavigationMode:");
         pw.println("  mode=" + mMode.name());
+        pw.println("  mNavBarGesturalHeight=:" + mNavBarGesturalHeight);
     }
 
     public interface NavigationModeChangeListener {
-
         void onNavigationModeChanged(Mode newMode);
     }
+
+    public interface OneHandedModeChangeListener {
+        void onOneHandedModeChanged(int newGesturalHeight);
+    }
 }
\ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 299e9e5..e4b05ae 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -352,10 +352,43 @@
         if (mSystemUiProxy != null) {
             try {
                 mSystemUiProxy.handleImageBundleAsScreenshot(screenImageBundle, locationInScreen,
-                        visibleInsets, task);
+                    visibleInsets, task);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed call handleImageBundleAsScreenshot");
             }
         }
     }
+
+    @Override
+    public void startOneHandedMode() {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.startOneHandedMode();
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call startOneHandedMode", e);
+            }
+        }
+    }
+
+    @Override
+    public void stopOneHandedMode() {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.stopOneHandedMode();
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call stopOneHandedMode", e);
+            }
+        }
+    }
+
+    @Override
+    public void expandNotificationPanel() {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.expandNotificationPanel();
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call expandNotificationPanel", e);
+            }
+        }
+    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
similarity index 79%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
rename to quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index e9614d1..3a47024 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -18,7 +18,8 @@
 
 import static android.view.Surface.ROTATION_0;
 
-import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.quickstep.views.OverviewActionsView.DISABLED_NO_THUMBNAIL;
 import static com.android.quickstep.views.OverviewActionsView.DISABLED_ROTATED;
 
 import android.annotation.SuppressLint;
@@ -38,13 +39,12 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.popup.SystemShortcut;
-import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.ResourceBasedOverride;
 import com.android.quickstep.util.RecentsOrientedState;
 import com.android.quickstep.views.OverviewActionsView;
+import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskThumbnailView;
 import com.android.quickstep.views.TaskView;
-import com.android.systemui.plugins.OverscrollPlugin;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 
@@ -90,20 +90,22 @@
         return shortcuts;
     }
 
-    public static final MainThreadInitializedObject<TaskOverlayFactory> INSTANCE =
-            forOverride(TaskOverlayFactory.class, R.string.task_overlay_factory_class);
-
-    /**
-     * @return a launcher-provided OverscrollPlugin if available, otherwise null
-     */
-    public OverscrollPlugin getLocalOverscrollPlugin() {
-        return null;
-    }
-
     public TaskOverlay createOverlay(TaskThumbnailView thumbnailView) {
         return new TaskOverlay(thumbnailView);
     }
 
+    /**
+     * Subclasses can attach any system listeners in this method, must be paired with
+     * {@link #removeListeners()}
+     */
+    public void initListeners() { }
+
+    /**
+     * Subclasses should remove any system listeners in this method, must be paired with
+     * {@link #initListeners()}
+     */
+    public void removeListeners() { }
+
     /** Note that these will be shown in order from top to bottom, if available for the task. */
     private static final TaskShortcutFactory[] MENU_OPTIONS = new TaskShortcutFactory[]{
             TaskShortcutFactory.APP_INFO,
@@ -146,26 +148,48 @@
          */
         public void initOverlay(Task task, ThumbnailData thumbnail, Matrix matrix,
                 boolean rotated) {
-            final boolean isAllowedByPolicy = thumbnail.isRealSnapshot;
+            getActionsView().updateDisabledFlags(DISABLED_NO_THUMBNAIL, thumbnail == null);
 
-            getActionsView().updateDisabledFlags(DISABLED_ROTATED, rotated);
+            if (thumbnail != null) {
+                getActionsView().updateDisabledFlags(DISABLED_ROTATED, rotated);
+                final boolean isAllowedByPolicy = thumbnail.isRealSnapshot;
 
-            getActionsView().setCallbacks(new OverlayUICallbacks() {
-                @Override
-                public void onShare() {
-                    if (isAllowedByPolicy) {
-                        mImageApi.startShareActivity();
-                    } else {
-                        showBlockedByPolicyMessage();
+                getActionsView().setCallbacks(new OverlayUICallbacks() {
+                    @Override
+                    public void onShare() {
+                        endLiveTileMode(isAllowedByPolicy);
+                        if (isAllowedByPolicy) {
+                            mImageApi.startShareActivity();
+                        } else {
+                            showBlockedByPolicyMessage();
+                        }
                     }
-                }
 
-                @SuppressLint("NewApi")
-                @Override
-                public void onScreenshot() {
-                    saveScreenshot(task);
+                    @SuppressLint("NewApi")
+                    @Override
+                    public void onScreenshot() {
+                        endLiveTileMode(isAllowedByPolicy);
+                        saveScreenshot(task);
+                    }
+                });
+            }
+        }
+
+        /**
+         * End rendering live tile in Overview.
+         *
+         * @param showScreenshot if it's true, we take a screenshot and switch to it.
+         */
+        public void endLiveTileMode(boolean showScreenshot) {
+            if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+                RecentsView recentsView = mThumbnailView.getTaskView().getRecentsView();
+                if (showScreenshot) {
+                    recentsView.switchToScreenshot(
+                            () -> recentsView.finishRecentsAnimation(true /* toRecents */, null));
+                } else {
+                    recentsView.finishRecentsAnimation(true /* toRecents */, null);
                 }
-            });
+            }
         }
 
         /**
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
similarity index 96%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/TaskShortcutFactory.java
rename to quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index ff051b6..3b245b3 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -18,7 +18,6 @@
 
 import static android.view.Display.DEFAULT_DISPLAY;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_SELECTIONS;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_SPLIT_SCREEN_TAP;
@@ -310,16 +309,11 @@
     TaskShortcutFactory WELLBEING = (activity, view) ->
             WellbeingModel.SHORTCUT_FACTORY.getShortcut(activity, view.getItemInfo());
 
-    TaskShortcutFactory SCREENSHOT = (activity, tv) -> {
-        if (ENABLE_OVERVIEW_ACTIONS.get()) {
-            return tv.getThumbnail().getTaskOverlay()
-                .getScreenshotShortcut(activity, tv.getItemInfo());
-        }
-        return null;
-    };
+    TaskShortcutFactory SCREENSHOT = (activity, tv) -> tv.getThumbnail().getTaskOverlay()
+            .getScreenshotShortcut(activity, tv.getItemInfo());
 
     TaskShortcutFactory MODAL = (activity, tv) -> {
-        if (ENABLE_OVERVIEW_ACTIONS.get() && ENABLE_OVERVIEW_SELECTIONS.get()) {
+        if (ENABLE_OVERVIEW_SELECTIONS.get()) {
             return tv.getThumbnail().getTaskOverlay().getModalStateSystemShortcut(tv.getItemInfo());
         }
         return null;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
similarity index 98%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
rename to quickstep/src/com/android/quickstep/TaskViewUtils.java
index e2e25f3..a5af181 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -41,7 +41,7 @@
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.statehandlers.DepthController;
-import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.DisplayController;
 import com.android.quickstep.util.SurfaceTransactionApplier;
 import com.android.quickstep.util.TaskViewSimulator;
 import com.android.quickstep.util.TransformParams;
@@ -146,7 +146,7 @@
         DeviceProfile dp = BaseActivity.fromContext(context).getDeviceProfile();
         // RecentsView never updates the display rotation until swipe-up so the value may be stale.
         // Use the display value instead.
-        int displayRotation = DefaultDisplay.INSTANCE.get(context).getInfo().rotation;
+        int displayRotation = DisplayController.getDefaultDisplay(context).getInfo().rotation;
 
         TaskViewSimulator topMostSimulator = null;
         if (targets.apps.length > 0) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
similarity index 90%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
rename to quickstep/src/com/android/quickstep/TouchInteractionService.java
index 6e0b517..f29a5c8 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -22,7 +22,6 @@
 
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.quickstep.GestureState.DEFAULT_STATE;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_MONITOR;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
@@ -58,10 +57,9 @@
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.R;
+import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.logging.UserEventDispatcher;
-import com.android.launcher3.model.AppLaunchTracker;
 import com.android.launcher3.provider.RestoreDbTask;
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.testing.TestLogging;
@@ -75,6 +73,7 @@
 import com.android.quickstep.inputconsumers.AccessibilityInputConsumer;
 import com.android.quickstep.inputconsumers.AssistantInputConsumer;
 import com.android.quickstep.inputconsumers.DeviceLockedInputConsumer;
+import com.android.quickstep.inputconsumers.OneHandedModeInputConsumer;
 import com.android.quickstep.inputconsumers.OtherActivityInputConsumer;
 import com.android.quickstep.inputconsumers.OverscrollInputConsumer;
 import com.android.quickstep.inputconsumers.OverviewInputConsumer;
@@ -100,24 +99,6 @@
 import java.io.PrintWriter;
 import java.util.Arrays;
 import java.util.LinkedList;
-import java.util.List;
-
-/**
- * Wrapper around a list for processing arguments.
- */
-class ArgList extends LinkedList<String> {
-    public ArgList(List<String> l) {
-        super(l);
-    }
-
-    public String peekArg() {
-        return peekFirst();
-    }
-
-    public String nextArg() {
-        return pollFirst().toLowerCase();
-    }
-}
 
 /**
  * Service connected by system-UI for handling touch interaction.
@@ -158,7 +139,6 @@
         }
 
         @BinderThread
-        @Override
         public void onOverviewToggle() {
             TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onOverviewToggle");
             mOverviewCommandHelper.onOverviewToggle();
@@ -182,7 +162,7 @@
         @BinderThread
         @Override
         public void onTip(int actionType, int viewType) {
-            mOverviewCommandHelper.onTip(actionType, viewType);
+            // Please delete this method from the interface
         }
 
         @BinderThread
@@ -206,18 +186,7 @@
         @BinderThread
         public void onBackAction(boolean completed, int downX, int downY, boolean isButton,
                 boolean gestureSwipeLeft) {
-            if (mOverviewComponentObserver == null) {
-                return;
-            }
-
-            final BaseActivityInterface activityInterface =
-                    mOverviewComponentObserver.getActivityInterface();
-            UserEventDispatcher.newInstance(getBaseContext()).logActionBack(completed, downX, downY,
-                    isButton, gestureSwipeLeft, activityInterface.getContainerType());
-
-            if (completed && !isButton && shouldNotifyBackGesture()) {
-                UI_HELPER_EXECUTOR.execute(TouchInteractionService.this::tryNotifyBackGesture);
-            }
+            // Remove this method from the interface
         }
 
         @BinderThread
@@ -237,27 +206,11 @@
             WindowBounds wb = new WindowBounds(bounds, insets);
             MAIN_EXECUTOR.execute(() -> SplitScreenBounds.INSTANCE.setSecondaryWindowBounds(wb));
         }
-
-        /** Deprecated methods **/
-        public void onQuickStep(MotionEvent motionEvent) { }
-
-        public void onQuickScrubEnd() { }
-
-        public void onQuickScrubProgress(float progress) { }
-
-        public void onQuickScrubStart() { }
-
-        public void onPreMotionEvent(int downHitTarget) { }
-
-        public void onMotionEvent(MotionEvent ev) {
-            ev.recycle();
-        }
-
-        public void onBind(ISystemUiProxy iSystemUiProxy) { }
     };
 
     private static boolean sConnected = false;
     private static boolean sIsInitialized = false;
+    private RotationTouchHelper mRotationTouchHelper;
 
     public static boolean isConnected() {
         return sConnected;
@@ -267,9 +220,9 @@
         return sIsInitialized;
     }
 
-    private final BaseSwipeUpHandler.Factory mLauncherSwipeHandlerFactory =
+    private final AbsSwipeUpHandler.Factory mLauncherSwipeHandlerFactory =
             this::createLauncherSwipeHandler;
-    private final BaseSwipeUpHandler.Factory mFallbackSwipeHandlerFactory =
+    private final AbsSwipeUpHandler.Factory mFallbackSwipeHandlerFactory =
             this::createFallbackSwipeHandler;
 
     private ActivityManagerWrapper mAM;
@@ -296,7 +249,9 @@
         mMainChoreographer = Choreographer.getInstance();
         mAM = ActivityManagerWrapper.getInstance();
         mDeviceState = new RecentsAnimationDeviceState(this);
+        mRotationTouchHelper = mDeviceState.getRotationTouchHelper();
         mDeviceState.addNavigationModeChangedCallback(this::onNavigationModeChanged);
+        mDeviceState.addOneHandedModeChangedCallback(this::onOneHandedModeOverlayChanged);
         mDeviceState.runOnUserUnlocked(this::onUserUnlocked);
         ProtoTracer.INSTANCE.get(this).add(this);
 
@@ -326,7 +281,7 @@
         mInputEventReceiver = mInputMonitorCompat.getInputReceiver(Looper.getMainLooper(),
                 mMainChoreographer, this::onInputEvent);
 
-        mDeviceState.updateGestureTouchRegions();
+        mRotationTouchHelper.updateGestureTouchRegions();
     }
 
     /**
@@ -337,6 +292,13 @@
         resetHomeBounceSeenOnQuickstepEnabledFirstTime();
     }
 
+    /**
+     * Called when the one handed mode overlay package changes, to recreate touch region.
+     */
+    private void onOneHandedModeOverlayChanged(int newGesturalHeight) {
+        initInputMonitor();
+    }
+
     @UiThread
     public void onUserUnlocked() {
         mTaskAnimationManager = new TaskAnimationManager();
@@ -470,9 +432,9 @@
             if (TestProtocol.sDebugTracing) {
                 Log.d(TestProtocol.NO_SWIPE_TO_HOME, "TouchInteractionService.onInputEvent:DOWN");
             }
-            mDeviceState.setOrientationTransformIfNeeded(event);
+            mRotationTouchHelper.setOrientationTransformIfNeeded(event);
 
-            if (mDeviceState.isInSwipeUpTouchRegion(event)) {
+            if (mRotationTouchHelper.isInSwipeUpTouchRegion(event)) {
                 if (TestProtocol.sDebugTracing) {
                     Log.d(TestProtocol.NO_SWIPE_TO_HOME,
                             "TouchInteractionService.onInputEvent:isInSwipeUpTouchRegion");
@@ -499,9 +461,19 @@
                             mGestureState,
                             InputConsumer.NO_OP, mInputMonitorCompat,
                             mOverviewComponentObserver.assistantGestureIsConstrained());
+                } else if (mDeviceState.canTriggerOneHandedAction(event)
+                    && !mDeviceState.isOneHandedModeActive()) {
+                    // Consume gesture event for triggering one handed feature.
+                    mUncheckedConsumer = new OneHandedModeInputConsumer(this, mDeviceState,
+                        InputConsumer.NO_OP, mInputMonitorCompat);
                 } else {
                     mUncheckedConsumer = InputConsumer.NO_OP;
                 }
+            } else if (mDeviceState.canTriggerOneHandedAction(event)
+                    && !mDeviceState.isOneHandedModeActive()) {
+                // Consume gesture event for triggering one handed feature.
+                mUncheckedConsumer = new OneHandedModeInputConsumer(this, mDeviceState,
+                        InputConsumer.NO_OP, mInputMonitorCompat);
             } else {
                 mUncheckedConsumer = InputConsumer.NO_OP;
             }
@@ -509,7 +481,7 @@
             // Other events
             if (mUncheckedConsumer != InputConsumer.NO_OP) {
                 // Only transform the event if we are handling it in a proper consumer
-                mDeviceState.setOrientationTransformIfNeeded(event);
+                mRotationTouchHelper.setOrientationTransformIfNeeded(event);
             }
         }
 
@@ -547,7 +519,7 @@
             gestureState.updatePreviouslyAppearedTaskIds(
                     previousGestureState.getPreviouslyAppearedTaskIds());
         } else {
-            gestureState.updateRunningTask(TraceHelper.whitelistIpcs("getRunningTask.0",
+            gestureState.updateRunningTask(TraceHelper.allowIpcs("getRunningTask.0",
                     () -> mAM.getRunningTask(false /* filterOnlyVisibleRecents */)));
         }
         return gestureState;
@@ -595,9 +567,8 @@
             if (FeatureFlags.ENABLE_QUICK_CAPTURE_GESTURE.get()) {
                 OverscrollPlugin plugin = null;
                 if (FeatureFlags.FORCE_LOCAL_OVERSCROLL_PLUGIN.get()) {
-                    TaskOverlayFactory factory =
-                            TaskOverlayFactory.INSTANCE.get(getApplicationContext());
-                    plugin = factory.getLocalOverscrollPlugin();  // may be null
+                    plugin = OverscrollPluginFactory.INSTANCE.get(
+                            getApplicationContext()).getLocalOverscrollPlugin();
                 }
 
                 // If not local plugin was forced, use the actual overscroll plugin if available.
@@ -626,6 +597,11 @@
                 base = new ScreenPinnedInputConsumer(this, newGestureState);
             }
 
+            if (mDeviceState.canTriggerOneHandedAction(event)) {
+                base = new OneHandedModeInputConsumer(this, mDeviceState, base,
+                        mInputMonitorCompat);
+            }
+
             if (mDeviceState.isAccessibilityMenuAvailable()) {
                 base = new AccessibilityInputConsumer(this, mDeviceState, base,
                         mInputMonitorCompat);
@@ -634,6 +610,11 @@
             if (mDeviceState.isScreenPinningActive()) {
                 base = mResetGestureInputConsumer;
             }
+
+            if (mDeviceState.canTriggerOneHandedAction(event)) {
+                base = new OneHandedModeInputConsumer(this, mDeviceState, base,
+                        mInputMonitorCompat);
+            }
         }
         return base;
     }
@@ -660,7 +641,7 @@
         if (AssistantUtilities.isExcludedAssistant(gestureState.getRunningTask())) {
             // In the case where we are in the excluded assistant state, ignore it and treat the
             // running activity as the task behind the assistant
-            gestureState.updateRunningTask(TraceHelper.whitelistIpcs("getRunningTask.assistant",
+            gestureState.updateRunningTask(TraceHelper.allowIpcs("getRunningTask.assistant",
                     () -> mAM.getRunningTask(true /* filterOnlyVisibleRecents */)));
             ComponentName homeComponent = mOverviewComponentObserver.getHomeIntent().getComponent();
             ComponentName runningComponent =
@@ -669,17 +650,17 @@
                     runningComponent != null && runningComponent.equals(homeComponent);
         }
 
-        if (gestureState.getRunningTask() == null) {
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get()
+                && gestureState.getActivityInterface().isInLiveTileMode()) {
+            return createOverviewInputConsumer(
+                    previousGestureState, gestureState, event, forceOverviewInputConsumer);
+        } else if (gestureState.getRunningTask() == null) {
             return mResetGestureInputConsumer;
         } else if (previousGestureState.isRunningAnimationToLauncher()
                 || gestureState.getActivityInterface().isResumed()
                 || forceOverviewInputConsumer) {
             return createOverviewInputConsumer(
                     previousGestureState, gestureState, event, forceOverviewInputConsumer);
-        } else if (ENABLE_QUICKSTEP_LIVE_TILE.get()
-                && gestureState.getActivityInterface().isInLiveTileMode()) {
-            return createOverviewInputConsumer(
-                    previousGestureState, gestureState, event, forceOverviewInputConsumer);
         } else if (mDeviceState.isGestureBlockedActivity(gestureState.getRunningTask())) {
             return mResetGestureInputConsumer;
         } else {
@@ -690,7 +671,7 @@
     private InputConsumer createOtherActivityInputConsumer(GestureState gestureState,
             MotionEvent event) {
 
-        final BaseSwipeUpHandler.Factory factory;
+        final AbsSwipeUpHandler.Factory factory;
         if (!mOverviewComponentObserver.isHomeAndOverviewSame()) {
             factory = mFallbackSwipeHandlerFactory;
         } else {
@@ -771,13 +752,7 @@
                 mOverviewComponentObserver.getActivityInterface();
         final Intent overviewIntent = new Intent(
                 mOverviewComponentObserver.getOverviewIntentIgnoreSysUiState());
-        if (activityInterface.getCreatedActivity() == null) {
-            // Make sure that UI states will be initialized.
-            activityInterface.createActivityInitListener((wasVisible) -> {
-                AppLaunchTracker.INSTANCE.get(TouchInteractionService.this);
-                return false;
-            }).register(overviewIntent);
-        } else if (fromInit) {
+        if (activityInterface.getCreatedActivity() != null && fromInit) {
             // The activity has been created before the initialization of overview service. It is
             // usually happens when booting or launcher is the top activity, so we should already
             // have the latest state.
@@ -801,6 +776,13 @@
         }
         if (mOverviewComponentObserver.canHandleConfigChanges(activity.getComponentName(),
                 activity.getResources().getConfiguration().diff(newConfig))) {
+            // Since navBar gestural height are different between portrait and landscape,
+            // can handle orientation changes and refresh navigation gestural region through
+            // onOneHandedModeChanged()
+            int newGesturalHeight = ResourceUtils.getNavbarSize(
+                    ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE,
+                    getApplicationContext().getResources());
+            mDeviceState.onOneHandedModeChanged(newGesturalHeight);
             return;
         }
 
@@ -810,10 +792,10 @@
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] rawArgs) {
         if (rawArgs.length > 0 && Utilities.IS_DEBUG_DEVICE) {
-            ArgList args = new ArgList(Arrays.asList(rawArgs));
-            switch (args.nextArg()) {
+            LinkedList<String> args = new LinkedList(Arrays.asList(rawArgs));
+            switch (args.pollFirst()) {
                 case "cmd":
-                    if (args.peekArg() == null) {
+                    if (args.peekFirst() == null) {
                         printAvailableCommands(pw);
                     } else {
                         onCommand(pw, args);
@@ -854,21 +836,21 @@
         pw.println("  clear-touch-log: Clears the touch interaction log");
     }
 
-    private void onCommand(PrintWriter pw, ArgList args) {
-        switch (args.nextArg()) {
+    private void onCommand(PrintWriter pw, LinkedList<String> args) {
+        switch (args.pollFirst()) {
             case "clear-touch-log":
                 ActiveGestureLog.INSTANCE.clear();
                 break;
         }
     }
 
-    private BaseSwipeUpHandler createLauncherSwipeHandler(
+    private AbsSwipeUpHandler createLauncherSwipeHandler(
             GestureState gestureState, long touchTimeMs, boolean continuingLastGesture) {
         return new LauncherSwipeHandlerV2(this, mDeviceState, mTaskAnimationManager,
                 gestureState, touchTimeMs, continuingLastGesture, mInputConsumer);
     }
 
-    private BaseSwipeUpHandler createFallbackSwipeHandler(
+    private AbsSwipeUpHandler createFallbackSwipeHandler(
             GestureState gestureState, long touchTimeMs, boolean continuingLastGesture) {
         return new FallbackSwipeHandler(this, mDeviceState, mTaskAnimationManager,
                 gestureState, touchTimeMs, continuingLastGesture, mInputConsumer);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/ViewUtils.java b/quickstep/src/com/android/quickstep/ViewUtils.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/ViewUtils.java
rename to quickstep/src/com/android/quickstep/ViewUtils.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java b/quickstep/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java
similarity index 95%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java
rename to quickstep/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java
index be3fdde..a4a7bd3 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java
@@ -21,7 +21,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.Utilities;
-import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.TouchController;
 import com.android.quickstep.RecentsActivity;
 import com.android.quickstep.SysUINavigationMode;
@@ -43,7 +43,7 @@
         SysUINavigationMode.Mode sysUINavigationMode = SysUINavigationMode.getMode(mActivity);
         if (sysUINavigationMode == SysUINavigationMode.Mode.NO_BUTTON) {
             NavBarPosition navBarPosition = new NavBarPosition(sysUINavigationMode,
-                    DefaultDisplay.INSTANCE.get(mActivity).getInfo());
+                    DisplayController.getDefaultDisplay(mActivity).getInfo());
             mTriggerSwipeUpTracker = new TriggerSwipeUpTouchTracker(mActivity,
                     true /* disableHorizontalSwipe */, navBarPosition,
                     null /* onInterceptTouch */, this);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
similarity index 89%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
rename to quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
index 3f1e7ba..24a7610 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
@@ -15,17 +15,19 @@
  */
 package com.android.quickstep.fallback;
 
-import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_MODAL;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
 import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_PEEK;
 import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_SCALE;
 import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
 import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
 import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
+import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
 import static com.android.quickstep.views.RecentsView.TASK_MODALNESS;
+import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
 
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.anim.PropertySetter;
@@ -82,10 +84,12 @@
                 MultiValueAlpha.VALUE, buttonAlpha, LINEAR);
 
         float[] scaleAndOffset = state.getOverviewScaleAndOffset(mActivity);
-        setter.setFloat(mRecentsView, SCALE_PROPERTY, scaleAndOffset[0],
+        setter.setFloat(mRecentsView, RECENTS_SCALE_PROPERTY, scaleAndOffset[0],
                 config.getInterpolator(ANIM_OVERVIEW_SCALE, LINEAR));
         setter.setFloat(mRecentsView, ADJACENT_PAGE_OFFSET, scaleAndOffset[1],
                 config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_X, LINEAR));
+        setter.setFloat(mRecentsView, TASK_SECONDARY_TRANSLATION, 0f,
+                config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, LINEAR));
 
         setter.setFloat(mRecentsView, TASK_MODALNESS, state.getOverviewModalness(),
                 config.getInterpolator(ANIM_OVERVIEW_MODAL, LINEAR));
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
similarity index 89%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java
rename to quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index d20bbe9..f5f5259 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -63,12 +63,6 @@
         mActivity.startHome();
     }
 
-    @Override
-    public boolean shouldUseMultiWindowTaskSizeStrategy() {
-        // Just use the activity task size for multi-window as well.
-        return false;
-    }
-
     /**
      * When starting gesture interaction from home, we add a temporary invisible tile corresponding
      * to the home task. This allows us to handle quick-switch similarly to a quick-switching
@@ -76,7 +70,7 @@
      */
     public void onGestureAnimationStartOnHome(RunningTaskInfo homeTaskInfo) {
         mHomeTaskInfo = homeTaskInfo;
-        onGestureAnimationStart(homeTaskInfo == null ? -1 : homeTaskInfo.taskId);
+        onGestureAnimationStart(homeTaskInfo);
     }
 
     /**
@@ -107,19 +101,20 @@
     }
 
     @Override
-    protected boolean shouldAddDummyTaskView(int runningTaskId) {
-        if (mHomeTaskInfo != null && mHomeTaskInfo.taskId == runningTaskId
+    protected boolean shouldAddStubTaskView(RunningTaskInfo runningTaskInfo) {
+        if (mHomeTaskInfo != null && runningTaskInfo != null &&
+                mHomeTaskInfo.taskId == runningTaskInfo.taskId
                 && getTaskViewCount() == 0) {
-            // Do not add a dummy task if we are running over home with empty recents, so that we
-            // show the empty recents message instead of showing a dummy task and later removing it.
+            // Do not add a stub task if we are running over home with empty recents, so that we
+            // show the empty recents message instead of showing a stub task and later removing it.
             return false;
         }
-        return super.shouldAddDummyTaskView(runningTaskId);
+        return super.shouldAddStubTaskView(runningTaskInfo);
     }
 
     @Override
     protected void applyLoadPlan(ArrayList<Task> tasks) {
-        // When quick-switching on 3p-launcher, we add a "dummy" tile corresponding to Launcher
+        // When quick-switching on 3p-launcher, we add a "stub" tile corresponding to Launcher
         // as well. This tile is never shown as we have setCurrentTaskHidden, but allows use to
         // track the index of the next task appropriately, as if we are switching on any other app.
         if (mHomeTaskInfo != null && mHomeTaskInfo.taskId == mRunningTaskId && !tasks.isEmpty()) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsDragLayer.java b/quickstep/src/com/android/quickstep/fallback/RecentsDragLayer.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsDragLayer.java
rename to quickstep/src/com/android/quickstep/fallback/RecentsDragLayer.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsState.java b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
similarity index 99%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsState.java
rename to quickstep/src/com/android/quickstep/fallback/RecentsState.java
index 211a30c..f15a9de 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsState.java
+++ b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
@@ -90,7 +90,6 @@
         return new float[] { NO_SCALE, NO_OFFSET };
     }
 
-
     private static class ModalState extends RecentsState {
 
         public ModalState(int id, int flags) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsTaskController.java b/quickstep/src/com/android/quickstep/fallback/RecentsTaskController.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsTaskController.java
rename to quickstep/src/com/android/quickstep/fallback/RecentsTaskController.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
similarity index 97%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
rename to quickstep/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
index 5ad48eb..0c2c92c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
@@ -99,7 +99,8 @@
             case ACTION_POINTER_DOWN: {
                 if (mState == STATE_INACTIVE) {
                     int pointerIndex = ev.getActionIndex();
-                    if (mDeviceState.isInSwipeUpTouchRegion(ev, pointerIndex)
+                    if (mDeviceState.getRotationTouchHelper()
+                            .isInSwipeUpTouchRegion(ev, pointerIndex)
                             && mDelegate.allowInterceptByParent()) {
                         setActive(ev);
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
rename to quickstep/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
rename to quickstep/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
similarity index 95%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
rename to quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
index 3a97216..584ff28 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
@@ -21,7 +21,7 @@
 
 import static com.android.launcher3.Utilities.squaredHypot;
 import static com.android.launcher3.Utilities.squaredTouchSlop;
-import static com.android.quickstep.BaseSwipeUpHandlerV2.MIN_PROGRESS_FOR_OVERVIEW;
+import static com.android.quickstep.AbsSwipeUpHandler.MIN_PROGRESS_FOR_OVERVIEW;
 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
 import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
 
@@ -41,7 +41,7 @@
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.DisplayController;
 import com.android.quickstep.AnimatedFloat;
 import com.android.quickstep.GestureState;
 import com.android.quickstep.InputConsumer;
@@ -60,7 +60,7 @@
 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams.Builder;
 
 /**
- * A dummy input consumer used when the device is still locked, e.g. from secure camera.
+ * A placeholder input consumer used when the device is still locked, e.g. from secure camera.
  */
 public class DeviceLockedInputConsumer implements InputConsumer,
         RecentsAnimationCallbacks.RecentsAnimationListener, BuilderProxy {
@@ -115,7 +115,7 @@
                 R.dimen.device_locked_y_offset);
 
         // Do not use DeviceProfile as the user data might be locked
-        mDisplaySize = DefaultDisplay.INSTANCE.get(context).getInfo().realSize;
+        mDisplaySize = DisplayController.getDefaultDisplay(context).getInfo().realSize;
 
         // Init states
         mStateCallback = new MultiStateCallback(STATE_NAMES);
@@ -147,7 +147,7 @@
                 if (!mThresholdCrossed) {
                     // Cancel interaction in case of multi-touch interaction
                     int ptrIdx = ev.getActionIndex();
-                    if (!mDeviceState.isInSwipeUpTouchRegion(ev, ptrIdx)) {
+                    if (!mDeviceState.getRotationTouchHelper().isInSwipeUpTouchRegion(ev, ptrIdx)) {
                         int action = ev.getAction();
                         ev.setAction(ACTION_CANCEL);
                         finishTouchTracking(ev);
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java
new file mode 100644
index 0000000..22bd334
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2019 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.quickstep.inputconsumers;
+
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_UP;
+
+import static com.android.launcher3.Utilities.squaredHypot;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.view.MotionEvent;
+
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.quickstep.InputConsumer;
+import com.android.quickstep.RecentsAnimationDeviceState;
+import com.android.quickstep.SystemUiProxy;
+import com.android.systemui.shared.system.InputMonitorCompat;
+
+/**
+ * Touch consumer for handling gesture event to launch one handed
+ * One handed gestural in quickstep only active on NO_BUTTON, TWO_BUTTONS, and portrait mode
+ */
+public class OneHandedModeInputConsumer extends DelegateInputConsumer {
+
+    private static final String TAG = "OneHandedModeInputConsumer";
+    private static final int ANGLE_MAX = 150;
+    private static final int ANGLE_MIN = 30;
+
+    private final Context mContext;
+    private final RecentsAnimationDeviceState mDeviceState;
+
+    private final float mDragDistThreshold;
+    private final float mSquaredSlop;
+
+    private final PointF mDownPos = new PointF();
+    private final PointF mLastPos = new PointF();
+
+    private boolean mPassedSlop;
+
+    public OneHandedModeInputConsumer(Context context, RecentsAnimationDeviceState deviceState,
+            InputConsumer delegate, InputMonitorCompat inputMonitor) {
+        super(delegate, inputMonitor);
+        mContext = context;
+        mDeviceState = deviceState;
+        mDragDistThreshold = context.getResources().getDimensionPixelSize(
+                R.dimen.gestures_onehanded_drag_threshold);
+        mSquaredSlop = Utilities.squaredTouchSlop(context);
+    }
+
+    @Override
+    public int getType() {
+        return TYPE_ONE_HANDED | mDelegate.getType();
+    }
+
+    @Override
+    public void onMotionEvent(MotionEvent ev) {
+        switch (ev.getActionMasked()) {
+            case ACTION_DOWN: {
+                mDownPos.set(ev.getX(), ev.getY());
+                mLastPos.set(mDownPos);
+                break;
+            }
+            case ACTION_MOVE: {
+                if (mState == STATE_DELEGATE_ACTIVE) {
+                    break;
+                }
+                if (!mDelegate.allowInterceptByParent()) {
+                    mState = STATE_DELEGATE_ACTIVE;
+                    break;
+                }
+
+                mLastPos.set(ev.getX(), ev.getY());
+                if (!mPassedSlop) {
+                    if (squaredHypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y)
+                            > mSquaredSlop) {
+                        if ((!mDeviceState.isOneHandedModeActive() && isValidStartAngle(
+                                mDownPos.x - mLastPos.x, mDownPos.y - mLastPos.y))
+                                || (mDeviceState.isOneHandedModeActive() && isValidExitAngle(
+                                mDownPos.x - mLastPos.x, mDownPos.y - mLastPos.y))) {
+                            mPassedSlop = true;
+                            setActive(ev);
+                        } else {
+                            mState = STATE_DELEGATE_ACTIVE;
+                        }
+                    }
+                } else {
+                    float distance = (float) Math.hypot(mLastPos.x - mDownPos.x,
+                            mLastPos.y - mDownPos.y);
+                    if (distance > mDragDistThreshold && mPassedSlop) {
+                        onStopGestureDetected();
+                    }
+                }
+                break;
+            }
+            case ACTION_UP: {
+                if (mLastPos.y >= mDownPos.y && mPassedSlop) {
+                    onStartGestureDetected();
+                }
+
+                mPassedSlop = false;
+                mState = STATE_INACTIVE;
+                break;
+            }
+            case ACTION_CANCEL:
+                mPassedSlop = false;
+                mState = STATE_INACTIVE;
+                break;
+        }
+
+        if (mState != STATE_ACTIVE) {
+            mDelegate.onMotionEvent(ev);
+        }
+    }
+
+    private void onStartGestureDetected() {
+        if (mDeviceState.isOneHandedModeEnabled()) {
+            if (!mDeviceState.isOneHandedModeActive()) {
+                SystemUiProxy.INSTANCE.get(mContext).startOneHandedMode();
+            }
+        } else if (mDeviceState.isSwipeToNotificationEnabled()) {
+            SystemUiProxy.INSTANCE.get(mContext).expandNotificationPanel();
+        }
+    }
+
+    private void onStopGestureDetected() {
+        if (!mDeviceState.isOneHandedModeEnabled() || !mDeviceState.isOneHandedModeActive()) {
+            return;
+        }
+
+        SystemUiProxy.INSTANCE.get(mContext).stopOneHandedMode();
+    }
+
+    private boolean isValidStartAngle(float deltaX, float deltaY) {
+        final float angle = (float) Math.toDegrees(Math.atan2(deltaY, deltaX));
+        return angle > -(ANGLE_MAX) && angle < -(ANGLE_MIN);
+    }
+
+    private boolean isValidExitAngle(float deltaX, float deltaY) {
+        final float angle = (float) Math.toDegrees(Math.atan2(deltaY, deltaX));
+        return angle > ANGLE_MIN && angle < ANGLE_MAX;
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
similarity index 97%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
rename to quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index 26df9c7..f02e5e6 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -53,12 +53,13 @@
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.TraceHelper;
 import com.android.quickstep.BaseActivityInterface;
-import com.android.quickstep.BaseSwipeUpHandler;
-import com.android.quickstep.BaseSwipeUpHandler.Factory;
+import com.android.quickstep.AbsSwipeUpHandler;
+import com.android.quickstep.AbsSwipeUpHandler.Factory;
 import com.android.quickstep.GestureState;
 import com.android.quickstep.InputConsumer;
 import com.android.quickstep.RecentsAnimationCallbacks;
 import com.android.quickstep.RecentsAnimationDeviceState;
+import com.android.quickstep.RotationTouchHelper;
 import com.android.quickstep.TaskAnimationManager;
 import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.CachedEventDispatcher;
@@ -86,12 +87,13 @@
     private final NavBarPosition mNavBarPosition;
     private final TaskAnimationManager mTaskAnimationManager;
     private final GestureState mGestureState;
+    private final RotationTouchHelper mRotationTouchHelper;
     private RecentsAnimationCallbacks mActiveCallbacks;
     private final CachedEventDispatcher mRecentsViewDispatcher = new CachedEventDispatcher();
     private final InputMonitorCompat mInputMonitorCompat;
     private final BaseActivityInterface mActivityInterface;
 
-    private final BaseSwipeUpHandler.Factory mHandlerFactory;
+    private final AbsSwipeUpHandler.Factory mHandlerFactory;
 
     private final Consumer<OtherActivityInputConsumer> mOnCompleteCallback;
     private final MotionPauseDetector mMotionPauseDetector;
@@ -99,7 +101,7 @@
 
     private VelocityTracker mVelocityTracker;
 
-    private BaseSwipeUpHandler mInteractionHandler;
+    private AbsSwipeUpHandler mInteractionHandler;
 
     private final boolean mIsDeferredDownTarget;
     private final PointF mDownPos = new PointF();
@@ -163,6 +165,7 @@
 
         mPassedPilferInputSlop = mPassedWindowMoveSlop = continuingPreviousGesture;
         mDisableHorizontalSwipe = !mPassedPilferInputSlop && disableHorizontalSwipe;
+        mRotationTouchHelper = mDeviceState.getRotationTouchHelper();
     }
 
     @Override
@@ -230,7 +233,7 @@
                 if (!mPassedPilferInputSlop) {
                     // Cancel interaction in case of multi-touch interaction
                     int ptrIdx = ev.getActionIndex();
-                    if (!mDeviceState.isInSwipeUpTouchRegion(ev, ptrIdx)) {
+                    if (!mRotationTouchHelper.isInSwipeUpTouchRegion(ev, ptrIdx)) {
                         forceCancelGesture(ev);
                     }
                 }
@@ -424,7 +427,7 @@
 
     @Override
     public void notifyOrientationSetup() {
-        mDeviceState.onStartGesture();
+        mRotationTouchHelper.onStartGesture();
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java
rename to quickstep/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
rename to quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
rename to quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java
rename to quickstep/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java
rename to quickstep/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java
rename to quickstep/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java
diff --git a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
index 0e2312b..81c4d0c 100644
--- a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
+++ b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
@@ -16,6 +16,7 @@
 package com.android.quickstep.interaction;
 
 import static com.android.launcher3.Utilities.squaredHypot;
+import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
 import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.ASSISTANT_COMPLETED;
 import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.ASSISTANT_NOT_STARTED_BAD_ANGLE;
 import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.ASSISTANT_NOT_STARTED_SWIPE_TOO_SHORT;
@@ -48,6 +49,7 @@
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.util.VibratorWrapper;
 import com.android.quickstep.SysUINavigationMode.Mode;
+import com.android.quickstep.util.MotionPauseDetector;
 import com.android.quickstep.util.NavBarPosition;
 import com.android.quickstep.util.TriggerSwipeUpTouchTracker;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -74,6 +76,7 @@
     private final PointF mAssistantStartDragPos = new PointF();
     private final PointF mDownPos = new PointF();
     private final PointF mLastPos = new PointF();
+    private final MotionPauseDetector mMotionPauseDetector;
     private boolean mTouchCameFromAssistantCorner;
     private boolean mTouchCameFromNavBar;
     private boolean mPassedAssistantSlop;
@@ -100,6 +103,7 @@
                 new TriggerSwipeUpTouchTracker(context, true /*disableHorizontalSwipe*/,
                         new NavBarPosition(Mode.NO_BUTTON, displayRotation),
                         null /*onInterceptTouch*/, this);
+        mMotionPauseDetector = new MotionPauseDetector(context);
 
         final Resources resources = context.getResources();
         mBottomGestureHeight =
@@ -177,12 +181,14 @@
                 }
                 mLaunchedAssistant = false;
                 mSwipeUpTouchTracker.init();
+                mMotionPauseDetector.clear();
+                mMotionPauseDetector.setOnMotionPauseListener(this::onMotionPauseChanged);
                 break;
             case MotionEvent.ACTION_MOVE:
+                mLastPos.set(event.getX(), event.getY());
                 if (!mAssistantGestureActive) {
                     break;
                 }
-                mLastPos.set(event.getX(), event.getY());
 
                 if (!mPassedAssistantSlop) {
                     // Normal gesture, ensure we pass the slop before we start tracking the gesture
@@ -213,6 +219,8 @@
                 break;
             case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_CANCEL:
+                mMotionPauseDetector.clear();
+                mMotionPauseDetector.setOnMotionPauseListener(null);
                 if (mGestureCallback != null && !intercepted && mTouchCameFromNavBar) {
                     mGestureCallback.onNavBarGestureAttempted(
                             HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION, new PointF());
@@ -239,9 +247,17 @@
         }
         mSwipeUpTouchTracker.onMotionEvent(event);
         mAssistantGestureDetector.onTouchEvent(event);
+        mMotionPauseDetector.addPosition(event);
+        mMotionPauseDetector.setDisallowPause(mLastPos.y >= mDisplaySize.y - mBottomGestureHeight);
         return intercepted;
     }
 
+    protected void onMotionPauseChanged(boolean isPaused) {
+        if (isPaused) {
+            VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC);
+        }
+    }
+
     /**
      * Determine if angle is larger than threshold for assistant detection
      */
diff --git a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
index 14e00dc..865b66e 100644
--- a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
@@ -16,8 +16,9 @@
 package com.android.quickstep.interaction;
 
 import static com.android.launcher3.anim.Interpolators.ACCEL;
-import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
-import static com.android.quickstep.BaseSwipeUpHandlerV2.MAX_SWIPE_DURATION;
+import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
+import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
+import static com.android.quickstep.AbsSwipeUpHandler.MAX_SWIPE_DURATION;
 import static com.android.quickstep.interaction.TutorialController.TutorialType.HOME_NAVIGATION_COMPLETE;
 import static com.android.quickstep.interaction.TutorialController.TutorialType.OVERVIEW_NAVIGATION_COMPLETE;
 
@@ -110,6 +111,7 @@
         AnimatorListenerAdapter resetTaskView = new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation, boolean isReverse) {
+                mFakeIconView.setVisibility(View.INVISIBLE);
                 mFakeTaskView.setVisibility(View.INVISIBLE);
                 mFakeTaskView.setAlpha(1);
                 mRunningWindowAnim = null;
@@ -131,6 +133,7 @@
             });
         } else {
             anim.setViewAlpha(mFakeTaskView, 0, ACCEL);
+            anim.setViewAlpha(mFakeIconView, 0, ACCEL);
             anim.addListener(resetTaskView);
         }
         if (onEndRunnable != null) {
@@ -181,8 +184,7 @@
 
         @Override
         public void updateFinalShift() {
-            float progress = mCurrentShift.value / mDragLengthFactor;
-            mWindowTransitionController.setPlayFraction(progress);
+            mWindowTransitionController.setProgress(mCurrentShift.value, mDragLengthFactor);
             mTaskViewSimulator.apply(mTransformParams);
         }
 
@@ -202,7 +204,7 @@
             // derivative of the scroll interpolator at zero, ie. 2.
             long baseDuration = Math.round(Math.abs(distanceToTravel / velocityPxPerMs.y));
             long duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration);
-            HomeAnimationFactory homeAnimFactory = new HomeAnimationFactory(null) {
+            HomeAnimationFactory homeAnimFactory = new HomeAnimationFactory() {
                 @Override
                 public AnimatorPlaybackController createActivityAnimationToHome() {
                     return AnimatorPlaybackController.wrap(new AnimatorSet(), duration);
@@ -218,6 +220,24 @@
                             fakeHomeIconLeft + fakeHomeIconSizePx,
                             fakeHomeIconTop + fakeHomeIconSizePx);
                 }
+
+                @Override
+                public void update(RectF rect, float progress, float radius) {
+                    mFakeIconView.setVisibility(View.VISIBLE);
+                    mFakeIconView.update(rect, progress,
+                            1f - SHAPE_PROGRESS_DURATION /* shapeProgressStart */,
+                            radius,
+                            false, /* isOpening */
+                            mFakeIconView, mDp,
+                            false /* isVerticalBarLayout */);
+                    mFakeIconView.setAlpha(1);
+                    mFakeTaskView.setAlpha(getWindowAlpha(progress));
+                }
+
+                @Override
+                public void onCancel() {
+                    mFakeIconView.setVisibility(View.INVISIBLE);
+                }
             };
             RectFSpringAnim windowAnim = createWindowAnimationToHome(startShift, homeAnimFactory);
             windowAnim.start(mContext, velocityPxPerMs);
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
index c1918c2..73f1f8c 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
@@ -28,6 +28,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.R;
+import com.android.launcher3.views.ClipIconView;
 import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureAttemptCallback;
 import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureAttemptCallback;
 
@@ -46,6 +47,7 @@
     final TextView mTitleTextView;
     final TextView mSubtitleTextView;
     final TextView mFeedbackView;
+    final ClipIconView mFakeIconView;
     final View mFakeTaskView;
     final View mRippleView;
     final RippleDrawable mRippleDrawable;
@@ -66,6 +68,7 @@
         mTitleTextView = rootView.findViewById(R.id.gesture_tutorial_fragment_title_view);
         mSubtitleTextView = rootView.findViewById(R.id.gesture_tutorial_fragment_subtitle_view);
         mFeedbackView = rootView.findViewById(R.id.gesture_tutorial_fragment_feedback_view);
+        mFakeIconView = rootView.findViewById(R.id.gesture_tutorial_fake_icon_view);
         mFakeTaskView = rootView.findViewById(R.id.gesture_tutorial_fake_task_view);
         mRippleView = rootView.findViewById(R.id.gesture_tutorial_ripple_view);
         mRippleDrawable = (RippleDrawable) mRippleView.getBackground();
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index eac45e9..059d158 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -16,6 +16,10 @@
 
 package com.android.quickstep.logging;
 
+import static android.text.format.DateUtils.DAY_IN_MILLIS;
+import static android.text.format.DateUtils.formatElapsedTime;
+
+import static com.android.launcher3.Utilities.getDevicePrefs;
 import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.FOLDER;
 import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.SEARCH_RESULT_CONTAINER;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORKSPACE_SNAPSHOT;
@@ -24,15 +28,18 @@
 import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__HOME;
 import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__OVERVIEW;
 
+import static java.lang.System.currentTimeMillis;
+
 import android.content.Context;
 import android.util.Log;
 
-import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
 
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
+import com.android.launcher3.logger.LauncherAtom.FolderContainer.ParentContainerCase;
 import com.android.launcher3.logger.LauncherAtom.FolderIcon;
 import com.android.launcher3.logger.LauncherAtom.FromState;
 import com.android.launcher3.logger.LauncherAtom.ToState;
@@ -52,24 +59,26 @@
 import com.android.systemui.shared.system.SysUiStatsLog;
 
 import java.util.ArrayList;
+import java.util.List;
 import java.util.Optional;
 import java.util.OptionalInt;
+import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
  * This class calls StatsLog compile time generated methods.
  *
  * To see if the logs are properly sent to statsd, execute following command.
+ * <ul>
  * $ wwdebug (to turn on the logcat printout)
  * $ wwlogcat (see logcat with grep filter on)
  * $ statsd_testdrive (see how ww is writing the proto to statsd buffer)
+ * </ul>
  */
 public class StatsLogCompatManager extends StatsLogManager {
 
     private static final String TAG = "StatsLog";
     private static final boolean IS_VERBOSE = Utilities.isPropertyEnabled(LogConfig.STATSLOG);
-
-    private static Context sContext;
-
+    private static final String LAST_SNAPSHOT_TIME_MILLIS = "LAST_SNAPSHOT_TIME_MILLIS";
     private static final InstanceId DEFAULT_INSTANCE_ID = InstanceId.fakeInstanceId(0);
     // LauncherAtom.ItemInfo.getDefaultInstance() should be used but until launcher proto migrates
     // from nano to lite, bake constant to prevent robo test failure.
@@ -77,8 +86,13 @@
     private static final int FOLDER_HIERARCHY_OFFSET = 100;
     private static final int SEARCH_RESULT_HIERARCHY_OFFSET = 200;
 
+    public static final CopyOnWriteArrayList<StatsLogConsumer> LOGS_CONSUMER =
+            new CopyOnWriteArrayList<>();
+
+    private final Context mContext;
+
     public StatsLogCompatManager(Context context) {
-        sContext = context;
+        mContext = context;
     }
 
     @Override
@@ -87,36 +101,40 @@
     }
 
     /**
-     * Logs a ranking event and accompanying {@link InstanceId} and package name.
+     * Logs impression of the current workspace with additional launcher events.
      */
     @Override
-    public void log(EventEnum rankingEvent, InstanceId instanceId, @Nullable String packageName,
-            int position) {
-        SysUiStatsLog.write(SysUiStatsLog.RANKING_SELECTED,
-                rankingEvent.getId() /* event_id = 1; */,
-                packageName /* package_name = 2; */,
-                instanceId.getId() /* instance_id = 3; */,
-                position /* position_picked = 4; */);
-    }
-
-    /**
-     * Logs the workspace layout information on the model thread.
-     */
-    @Override
-    public void logSnapshot() {
-        LauncherAppState.getInstance(sContext).getModel().enqueueModelUpdateTask(
-                new SnapshotWorker());
+    public void logSnapshot(List<EventEnum> extraEvents) {
+        LauncherAppState.getInstance(mContext).getModel().enqueueModelUpdateTask(
+                new SnapshotWorker(extraEvents));
     }
 
     private class SnapshotWorker extends BaseModelUpdateTask {
         private final InstanceId mInstanceId;
-        SnapshotWorker() {
-            mInstanceId = new InstanceIdSequence(
-                    1 << 20 /*InstanceId.INSTANCE_ID_MAX*/).newInstanceId();
+        private final List<EventEnum> mExtraEvents;
+
+        SnapshotWorker(List<EventEnum> extraEvents) {
+            mInstanceId = new InstanceIdSequence(1 << 20 /*InstanceId.INSTANCE_ID_MAX*/)
+                    .newInstanceId();
+            this.mExtraEvents = extraEvents;
         }
 
         @Override
         public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+            long lastSnapshotTimeMillis = getDevicePrefs(mContext)
+                    .getLong(LAST_SNAPSHOT_TIME_MILLIS, 0);
+            // Log snapshot only if previous snapshot was older than a day
+            if (currentTimeMillis() - lastSnapshotTimeMillis < DAY_IN_MILLIS) {
+                if (IS_VERBOSE) {
+                    String elapsedTime = formatElapsedTime(
+                            (currentTimeMillis() - lastSnapshotTimeMillis) / 1000);
+                    Log.d(TAG, String.format(
+                            "Skipped snapshot logging since previous snapshot was %s old.",
+                            elapsedTime));
+                }
+                return;
+            }
+
             IntSparseArrayMap<FolderInfo> folders = dataModel.folders.clone();
             ArrayList<ItemInfo> workspaceItems = (ArrayList) dataModel.workspaceItems.clone();
             ArrayList<LauncherAppWidgetInfo> appWidgets = (ArrayList) dataModel.appWidgets.clone();
@@ -132,16 +150,22 @@
                         LauncherAtom.ItemInfo atomInfo = info.buildProto(fInfo);
                         writeSnapshot(atomInfo, mInstanceId);
                     }
-                } catch (Exception e) { }
+                } catch (Exception e) {
+                }
             }
             for (ItemInfo info : appWidgets) {
                 LauncherAtom.ItemInfo atomInfo = info.buildProto(null);
                 writeSnapshot(atomInfo, mInstanceId);
             }
+            mExtraEvents
+                    .forEach(eventName -> logger().withInstanceId(mInstanceId).log(eventName));
+
+            getDevicePrefs(mContext).edit()
+                    .putLong(LAST_SNAPSHOT_TIME_MILLIS, currentTimeMillis()).apply();
         }
     }
 
-    private static void writeSnapshot(LauncherAtom.ItemInfo info, InstanceId instanceId) {
+    private void writeSnapshot(LauncherAtom.ItemInfo info, InstanceId instanceId) {
         if (IS_VERBOSE) {
             Log.d(TAG, String.format("\nwriteSnapshot(%d):\n%s", instanceId.getId(), info));
         }
@@ -175,6 +199,7 @@
     private static class StatsCompatLogger implements StatsLogger {
 
         private static final ItemInfo DEFAULT_ITEM_INFO = new ItemInfo();
+
         private ItemInfo mItemInfo = DEFAULT_ITEM_INFO;
         private InstanceId mInstanceId = DEFAULT_INSTANCE_ID;
         private OptionalInt mRank = OptionalInt.empty();
@@ -253,36 +278,35 @@
                 return;
             }
 
-            if (mItemInfo.container < 0) {
-                // Item is not within a folder. Write to StatsLog in same thread.
-                write(event, mInstanceId, applyOverwrites(mItemInfo.buildProto()), mSrcState,
-                        mDstState);
+            LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
+            if (mItemInfo.container < 0 || appState == null) {
+                // Write log on the model thread so that logs do not go out of order
+                // (for eg: drop comes after drag)
+                Executors.MODEL_EXECUTOR.execute(
+                        () -> write(event, applyOverwrites(mItemInfo.buildProto())));
             } else {
                 // Item is inside the folder, fetch folder info in a BG thread
                 // and then write to StatsLog.
-                LauncherAppState.getInstance(sContext).getModel().enqueueModelUpdateTask(
+                appState.getModel().enqueueModelUpdateTask(
                         new BaseModelUpdateTask() {
                             @Override
                             public void execute(LauncherAppState app, BgDataModel dataModel,
                                     AllAppsList apps) {
                                 FolderInfo folderInfo = dataModel.folders.get(mItemInfo.container);
-                                write(event, mInstanceId,
-                                        applyOverwrites(mItemInfo.buildProto(folderInfo)),
-                                        mSrcState, mDstState);
+                                write(event, applyOverwrites(mItemInfo.buildProto(folderInfo)));
                             }
                         });
             }
         }
 
         private LauncherAtom.ItemInfo applyOverwrites(LauncherAtom.ItemInfo atomInfo) {
-            LauncherAtom.ItemInfo.Builder itemInfoBuilder =
-                    (LauncherAtom.ItemInfo.Builder) atomInfo.toBuilder();
+            LauncherAtom.ItemInfo.Builder itemInfoBuilder = atomInfo.toBuilder();
 
             mRank.ifPresent(itemInfoBuilder::setRank);
             mContainerInfo.ifPresent(itemInfoBuilder::setContainerInfo);
 
             if (mFromState.isPresent() || mToState.isPresent() || mEditText.isPresent()) {
-                FolderIcon.Builder folderIconBuilder = (FolderIcon.Builder) itemInfoBuilder
+                FolderIcon.Builder folderIconBuilder = itemInfoBuilder
                         .getFolderIcon()
                         .toBuilder();
                 mFromState.ifPresent(folderIconBuilder::setFromLabelState);
@@ -293,8 +317,11 @@
             return itemInfoBuilder.build();
         }
 
-        private void write(EventEnum event, InstanceId instanceId, LauncherAtom.ItemInfo atomInfo,
-                int srcState, int dstState) {
+        @WorkerThread
+        private void write(EventEnum event, LauncherAtom.ItemInfo atomInfo) {
+            InstanceId instanceId = mInstanceId;
+            int srcState = mSrcState;
+            int dstState = mDstState;
             if (IS_VERBOSE) {
                 String name = (event instanceof Enum) ? ((Enum) event).name() :
                         event.getId() + "";
@@ -307,6 +334,10 @@
                                 atomInfo));
             }
 
+            for (StatsLogConsumer consumer : LOGS_CONSUMER) {
+                consumer.consume(event, atomInfo);
+            }
+
             SysUiStatsLog.write(
                     SysUiStatsLog.LAUNCHER_EVENT,
                     SysUiStatsLog.LAUNCHER_UICHANGED__ACTION__DEFAULT_ACTION /* deprecated */,
@@ -337,7 +368,7 @@
     }
 
     private static int getCardinality(LauncherAtom.ItemInfo info) {
-        switch (info.getContainerInfo().getContainerCase()){
+        switch (info.getContainerInfo().getContainerCase()) {
             case PREDICTED_HOTSEAT_CONTAINER:
                 return info.getContainerInfo().getPredictedHotseatContainer().getCardinality();
             case SEARCH_RESULT_CONTAINER:
@@ -402,9 +433,16 @@
     }
 
     private static int getPageId(LauncherAtom.ItemInfo info) {
+        if (info.hasTask()) {
+            return info.getTask().getIndex();
+        }
         switch (info.getContainerInfo().getContainerCase()) {
             case FOLDER:
                 return info.getContainerInfo().getFolder().getPageIndex();
+            case HOTSEAT:
+                return info.getContainerInfo().getHotseat().getIndex();
+            case PREDICTED_HOTSEAT_CONTAINER:
+                return info.getContainerInfo().getPredictedHotseatContainer().getIndex();
             default:
                 return info.getContainerInfo().getWorkspace().getPageIndex();
         }
@@ -413,6 +451,10 @@
     private static int getParentPageId(LauncherAtom.ItemInfo info) {
         switch (info.getContainerInfo().getContainerCase()) {
             case FOLDER:
+                if (info.getContainerInfo().getFolder().getParentContainerCase()
+                        == ParentContainerCase.HOTSEAT) {
+                    return info.getContainerInfo().getFolder().getHotseat().getIndex();
+                }
                 return info.getContainerInfo().getFolder().getWorkspace().getPageIndex();
             case SEARCH_RESULT_CONTAINER:
                 return info.getContainerInfo().getSearchResultContainer().getWorkspace()
@@ -446,7 +488,16 @@
                 return "ALLAPPS";
             default:
                 return "INVALID";
-
         }
     }
+
+
+    /**
+     * Interface to get stats log while it is dispatched to the system
+     */
+    public interface StatsLogConsumer {
+
+        @WorkerThread
+        void consume(EventEnum event, LauncherAtom.ItemInfo atomInfo);
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/logging/UserEventDispatcherExtension.java b/quickstep/src/com/android/quickstep/logging/UserEventDispatcherExtension.java
deleted file mode 100644
index 9ca7f23..0000000
--- a/quickstep/src/com/android/quickstep/logging/UserEventDispatcherExtension.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2018 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.quickstep.logging;
-
-import android.content.Context;
-import android.util.Log;
-
-import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
-import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.CANCEL_TARGET;
-import static com.android.systemui.shared.system.LauncherEventUtil.DISMISS;
-import static com.android.systemui.shared.system.LauncherEventUtil.RECENTS_QUICK_SCRUB_ONBOARDING_TIP;
-import static com.android.systemui.shared.system.LauncherEventUtil.RECENTS_SWIPE_UP_ONBOARDING_TIP;
-import static com.android.systemui.shared.system.LauncherEventUtil.VISIBLE;
-
-import com.android.launcher3.logging.UserEventDispatcher;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
-import com.android.systemui.shared.system.MetricsLoggerCompat;
-
-/**
- * This class handles AOSP MetricsLogger function calls and logging around
- * quickstep interactions.
- */
-@SuppressWarnings("unused")
-public class UserEventDispatcherExtension extends UserEventDispatcher {
-
-    public static final int ALL_APPS_PREDICTION_TIPS = 2;
-
-    private static final String TAG = "UserEventDispatcher";
-
-    public UserEventDispatcherExtension(Context context) { }
-
-    public void logStateChangeAction(int action, int dir, int downX, int downY,
-                                     int srcChildTargetType, int srcParentContainerType,
-                                     int dstContainerType, int pageIndex) {
-        new MetricsLoggerCompat().visibility(MetricsLoggerCompat.OVERVIEW_ACTIVITY,
-                dstContainerType == LauncherLogProto.ContainerType.TASKSWITCHER);
-        super.logStateChangeAction(action, dir, downX, downY, srcChildTargetType,
-                srcParentContainerType, dstContainerType, pageIndex);
-    }
-
-    public void logActionTip(int actionType, int viewType) {
-        LauncherLogProto.Action action = new LauncherLogProto.Action();
-        LauncherLogProto.Target target = new LauncherLogProto.Target();
-        switch(actionType) {
-            case VISIBLE:
-                action.type = LauncherLogProto.Action.Type.TIP;
-                target.type = LauncherLogProto.Target.Type.CONTAINER;
-                target.containerType = LauncherLogProto.ContainerType.TIP;
-                break;
-            case DISMISS:
-                action.type = LauncherLogProto.Action.Type.TOUCH;
-                action.touch = LauncherLogProto.Action.Touch.TAP;
-                target.type = LauncherLogProto.Target.Type.CONTROL;
-                target.controlType = CANCEL_TARGET;
-                break;
-            default:
-                Log.e(TAG, "Unexpected action type = " + actionType);
-        }
-
-        switch(viewType) {
-            case RECENTS_QUICK_SCRUB_ONBOARDING_TIP:
-                target.tipType = LauncherLogProto.TipType.QUICK_SCRUB_TEXT;
-                break;
-            case RECENTS_SWIPE_UP_ONBOARDING_TIP:
-                target.tipType = LauncherLogProto.TipType.SWIPE_UP_TEXT;
-                break;
-            default:
-                Log.e(TAG, "Unexpected viewType = " + viewType);
-        }
-        LauncherLogProto.LauncherEvent event = newLauncherEvent(action, target);
-        dispatchUserEvent(event, null);
-    }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ActiveGestureLog.java b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/util/ActiveGestureLog.java
rename to quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
diff --git a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
new file mode 100644
index 0000000..deb70e0
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2020 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.quickstep.util;
+
+import static com.android.launcher3.anim.Interpolators.DEACCEL;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
+import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
+import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
+
+import android.animation.TimeInterpolator;
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.util.FloatProperty;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.quickstep.LauncherActivityInterface;
+import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.views.RecentsView;
+
+/**
+ * Controls an animation that can go beyond progress = 1, at which point resistance should be
+ * applied. Internally, this is a wrapper around 2 {@link AnimatorPlaybackController}s, one that
+ * runs from progress 0 to 1 like normal, then one that seamlessly continues that animation but
+ * starts applying resistance as well.
+ */
+public class AnimatorControllerWithResistance {
+
+    /**
+     * How much farther we can drag past overview in 2-button mode, as a factor of the distance
+     * it takes to drag from an app to overview.
+     */
+    public static final float TWO_BUTTON_EXTRA_DRAG_FACTOR = 0.25f;
+
+    private enum RecentsResistanceParams {
+        FROM_APP(0.75f, 0.5f, 1f),
+        FROM_OVERVIEW(1f, 0.75f, 0.5f);
+
+        RecentsResistanceParams(float scaleStartResist, float scaleMaxResist,
+                float translationFactor) {
+            this.scaleStartResist = scaleStartResist;
+            this.scaleMaxResist = scaleMaxResist;
+            this.translationFactor = translationFactor;
+        }
+
+        /**
+         * Start slowing down the rate of scaling down when recents view is smaller than this scale.
+         */
+        public final float scaleStartResist;
+
+        /**
+         * Recents view will reach this scale at the very end of the drag.
+         */
+        public final float scaleMaxResist;
+
+        /**
+         * How much translation to apply to RecentsView when the drag reaches the top of the screen,
+         * where 0 will keep it centered and 1 will have it barely touch the top of the screen.
+         */
+        public final float translationFactor;
+    }
+
+    private static final TimeInterpolator RECENTS_SCALE_RESIST_INTERPOLATOR = DEACCEL;
+    private static final TimeInterpolator RECENTS_TRANSLATE_RESIST_INTERPOLATOR = LINEAR;
+
+    private final AnimatorPlaybackController mNormalController;
+    private final AnimatorPlaybackController mResistanceController;
+
+    // Initialize to -1 so the first 0 gets applied.
+    private float mLastNormalProgress = -1;
+    private float mLastResistProgress;
+
+    public AnimatorControllerWithResistance(AnimatorPlaybackController normalController,
+            AnimatorPlaybackController resistanceController) {
+        mNormalController = normalController;
+        mResistanceController = resistanceController;
+    }
+
+    public AnimatorPlaybackController getNormalController() {
+        return mNormalController;
+    }
+
+    /**
+     * Applies the current progress of the animation.
+     * @param progress From 0 to maxProgress, where 1 is the target we are animating towards.
+     * @param maxProgress > 1, this is where the resistance will be applied.
+     */
+    public void setProgress(float progress, float maxProgress) {
+        float normalProgress = Utilities.boundToRange(progress, 0, 1);
+        if (normalProgress != mLastNormalProgress) {
+            mLastNormalProgress = normalProgress;
+            mNormalController.setPlayFraction(normalProgress);
+        }
+        if (maxProgress <= 1) {
+            return;
+        }
+        float resistProgress = progress <= 1 ? 0 : Utilities.getProgress(progress, 1, maxProgress);
+        if (resistProgress != mLastResistProgress) {
+            mLastResistProgress = resistProgress;
+            mResistanceController.setPlayFraction(resistProgress);
+        }
+    }
+
+    /**
+     * Applies resistance to recents when swiping up past its target position.
+     * @param normalController The controller to run from 0 to 1 before this resistance applies.
+     * @param context Used to compute start and end values.
+     * @param recentsOrientedState Used to compute start and end values.
+     * @param dp Used to compute start and end values.
+     * @param scaleTarget The target for the scaleProperty.
+     * @param scaleProperty Animate the value to change the scale of the window/recents view.
+     * @param translationTarget The target for the translationProperty.
+     * @param translationProperty Animate the value to change the translation of the recents view.
+     */
+    public static <SCALE, TRANSLATION> AnimatorControllerWithResistance createForRecents(
+            AnimatorPlaybackController normalController, Context context,
+            RecentsOrientedState recentsOrientedState, DeviceProfile dp, SCALE scaleTarget,
+            FloatProperty<SCALE> scaleProperty, TRANSLATION translationTarget,
+            FloatProperty<TRANSLATION> translationProperty) {
+
+        RecentsParams params = new RecentsParams(context, recentsOrientedState, dp, scaleTarget,
+                scaleProperty, translationTarget, translationProperty);
+        PendingAnimation resistAnim = createRecentsResistanceAnim(params);
+
+        AnimatorPlaybackController resistanceController = resistAnim.createPlaybackController();
+        return new AnimatorControllerWithResistance(normalController, resistanceController);
+    }
+
+    /**
+     * Creates the resistance animation for {@link #createForRecents}, or can be used separately
+     * when starting from recents, i.e. {@link #createRecentsResistanceFromOverviewAnim}.
+     */
+    public static <SCALE, TRANSLATION> PendingAnimation createRecentsResistanceAnim(
+            RecentsParams<SCALE, TRANSLATION> params) {
+        Rect startRect = new Rect();
+        PagedOrientationHandler orientationHandler = params.recentsOrientedState
+                .getOrientationHandler();
+        LauncherActivityInterface.INSTANCE.calculateTaskSize(params.context, params.dp, startRect,
+                orientationHandler);
+        long distanceToCover = startRect.bottom;
+        boolean isTwoButtonMode = SysUINavigationMode.getMode(params.context) == TWO_BUTTONS;
+        if (isTwoButtonMode) {
+            // We can only drag a small distance past overview, not to the top of the screen.
+            distanceToCover = (long)
+                    ((params.dp.heightPx - startRect.bottom) * TWO_BUTTON_EXTRA_DRAG_FACTOR);
+        }
+        PendingAnimation resistAnim = params.resistAnim != null
+                ? params.resistAnim
+                : new PendingAnimation(distanceToCover * 2);
+
+        PointF pivot = new PointF();
+        float fullscreenScale = params.recentsOrientedState.getFullScreenScaleAndPivot(
+                startRect, params.dp, pivot);
+        float prevScaleRate = (fullscreenScale - params.startScale)
+                / (params.dp.heightPx - startRect.bottom);
+        // This is what the scale would be at the end of the drag if we didn't apply resistance.
+        float endScale = params.startScale - prevScaleRate * distanceToCover;
+        final TimeInterpolator scaleInterpolator;
+        if (isTwoButtonMode) {
+            // We are bounded by the distance of the drag, so we don't need to apply resistance.
+            scaleInterpolator = LINEAR;
+        } else {
+            // Create an interpolator that resists the scale so the scale doesn't get smaller than
+            // RECENTS_SCALE_MAX_RESIST.
+            float startResist = Utilities.getProgress(params.resistanceParams.scaleStartResist,
+                    params.startScale, endScale);
+            float maxResist = Utilities.getProgress(params.resistanceParams.scaleMaxResist,
+                    params.startScale, endScale);
+            scaleInterpolator = t -> {
+                if (t < startResist) {
+                    return t;
+                }
+                float resistProgress = Utilities.getProgress(t, startResist, 1);
+                resistProgress = RECENTS_SCALE_RESIST_INTERPOLATOR.getInterpolation(resistProgress);
+                return startResist + resistProgress * (maxResist - startResist);
+            };
+        }
+        resistAnim.addFloat(params.scaleTarget, params.scaleProperty, params.startScale, endScale,
+                scaleInterpolator);
+
+        if (!isTwoButtonMode) {
+            // Compute where the task view would be based on the end scale, if we didn't translate.
+            RectF endRectF = new RectF(startRect);
+            Matrix temp = new Matrix();
+            temp.setScale(params.resistanceParams.scaleMaxResist,
+                    params.resistanceParams.scaleMaxResist, pivot.x, pivot.y);
+            temp.mapRect(endRectF);
+            // Translate such that the task view touches the top of the screen when drag does.
+            float endTranslation = endRectF.top
+                    * orientationHandler.getSecondaryTranslationDirectionFactor()
+                    * params.resistanceParams.translationFactor;
+            resistAnim.addFloat(params.translationTarget, params.translationProperty,
+                    params.startTranslation, endTranslation, RECENTS_TRANSLATE_RESIST_INTERPOLATOR);
+        }
+
+        return resistAnim;
+    }
+
+    /**
+     * Helper method to update or create a PendingAnimation suitable for animating
+     * a RecentsView interaction that started from the overview state.
+     */
+    public static PendingAnimation createRecentsResistanceFromOverviewAnim(
+            Launcher launcher, @Nullable PendingAnimation resistanceAnim) {
+        RecentsView recentsView = launcher.getOverviewPanel();
+        RecentsParams params = new RecentsParams(launcher, recentsView.getPagedViewOrientedState(),
+                launcher.getDeviceProfile(), recentsView, RECENTS_SCALE_PROPERTY, recentsView,
+                TASK_SECONDARY_TRANSLATION)
+                .setResistAnim(resistanceAnim)
+                .setResistanceParams(RecentsResistanceParams.FROM_OVERVIEW)
+                .setStartScale(recentsView.getScaleX());
+        return createRecentsResistanceAnim(params);
+    }
+
+    /**
+     * Params to compute resistance when scaling/translating recents.
+     */
+    private static class RecentsParams<SCALE, TRANSLATION> {
+        // These are all required and can't have default values, hence are final.
+        public final Context context;
+        public final RecentsOrientedState recentsOrientedState;
+        public final DeviceProfile dp;
+        public final SCALE scaleTarget;
+        public final FloatProperty<SCALE> scaleProperty;
+        public final TRANSLATION translationTarget;
+        public final FloatProperty<TRANSLATION> translationProperty;
+
+        // These are not required, or can have a default value that is generally correct.
+        @Nullable public PendingAnimation resistAnim = null;
+        public RecentsResistanceParams resistanceParams = RecentsResistanceParams.FROM_APP;
+        public float startScale = 1f;
+        public float startTranslation = 0f;
+
+        private RecentsParams(Context context, RecentsOrientedState recentsOrientedState,
+                DeviceProfile dp, SCALE scaleTarget, FloatProperty<SCALE> scaleProperty,
+                TRANSLATION translationTarget, FloatProperty<TRANSLATION> translationProperty) {
+            this.context = context;
+            this.recentsOrientedState = recentsOrientedState;
+            this.dp = dp;
+            this.scaleTarget = scaleTarget;
+            this.scaleProperty = scaleProperty;
+            this.translationTarget = translationTarget;
+            this.translationProperty = translationProperty;
+        }
+
+        private RecentsParams setResistAnim(PendingAnimation resistAnim) {
+            this.resistAnim = resistAnim;
+            return this;
+        }
+
+        private RecentsParams setResistanceParams(RecentsResistanceParams resistanceParams) {
+            this.resistanceParams = resistanceParams;
+            return this;
+        }
+
+        private RecentsParams setStartScale(float startScale) {
+            this.startScale = startScale;
+            return this;
+        }
+
+        private RecentsParams setStartTranslation(float startTranslation) {
+            this.startTranslation = startTranslation;
+            return this;
+        }
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AssistantUtilities.java b/quickstep/src/com/android/quickstep/util/AssistantUtilities.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/util/AssistantUtilities.java
rename to quickstep/src/com/android/quickstep/util/AssistantUtilities.java
diff --git a/quickstep/src/com/android/quickstep/util/InputConsumerProxy.java b/quickstep/src/com/android/quickstep/util/InputConsumerProxy.java
new file mode 100644
index 0000000..3e87f48
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/InputConsumerProxy.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2020 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.quickstep.util;
+
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_UP;
+
+import android.util.Log;
+import android.view.InputEvent;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+import com.android.quickstep.InputConsumer;
+import com.android.systemui.shared.system.InputConsumerController;
+
+import java.util.function.Supplier;
+
+/**
+ * Utility class which manages proxying input events from {@link InputConsumerController}
+ * to an {@link InputConsumer}
+ */
+public class InputConsumerProxy {
+
+    private static final String TAG = "InputConsumerProxy";
+
+    private final InputConsumerController mInputConsumerController;
+    private final Supplier<InputConsumer> mConsumerSupplier;
+
+    // The consumer is created lazily on demand.
+    private InputConsumer mInputConsumer;
+
+    private boolean mDestroyed = false;
+    private boolean mTouchInProgress = false;
+    private boolean mDestroyPending = false;
+
+    public InputConsumerProxy(InputConsumerController inputConsumerController,
+            Supplier<InputConsumer> consumerSupplier) {
+        mInputConsumerController = inputConsumerController;
+        mConsumerSupplier = consumerSupplier;
+    }
+
+    public void enable() {
+        if (mDestroyed) {
+            return;
+        }
+        mInputConsumerController.setInputListener(this::onInputConsumerEvent);
+    }
+
+    private boolean onInputConsumerEvent(InputEvent ev) {
+        if (ev instanceof MotionEvent) {
+            onInputConsumerMotionEvent((MotionEvent) ev);
+        } else if (ev instanceof KeyEvent) {
+            if (mInputConsumer == null) {
+                mInputConsumer = mConsumerSupplier.get();
+            }
+            mInputConsumer.onKeyEvent((KeyEvent) ev);
+            return true;
+        }
+        return false;
+    }
+
+    private boolean onInputConsumerMotionEvent(MotionEvent ev) {
+        int action = ev.getAction();
+
+        // Just to be safe, verify that ACTION_DOWN comes before any other action,
+        // and ignore any ACTION_DOWN after the first one (though that should not happen).
+        if (!mTouchInProgress && action != ACTION_DOWN) {
+            Log.w(TAG, "Received non-down motion before down motion: " + action);
+            return false;
+        }
+        if (mTouchInProgress && action == ACTION_DOWN) {
+            Log.w(TAG, "Received down motion while touch was already in progress");
+            return false;
+        }
+
+        if (action == ACTION_DOWN) {
+            mTouchInProgress = true;
+            if (mInputConsumer == null) {
+                mInputConsumer = mConsumerSupplier.get();
+            }
+        } else if (action == ACTION_CANCEL || action == ACTION_UP) {
+            // Finish any pending actions
+            mTouchInProgress = false;
+            if (mDestroyPending) {
+                destroy();
+            }
+        }
+        if (mInputConsumer != null) {
+            mInputConsumer.onMotionEvent(ev);
+        }
+
+        return true;
+    }
+
+    public void destroy() {
+        if (mTouchInProgress) {
+            mDestroyPending = true;
+            return;
+        }
+        mDestroyPending = false;
+        mDestroyed = true;
+        mInputConsumerController.setInputListener(null);
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
index ae19d73..b88a195 100644
--- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java
+++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
@@ -15,7 +15,6 @@
  */
 package com.android.quickstep.util;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
 import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
 
 import android.content.Context;
@@ -45,11 +44,11 @@
     public static int getShelfTrackingDistance(Context context, DeviceProfile dp,
             PagedOrientationHandler orientationHandler) {
         // Track the bottom of the window.
-        if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(context)) {
+        if (removeShelfFromOverview(context)) {
             Rect taskSize = new Rect();
             LauncherActivityInterface.INSTANCE.calculateTaskSize(context, dp, taskSize,
                     orientationHandler);
-            return (dp.heightPx - taskSize.height()) / 2;
+            return orientationHandler.getDistanceToBottomOfRect(dp, taskSize);
         }
         int shelfHeight = dp.hotseatBarSizePx + dp.getInsets().bottom;
         int spaceBetweenShelfAndRecents = (int) context.getResources().getDimension(
diff --git a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
index a5d4568..b862936 100644
--- a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
+++ b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
@@ -15,12 +15,13 @@
  */
 package com.android.quickstep.util;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_LSQ_VELOCITY_PROVIDER;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_SYSTEM_VELOCITY_PROVIDER;
 
 import android.content.Context;
 import android.content.res.Resources;
 import android.util.Log;
 import android.view.MotionEvent;
+import android.view.VelocityTracker;
 
 import com.android.launcher3.Alarm;
 import com.android.launcher3.R;
@@ -92,8 +93,8 @@
         mForcePauseTimeout = new Alarm();
         mForcePauseTimeout.setOnAlarmListener(alarm -> updatePaused(true /* isPaused */));
         mMakePauseHarderToTrigger = makePauseHarderToTrigger;
-        mVelocityProvider = ENABLE_LSQ_VELOCITY_PROVIDER.get()
-                ? new LSqVelocityProvider(axis) : new LinearVelocityProvider(axis);
+        mVelocityProvider = ENABLE_SYSTEM_VELOCITY_PROVIDER.get()
+                ? new SystemVelocityProvider(axis) : new LinearVelocityProvider(axis);
     }
 
     /**
@@ -264,136 +265,28 @@
         }
     }
 
-    /**
-     * Java implementation of {@link android.view.VelocityTracker} using the Least Square (deg 2)
-     * algorithm.
-     */
-    private static class LSqVelocityProvider implements VelocityProvider {
+    private static class SystemVelocityProvider implements VelocityProvider {
 
-        // Maximum age of a motion event to be considered when calculating the velocity.
-        private static final long HORIZON_MS = 100;
-        // Number of samples to keep.
-        private static final int HISTORY_SIZE = 20;
-
-        // Position history are stored in a circular array
-        private final float[] mHistoricTimes = new float[HISTORY_SIZE];
-        private final float[] mHistoricPos = new float[HISTORY_SIZE];
-        private int mHistoryCount = 0;
-        private int mHistoryStart = 0;
-
+        private final VelocityTracker mVelocityTracker;
         private final int mAxis;
 
-        LSqVelocityProvider(int axis) {
+        SystemVelocityProvider(int axis) {
+            mVelocityTracker = VelocityTracker.obtain();
             mAxis = axis;
         }
 
         @Override
-        public void clear() {
-            mHistoryCount = mHistoryStart = 0;
-        }
-
-        private void addPositionAndTime(float eventTime, float eventPosition) {
-            mHistoricTimes[mHistoryStart] = eventTime;
-            mHistoricPos[mHistoryStart] = eventPosition;
-            mHistoryStart++;
-            if (mHistoryStart >= HISTORY_SIZE) {
-                mHistoryStart = 0;
-            }
-            mHistoryCount = Math.min(HISTORY_SIZE, mHistoryCount + 1);
+        public Float addMotionEvent(MotionEvent ev, int pointer) {
+            mVelocityTracker.addMovement(ev);
+            mVelocityTracker.computeCurrentVelocity(1); // px / ms
+            return mAxis == MotionEvent.AXIS_X
+                    ? mVelocityTracker.getXVelocity(pointer)
+                    : mVelocityTracker.getYVelocity(pointer);
         }
 
         @Override
-        public Float addMotionEvent(MotionEvent ev, int pointer) {
-            // Add all historic points
-            int historyCount = ev.getHistorySize();
-            for (int i = 0; i < historyCount; i++) {
-                addPositionAndTime(
-                        ev.getHistoricalEventTime(i), ev.getHistoricalAxisValue(mAxis, pointer, i));
-            }
-
-            // Start index for the last position (about to be added)
-            int eventStartIndex = mHistoryStart;
-            addPositionAndTime(ev.getEventTime(), ev.getAxisValue(mAxis, pointer));
-            return solveUnweightedLeastSquaresDeg2(eventStartIndex);
-        }
-
-        /**
-         * Solves the instantaneous velocity.
-         * Based on solveUnweightedLeastSquaresDeg2 in VelocityTracker.cpp
-         */
-        private Float solveUnweightedLeastSquaresDeg2(final int pointPos) {
-            final float eventTime = mHistoricTimes[pointPos];
-
-            float sxi = 0, sxiyi = 0, syi = 0, sxi2 = 0, sxi3 = 0, sxi2yi = 0, sxi4 = 0;
-            int count = 0;
-            for (int i = 0; i < mHistoryCount; i++) {
-                int index = pointPos - i;
-                if (index < 0) {
-                    index += HISTORY_SIZE;
-                }
-
-                float time = mHistoricTimes[index];
-                float age = eventTime - time;
-                if (age > HORIZON_MS) {
-                    break;
-                }
-                count++;
-                float xi = -age;
-
-                float yi = mHistoricPos[index];
-                float xi2 = xi * xi;
-                float xi3 = xi2 * xi;
-                float xi4 = xi3 * xi;
-                float xiyi = xi * yi;
-                float xi2yi = xi2 * yi;
-
-                sxi += xi;
-                sxi2 += xi2;
-                sxiyi += xiyi;
-                sxi2yi += xi2yi;
-                syi += yi;
-                sxi3 += xi3;
-                sxi4 += xi4;
-            }
-
-            if (count < 3) {
-                // Too few samples
-                if (count == 2) {
-                    int endPos = pointPos - 1;
-                    if (endPos < 0) {
-                        endPos += HISTORY_SIZE;
-                    }
-                    float denominator = eventTime - mHistoricTimes[endPos];
-                    if (denominator != 0) {
-                        return (eventTime - mHistoricPos[endPos]) / denominator;
-
-                    }
-                }
-                return null;
-            }
-
-            float Sxx = sxi2 - sxi * sxi / count;
-            float Sxy = sxiyi - sxi * syi / count;
-            float Sxx2 = sxi3 - sxi * sxi2 / count;
-            float Sx2y = sxi2yi - sxi2 * syi / count;
-            float Sx2x2 = sxi4 - sxi2 * sxi2 / count;
-
-            float denominator = Sxx * Sx2x2 - Sxx2 * Sxx2;
-            if (denominator == 0) {
-                // division by 0 when computing velocity
-                return null;
-            }
-            // Compute a
-            // float numerator = Sx2y*Sxx - Sxy*Sxx2;
-
-            // Compute b
-            float numerator = Sxy * Sx2x2 - Sx2y * Sxx2;
-            float b = numerator / denominator;
-
-            // Compute c
-            // float c = syi/count - b * sxi/count - a * sxi2/count;
-
-            return b;
+        public void clear() {
+            mVelocityTracker.clear();
         }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/util/NavBarPosition.java b/quickstep/src/com/android/quickstep/util/NavBarPosition.java
index 0a98e1b..449dba8 100644
--- a/quickstep/src/com/android/quickstep/util/NavBarPosition.java
+++ b/quickstep/src/com/android/quickstep/util/NavBarPosition.java
@@ -19,7 +19,7 @@
 
 import android.view.Surface;
 
-import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.DisplayController.Info;
 import com.android.quickstep.SysUINavigationMode;
 
 /**
@@ -30,7 +30,7 @@
     private final SysUINavigationMode.Mode mMode;
     private final int mDisplayRotation;
 
-    public NavBarPosition(SysUINavigationMode.Mode mode, DefaultDisplay.Info info) {
+    public NavBarPosition(SysUINavigationMode.Mode mode, Info info) {
         mMode = mode;
         mDisplayRotation = info.rotation;
     }
diff --git a/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java b/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java
new file mode 100644
index 0000000..4a298d3
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2020 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.quickstep.util;
+
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.anim.Interpolators.DEACCEL;
+import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
+import static com.android.launcher3.anim.Interpolators.INSTANT;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_COMPONENTS;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_ACTIONS_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
+import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_PEEK;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.util.Log;
+import android.view.animation.Interpolator;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.statemanager.StateManager;
+import com.android.launcher3.states.StateAnimationConfig;
+import com.android.quickstep.views.RecentsView;
+
+/**
+ * Runs an animation from overview to home. Currently, this animation is just a wrapper around the
+ * normal state transition, in order to keep RecentsView at the same scale and translationY that
+ * it started out at as it translates offscreen. It also scrolls RecentsView to page 0 and may play
+ * a {@link StaggeredWorkspaceAnim} if we're starting from an upward fling.
+ */
+public class OverviewToHomeAnim {
+
+    private static final String TAG = "OverviewToHomeAnim";
+
+    // Constants to specify how to scroll RecentsView to the default page if it's not already there.
+    private static final int DEFAULT_PAGE = 0;
+    private static final int PER_PAGE_SCROLL_DURATION = 150;
+    private static final int MAX_PAGE_SCROLL_DURATION = 750;
+
+    private final Launcher mLauncher;
+    private final Runnable mOnReachedHome;
+
+    // Only run mOnReachedHome when both of these are true.
+    private boolean mIsHomeStaggeredAnimFinished;
+    private boolean mIsOverviewHidden;
+
+    public OverviewToHomeAnim(Launcher launcher, Runnable onReachedHome) {
+        mLauncher = launcher;
+        mOnReachedHome = onReachedHome;
+    }
+
+    /**
+     * Starts the animation. If velocity < 0 (i.e. upwards), also plays a
+     * {@link StaggeredWorkspaceAnim}.
+     */
+    public void animateWithVelocity(float velocity) {
+        StateManager<LauncherState> stateManager = mLauncher.getStateManager();
+        LauncherState startState = stateManager.getState();
+        if (startState != OVERVIEW) {
+            Log.e(TAG, "animateFromOverviewToHome: unexpected start state " + startState);
+        }
+        AnimatorSet anim = new AnimatorSet();
+
+        boolean playStaggeredWorkspaceAnim = velocity < 0;
+        if (playStaggeredWorkspaceAnim) {
+            StaggeredWorkspaceAnim staggeredWorkspaceAnim = new StaggeredWorkspaceAnim(
+                    mLauncher, velocity, false /* animateOverviewScrim */);
+            staggeredWorkspaceAnim.addAnimatorListener(new AnimationSuccessListener() {
+                @Override
+                public void onAnimationSuccess(Animator animator) {
+                    mIsHomeStaggeredAnimFinished = true;
+                    maybeOverviewToHomeAnimComplete();
+                }
+            });
+            anim.play(staggeredWorkspaceAnim.getAnimators());
+        } else {
+            mIsHomeStaggeredAnimFinished = true;
+        }
+
+        RecentsView recentsView = mLauncher.getOverviewPanel();
+        int numPagesToScroll = recentsView.getNextPage() - DEFAULT_PAGE;
+        int scrollDuration = Math.min(MAX_PAGE_SCROLL_DURATION,
+                numPagesToScroll * PER_PAGE_SCROLL_DURATION);
+        int duration = Math.max(scrollDuration, startState.getTransitionDuration(mLauncher));
+
+        StateAnimationConfig config = new UseFirstInterpolatorStateAnimConfig();
+        config.duration = duration;
+        config.animFlags = playStaggeredWorkspaceAnim
+                // StaggeredWorkspaceAnim doesn't animate overview, so we handle it here.
+                ? PLAY_ATOMIC_OVERVIEW_PEEK
+                : ANIM_ALL_COMPONENTS;
+        boolean isLayoutNaturalToLauncher = recentsView.getPagedOrientationHandler()
+                .isLayoutNaturalToLauncher();
+        config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, isLayoutNaturalToLauncher
+                ? DEACCEL : FINAL_FRAME);
+        config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, FINAL_FRAME);
+        config.setInterpolator(ANIM_OVERVIEW_SCALE, FINAL_FRAME);
+        config.setInterpolator(ANIM_OVERVIEW_ACTIONS_FADE, INSTANT);
+        if (!isLayoutNaturalToLauncher) {
+            config.setInterpolator(ANIM_OVERVIEW_FADE, DEACCEL);
+        }
+        AnimatorSet stateAnim = stateManager.createAtomicAnimation(
+                startState, NORMAL, config);
+        stateAnim.addListener(new AnimationSuccessListener() {
+            @Override
+            public void onAnimationSuccess(Animator animator) {
+                mIsOverviewHidden = true;
+                maybeOverviewToHomeAnimComplete();
+            }
+        });
+        anim.play(stateAnim);
+        stateManager.setCurrentAnimation(anim, NORMAL);
+        anim.start();
+        recentsView.snapToPage(DEFAULT_PAGE, duration);
+    }
+
+    private void maybeOverviewToHomeAnimComplete() {
+        if (mIsHomeStaggeredAnimFinished && mIsOverviewHidden) {
+            mOnReachedHome.run();
+        }
+    }
+
+    /**
+     * Wrapper around StateAnimationConfig that doesn't allow interpolators to be set if they are
+     * already set. This ensures they aren't overridden before being used.
+     */
+    private static class UseFirstInterpolatorStateAnimConfig extends StateAnimationConfig {
+        @Override
+        public void setInterpolator(int animId, Interpolator interpolator) {
+            if (mInterpolators[animId] == null || interpolator == null) {
+                super.setInterpolator(animId, interpolator);
+            }
+        }
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ProtoTracer.java b/quickstep/src/com/android/quickstep/util/ProtoTracer.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/util/ProtoTracer.java
rename to quickstep/src/com/android/quickstep/util/ProtoTracer.java
diff --git a/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java b/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
index 9ed2bbe..b10adb4 100644
--- a/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
+++ b/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
@@ -21,19 +21,18 @@
 import static com.android.launcher3.LauncherState.HINT_STATE;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
 import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
 
 import android.content.SharedPreferences;
 
-import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.hybridhotseat.HotseatPredictionController;
 import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.statemanager.StateManager.StateListener;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.launcher3.util.OnboardingPrefs;
 import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.views.AllAppsEduView;
@@ -41,9 +40,9 @@
 /**
  * Extends {@link OnboardingPrefs} for quickstep-specific onboarding data.
  */
-public class QuickstepOnboardingPrefs extends OnboardingPrefs<BaseQuickstepLauncher> {
+public class QuickstepOnboardingPrefs extends OnboardingPrefs<QuickstepLauncher> {
 
-    public QuickstepOnboardingPrefs(BaseQuickstepLauncher launcher, SharedPreferences sharedPrefs) {
+    public QuickstepOnboardingPrefs(QuickstepLauncher launcher, SharedPreferences sharedPrefs) {
         super(launcher, sharedPrefs);
 
         StateManager<LauncherState> stateManager = launcher.getStateManager();
@@ -66,8 +65,7 @@
         }
 
         boolean shelfBounceSeen = getBoolean(SHELF_BOUNCE_SEEN);
-        if (!shelfBounceSeen && ENABLE_OVERVIEW_ACTIONS.get()
-                && removeShelfFromOverview(launcher)) {
+        if (!shelfBounceSeen && removeShelfFromOverview(launcher)) {
             // There's no shelf in overview, so don't bounce it (can't get to all apps anyway).
             shelfBounceSeen = true;
             mSharedPrefs.edit().putBoolean(SHELF_BOUNCE_SEEN, shelfBounceSeen).apply();
@@ -87,20 +85,6 @@
             });
         }
 
-        if (!hasReachedMaxCount(ALL_APPS_COUNT)) {
-            stateManager.addStateListener(new StateListener<LauncherState>() {
-                @Override
-                public void onStateTransitionComplete(LauncherState finalState) {
-                    if (finalState == ALL_APPS) {
-                        if (incrementEventCount(ALL_APPS_COUNT)) {
-                            stateManager.removeStateListener(this);
-                            mLauncher.getScrimView().updateDragHandleVisibility();
-                        }
-                    }
-                }
-            });
-        }
-
         if (FeatureFlags.ENABLE_HYBRID_HOTSEAT.get() && !hasReachedMaxCount(
                 HOTSEAT_DISCOVERY_TIP_COUNT)) {
             stateManager.addStateListener(new StateListener<LauncherState>() {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAtomicAnimationFactory.java b/quickstep/src/com/android/quickstep/util/RecentsAtomicAnimationFactory.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAtomicAnimationFactory.java
rename to quickstep/src/com/android/quickstep/util/RecentsAtomicAnimationFactory.java
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index d822b6c..df9b0cf 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -33,7 +33,6 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.SharedPreferences;
-import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.graphics.Matrix;
@@ -48,7 +47,6 @@
 
 import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile;
@@ -58,7 +56,6 @@
 import com.android.launcher3.util.WindowBounds;
 import com.android.quickstep.BaseActivityInterface;
 import com.android.quickstep.SysUINavigationMode;
-import com.android.systemui.shared.system.ConfigurationCompat;
 
 import java.lang.annotation.Retention;
 import java.util.function.IntConsumer;
@@ -74,7 +71,7 @@
 public final class RecentsOrientedState implements SharedPreferences.OnSharedPreferenceChangeListener {
 
     private static final String TAG = "RecentsOrientedState";
-    private static final boolean DEBUG = true;
+    private static final boolean DEBUG = false;
 
     private ContentObserver mSystemAutoRotateObserver = new ContentObserver(new Handler()) {
         @Override
@@ -91,6 +88,7 @@
     private @SurfaceRotation int mTouchRotation = ROTATION_0;
     private @SurfaceRotation int mDisplayRotation = ROTATION_0;
     private @SurfaceRotation int mRecentsActivityRotation = ROTATION_0;
+    private @SurfaceRotation int mRecentsRotation = ROTATION_0 - 1;
 
     // Launcher activity supports multiple orientation, but fallback activity does not
     private static final int FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_ACTIVITY = 1 << 0;
@@ -133,8 +131,6 @@
     private int mFlags;
     private int mPreviousRotation = ROTATION_0;
 
-    @Nullable private Configuration mActivityConfiguration;
-
     /**
      * @param rotationChangeListener Callback for receiving rotation events when rotation watcher
      *                              is enabled
@@ -170,11 +166,11 @@
     }
 
     /**
-     * Sets the configuration for the recents activity, which could affect the activity's rotation
+     * Sets the rotation for the recents activity, which could affect the appearance of task view.
      * @see #update(int, int)
      */
-    public boolean setActivityConfiguration(Configuration activityConfiguration) {
-        mActivityConfiguration = activityConfiguration;
+    public boolean setRecentsRotation(@SurfaceRotation int recentsRotation) {
+        mRecentsRotation = recentsRotation;
         return update(mTouchRotation, mDisplayRotation);
     }
 
@@ -231,9 +227,7 @@
     @SurfaceRotation
     private int inferRecentsActivityRotation(@SurfaceRotation int displayRotation) {
         if (isRecentsActivityRotationAllowed()) {
-            return mActivityConfiguration == null
-                    ? displayRotation
-                    : ConfigurationCompat.getWindowConfigurationRotation(mActivityConfiguration);
+            return mRecentsRotation < ROTATION_0 ? displayRotation : mRecentsRotation;
         } else {
             return ROTATION_0;
         }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java b/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
rename to quickstep/src/com/android/quickstep/util/RectFSpringAnim.java
diff --git a/quickstep/src/com/android/quickstep/util/SplitScreenBounds.java b/quickstep/src/com/android/quickstep/util/SplitScreenBounds.java
index a770e8e..176478f 100644
--- a/quickstep/src/com/android/quickstep/util/SplitScreenBounds.java
+++ b/quickstep/src/com/android/quickstep/util/SplitScreenBounds.java
@@ -32,7 +32,7 @@
 import androidx.annotation.UiThread;
 
 import com.android.launcher3.R;
-import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.WindowBounds;
 
 import java.util.ArrayList;
@@ -77,7 +77,7 @@
 
         WindowBounds bounds = new WindowBounds(wm.getBounds(),
                 new Rect(insets.left, insets.top, insets.right, insets.bottom));
-        int rotation = DefaultDisplay.INSTANCE.get(context).getInfo().rotation;
+        int rotation = DisplayController.getDefaultDisplay(context).getInfo().rotation;
         int halfDividerSize = context.getResources()
                 .getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2;
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
similarity index 97%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
rename to quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
index 3cafd42..4120331 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
+++ b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
@@ -205,13 +205,15 @@
         ResourceProvider rp = DynamicResource.provider(v.getContext());
         float stiffness = rp.getFloat(R.dimen.staggered_stiffness);
         float damping = rp.getFloat(R.dimen.staggered_damping_ratio);
+        float endTransY = 0;
+        float springVelocity = Math.abs(mVelocity) * Math.signum(endTransY - mSpringTransY);
         ValueAnimator springTransY = new SpringAnimationBuilder(v.getContext())
                 .setStiffness(stiffness)
                 .setDampingRatio(damping)
                 .setMinimumVisibleChange(1f)
                 .setStartValue(mSpringTransY)
-                .setEndValue(0)
-                .setStartVelocity(mVelocity)
+                .setEndValue(endTransY)
+                .setStartVelocity(springVelocity)
                 .build(v, VIEW_TRANSLATE_Y);
         springTransY.setStartDelay(startDelay);
         springTransY.addListener(new AnimatorListenerAdapter() {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SurfaceTransactionApplier.java b/quickstep/src/com/android/quickstep/util/SurfaceTransactionApplier.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/util/SurfaceTransactionApplier.java
rename to quickstep/src/com/android/quickstep/util/SurfaceTransactionApplier.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskCornerRadius.java b/quickstep/src/com/android/quickstep/util/TaskCornerRadius.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskCornerRadius.java
rename to quickstep/src/com/android/quickstep/util/TaskCornerRadius.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
similarity index 85%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java
rename to quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index c9ed498..903e87a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -15,6 +15,7 @@
  */
 package com.android.quickstep.util;
 
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.states.RotationHelper.deltaRotation;
 import static com.android.launcher3.touch.PagedOrientationHandler.MATRIX_POST_TRANSLATE;
 import static com.android.quickstep.util.RecentsOrientedState.postDisplayRotation;
@@ -31,7 +32,6 @@
 import android.util.IntProperty;
 
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.touch.PagedOrientationHandler;
@@ -72,6 +72,8 @@
     private final BaseActivityInterface mSizeStrategy;
 
     private final Rect mTaskRect = new Rect();
+    private float mOffsetY;
+    private boolean mDrawsBelowRecents;
     private final PointF mPivot = new PointF();
     private DeviceProfile mDp;
 
@@ -91,8 +93,8 @@
     // RecentsView properties
     public final AnimatedFloat recentsViewScale = new AnimatedFloat();
     public final AnimatedFloat fullScreenProgress = new AnimatedFloat();
+    public final AnimatedFloat recentsViewSecondaryTranslation = new AnimatedFloat();
     private final ScrollState mScrollState = new ScrollState();
-    private final int mPageSpacing;
 
     // Cached calculations
     private boolean mLayoutValid = false;
@@ -106,7 +108,6 @@
         mOrientationState.setGestureActive(true);
 
         mCurrentFullscreenParams = new FullscreenDrawParams(context);
-        mPageSpacing = context.getResources().getDimensionPixelSize(R.dimen.recents_page_spacing);
     }
 
     /**
@@ -129,8 +130,8 @@
     /**
      * @see com.android.quickstep.views.RecentsView#onConfigurationChanged(Configuration)
      */
-    public void setRecentsConfiguration(Configuration configuration) {
-        mOrientationState.setActivityConfiguration(configuration);
+    public void setRecentsRotation(int recentsRotation) {
+        mOrientationState.setRecentsRotation(recentsRotation);
         mLayoutValid = false;
     }
 
@@ -177,8 +178,16 @@
         }
     }
 
+    public void setOffsetY(float offsetY) {
+        mOffsetY = offsetY;
+    }
+
+    public void setDrawsBelowRecents(boolean drawsBelowRecents) {
+        mDrawsBelowRecents = drawsBelowRecents;
+    }
+
     /**
-     * Adds animation for all the components corresponding to transition from an app to overview
+     * Adds animation for all the components corresponding to transition from an app to overview.
      */
     public void addAppToOverviewAnim(PendingAnimation pa, TimeInterpolator interpolator) {
         pa.addFloat(fullScreenProgress, AnimatedFloat.VALUE, 1, 0, interpolator);
@@ -186,6 +195,14 @@
     }
 
     /**
+     * Adds animation for all the components corresponding to transition from overview to the app.
+     */
+    public void addOverviewToAppAnim(PendingAnimation pa, TimeInterpolator interpolator) {
+        pa.addFloat(fullScreenProgress, AnimatedFloat.VALUE, 0, 1, interpolator);
+        pa.addFloat(recentsViewScale, AnimatedFloat.VALUE, 1, getFullScreenScale(), interpolator);
+    }
+
+    /**
      * Returns the current clipped/visible window bounds in the window coordinate space
      */
     public RectF getCurrentCropRect() {
@@ -197,6 +214,15 @@
         return mTempRectF;
     }
 
+    /**
+     * Returns the current task bounds in the Launcher coordinate space.
+     */
+    public RectF getCurrentRect() {
+        RectF result = getCurrentCropRect();
+        mMatrix.mapRect(result);
+        return result;
+    }
+
     public RecentsOrientedState getOrientationState() {
         return mOrientationState;
     }
@@ -252,7 +278,8 @@
             int start = mOrientationState.getOrientationHandler()
                     .getPrimaryValue(mTaskRect.left, mTaskRect.top);
             mScrollState.screenCenter = start + mScrollState.scroll + mScrollState.halfPageSize;
-            mScrollState.updateInterpolation(start, mPageSpacing);
+            mScrollState.pageParentScale = recentsViewScale.value;
+            mScrollState.updateInterpolation(start);
             mCurveScale = TaskView.getCurveScaleForInterpolation(mScrollState.linearInterpolation);
         }
 
@@ -271,13 +298,15 @@
         mMatrix.postScale(scale, scale);
 
         // Apply TaskView matrix: translate, scale, scroll
-        mMatrix.postTranslate(mTaskRect.left, mTaskRect.top);
+        mMatrix.postTranslate(mTaskRect.left, mTaskRect.top + mOffsetY);
         mMatrix.postScale(mCurveScale, mCurveScale, taskWidth / 2, taskHeight / 2);
         mOrientationState.getOrientationHandler().set(
                 mMatrix, MATRIX_POST_TRANSLATE, mScrollState.scroll);
 
-        // Apply recensView matrix
+        // Apply RecentsView matrix
         mMatrix.postScale(recentsViewScale.value, recentsViewScale.value, mPivot.x, mPivot.y);
+        mOrientationState.getOrientationHandler().setSecondary(mMatrix, MATRIX_POST_TRANSLATE,
+                recentsViewSecondaryTranslation.value);
         applyWindowToHomeRotation(mMatrix);
 
         // Crop rect is the inverse of thumbnail matrix
@@ -295,6 +324,12 @@
         builder.withMatrix(mMatrix)
                 .withWindowCrop(mTmpCropRect)
                 .withCornerRadius(getCurrentCornerRadius());
+
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && params.getRecentsSurface() != null) {
+            // When relativeLayer = 0, it reverts the surfaces back to the original order.
+            builder.withRelativeLayerTo(params.getRecentsSurface(),
+                    mDrawsBelowRecents ? Integer.MIN_VALUE : 0);
+        }
     }
 
     /**
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TransformParams.java b/quickstep/src/com/android/quickstep/util/TransformParams.java
similarity index 90%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/util/TransformParams.java
rename to quickstep/src/com/android/quickstep/util/TransformParams.java
index 0135f74..756331d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TransformParams.java
+++ b/quickstep/src/com/android/quickstep/util/TransformParams.java
@@ -16,6 +16,7 @@
 package com.android.quickstep.util;
 
 import android.util.FloatProperty;
+import android.view.SurfaceControl;
 
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.Interpolators;
@@ -58,6 +59,7 @@
     private float mCornerRadius;
     private RemoteAnimationTargets mTargetSet;
     private SurfaceTransactionApplier mSyncTransactionApplier;
+    private SurfaceControl mRecentsSurface;
 
     private BuilderProxy mHomeBuilderProxy = BuilderProxy.ALWAYS_VISIBLE;
     private BuilderProxy mBaseBuilderProxy = BuilderProxy.ALWAYS_VISIBLE;
@@ -138,6 +140,8 @@
     public SurfaceParams[] createSurfaceParams(BuilderProxy proxy) {
         RemoteAnimationTargets targets = mTargetSet;
         SurfaceParams[] surfaceParams = new SurfaceParams[targets.unfilteredApps.length];
+        mRecentsSurface = getRecentsSurface(targets);
+
         for (int i = 0; i < targets.unfilteredApps.length; i++) {
             RemoteAnimationTargetCompat app = targets.unfilteredApps[i];
             SurfaceParams.Builder builder = new SurfaceParams.Builder(app.leash);
@@ -165,6 +169,20 @@
         return surfaceParams;
     }
 
+    private static SurfaceControl getRecentsSurface(RemoteAnimationTargets targets) {
+        for (int i = 0; i < targets.unfilteredApps.length; i++) {
+            RemoteAnimationTargetCompat app = targets.unfilteredApps[i];
+            if (app.mode == targets.targetMode) {
+                if (app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_RECENTS) {
+                    return app.leash.getSurfaceControl();
+                }
+            } else {
+                return app.leash.getSurfaceControl();
+            }
+        }
+        return null;
+    }
+
     // Pubic getters so outside packages can read the values.
 
     public float getProgress() {
@@ -179,6 +197,10 @@
         return mCornerRadius;
     }
 
+    public SurfaceControl getRecentsSurface() {
+        return mRecentsSurface;
+    }
+
     public RemoteAnimationTargets getTargetSet() {
         return mTargetSet;
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TriggerSwipeUpTouchTracker.java b/quickstep/src/com/android/quickstep/util/TriggerSwipeUpTouchTracker.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/util/TriggerSwipeUpTouchTracker.java
rename to quickstep/src/com/android/quickstep/util/TriggerSwipeUpTouchTracker.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/AllAppsEduView.java b/quickstep/src/com/android/quickstep/views/AllAppsEduView.java
similarity index 90%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/views/AllAppsEduView.java
rename to quickstep/src/com/android/quickstep/views/AllAppsEduView.java
index 0979c07..97a239c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/AllAppsEduView.java
+++ b/quickstep/src/com/android/quickstep/views/AllAppsEduView.java
@@ -16,6 +16,7 @@
 package com.android.quickstep.views;
 
 import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.anim.Interpolators.ACCEL;
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
@@ -42,8 +43,10 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.util.Themes;
@@ -51,6 +54,7 @@
 
 /**
  * View used to educate the user on how to access All Apps when in No Nav Button navigation mode.
+ * Consumes all touches until after the animation is completed and the view is removed.
  */
 public class AllAppsEduView extends AbstractFloatingView {
 
@@ -101,18 +105,23 @@
     }
 
     @Override
-    public void logActionCommand(int command) {
-        // TODO
-    }
-
-    @Override
     protected boolean isOfType(int type) {
         return (type & TYPE_ALL_APPS_EDU) != 0;
     }
 
     @Override
+    public boolean onBackPressed() {
+        return true;
+    }
+
+    @Override
+    public boolean canInterceptEventsInSystemGestureRegion() {
+        return true;
+    }
+
+    @Override
     public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
-        return mAnimation != null && mAnimation.isRunning();
+        return true;
     }
 
     private void playAnimation() {
@@ -139,7 +148,12 @@
         config.userControlled = false;
         AnimatorPlaybackController stateAnimationController =
                 mLauncher.getStateManager().createAnimationToNewWorkspace(ALL_APPS, config);
-        float maxAllAppsProgress = 0.15f;
+        float maxAllAppsProgress = mLauncher.getDeviceProfile().isLandscape ? 0.35f : 0.15f;
+
+        AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
+        PendingAnimation allAppsAlpha = new PendingAnimation(config.duration);
+        allAppsController.setAlphas(ALL_APPS, config, allAppsAlpha);
+        mAnimation.play(allAppsAlpha.buildAnim());
 
         ValueAnimator intro = ValueAnimator.ofFloat(0, 1f);
         intro.setInterpolator(LINEAR);
@@ -191,7 +205,8 @@
             @Override
             public void onAnimationEnd(Animator animation) {
                 mAnimation = null;
-                stateAnimationController.dispatchOnCancel();
+                // Handles cancelling the animation used to hint towards All Apps.
+                mLauncher.getStateManager().goToState(NORMAL, false);
                 handleClose(false);
             }
         });
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/ClearAllButton.java b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
similarity index 93%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/views/ClearAllButton.java
rename to quickstep/src/com/android/quickstep/views/ClearAllButton.java
index fd74357..0837300 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/ClearAllButton.java
+++ b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
@@ -45,12 +45,15 @@
     private float mVisibilityAlpha = 1;
 
     private boolean mIsRtl;
+    private final float mOriginalTranslationX, mOriginalTranslationY;
 
     private int mScrollOffset;
 
     public ClearAllButton(Context context, AttributeSet attrs) {
         super(context, attrs);
         mIsRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+        mOriginalTranslationX = getTranslationX();
+        mOriginalTranslationY = getTranslationY();
     }
 
     @Override
@@ -99,7 +102,8 @@
 
         float shift = Math.min(scrollState.scrollFromEdge, orientationSize);
         float translation = mIsRtl ? (mScrollOffset - shift) : (mScrollOffset + shift);
-        orientationHandler.setPrimaryAndResetSecondaryTranslate(this, translation);
+        orientationHandler.setPrimaryAndResetSecondaryTranslate(
+                this, translation, mOriginalTranslationX, mOriginalTranslationY);
         mScrollAlpha = 1 - shift / orientationSize;
         updateAlpha();
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java
rename to quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/IconView.java b/quickstep/src/com/android/quickstep/views/IconView.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/views/IconView.java
rename to quickstep/src/com/android/quickstep/views/IconView.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
similarity index 70%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
rename to quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 846b944..b338bd0 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -15,7 +15,6 @@
  */
 package com.android.quickstep.views;
 
-import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
@@ -31,7 +30,6 @@
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.os.Build;
-import android.os.UserHandle;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.Surface;
@@ -40,21 +38,14 @@
 import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.Hotseat;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.appprediction.PredictionUiStateManager;
-import com.android.launcher3.appprediction.PredictionUiStateManager.Client;
-import com.android.launcher3.model.AppLaunchTracker;
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.statemanager.StateManager.StateListener;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
-import com.android.launcher3.util.TraceHelper;
-import com.android.launcher3.views.ScrimView;
 import com.android.quickstep.LauncherActivityInterface;
 import com.android.quickstep.SysUINavigationMode;
-import com.android.quickstep.util.TransformParams;
+import com.android.quickstep.util.OverviewToHomeAnim;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.RecentsExtraCard;
-import com.android.systemui.shared.recents.model.Task;
 
 /**
  * {@link RecentsView} used in Launcher activity
@@ -63,8 +54,6 @@
 public class LauncherRecentsView extends RecentsView<BaseQuickstepLauncher>
         implements StateListener<LauncherState> {
 
-    private final TransformParams mTransformParams = new TransformParams();
-
     private RecentsExtraCard mRecentsExtraCardPlugin;
     private RecentsExtraViewContainer mRecentsExtraViewContainer;
     private PluginListener<RecentsExtraCard> mRecentsExtraCardPluginListener =
@@ -105,23 +94,14 @@
 
     @Override
     public void startHome() {
+        Runnable onReachedHome = () -> mActivity.getStateManager().goToState(NORMAL, false);
+        OverviewToHomeAnim overviewToHomeAnim = new OverviewToHomeAnim(mActivity, onReachedHome);
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             switchToScreenshot(null,
                     () -> finishRecentsAnimation(true /* toRecents */,
-                            () -> mActivity.getStateManager().goToState(NORMAL)));
+                            () -> overviewToHomeAnim.animateWithVelocity(0)));
         } else {
-            mActivity.getStateManager().goToState(NORMAL);
-        }
-    }
-
-    @Override
-    public void setTranslationY(float translationY) {
-        super.setTranslationY(translationY);
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            LauncherState state = mActivity.getStateManager().getState();
-            if (state == OVERVIEW || state == ALL_APPS) {
-                redrawLiveTile(false);
-            }
+            overviewToHomeAnim.animateWithVelocity(0);
         }
     }
 
@@ -147,29 +127,10 @@
         }
         anim.play(ObjectAnimator.ofFloat(
                 mActivity.getAllAppsController(), ALL_APPS_PROGRESS, allAppsProgressOffscreen));
-
-        ObjectAnimator dragHandleAnim = ObjectAnimator.ofInt(
-                mActivity.getScrimView(), ScrimView.DRAG_HANDLE_ALPHA, 0);
-        dragHandleAnim.setInterpolator(Interpolators.ACCEL_2);
-        anim.play(dragHandleAnim);
-
         return anim;
     }
 
     @Override
-    protected void onTaskLaunchAnimationUpdate(float progress, TaskView tv) {
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            if (tv.isRunningTask()) {
-                mTransformParams.setProgress(1 - progress)
-                        .setSyncTransactionApplier(mSyncTransactionApplier);
-                // TODO: Revisit live tiles
-            } else {
-                redrawLiveTile(true);
-            }
-        }
-    }
-
-    @Override
     protected void onTaskLaunchAnimationEnd(boolean success) {
         if (success) {
             mActivity.getStateManager().goToState(NORMAL, false /* animate */);
@@ -181,65 +142,10 @@
     }
 
     @Override
-    public void onTaskLaunched(Task task) {
-        UserHandle user =  UserHandle.of(task.key.userId);
-        AppLaunchTracker.INSTANCE.get(getContext()).onStartApp(task.getTopComponent(), user,
-                AppLaunchTracker.CONTAINER_OVERVIEW);
-    }
-
-    @Override
-    public boolean shouldUseMultiWindowTaskSizeStrategy() {
-        return TraceHelper.whitelistIpcs("isInMultiWindowMode", mActivity::isInMultiWindowMode);
-    }
-
-    @Override
-    public void scrollTo(int x, int y) {
-        super.scrollTo(x, y);
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile) {
-            redrawLiveTile(true);
-        }
-    }
-
-    @Override
-    public TransformParams getLiveTileParams(
-            boolean mightNeedToRefill) {
-        if (!mEnableDrawingLiveTile || mRecentsAnimationController == null
-                || mRecentsAnimationTargets == null) {
-            return null;
-        }
-        TaskView taskView = getRunningTaskView();
-        if (taskView != null) {
-            taskView.getThumbnail().getGlobalVisibleRect(mTempRect);
-            int offsetX = (int) (mTaskWidth * taskView.getScaleX() * getScaleX()
-                    - mTempRect.width());
-            int offsetY = (int) (mTaskHeight * taskView.getScaleY() * getScaleY()
-                    - mTempRect.height());
-            if (((mCurrentPage != 0) || mightNeedToRefill) && offsetX > 0) {
-                if (mTempRect.left - offsetX < 0) {
-                    mTempRect.left -= offsetX;
-                } else {
-                    mTempRect.right += offsetX;
-                }
-            }
-            if (mightNeedToRefill && offsetY > 0) {
-                mTempRect.top -= offsetY;
-            }
-            mTransformParams.setProgress(1f)
-                    .setTargetAlpha(taskView.getAlpha())
-                    .setSyncTransactionApplier(mSyncTransactionApplier)
-                    .setTargetSet(mRecentsAnimationTargets);
-        }
-        return mTransformParams;
-    }
-
-    @Override
     public void reset() {
         super.reset();
 
         setLayoutRotation(Surface.ROTATION_0, Surface.ROTATION_0);
-        // We are moving to home or some other UI with no recents. Switch back to the home client,
-        // the home predictions should have been updated when the activity was resumed.
-        PredictionUiStateManager.INSTANCE.get(getContext()).switchClient(Client.HOME);
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LiveTileOverlay.java b/quickstep/src/com/android/quickstep/views/LiveTileOverlay.java
similarity index 92%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/views/LiveTileOverlay.java
rename to quickstep/src/com/android/quickstep/views/LiveTileOverlay.java
index 30c9f77..c6c2d7e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LiveTileOverlay.java
+++ b/quickstep/src/com/android/quickstep/views/LiveTileOverlay.java
@@ -40,14 +40,13 @@
     public static final LiveTileOverlay INSTANCE = new LiveTileOverlay();
 
     private final Paint mPaint = new Paint();
+    private final RectF mCurrentRect = new RectF();
     private final Rect mBoundsRect = new Rect();
 
-    private RectF mCurrentRect;
     private float mCornerRadius;
     private Drawable mIcon;
     private Animator mIconAnimator;
 
-    private boolean mDrawEnabled = true;
     private float mIconAnimationProgress = 0f;
     private boolean mIsAttached;
 
@@ -58,7 +57,7 @@
     public void update(RectF currentRect, float cornerRadius) {
         invalidateSelf();
 
-        mCurrentRect = currentRect;
+        mCurrentRect.set(currentRect);
         mCornerRadius = cornerRadius;
 
         mCurrentRect.roundOut(mBoundsRect);
@@ -93,16 +92,9 @@
         return mIconAnimationProgress;
     }
 
-    public void setDrawEnabled(boolean drawEnabled) {
-        if (mDrawEnabled != drawEnabled) {
-            mDrawEnabled = drawEnabled;
-            invalidateSelf();
-        }
-    }
-
     @Override
     public void draw(Canvas canvas) {
-        if (mCurrentRect != null && mDrawEnabled) {
+        if (mCurrentRect != null) {
             canvas.drawRoundRect(mCurrentRect, mCornerRadius, mCornerRadius, mPaint);
             if (mIcon != null && mIconAnimationProgress > 0f) {
                 canvas.save();
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
similarity index 92%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java
rename to quickstep/src/com/android/quickstep/views/OverviewActionsView.java
index a2da398..8f60991 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
@@ -16,7 +16,6 @@
 
 package com.android.quickstep.views;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_SHARE;
 import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
 
@@ -53,7 +52,6 @@
 
     @IntDef(flag = true, value = {
             HIDDEN_UNSUPPORTED_NAVIGATION,
-            HIDDEN_DISABLED_FEATURE,
             HIDDEN_NON_ZERO_ROTATION,
             HIDDEN_NO_TASKS,
             HIDDEN_GESTURE_RUNNING,
@@ -62,20 +60,21 @@
     public @interface ActionsHiddenFlags { }
 
     public static final int HIDDEN_UNSUPPORTED_NAVIGATION = 1 << 0;
-    public static final int HIDDEN_DISABLED_FEATURE = 1 << 1;
-    public static final int HIDDEN_NON_ZERO_ROTATION = 1 << 2;
-    public static final int HIDDEN_NO_TASKS = 1 << 3;
-    public static final int HIDDEN_GESTURE_RUNNING = 1 << 4;
-    public static final int HIDDEN_NO_RECENTS = 1 << 5;
+    public static final int HIDDEN_NON_ZERO_ROTATION = 1 << 1;
+    public static final int HIDDEN_NO_TASKS = 1 << 2;
+    public static final int HIDDEN_GESTURE_RUNNING = 1 << 3;
+    public static final int HIDDEN_NO_RECENTS = 1 << 4;
 
     @IntDef(flag = true, value = {
             DISABLED_SCROLLING,
-            DISABLED_ROTATED})
+            DISABLED_ROTATED,
+            DISABLED_NO_THUMBNAIL})
     @Retention(RetentionPolicy.SOURCE)
     public @interface ActionsDisabledFlags { }
 
     public static final int DISABLED_SCROLLING = 1 << 0;
     public static final int DISABLED_ROTATED = 1 << 1;
+    public static final int DISABLED_NO_THUMBNAIL = 1 << 2;
 
     private static final int INDEX_CONTENT_ALPHA = 0;
     private static final int INDEX_VISIBILITY_ALPHA = 1;
@@ -103,6 +102,7 @@
     public OverviewActionsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr, 0);
         mMultiValueAlpha = new MultiValueAlpha(this, 4);
+        mMultiValueAlpha.setUpdateVisibility(true);
     }
 
     @Override
@@ -142,7 +142,6 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        updateHiddenFlags(HIDDEN_DISABLED_FEATURE, !ENABLE_OVERVIEW_ACTIONS.get());
         updateHiddenFlags(HIDDEN_UNSUPPORTED_NAVIGATION, !removeShelfFromOverview(getContext()));
     }
 
@@ -166,7 +165,6 @@
         }
         boolean isHidden = mHiddenFlags != 0;
         mMultiValueAlpha.getProperty(INDEX_HIDDEN_FLAGS_ALPHA).setValue(isHidden ? 0 : 1);
-        setVisibility(isHidden ? INVISIBLE : VISIBLE);
     }
 
     /**
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsExtraViewContainer.java b/quickstep/src/com/android/quickstep/views/RecentsExtraViewContainer.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsExtraViewContainer.java
rename to quickstep/src/com/android/quickstep/views/RecentsExtraViewContainer.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
similarity index 85%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
rename to quickstep/src/com/android/quickstep/views/RecentsView.java
index 7b24b03..982a7c4 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -22,9 +22,9 @@
 
 import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
 import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS;
-import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
+import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
 import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
 import static com.android.launcher3.Utilities.mapToRange;
 import static com.android.launcher3.Utilities.squaredHypot;
@@ -55,15 +55,14 @@
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
-import android.app.ActivityManager;
-import android.content.ComponentName;
+import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
-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.graphics.RectF;
 import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
@@ -80,6 +79,7 @@
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
+import android.view.TouchDelegate;
 import android.view.View;
 import android.view.ViewDebug;
 import android.view.ViewGroup;
@@ -94,6 +94,7 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Insettable;
 import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherState;
 import com.android.launcher3.PagedView;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
@@ -109,13 +110,12 @@
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.touch.PagedOrientationHandler.CurveProperties;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.DynamicResource;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.util.OverScroller;
+import com.android.launcher3.util.ResourceBasedOverride.Overrides;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.ViewPool;
 import com.android.quickstep.BaseActivityInterface;
@@ -124,6 +124,7 @@
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.RecentsModel.TaskVisualsChangeListener;
 import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.TaskOverlayFactory;
 import com.android.quickstep.TaskThumbnailCache;
 import com.android.quickstep.TaskUtils;
 import com.android.quickstep.ViewUtils;
@@ -131,13 +132,14 @@
 import com.android.quickstep.util.RecentsOrientedState;
 import com.android.quickstep.util.SplitScreenBounds;
 import com.android.quickstep.util.SurfaceTransactionApplier;
+import com.android.quickstep.util.TaskViewSimulator;
 import com.android.quickstep.util.TransformParams;
 import com.android.systemui.plugins.ResourceProvider;
 import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
 import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task.TaskKey;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.LauncherEventUtil;
 import com.android.systemui.shared.system.PackageManagerWrapper;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 
@@ -147,7 +149,7 @@
 /**
  * A list of recent tasks.
  */
-@TargetApi(Build.VERSION_CODES.P)
+@TargetApi(Build.VERSION_CODES.R)
 public abstract class RecentsView<T extends StatefulActivity> extends PagedView implements
         Insettable, TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback,
         InvariantDeviceProfile.OnIDPChangeListener, TaskVisualsChangeListener,
@@ -210,15 +212,51 @@
                 }
             };
 
+    public static final FloatProperty<RecentsView> TASK_SECONDARY_TRANSLATION =
+            new FloatProperty<RecentsView>("taskSecondaryTranslation") {
+                @Override
+                public void setValue(RecentsView recentsView, float v) {
+                    recentsView.setTaskViewsSecondaryTranslation(v);
+                }
+
+                @Override
+                public Float get(RecentsView recentsView) {
+                    return recentsView.mTaskViewsSecondaryTranslation;
+                }
+            };
+
+    /** Same as normal SCALE_PROPERTY, but also updates page offsets that depend on this scale. */
+    public static final FloatProperty<RecentsView> RECENTS_SCALE_PROPERTY =
+            new FloatProperty<RecentsView>("recentsScale") {
+                @Override
+                public void setValue(RecentsView view, float scale) {
+                    view.setScaleX(scale);
+                    view.setScaleY(scale);
+                    view.mLastComputedTaskPushOutDistance = null;
+                    view.updatePageOffsets();
+                    view.setTaskViewsSecondaryTranslation(view.mTaskViewsSecondaryTranslation);
+                }
+
+                @Override
+                public Float get(RecentsView view) {
+                    return view.getScaleX();
+                }
+            };
+
     protected RecentsOrientedState mOrientationState;
     protected final BaseActivityInterface mSizeStrategy;
     protected RecentsAnimationController mRecentsAnimationController;
-    protected RecentsAnimationTargets mRecentsAnimationTargets;
     protected SurfaceTransactionApplier mSyncTransactionApplier;
     protected int mTaskWidth;
     protected int mTaskHeight;
+    protected final TransformParams mLiveTileParams = new TransformParams();
+    protected final TaskViewSimulator mLiveTileTaskViewSimulator;
+    protected final Rect mLastComputedTaskSize = new Rect();
+    // How much a task that is directly offscreen will be pushed out due to RecentsView scale/pivot.
+    protected Float mLastComputedTaskPushOutDistance = null;
     protected boolean mEnableDrawingLiveTile = false;
     protected final Rect mTempRect = new Rect();
+    protected final RectF mTempRectF = new RectF();
     private final PointF mTempPointF = new PointF();
 
     private static final int DISMISS_TASK_DURATION = 300;
@@ -242,12 +280,15 @@
 
     private final ViewPool<TaskView> mTaskViewPool;
 
+    private final TaskOverlayFactory mTaskOverlayFactory;
+
     private boolean mDwbToastShown;
     protected boolean mDisallowScrollToClearAll;
     private boolean mOverlayEnabled;
     protected boolean mFreezeViewVisibility;
 
     private float mAdjacentPageOffset = 0;
+    private float mTaskViewsSecondaryTranslation = 0;
 
     /**
      * TODO: Call reloadIdNeeded in onTaskStackChanged.
@@ -377,7 +418,7 @@
                     mOrientationState.setMultiWindowMode(inMultiWindowMode);
                     setLayoutRotation(mOrientationState.getTouchRotation(),
                             mOrientationState.getDisplayRotation());
-                    rotateAllChildTasks();
+                    updateChildTaskOrientations();
                 }
                 if (!inMultiWindowMode && mOverviewStateEnabled) {
                     // TODO: Re-enable layout transitions for addition of the unpinned task
@@ -394,7 +435,8 @@
         mActivity = BaseActivity.fromContext(context);
         mOrientationState = new RecentsOrientedState(
                 context, mSizeStrategy, this::animateRecentsRotationInPlace);
-        mOrientationState.setActivityConfiguration(context.getResources().getConfiguration());
+        final int rotation = mActivity.getDisplay().getRotation();
+        mOrientationState.setRecentsRotation(rotation);
 
         mFastFlingVelocity = getResources()
                 .getDimensionPixelSize(R.dimen.recents_fast_fling_velocity);
@@ -428,8 +470,20 @@
         updateEmptyMessage();
         mOrientationHandler = mOrientationState.getOrientationHandler();
 
+        mTaskOverlayFactory = Overrides.getObject(
+                TaskOverlayFactory.class,
+                context.getApplicationContext(),
+                R.string.task_overlay_factory_class);
+
         // Initialize quickstep specific cache params here, as this is constructed only once
         mActivity.getViewCache().setCacheSize(R.layout.digital_wellbeing_toast, 5);
+
+        mLiveTileTaskViewSimulator = new TaskViewSimulator(getContext(), getSizeStrategy());
+        mLiveTileTaskViewSimulator.recentsViewScale.value = 1;
+        mLiveTileTaskViewSimulator.setLayoutRotation(getPagedViewOrientedState().getTouchRotation(),
+                getPagedViewOrientedState().getDisplayRotation());
+        mLiveTileTaskViewSimulator.setRecentsRotation(rotation);
+        mLiveTileTaskViewSimulator.setDrawsBelowRecents(true);
     }
 
     public OverScroller getScroller() {
@@ -514,6 +568,7 @@
         mActivity.addMultiWindowModeChangedListener(mMultiWindowModeChangedListener);
         ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
         mSyncTransactionApplier = new SurfaceTransactionApplier(this);
+        mLiveTileParams.setSyncTransactionApplier(mSyncTransactionApplier);
         RecentsModel.INSTANCE.get(getContext()).addThumbnailChangeListener(this);
         mIdp.addOnChangeListener(this);
         mIPinnedStackAnimationListener.setActivity(mActivity);
@@ -521,6 +576,7 @@
                 mIPinnedStackAnimationListener);
         mOrientationState.initListeners();
         SplitScreenBounds.INSTANCE.addOnChangeListener(this);
+        mTaskOverlayFactory.initListeners();
     }
 
     @Override
@@ -531,12 +587,14 @@
         mActivity.removeMultiWindowModeChangedListener(mMultiWindowModeChangedListener);
         ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener);
         mSyncTransactionApplier = null;
+        mLiveTileParams.setSyncTransactionApplier(null);
         RecentsModel.INSTANCE.get(getContext()).removeThumbnailChangeListener(this);
         mIdp.removeOnChangeListener(this);
         SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener(null);
         SplitScreenBounds.INSTANCE.removeOnChangeListener(this);
         mIPinnedStackAnimationListener.setActivity(null);
         mOrientationState.destroyListeners();
+        mTaskOverlayFactory.removeListeners();
     }
 
     @Override
@@ -612,9 +670,6 @@
     public void onDigitalWellbeingToastShown() {
         if (!mDwbToastShown) {
             mDwbToastShown = true;
-            mActivity.getUserEventDispatcher().logActionTip(
-                    LauncherEventUtil.VISIBLE,
-                    LauncherLogProto.TipType.DWB_TOAST);
         }
     }
 
@@ -649,6 +704,16 @@
     @Override
     public boolean onTouchEvent(MotionEvent ev) {
         super.onTouchEvent(ev);
+
+        TaskView taskView = getCurrentPageTaskView();
+        if (taskView != null) {
+            TouchDelegate mChildTouchDelegate = taskView.getIconTouchDelegate(ev);
+            if (mChildTouchDelegate != null && mChildTouchDelegate.onTouchEvent(ev)) {
+                // Keep consuming events to pass to delegate
+                return true;
+            }
+        }
+
         final int x = (int) ev.getX();
         final int y = (int) ev.getY();
         switch (ev.getAction()) {
@@ -807,6 +872,14 @@
                 taskView.setModalness(mTaskModalness);
             }
         }
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            // Since we reuse the same mLiveTileTaskViewSimulator in the RecentsView, we need
+            // to reset the params after it settles in Overview from swipe up so that we don't
+            // render with obsolete param values.
+            mLiveTileTaskViewSimulator.fullScreenProgress.value = 0;
+            mLiveTileTaskViewSimulator.recentsViewScale.value = 1;
+            mLiveTileTaskViewSimulator.setOffsetY(0);
+        }
         if (mRunningTaskTileHidden) {
             setRunningTaskHidden(mRunningTaskTileHidden);
         }
@@ -848,6 +921,7 @@
     public void setInsets(Rect insets) {
         mInsets.set(insets);
         resetPaddingFromTaskSize();
+        mLiveTileTaskViewSimulator.setDp(mActivity.getDeviceProfile());
     }
 
     private void resetPaddingFromTaskSize() {
@@ -865,6 +939,7 @@
     public void getTaskSize(Rect outRect) {
         mSizeStrategy.calculateTaskSize(mActivity, mActivity.getDeviceProfile(), outRect,
                 mOrientationHandler);
+        mLastComputedTaskSize.set(outRect);
     }
 
     /** Gets the task size for modal state. */
@@ -889,6 +964,12 @@
 
         // Update the high res thumbnail loader state
         mModel.getThumbnailCache().getHighResLoadingState().setFlingingFast(isFlingingFast);
+
+        mLiveTileTaskViewSimulator.setScroll(getScrollOffset());
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile
+                && mLiveTileParams.getTargetSet() != null) {
+            redrawLiveTile();
+        }
         return scrolling;
     }
 
@@ -906,8 +987,8 @@
         final int pageCount = getPageCount();
         for (int i = 0; i < pageCount; i++) {
             View page = getPageAt(i);
-            mScrollState.updateInterpolation(mOrientationHandler.getChildStartWithTranslation(page),
-                    mPageSpacing);
+            mScrollState.updateInterpolation(
+                    mOrientationHandler.getChildStartWithTranslation(page));
             ((PageCallbacks) page).onPageScroll(mScrollState);
         }
     }
@@ -996,7 +1077,7 @@
         mTaskListChangeId = -1;
 
         mRecentsAnimationController = null;
-        mRecentsAnimationTargets = null;
+        mLiveTileParams.setTargetSet(null);
 
         unloadVisibleTaskData();
         setCurrentPage(0);
@@ -1041,13 +1122,13 @@
     /**
      * Called when a gesture from an app is starting.
      */
-    public void onGestureAnimationStart(int runningTaskId) {
+    public void onGestureAnimationStart(RunningTaskInfo runningTaskInfo) {
         // This needs to be called before the other states are set since it can create the task view
         if (mOrientationState.setGestureActive(true)) {
             updateOrientationHandler();
         }
 
-        showCurrentTask(runningTaskId);
+        showCurrentTask(runningTaskInfo);
         setEnableFreeScroll(false);
         setEnableDrawingLiveTile(false);
         setRunningTaskHidden(true);
@@ -1078,7 +1159,7 @@
         pa.addListener(AnimationSuccessListener.forRunnable(() -> {
             setLayoutRotation(newRotation, mOrientationState.getDisplayRotation());
             mActivity.getDragLayer().recreateControllers();
-            rotateAllChildTasks();
+            updateChildTaskOrientations();
             setRecentsChangedOrientation(false).start();
         }));
         pa.start();
@@ -1099,7 +1180,7 @@
     }
 
 
-    private void rotateAllChildTasks() {
+    private void updateChildTaskOrientations() {
         for (int i = 0; i < getTaskViewCount(); i++) {
             getTaskViewAt(i).setOrientationState(mOrientationState);
         }
@@ -1125,10 +1206,10 @@
     }
 
     /**
-     * Returns true if we should add a dummy taskView for the running task id
+     * Returns true if we should add a stub taskView for the running task id
      */
-    protected boolean shouldAddDummyTaskView(int runningTaskId) {
-        return getTaskView(runningTaskId) == null;
+    protected boolean shouldAddStubTaskView(RunningTaskInfo runningTaskInfo) {
+        return runningTaskInfo != null && getTaskView(runningTaskInfo.taskId) == null;
     }
 
     /**
@@ -1137,8 +1218,8 @@
      * All subsequent calls to reload will keep the task as the first item until {@link #reset()}
      * is called.  Also scrolls the view to this task.
      */
-    public void showCurrentTask(int runningTaskId) {
-        if (shouldAddDummyTaskView(runningTaskId)) {
+    public void showCurrentTask(RunningTaskInfo runningTaskInfo) {
+        if (shouldAddStubTaskView(runningTaskInfo)) {
             boolean wasEmpty = getChildCount() == 0;
             // Add an empty view for now until the task plan is loaded and applied
             final TaskView taskView = mTaskViewPool.getView();
@@ -1148,10 +1229,7 @@
             }
             // The temporary running task is only used for the duration between the start of the
             // gesture and the task list is loaded and applied
-            mTmpRunningTask = new Task(new Task.TaskKey(runningTaskId, 0, new Intent(),
-                    new ComponentName(getContext(), getClass()), 0, 0), null, null, "", "", 0, 0,
-                    false, true, false, false, new ActivityManager.TaskDescription(), 0,
-                    new ComponentName("", ""), false);
+            mTmpRunningTask = Task.from(new TaskKey(runningTaskInfo), runningTaskInfo, false);
             taskView.bind(mTmpRunningTask, mOrientationState);
 
             // Measure and layout immediately so that the scroll values is updated instantly
@@ -1162,7 +1240,7 @@
         }
 
         boolean runningTaskTileHidden = mRunningTaskTileHidden;
-        setCurrentTask(runningTaskId);
+        setCurrentTask(runningTaskInfo == null ? -1 : runningTaskInfo.taskId);
         setCurrentPage(getRunningTaskIndex());
         setRunningTaskViewShowScreenshot(false);
         setRunningTaskHidden(runningTaskTileHidden);
@@ -1332,10 +1410,14 @@
         /**
          * Updates linearInterpolation for the provided child position
          */
-        public void updateInterpolation(float childStart, int pageSpacing) {
-            float pageCenter = childStart + halfPageSize;
+        public void updateInterpolation(float childStart) {
+            float scaledHalfPageSize = halfPageSize / pageParentScale;
+            float pageCenter = childStart + scaledHalfPageSize;
             float distanceFromScreenCenter = screenCenter - pageCenter;
-            float distanceToReachEdge = halfScreenSize + halfPageSize + pageSpacing;
+            // How far the page has to move from the center to be offscreen, taking into account
+            // the EDGE_SCALE_DOWN_FACTOR that will be applied at that position.
+            float distanceToReachEdge = halfScreenSize
+                    + scaledHalfPageSize * (1 - TaskView.EDGE_SCALE_DOWN_FACTOR);
             linearInterpolation = Math.min(1,
                     Math.abs(distanceFromScreenCenter) / distanceToReachEdge);
         }
@@ -1358,7 +1440,7 @@
         FloatProperty<View> secondaryViewTranslate =
             mOrientationHandler.getSecondaryViewTranslate();
         int secondaryTaskDimension = mOrientationHandler.getSecondaryDimension(taskView);
-        int verticalFactor = mOrientationHandler.getTaskDismissDirectionFactor();
+        int verticalFactor = mOrientationHandler.getSecondaryTranslationDirectionFactor();
 
         ResourceProvider rp = DynamicResource.provider(mActivity);
         SpringProperty sp = new SpringProperty(SpringProperty.FLAG_CAN_SPRING_ON_START)
@@ -1373,8 +1455,6 @@
         if (taskView.getTask() != null) {
             ActivityManagerWrapper.getInstance().removeTask(taskView.getTask().key.id);
             ComponentKey compKey = TaskUtils.getLaunchComponentKeyForTask(taskView.getTask().key);
-            mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
-                    endState.logAction, Direction.UP, index, compKey);
             mActivity.getStatsLogManager().logger().withItemInfo(taskView.getItemInfo())
                     .log(LAUNCHER_TASK_DISMISS_SWIPE_UP);
         }
@@ -1450,6 +1530,13 @@
             anim.addOnFrameCallback(this::updateCurveProperties);
         }
 
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && getRunningTaskView() == taskView) {
+            anim.addOnFrameCallback(() -> {
+                mLiveTileTaskViewSimulator.setOffsetY(taskView.getTranslationY());
+                redrawLiveTile();
+            });
+        }
+
         // Add a tiny bit of translation Z, so that it draws on top of other views
         if (animateTaskView) {
             taskView.setTranslationZ(0.1f);
@@ -1652,15 +1739,26 @@
         super.setVisibility(visibility);
         if (mActionsView != null) {
             mActionsView.updateHiddenFlags(HIDDEN_NO_RECENTS, visibility != VISIBLE);
+            if (visibility != VISIBLE) {
+                mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, false);
+            }
         }
     }
 
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
-        if (mOrientationState.setActivityConfiguration(newConfig)) {
+        final int rotation = mActivity.getDisplay().getRotation();
+        if (mOrientationState.setRecentsRotation(rotation)) {
             updateOrientationHandler();
         }
+        mLiveTileTaskViewSimulator.setRecentsRotation(rotation);
+        // If overview is in modal state when rotate, reset it to overview state without running
+        // animation.
+        if (mActivity.isInState(OVERVIEW_MODAL_TASK)) {
+            mActivity.getStateManager().goToState(LauncherState.OVERVIEW, false);
+            resetModalVisuals();
+        }
     }
 
     public void setLayoutRotation(int touchRotation, int displayRotation) {
@@ -1680,14 +1778,17 @@
                 : View.LAYOUT_DIRECTION_RTL);
         mClearAllButton.setRotation(mOrientationHandler.getDegreesRotated());
         mActivity.getDragLayer().recreateControllers();
-        boolean isInLandscape = mOrientationState.getTouchRotation() != 0
+        boolean isInLandscape = mOrientationState.getTouchRotation() != ROTATION_0
                 || mOrientationState.getRecentsActivityRotation() != ROTATION_0;
         mActionsView.updateHiddenFlags(HIDDEN_NON_ZERO_ROTATION,
                 !mOrientationState.canRecentsActivityRotate() && isInLandscape);
+        updateChildTaskOrientations();
         resetPaddingFromTaskSize();
         requestLayout();
         // Reapply the current page to update page scrolls.
         setCurrentPage(mCurrentPage);
+        mLiveTileTaskViewSimulator.setLayoutRotation(getPagedViewOrientedState().getTouchRotation(),
+                getPagedViewOrientedState().getDisplayRotation());
     }
 
     public RecentsOrientedState getPagedViewOrientedState() {
@@ -1769,14 +1870,15 @@
         setPivotX(mTempPointF.x);
         setPivotY(mTempPointF.y);
         setTaskModalness(mTaskModalness);
+        mLastComputedTaskPushOutDistance = null;
         updatePageOffsets();
         setImportantForAccessibility(isModal() ? IMPORTANT_FOR_ACCESSIBILITY_NO
                 : IMPORTANT_FOR_ACCESSIBILITY_AUTO);
     }
 
     private void updatePageOffsets() {
-        float offset = mAdjacentPageOffset * getWidth();
-        float modalOffset = ACCEL_0_75.getInterpolation(mTaskModalness) * getWidth();
+        float offset = mAdjacentPageOffset;
+        float modalOffset = ACCEL_0_75.getInterpolation(mTaskModalness);
         if (mIsRtl) {
             offset = -offset;
             modalOffset = -modalOffset;
@@ -1785,19 +1887,100 @@
 
         TaskView runningTask = mRunningTaskId == -1 || !mRunningTaskTileHidden
                 ? null : getTaskView(mRunningTaskId);
-        int midPoint = runningTask == null ? -1 : indexOfChild(runningTask);
-        int currentPage = getCurrentPage();
+        int midpoint = runningTask == null ? -1 : indexOfChild(runningTask);
+        int modalMidpoint = getCurrentPage();
+
+        float midpointOffsetSize = 0;
+        float leftOffsetSize = midpoint - 1 >= 0
+                ? -getOffsetSize(midpoint - 1, midpoint, offset)
+                : 0;
+        float rightOffsetSize = midpoint + 1 < count
+                ? getOffsetSize(midpoint + 1, midpoint, offset)
+                : 0;
+
+        float modalMidpointOffsetSize = 0;
+        float modalLeftOffsetSize = modalMidpoint - 1 >= 0
+                ? -getOffsetSize(modalMidpoint - 1, modalMidpoint, modalOffset)
+                : 0;
+        float modalRightOffsetSize = modalMidpoint + 1 < count
+                ? getOffsetSize(modalMidpoint + 1, modalMidpoint, modalOffset)
+                : 0;
 
         for (int i = 0; i < count; i++) {
-            float translation = i == midPoint ? 0 : (i < midPoint ? -offset : offset);
-            float modalTranslation =
-                    i == currentPage ? 0 : (i < currentPage ? -modalOffset : modalOffset);
-            getChildAt(i).setTranslationX(translation + modalTranslation);
+            float translation = i == midpoint
+                    ? midpointOffsetSize
+                    : i < midpoint
+                            ? leftOffsetSize
+                            : rightOffsetSize;
+            float modalTranslation = i == modalMidpoint
+                    ? modalMidpointOffsetSize
+                    : i < modalMidpoint
+                            ? modalLeftOffsetSize
+                            : modalRightOffsetSize;
+            float totalTranslation = translation + modalTranslation;
+            mOrientationHandler.getPrimaryViewTranslate().set(getChildAt(i),
+                    totalTranslation * mOrientationHandler.getPrimaryTranslationDirectionFactor());
         }
         updateCurveProperties();
     }
 
     /**
+     * Computes the distance to offset the given child such that it is completely offscreen when
+     * translating away from the given midpoint.
+     * @param offsetProgress From 0 to 1 where 0 means no offset and 1 means offset offscreen.
+     */
+    private float getOffsetSize(int childIndex, int midpointIndex, float offsetProgress) {
+        if (offsetProgress == 0) {
+            // Don't bother calculating everything below if we won't offset anyway.
+            return 0;
+        }
+        // First, get the position of the task relative to the midpoint. If there is no midpoint
+        // then we just use the normal (centered) task position.
+        mTempRectF.set(mLastComputedTaskSize);
+        RectF taskPosition = mTempRectF;
+        float desiredLeft = getWidth();
+        float distanceToOffscreen = desiredLeft - taskPosition.left;
+        // Used to calculate the scale of the task view based on its new offset.
+        float centerToOffscreenProgress = Math.abs(offsetProgress);
+        if (midpointIndex > -1) {
+            // When there is a midpoint reference task, adjacent tasks have less distance to travel
+            // to reach offscreen. Offset the task position to the task's starting point.
+            View child = getChildAt(childIndex);
+            View midpointChild = getChildAt(midpointIndex);
+            int distanceFromMidpoint = Math.abs(mOrientationHandler.getChildStart(child)
+                    - mOrientationHandler.getChildStart(midpointChild)
+                    + getDisplacementFromScreenCenter(midpointIndex));
+            taskPosition.offset(distanceFromMidpoint, 0);
+            centerToOffscreenProgress = Utilities.mapRange(centerToOffscreenProgress,
+                    distanceFromMidpoint / distanceToOffscreen, 1);
+        }
+        // Find the task's scale based on its offscreen progress, then see how far it still needs to
+        // move to be completely offscreen.
+        Utilities.scaleRectFAboutCenter(taskPosition,
+                TaskView.getCurveScaleForInterpolation(centerToOffscreenProgress));
+        distanceToOffscreen = desiredLeft - taskPosition.left;
+        // Finally, we need to account for RecentsView scale, because it moves tasks based on its
+        // pivot. To do this, we move the task position to where it would be offscreen at scale = 1
+        // (computed above), then we apply the scale via getMatrix() to determine how much that
+        // moves the task from its desired position, and adjust the computed distance accordingly.
+        if (mLastComputedTaskPushOutDistance == null) {
+            taskPosition.offsetTo(desiredLeft, 0);
+            getMatrix().mapRect(taskPosition);
+            mLastComputedTaskPushOutDistance = (taskPosition.left - desiredLeft) / getScaleX();
+        }
+        distanceToOffscreen -= mLastComputedTaskPushOutDistance;
+        return distanceToOffscreen * offsetProgress;
+    }
+
+    private void setTaskViewsSecondaryTranslation(float translation) {
+        mTaskViewsSecondaryTranslation = translation;
+        for (int i = 0; i < getTaskViewCount(); i++) {
+            TaskView task = getTaskViewAt(i);
+            mOrientationHandler.getSecondaryViewTranslate().set(task, translation / getScaleY());
+        }
+    }
+
+    /**
      * TODO: Do not assume motion across X axis for adjacent page
      */
     public float getPageOffsetScale() {
@@ -1895,7 +2078,7 @@
         float toScale = getMaxScaleForFullScreen();
         if (launchingCenterTask) {
             RecentsView recentsView = tv.getRecentsView();
-            anim.play(ObjectAnimator.ofFloat(recentsView, SCALE_PROPERTY, toScale));
+            anim.play(ObjectAnimator.ofFloat(recentsView, RECENTS_SCALE_PROPERTY, toScale));
             anim.play(ObjectAnimator.ofFloat(recentsView, FULLSCREEN_PROGRESS, 1));
         } else {
             // We are launching an adjacent task, so parallax the center and other adjacent task.
@@ -1945,8 +2128,6 @@
                             ? targetSysUiFlags
                             : 0);
 
-            onTaskLaunchAnimationUpdate(animator.getAnimatedFraction(), tv);
-
             // Passing the threshold from taskview to fullscreen app will vibrate
             final boolean passed = animator.getAnimatedFraction() >=
                     SUCCESS_TRANSITION_PROGRESS;
@@ -1970,6 +2151,10 @@
 
         mPendingAnimation = new PendingAnimation(duration);
         mPendingAnimation.add(anim);
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            mLiveTileTaskViewSimulator.addOverviewToAppAnim(mPendingAnimation, interpolator);
+            mPendingAnimation.addOnFrameCallback(this::redrawLiveTile);
+        }
         mPendingAnimation.addEndListener((endState) -> {
             if (endState.isSuccess) {
                 Consumer<Boolean> onLaunchResult = (result) -> {
@@ -1981,9 +2166,6 @@
                 tv.launchTask(false, onLaunchResult, getHandler());
                 Task task = tv.getTask();
                 if (task != null) {
-                    mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
-                            endState.logAction, Direction.DOWN, indexOfChild(tv),
-                            TaskUtils.getLaunchComponentKeyForTask(task.key));
                     mActivity.getStatsLogManager().logger().withItemInfo(tv.getItemInfo())
                             .log(LAUNCHER_TASK_LAUNCH_SWIPE_DOWN);
                 }
@@ -1995,22 +2177,12 @@
         return mPendingAnimation;
     }
 
-    protected void onTaskLaunchAnimationUpdate(float progress, TaskView tv) {
-    }
-
-    public abstract boolean shouldUseMultiWindowTaskSizeStrategy();
-
     protected void onTaskLaunchAnimationEnd(boolean success) {
         if (success) {
             resetTaskVisuals();
         }
     }
 
-    /**
-     * Called when task activity is launched
-     */
-    public void onTaskLaunched(Task task){ }
-
     @Override
     protected void notifyPageSwitchListener(int prevPage) {
         super.notifyPageSwitchListener(prevPage);
@@ -2071,13 +2243,23 @@
         mEnableDrawingLiveTile = enableDrawingLiveTile;
     }
 
-    public void redrawLiveTile(boolean mightNeedToRefill) { }
+    public void redrawLiveTile() {
+        mLiveTileTaskViewSimulator.apply(mLiveTileParams);
+    }
+
+    public TaskViewSimulator getLiveTileTaskViewSimulator() {
+        return mLiveTileTaskViewSimulator;
+    }
 
     // TODO: To be removed in a follow up CL
     public void setRecentsAnimationTargets(RecentsAnimationController recentsAnimationController,
             RecentsAnimationTargets recentsAnimationTargets) {
         mRecentsAnimationController = recentsAnimationController;
-        mRecentsAnimationTargets = recentsAnimationTargets;
+        if (recentsAnimationTargets != null) {
+            mLiveTileTaskViewSimulator.setPreview(
+                    recentsAnimationTargets.apps[recentsAnimationTargets.apps.length - 1]);
+            mLiveTileParams.setTargetSet(recentsAnimationTargets);
+        }
     }
 
     public void setLiveTileOverlayAttached(boolean liveTileOverlayAttached) {
@@ -2101,13 +2283,13 @@
         mRecentsAnimationController.finish(toRecents, () -> {
             if (onFinishComplete != null) {
                 onFinishComplete.run();
-                // After we finish the recents animation, the current task id should be correctly
-                // reset so that when the task is launched from Overview later, it goes through the
-                // flow of starting a new task instead of finishing recents animation to app. A
-                // typical example of this is (1) user swipes up from app to Overview (2) user
-                // taps on QSB (3) user goes back to Overview and launch the most recent task.
-                setCurrentTask(-1);
             }
+            // After we finish the recents animation, the current task id should be correctly
+            // reset so that when the task is launched from Overview later, it goes through the
+            // flow of starting a new task instead of finishing recents animation to app. A
+            // typical example of this is (1) user swipes up from app to Overview (2) user
+            // taps on QSB (3) user goes back to Overview and launch the most recent task.
+            setCurrentTask(-1);
         });
     }
 
@@ -2181,7 +2363,14 @@
         if (pageIndex == -1) {
             return 0;
         }
-        return getScrollForPage(pageIndex) - mOrientationHandler.getPrimaryScroll(this);
+        // Unbound the scroll (due to overscroll) if the adjacent tasks are offset away from it.
+        // This allows the page to move freely, given there's no visual indication why it shouldn't.
+        int boundedScroll = mOrientationHandler.getPrimaryScroll(this);
+        int unboundedScroll = getUnboundedScroll();
+        float unboundedProgress = mAdjacentPageOffset;
+        int scroll = Math.round(unboundedScroll * unboundedProgress
+                + boundedScroll * (1 - unboundedProgress));
+        return getScrollForPage(pageIndex) - scroll;
     }
 
     public Consumer<MotionEvent> getEventDispatcher(float navbarRotation) {
@@ -2213,11 +2402,6 @@
         };
     }
 
-    public TransformParams getLiveTileParams(
-            boolean mightNeedToRefill) {
-        return null;
-    }
-
     private void updateEnabledOverlays() {
         int overlayEnabledPage = mOverlayEnabled ? getNextPage() : -1;
         int taskCount = getTaskViewCount();
@@ -2233,7 +2417,19 @@
         }
     }
 
-    /** If it's in the live tile mode, switch the running task into screenshot mode. */
+    /**
+     * Switch the current running task view to static snapshot mode,
+     * capturing the snapshot at the same time.
+     */
+    public void switchToScreenshot(Runnable onFinishRunnable) {
+        switchToScreenshot(mRunningTaskId == -1 ? null
+                : mRecentsAnimationController.screenshotTask(mRunningTaskId), onFinishRunnable);
+    }
+
+    /**
+     * Switch the current running task view to static snapshot mode, using the
+     * provided thumbnail data as the snapshot.
+     */
     public void switchToScreenshot(ThumbnailData thumbnailData, Runnable onFinishRunnable) {
         TaskView taskView = getRunningTaskView();
         if (taskView != null) {
@@ -2283,6 +2479,10 @@
      */
     public void setModalStateEnabled(boolean isModalState) { }
 
+    public TaskOverlayFactory getTaskOverlayFactory() {
+        return mTaskOverlayFactory;
+    }
+
     public BaseActivityInterface getSizeStrategy() {
         return mSizeStrategy;
     }
diff --git a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
index 19e278b..e6613eb 100644
--- a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
+++ b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
@@ -16,9 +16,7 @@
 package com.android.quickstep.views;
 
 import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA;
-import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.LauncherState.QUICK_SWITCH;
 import static com.android.launcher3.anim.Interpolators.ACCEL;
 import static com.android.launcher3.anim.Interpolators.ACCEL_2;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
@@ -38,13 +36,10 @@
 
 import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.uioverrides.states.OverviewState;
-import com.android.launcher3.util.OnboardingPrefs;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.ScrimView;
 import com.android.quickstep.SysUINavigationMode;
@@ -77,15 +72,11 @@
     private final float mRadius;
     private final int mMaxScrimAlpha;
     private final Paint mPaint;
-    private final OnboardingPrefs mOnboardingPrefs;
 
     // Mid point where the alpha changes
     private int mMidAlpha;
     private float mMidProgress;
 
-    // The progress at which the drag handle starts moving up with the shelf.
-    private float mDragHandleProgress;
-
     private Interpolator mBeforeMidProgressColorInterpolator = ACCEL;
     private Interpolator mAfterMidProgressColorInterpolator = ACCEL;
 
@@ -103,7 +94,6 @@
     private boolean mRemainingScreenPathValid = false;
 
     private Mode mSysUINavigationMode;
-    private boolean mIsTwoZoneSwipeModel;
 
     public ShelfScrimView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -112,7 +102,6 @@
         mEndAlpha = Color.alpha(mEndScrim);
         mRadius = BOTTOM_CORNER_RADIUS_RATIO * Themes.getDialogCornerRadius(context);
         mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-        mOnboardingPrefs = mLauncher.getOnboardingPrefs();
 
         // Just assume the easiest UI for now, until we have the proper layout information.
         mDrawingFlatColor = true;
@@ -145,11 +134,9 @@
             // Show the shelf more quickly before reaching overview progress.
             mBeforeMidProgressColorInterpolator = ACCEL_2;
             mAfterMidProgressColorInterpolator = ACCEL;
-            mIsTwoZoneSwipeModel = FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get();
         } else {
             mBeforeMidProgressColorInterpolator = ACCEL;
             mAfterMidProgressColorInterpolator = Interpolators.clampToProgress(ACCEL, 0.5f, 1f);
-            mIsTwoZoneSwipeModel = false;
         }
     }
 
@@ -164,9 +151,7 @@
 
             Context context = getContext();
             if ((OVERVIEW.getVisibleElements(mLauncher) & ALL_APPS_HEADER_EXTRA) == 0) {
-                mDragHandleProgress = 1;
-                if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()
-                        && SysUINavigationMode.removeShelfFromOverview(context)) {
+                if (SysUINavigationMode.removeShelfFromOverview(context)) {
                     // Fade in all apps background quickly to distinguish from swiping from nav bar.
                     mMidAlpha = Themes.getAttrInteger(context, R.attr.allAppsInterimScrimAlpha);
                     mMidProgress = OverviewState.getDefaultVerticalProgress(mLauncher);
@@ -182,29 +167,22 @@
                         + hotseatPadding.bottom + hotseatPadding.top;
                 float dragHandleTop =
                         Math.min(hotseatSize, LayoutUtils.getDefaultSwipeHeight(context, dp));
-                mDragHandleProgress =  1 - (dragHandleTop / mShiftRange);
             }
-            mTopOffset = dp.getInsets().top - mDragHandleSize.y;
+            mTopOffset = dp.getInsets().top;
             mShelfTopAtThreshold = mShiftRange * SCRIM_CATCHUP_THRESHOLD + mTopOffset;
         }
         updateColors();
         updateSysUiColors();
-        updateDragHandleAlpha();
         invalidate();
     }
 
     @Override
     public void updateColors() {
         super.updateColors();
-        mDragHandleOffset = 0;
         if (mDrawingFlatColor) {
             return;
         }
 
-        if (mProgress < mDragHandleProgress) {
-            mDragHandleOffset = mShiftRange * (mDragHandleProgress - mProgress);
-        }
-
         if (mProgress >= SCRIM_CATCHUP_THRESHOLD) {
             mShelfTop = mShiftRange * mProgress + mTopOffset;
         } else {
@@ -215,13 +193,6 @@
         if (mProgress >= 1) {
             mRemainingScreenColor = 0;
             mShelfColor = 0;
-            LauncherState state = mLauncher.getStateManager().getState();
-            if (mSysUINavigationMode == Mode.NO_BUTTON
-                    && (state == BACKGROUND_APP || state == QUICK_SWITCH)
-                    && mLauncher.getShelfPeekAnim().isPeeking()) {
-                // Show the shelf background when peeking during swipe up.
-                mShelfColor = setColorAlphaBound(mEndScrim, mMidAlpha);
-            }
         } else if (mProgress >= mMidProgress) {
             mRemainingScreenColor = 0;
 
@@ -259,19 +230,7 @@
     }
 
     @Override
-    protected boolean shouldDragHandleBeVisible() {
-        boolean needsAllAppsEdu = mIsTwoZoneSwipeModel
-                && !mOnboardingPrefs.hasReachedMaxCount(OnboardingPrefs.ALL_APPS_COUNT);
-        return needsAllAppsEdu || super.shouldDragHandleBeVisible();
-    }
-
-    @Override
     protected void onDraw(Canvas canvas) {
-        drawBackground(canvas);
-        drawDragHandle(canvas);
-    }
-
-    private void drawBackground(Canvas canvas) {
         if (mDrawingFlatColor) {
             if (mCurrentFlatColor != 0) {
                 canvas.drawColor(mCurrentFlatColor);
@@ -311,9 +270,4 @@
         mPaint.setColor(mShelfColor);
         canvas.drawRoundRect(0, mShelfTop, width, height + mRadius, mRadius, mRadius, mPaint);
     }
-
-    @Override
-    public float getVisualTop() {
-        return mShelfTop;
-    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
similarity index 94%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java
rename to quickstep/src/com/android/quickstep/views/TaskMenuView.java
index ef66b7a..656d59e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -133,11 +133,6 @@
     }
 
     @Override
-    public void logActionCommand(int command) {
-        // TODO
-    }
-
-    @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
 
@@ -169,7 +164,9 @@
         }
         if (mIsOpen) {
             mOptionLayout.removeAllViews();
-            populateAndLayoutMenu();
+            if (!populateAndLayoutMenu()) {
+                close(false);
+            }
         }
     }
 
@@ -186,14 +183,22 @@
         }
         mActivity.getDragLayer().addView(this);
         mTaskView = taskView;
-        populateAndLayoutMenu();
+        if (!populateAndLayoutMenu()) {
+            return false;
+        }
         post(this::animateOpen);
         return true;
     }
 
-    private void populateAndLayoutMenu() {
+    /** @return true if successfully able to populate task view menu, false otherwise */
+    private boolean populateAndLayoutMenu() {
+        if (mTaskView.getTask().icon == null) {
+            // Icon may not be loaded
+            return false;
+        }
         addMenuOptions(mTaskView);
         orientAroundTaskView(mTaskView);
+        return true;
     }
 
     private void addMenuOptions(TaskView taskView) {
@@ -240,8 +245,10 @@
         setLayoutParams(params);
         setScaleX(taskView.getScaleX());
         setScaleY(taskView.getScaleY());
+        boolean canActivityRotate = taskView.getRecentsView()
+            .mOrientationState.canRecentsActivityRotate();
         mOptionLayout.setOrientation(orientationHandler
-                .getTaskMenuLayoutOrientation(mOptionLayout));
+                .getTaskMenuLayoutOrientation(canActivityRotate, mOptionLayout));
         setPosition(sTempRect.left - insets.left, sTempRect.top - insets.top,
             taskView.getPagedOrientationHandler());
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
similarity index 80%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
rename to quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index b2f937f..dfb3860 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -52,14 +52,12 @@
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.util.Themes;
-import com.android.quickstep.TaskOverlayFactory;
 import com.android.quickstep.TaskOverlayFactory.TaskOverlay;
 import com.android.quickstep.views.TaskView.FullscreenDrawParams;
 import com.android.systemui.plugins.OverviewScreenshotActions;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.shared.system.ConfigurationCompat;
 
 /**
  * A task in the Recents view.
@@ -86,7 +84,7 @@
             };
 
     private final BaseActivity mActivity;
-    private final TaskOverlay mOverlay;
+    private TaskOverlay mOverlay;
     private final boolean mIsDarkTextTheme;
     private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
     private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
@@ -119,14 +117,13 @@
 
     public TaskThumbnailView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
-        mOverlay = TaskOverlayFactory.INSTANCE.get(context).createOverlay(this);
         mPaint.setFilterBitmap(true);
         mBackgroundPaint.setColor(Color.WHITE);
         mClearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
         mDimmingPaintAfterClearing.setColor(Color.BLACK);
         mActivity = BaseActivity.fromContext(context);
         mIsDarkTextTheme = Themes.getAttrBoolean(mActivity, R.attr.isWorkspaceDarkText);
-        // Initialize with dummy value. It is overridden later by TaskView
+        // Initialize with placeholder value. It is overridden later by TaskView
         mFullscreenParams = TEMP_PARAMS.get(context);
     }
 
@@ -135,7 +132,7 @@
      * @param task
      */
     public void bind(Task task) {
-        mOverlay.reset();
+        getTaskOverlay().reset();
         mTask = task;
         int color = task == null ? Color.BLACK : task.colorBackground | 0xFF000000;
         mPaint.setColor(color);
@@ -177,7 +174,7 @@
             mBitmapShader = null;
             mThumbnailData = null;
             mPaint.setShader(null);
-            mOverlay.reset();
+            getTaskOverlay().reset();
         }
         if (mOverviewScreenshotActionsPlugin != null) {
             mOverviewScreenshotActionsPlugin.setupActions(getTaskView(), getThumbnail(), mActivity);
@@ -201,6 +198,9 @@
     }
 
     public TaskOverlay getTaskOverlay() {
+        if (mOverlay == null) {
+            mOverlay = getTaskView().getRecentsView().getTaskOverlayFactory().createOverlay(this);
+        }
         return mOverlay;
     }
 
@@ -327,22 +327,14 @@
         // Draw the background in all cases, except when the thumbnail data is opaque
         final boolean drawBackgroundOnly = mTask == null || mTask.isLocked || mBitmapShader == null
                 || mThumbnailData == null;
-        if (drawBackgroundOnly || mPreviewPositionHelper.mClipBottom > 0
-                || mThumbnailData.isTranslucent) {
+        if (drawBackgroundOnly || mThumbnailData.isTranslucent) {
             canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mBackgroundPaint);
             if (drawBackgroundOnly) {
                 return;
             }
         }
 
-        if (mPreviewPositionHelper.mClipBottom > 0) {
-            canvas.save();
-            canvas.clipRect(x, y, width, mPreviewPositionHelper.mClipBottom);
-            canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mPaint);
-            canvas.restore();
-        } else {
-            canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mPaint);
-        }
+        canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mPaint);
     }
 
     public TaskView getTaskView() {
@@ -357,11 +349,11 @@
     }
 
     private void updateOverlay() {
-        if (mOverlayEnabled && mBitmapShader != null && mThumbnailData != null) {
-            mOverlay.initOverlay(mTask, mThumbnailData, mPreviewPositionHelper.mMatrix,
+        if (mOverlayEnabled) {
+            getTaskOverlay().initOverlay(mTask, mThumbnailData, mPreviewPositionHelper.mMatrix,
                     mPreviewPositionHelper.mIsOrientationChanged);
         } else {
-            mOverlay.reset();
+            getTaskOverlay().reset();
         }
     }
 
@@ -380,13 +372,12 @@
     }
 
     private void updateThumbnailMatrix() {
-        mPreviewPositionHelper.mClipBottom = -1;
         mPreviewPositionHelper.mIsOrientationChanged = false;
         if (mBitmapShader != null && mThumbnailData != null) {
             mPreviewRect.set(0, 0, mThumbnailData.thumbnail.getWidth(),
                     mThumbnailData.thumbnail.getHeight());
-            int currentRotation = ConfigurationCompat.getWindowConfigurationRotation(
-                    mActivity.getResources().getConfiguration());
+            int currentRotation = getTaskView().getRecentsView().getPagedViewOrientedState()
+                    .getRecentsActivityRotation();
             mPreviewPositionHelper.updateThumbnailMatrix(mPreviewRect, mThumbnailData,
                     getMeasuredWidth(), getMeasuredHeight(), mActivity.getDeviceProfile(),
                     currentRotation);
@@ -474,40 +465,97 @@
         /**
          * Updates the matrix based on the provided parameters
          */
-        public void updateThumbnailMatrix(Rect thumbnailPosition, ThumbnailData thumbnailData,
+        public void updateThumbnailMatrix(Rect thumbnailBounds, ThumbnailData thumbnailData,
                 int canvasWidth, int canvasHeight, DeviceProfile dp, int currentRotation) {
             boolean isRotated = false;
             boolean isOrientationDifferent;
-            mClipBottom = -1;
 
-            float scale = thumbnailData.scale;
-            Rect activityInsets = dp.getInsets();
-            Rect thumbnailInsets = getBoundedInsets(activityInsets, thumbnailData.insets);
-            final float thumbnailWidth = thumbnailPosition.width()
-                    - (thumbnailInsets.left + thumbnailInsets.right) * scale;
-            final float thumbnailHeight = thumbnailPosition.height()
-                    - (thumbnailInsets.top + thumbnailInsets.bottom) * scale;
-
-            final float thumbnailScale;
             int thumbnailRotation = thumbnailData.rotation;
             int deltaRotate = getRotationDelta(currentRotation, thumbnailRotation);
+            RectF thumbnailClipHint = new RectF(thumbnailData.insets);
+
+            float scale = thumbnailData.scale;
+            final float thumbnailScale;
 
             // Landscape vs portrait change
             boolean windowingModeSupportsRotation = !dp.isMultiWindowMode
                     && thumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN;
             isOrientationDifferent = isOrientationChange(deltaRotate)
                     && windowingModeSupportsRotation;
-            if (canvasWidth == 0) {
+            if (canvasWidth == 0 || canvasHeight == 0 || scale == 0) {
                 // If we haven't measured , skip the thumbnail drawing and only draw the background
                 // color
                 thumbnailScale = 0f;
             } else {
                 // Rotate the screenshot if not in multi-window mode
                 isRotated = deltaRotate > 0 && windowingModeSupportsRotation;
-                // Scale the screenshot to always fit the width of the card.
-                thumbnailScale = isOrientationDifferent
-                        ? canvasWidth / thumbnailHeight
-                        : canvasWidth / thumbnailWidth;
+
+                float surfaceWidth = thumbnailBounds.width() / scale;
+                float surfaceHeight = thumbnailBounds.height() / scale;
+                float availableWidth = surfaceWidth
+                        - (thumbnailClipHint.left + thumbnailClipHint.right);
+                float availableHeight = surfaceHeight
+                        - (thumbnailClipHint.top + thumbnailClipHint.bottom);
+
+                final float targetW, targetH;
+                if (isOrientationDifferent) {
+                    targetW = canvasHeight;
+                    targetH = canvasWidth;
+                } else {
+                    targetW = canvasWidth;
+                    targetH = canvasHeight;
+                }
+                float canvasAspect = targetW / targetH;
+
+                // Update the clipHint such that
+                //   > the final clipped position has same aspect ratio as requested by canvas
+                //   > the clipped region is within the task insets if possible
+                //   > the clipped region is not scaled up when drawing. If that is not possible
+                //     while staying within the taskInsets, move outside the insets.
+                float croppedWidth = availableWidth;
+                if (croppedWidth < targetW) {
+                    croppedWidth = Math.min(targetW, surfaceWidth);
+                }
+
+                float croppedHeight = croppedWidth / canvasAspect;
+                if (croppedHeight > availableHeight) {
+                    croppedHeight = availableHeight;
+                    if (croppedHeight < targetH) {
+                        croppedHeight = Math.min(targetH, surfaceHeight);
+                    }
+                    croppedWidth = croppedHeight * canvasAspect;
+
+                    // One last check in case the task aspect radio messed up something
+                    if (croppedWidth > surfaceWidth) {
+                        croppedWidth = surfaceWidth;
+                        croppedHeight = croppedWidth / canvasAspect;
+                    }
+                }
+
+                // Update the clip hints
+                float halfExtraW = (availableWidth - croppedWidth) / 2;
+                thumbnailClipHint.left += halfExtraW;
+                thumbnailClipHint.right += halfExtraW;
+                if (thumbnailClipHint.left < 0) {
+                    thumbnailClipHint.right += thumbnailClipHint.left;
+                    thumbnailClipHint.left = 0;
+                } else if (thumbnailClipHint.right < 0) {
+                    thumbnailClipHint.left += thumbnailClipHint.right;
+                    thumbnailClipHint.right = 0;
+                }
+
+                float halfExtraH = (availableHeight - croppedHeight) / 2;
+                thumbnailClipHint.top += halfExtraH;
+                thumbnailClipHint.bottom += halfExtraH;
+                if (thumbnailClipHint.top < 0) {
+                    thumbnailClipHint.bottom += thumbnailClipHint.top;
+                    thumbnailClipHint.top = 0;
+                } else if (thumbnailClipHint.bottom < 0) {
+                    thumbnailClipHint.top += thumbnailClipHint.bottom;
+                    thumbnailClipHint.bottom = 0;
+                }
+
+                thumbnailScale = targetW / (croppedWidth * scale);
             }
 
             Rect splitScreenInsets = dp.getInsets();
@@ -517,24 +565,24 @@
                     mClippedInsets.offsetTo(splitScreenInsets.left * scale,
                             splitScreenInsets.top * scale);
                 } else {
-                    mClippedInsets.offsetTo(thumbnailInsets.left * scale,
-                            thumbnailInsets.top * scale);
+                    mClippedInsets.offsetTo(thumbnailClipHint.left * scale,
+                            thumbnailClipHint.top * scale);
                 }
                 mMatrix.setTranslate(
-                        -thumbnailInsets.left * scale,
-                        -thumbnailInsets.top * scale);
+                        -thumbnailClipHint.left * scale,
+                        -thumbnailClipHint.top * scale);
             } else {
-                setThumbnailRotation(deltaRotate, thumbnailInsets, scale, thumbnailPosition);
+                setThumbnailRotation(deltaRotate, thumbnailClipHint, scale, thumbnailBounds);
             }
 
             final float widthWithInsets;
             final float heightWithInsets;
             if (isOrientationDifferent) {
-                widthWithInsets = thumbnailPosition.height() * thumbnailScale;
-                heightWithInsets = thumbnailPosition.width() * thumbnailScale;
+                widthWithInsets = thumbnailBounds.height() * thumbnailScale;
+                heightWithInsets = thumbnailBounds.width() * thumbnailScale;
             } else {
-                widthWithInsets = thumbnailPosition.width() * thumbnailScale;
-                heightWithInsets = thumbnailPosition.height() * thumbnailScale;
+                widthWithInsets = thumbnailBounds.width() * thumbnailScale;
+                heightWithInsets = thumbnailBounds.height() * thumbnailScale;
             }
             mClippedInsets.left *= thumbnailScale;
             mClippedInsets.top *= thumbnailScale;
@@ -550,22 +598,9 @@
             }
 
             mMatrix.postScale(thumbnailScale, thumbnailScale);
-
-            float bitmapHeight = Math.max(0,
-                    (isOrientationDifferent ? thumbnailWidth : thumbnailHeight) * thumbnailScale);
-            if (Math.round(bitmapHeight) < canvasHeight) {
-                mClipBottom = bitmapHeight;
-            }
             mIsOrientationChanged = isOrientationDifferent;
         }
 
-        private Rect getBoundedInsets(Rect activityInsets, Rect insets) {
-            return new Rect(Math.min(insets.left, activityInsets.left),
-                    Math.min(insets.top, activityInsets.top),
-                    Math.min(insets.right, activityInsets.right),
-                    Math.min(insets.bottom, activityInsets.bottom));
-        }
-
         private int getRotationDelta(int oldRotation, int newRotation) {
             int delta = newRotation - oldRotation;
             if (delta < 0) delta += 4;
@@ -581,12 +616,12 @@
             return deltaRotation == Surface.ROTATION_90 || deltaRotation == Surface.ROTATION_270;
         }
 
-        private void setThumbnailRotation(int deltaRotate, Rect thumbnailInsets, float scale,
+        private void setThumbnailRotation(int deltaRotate, RectF thumbnailInsets, float scale,
                 Rect thumbnailPosition) {
-            int newLeftInset = 0;
-            int newTopInset = 0;
-            int translateX = 0;
-            int translateY = 0;
+            float newLeftInset = 0;
+            float newTopInset = 0;
+            float translateX = 0;
+            float translateY = 0;
 
             mMatrix.setRotate(90 * deltaRotate);
             switch (deltaRotate) { /* Counter-clockwise */
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
similarity index 93%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
rename to quickstep/src/com/android/quickstep/views/TaskView.java
index 222f6e6..259cd4d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -22,16 +22,19 @@
 import static android.view.Gravity.END;
 import static android.view.Gravity.START;
 import static android.view.Gravity.TOP;
+import static android.view.Surface.ROTATION_180;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
 import static android.widget.Toast.LENGTH_SHORT;
 
 import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
 import static com.android.launcher3.Utilities.comp;
+import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent
-        .LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP;
 
 import android.animation.Animator;
@@ -53,7 +56,9 @@
 import android.util.AttributeSet;
 import android.util.FloatProperty;
 import android.util.Log;
+import android.view.MotionEvent;
 import android.view.Surface;
+import android.view.TouchDelegate;
 import android.view.View;
 import android.view.ViewOutlineProvider;
 import android.view.accessibility.AccessibilityNodeInfo;
@@ -78,6 +83,7 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.TransformingTouchDelegate;
 import com.android.launcher3.util.ViewPool.Reusable;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.TaskIconCache;
@@ -122,6 +128,13 @@
 
     public static final long SCALE_ICON_DURATION = 120;
     private static final long DIM_ANIM_DURATION = 700;
+    /**
+     * This technically can be a vanilla {@link TouchDelegate} class, however that class requires
+     * setting the touch bounds at construction, so we'd repeatedly be created many instances
+     * unnecessarily as scrolling occurs, whereas {@link TransformingTouchDelegate} allows touch
+     * delegated bounds only to be updated.
+     */
+    private TransformingTouchDelegate mIconTouchDelegate;
 
     private static final List<Rect> SYSTEM_GESTURE_EXCLUSION_RECT =
             Collections.singletonList(new Rect());
@@ -186,6 +199,7 @@
     private int mStackHeight;
     private View mContextualChipWrapper;
     private View mContextualChip;
+    private final float[] mIconCenterCoords = new float[2];
 
     public TaskView(Context context) {
         this(context, null);
@@ -211,10 +225,6 @@
             } else {
                 launchTask(true /* animate */);
             }
-
-            mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
-                    Touch.TAP, Direction.NONE, getRecentsView().indexOfChild(this),
-                    TaskUtils.getLaunchComponentKeyForTask(getTask().key));
             mActivity.getStatsLogManager().logger().withItemInfo(getItemInfo())
                     .log(LAUNCHER_TASK_LAUNCH_TAP);
         });
@@ -231,14 +241,14 @@
      */
     public WorkspaceItemInfo getItemInfo() {
         ComponentKey componentKey = TaskUtils.getLaunchComponentKeyForTask(getTask().key);
-        WorkspaceItemInfo dummyInfo = new WorkspaceItemInfo();
-        dummyInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_TASK;
-        dummyInfo.container = LauncherSettings.Favorites.CONTAINER_TASKSWITCHER;
-        dummyInfo.user = componentKey.user;
-        dummyInfo.intent = new Intent().setComponent(componentKey.componentName);
-        dummyInfo.title = TaskUtils.getTitle(getContext(), getTask());
-        dummyInfo.screenId = getRecentsView().indexOfChild(this);
-        return dummyInfo;
+        WorkspaceItemInfo stubInfo = new WorkspaceItemInfo();
+        stubInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_TASK;
+        stubInfo.container = LauncherSettings.Favorites.CONTAINER_TASKSWITCHER;
+        stubInfo.user = componentKey.user;
+        stubInfo.intent = new Intent().setComponent(componentKey.componentName);
+        stubInfo.title = TaskUtils.getTitle(getContext(), getTask());
+        stubInfo.screenId = getRecentsView().indexOfChild(this);
+        return stubInfo;
     }
 
     @Override
@@ -246,6 +256,26 @@
         super.onFinishInflate();
         mSnapshotView = findViewById(R.id.snapshot);
         mIconView = findViewById(R.id.icon);
+        mIconTouchDelegate = new TransformingTouchDelegate(mIconView);
+    }
+
+    public TouchDelegate getIconTouchDelegate(MotionEvent event) {
+        if (event.getAction() == MotionEvent.ACTION_DOWN) {
+            computeAndSetIconTouchDelegate();
+        }
+        return mIconTouchDelegate;
+    }
+
+    private void computeAndSetIconTouchDelegate() {
+        float iconHalfSize = mIconView.getWidth() / 2f;
+        mIconCenterCoords[0] = mIconCenterCoords[1] = iconHalfSize;
+        getDescendantCoordRelativeToAncestor(mIconView, mActivity.getDragLayer(), mIconCenterCoords,
+                false);
+        mIconTouchDelegate.setBounds(
+                (int) (mIconCenterCoords[0] - iconHalfSize),
+                (int) (mIconCenterCoords[1] - iconHalfSize),
+                (int) (mIconCenterCoords[0] + iconHalfSize),
+                (int) (mIconCenterCoords[1] + iconHalfSize));
     }
 
     /**
@@ -332,19 +362,11 @@
     public void launchTask(boolean animate, boolean freezeTaskList, Consumer<Boolean> resultCallback,
             Handler resultCallbackHandler) {
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            RecentsView recentsView = getRecentsView();
             if (isRunningTask()) {
-                recentsView.finishRecentsAnimation(false /* toRecents */,
+                getRecentsView().finishRecentsAnimation(false /* toRecents */,
                         () -> resultCallbackHandler.post(() -> resultCallback.accept(true)));
             } else {
-                // This is a workaround against the WM issue that app open is not correctly animated
-                // when recents animation is being cleaned up (b/143774568). When that's possible,
-                // we should rely on the framework side to cancel the recents animation, and we will
-                // clean up the screenshot on the launcher side while we launch the next task.
-                recentsView.switchToScreenshot(null,
-                        () -> recentsView.finishRecentsAnimation(true /* toRecents */,
-                                () -> launchTaskInternal(animate, freezeTaskList, resultCallback,
-                                        resultCallbackHandler)));
+                launchTaskInternal(animate, freezeTaskList, resultCallback, resultCallbackHandler);
             }
         } else {
             launchTaskInternal(animate, freezeTaskList, resultCallback, resultCallbackHandler);
@@ -385,7 +407,6 @@
                             }
                         }, resultCallbackHandler);
             }
-            getRecentsView().onTaskLaunched(mTask);
         }
     }
 
@@ -468,18 +489,18 @@
         int thumbnailPadding = (int) getResources().getDimension(R.dimen.task_thumbnail_top_margin);
         LayoutParams iconParams = (LayoutParams) mIconView.getLayoutParams();
         switch (orientationHandler.getRotation()) {
-            case Surface.ROTATION_90:
+            case ROTATION_90:
                 iconParams.gravity = (isRtl ? START : END) | CENTER_VERTICAL;
                 iconParams.rightMargin = -thumbnailPadding;
                 iconParams.leftMargin = 0;
                 iconParams.topMargin = snapshotParams.topMargin / 2;
                 break;
-            case Surface.ROTATION_180:
+            case ROTATION_180:
                 iconParams.gravity = BOTTOM | CENTER_HORIZONTAL;
                 iconParams.bottomMargin = -thumbnailPadding;
                 iconParams.leftMargin = iconParams.topMargin = iconParams.rightMargin = 0;
                 break;
-            case Surface.ROTATION_270:
+            case ROTATION_270:
                 iconParams.gravity = (isRtl ? END : START) | CENTER_VERTICAL;
                 iconParams.leftMargin = -thumbnailPadding;
                 iconParams.rightMargin = 0;
diff --git a/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java b/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java
deleted file mode 100644
index 5904fcd..0000000
--- a/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java
+++ /dev/null
@@ -1,162 +0,0 @@
-/**
- * Copyright (C) 2019 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.quickstep;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-
-import android.app.prediction.AppPredictor;
-import android.app.prediction.AppTarget;
-import android.app.prediction.AppTargetId;
-import android.content.ComponentName;
-import android.content.pm.LauncherActivityInfo;
-import android.content.pm.LauncherApps;
-import android.os.Process;
-import android.view.View;
-
-import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.appprediction.PredictionRowView;
-import com.android.launcher3.appprediction.PredictionUiStateManager;
-import com.android.launcher3.appprediction.PredictionUiStateManager.Client;
-import com.android.launcher3.model.AppLaunchTracker;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@LargeTest
-@RunWith(AndroidJUnit4.class)
-public class AppPredictionsUITests extends AbstractQuickStepTest {
-
-    private LauncherActivityInfo mSampleApp1;
-    private LauncherActivityInfo mSampleApp2;
-    private LauncherActivityInfo mSampleApp3;
-
-    private AppPredictor.Callback mCallback;
-
-    @Before
-    public void setUp() throws Exception {
-        super.setUp();
-
-        List<LauncherActivityInfo> activities = mTargetContext.getSystemService(LauncherApps.class)
-                .getActivityList(null, Process.myUserHandle());
-        mSampleApp1 = activities.get(0);
-        mSampleApp2 = activities.get(1);
-        mSampleApp3 = activities.get(2);
-
-        // Disable app tracker
-        AppLaunchTracker.INSTANCE.initializeForTesting(new AppLaunchTracker());
-        PredictionUiStateManager.INSTANCE.initializeForTesting(null);
-
-        mCallback = PredictionUiStateManager.INSTANCE.get(mTargetContext).appPredictorCallback(
-                Client.HOME);
-
-        mDevice.setOrientationNatural();
-    }
-
-    @After
-    public void tearDown() throws Throwable {
-        AppLaunchTracker.INSTANCE.initializeForTesting(null);
-        PredictionUiStateManager.INSTANCE.initializeForTesting(null);
-        mDevice.unfreezeRotation();
-    }
-
-    /**
-     * Test that prediction UI is updated as soon as we get predictions from the system
-     */
-    @Test
-    public void testPredictionExistsInAllApps() {
-        mLauncher.pressHome().switchToAllApps();
-
-        // Dispatch an update
-        sendPredictionUpdate(mSampleApp1, mSampleApp2);
-        // The first update should apply immediately.
-        waitForLauncherCondition("Predictions were not updated in loading state",
-                launcher -> getPredictedApp(launcher).size() == 2);
-    }
-
-    /**
-     * Test that prediction update is deferred if it is already visible
-     */
-    @Test
-    public void testPredictionsDeferredUntilHome() {
-        mDevice.pressHome();
-        sendPredictionUpdate(mSampleApp1, mSampleApp2);
-        mLauncher.pressHome().switchToAllApps();
-        waitForLauncherCondition("Predictions were not updated in loading state",
-                launcher -> getPredictedApp(launcher).size() == 2);
-
-        // Update predictions while all-apps is visible
-        sendPredictionUpdate(mSampleApp1, mSampleApp2, mSampleApp3);
-        assertEquals(2, getFromLauncher(this::getPredictedApp).size());
-
-        // Go home and go back to all-apps
-        mLauncher.pressHome().switchToAllApps();
-        assertEquals(3, getFromLauncher(this::getPredictedApp).size());
-    }
-
-    @Test
-    public void testPredictionsDisabled() {
-        mDevice.pressHome();
-        sendPredictionUpdate();
-        mLauncher.pressHome().switchToAllApps();
-
-        waitForLauncherCondition("Predictions were not updated in loading state",
-                launcher -> launcher.getAppsView().getFloatingHeaderView()
-                        .findFixedRowByType(PredictionRowView.class).getVisibility() == View.GONE);
-        assertFalse(PredictionUiStateManager.INSTANCE.get(mTargetContext)
-                .getCurrentState().isEnabled);
-    }
-
-    public ArrayList<BubbleTextView> getPredictedApp(Launcher launcher) {
-        PredictionRowView container = launcher.getAppsView().getFloatingHeaderView()
-                .findFixedRowByType(PredictionRowView.class);
-
-        ArrayList<BubbleTextView> predictedAppViews = new ArrayList<>();
-        for (int i = 0; i < container.getChildCount(); i++) {
-            View view = container.getChildAt(i);
-            if (view instanceof BubbleTextView && view.getVisibility() == View.VISIBLE) {
-                predictedAppViews.add((BubbleTextView) view);
-            }
-        }
-        return predictedAppViews;
-    }
-
-    private void sendPredictionUpdate(LauncherActivityInfo... activities) {
-        getOnUiThread(() -> {
-            List<AppTarget> targets = new ArrayList<>(activities.length);
-            for (LauncherActivityInfo info : activities) {
-                ComponentName cn = info.getComponentName();
-                AppTarget target = new AppTarget.Builder(
-                        new AppTargetId("app:" + cn), cn.getPackageName(), info.getUser())
-                        .setClassName(cn.getClassName())
-                        .build();
-                targets.add(target);
-            }
-            mCallback.onTargetsAvailable(targets);
-            return null;
-        });
-    }
-}
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
index 40265c4..0f6671d 100644
--- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
+++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
@@ -188,7 +188,7 @@
                     SYS_UI_NAVIGATION_MODE.removeModeChangeListener(listener));
 
             Wait.atMost(() -> "Navigation mode didn't change to " + expectedMode,
-                    () -> currentSysUiNavigationMode() == expectedMode, 60000 /* b/148422894 */,
+                    () -> currentSysUiNavigationMode() == expectedMode, WAIT_TIME_MS,
                     launcher);
             // b/139137636
 //            assertTrue(launcher, "Navigation mode didn't change to " + expectedMode,
@@ -202,7 +202,7 @@
         Wait.atMost(() -> "Switching nav mode: "
                         + launcher.getNavigationModeMismatchError(),
                 () -> launcher.getNavigationModeMismatchError() == null,
-                60000 /* b/148422894 */, launcher);
+                WAIT_TIME_MS, launcher);
         AbstractLauncherUiTest.checkDetectedLeaks(launcher);
         return true;
     }
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index bf093fd..ecd4e2b 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -36,6 +36,7 @@
 import com.android.launcher3.tapl.Background;
 import com.android.launcher3.tapl.LauncherInstrumentation.NavigationModel;
 import com.android.launcher3.tapl.Overview;
+import com.android.launcher3.tapl.OverviewActions;
 import com.android.launcher3.tapl.OverviewTask;
 import com.android.launcher3.tapl.TestHelpers;
 import com.android.launcher3.ui.TaplTestsLauncher3;
@@ -68,11 +69,14 @@
         });
     }
 
-    private void startTestApps() throws Exception {
+    public static void startTestApps() throws Exception {
         startAppFast(getAppPackageName());
         startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
         startTestActivity(2);
+    }
 
+    private void startTestAppsWithCheck() throws Exception {
+        startTestApps();
         executeOnLauncher(launcher -> assertTrue(
                 "Launcher activity is the top activity; expecting another activity to be the top "
                         + "one",
@@ -105,7 +109,7 @@
     @Test
     @PortraitLandscape
     public void testOverview() throws Exception {
-        startTestApps();
+        startTestAppsWithCheck();
         // mLauncher.pressHome() also tests an important case of pressing home while in background.
         Overview overview = mLauncher.pressHome().switchToOverview();
         assertTrue("Launcher internal state didn't switch to Overview",
@@ -189,6 +193,22 @@
                         0, getTaskCount(launcher)));
     }
 
+    /**
+     * Smoke test for action buttons: Presses all the buttons and makes sure no crashes occur.
+     */
+    @Test
+    @NavigationModeSwitch
+    @PortraitLandscape
+    public void testOverviewActions() throws Exception {
+        if (mLauncher.getNavigationModel() != NavigationModel.TWO_BUTTON) {
+            startTestAppsWithCheck();
+            OverviewActions actionsView =
+                    mLauncher.pressHome().switchToOverview().getOverviewActions();
+            actionsView.clickAndDismissScreenshot();
+            actionsView.clickAndDismissShare();
+        }
+    }
+
     private int getCurrentOverviewPage(Launcher launcher) {
         return launcher.<RecentsView>getOverviewPanel().getCurrentPage();
     }
diff --git a/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java b/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java
index 115294a..72116eb 100644
--- a/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java
+++ b/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java
@@ -71,7 +71,7 @@
 
 /**
  * Test to verify view inflation does not happen during swipe up.
- * To verify view inflation, we setup a dummy ViewConfiguration and check if any call to that class
+ * To verify view inflation, we setup a stub ViewConfiguration and check if any call to that class
  * does from a View.init method or not.
  *
  * Alternative approaches considered:
@@ -138,13 +138,13 @@
     @Test
     @NavigationModeSwitch(mode = ZERO_BUTTON)
     public void testSwipeUpFromApp_widget_update() {
-        String dummyText = "Some random dummy text";
+        String stubText = "Some random stub text";
 
         executeSwipeUpTestWithWidget(
                 widgetId -> { },
                 widgetId -> AppWidgetManager.getInstance(getContext())
-                        .updateAppWidget(widgetId, createMainWidgetViews(dummyText)),
-                dummyText);
+                        .updateAppWidget(widgetId, createMainWidgetViews(stubText)),
+                stubText);
     }
 
     @Test
diff --git a/res/animator-v23/discovery_bounce.xml b/res/animator-v23/discovery_bounce.xml
deleted file mode 100644
index f554853..0000000
--- a/res/animator-v23/discovery_bounce.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?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.
-*/
--->
-<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
-    android:duration="2166"
-    android:repeatCount="5">
-    <propertyValuesHolder
-        android:propertyName="progress"
-        android:valueType="floatType">
-        <keyframe
-            android:fraction="0"
-            android:value="1f" />
-        <keyframe
-            android:fraction="0.246"
-            android:value="1f" />
-        <keyframe
-            android:fraction=".423"
-            android:interpolator="@interpolator/disco_bounce"
-            android:value="0.9738f" />
-        <keyframe
-            android:fraction="0.754"
-            android:interpolator="@interpolator/disco_bounce"
-            android:value="1f" />
-        <keyframe
-            android:fraction="1"
-            android:value="1f" />
-    </propertyValuesHolder>
-</objectAnimator>
diff --git a/res/animator/discovery_bounce.xml b/res/animator/discovery_bounce.xml
index f02ebdb..f554853 100644
--- a/res/animator/discovery_bounce.xml
+++ b/res/animator/discovery_bounce.xml
@@ -16,20 +16,28 @@
 ** limitations under the License.
 */
 -->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-    android:ordering="sequentially">
-    <objectAnimator
-        android:duration="166"
-        android:interpolator="@interpolator/disco_bounce"
+<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:duration="2166"
+    android:repeatCount="5">
+    <propertyValuesHolder
         android:propertyName="progress"
-        android:startOffset="750"
-        android:valueFrom="1f"
-        android:valueTo="0.9438f"
-        android:valueType="floatType" />
-    <objectAnimator
-        android:duration="500"
-        android:interpolator="@interpolator/disco_bounce"
-        android:propertyName="progress"
-        android:valueTo="1f"
-        android:valueType="floatType" />
-</set>
+        android:valueType="floatType">
+        <keyframe
+            android:fraction="0"
+            android:value="1f" />
+        <keyframe
+            android:fraction="0.246"
+            android:value="1f" />
+        <keyframe
+            android:fraction=".423"
+            android:interpolator="@interpolator/disco_bounce"
+            android:value="0.9738f" />
+        <keyframe
+            android:fraction="0.754"
+            android:interpolator="@interpolator/disco_bounce"
+            android:value="1f" />
+        <keyframe
+            android:fraction="1"
+            android:value="1f" />
+    </propertyValuesHolder>
+</objectAnimator>
diff --git a/res/color-v24/all_apps_bg_hand_fill.xml b/res/color/all_apps_bg_hand_fill.xml
similarity index 100%
rename from res/color-v24/all_apps_bg_hand_fill.xml
rename to res/color/all_apps_bg_hand_fill.xml
diff --git a/res/color-v24/all_apps_bg_hand_fill_dark.xml b/res/color/all_apps_bg_hand_fill_dark.xml
similarity index 100%
rename from res/color-v24/all_apps_bg_hand_fill_dark.xml
rename to res/color/all_apps_bg_hand_fill_dark.xml
diff --git a/res/drawable-v26/ic_deepshortcut_placeholder.xml b/res/drawable-v26/ic_deepshortcut_placeholder.xml
deleted file mode 100644
index 3fa8506..0000000
--- a/res/drawable-v26/ic_deepshortcut_placeholder.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2017 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
-    <background android:drawable="?attr/popupColorSecondary"/>
-    <foreground android:drawable="?attr/popupColorSecondary"/>
-</adaptive-icon>
diff --git a/res/drawable-v26/ic_launcher_home.xml b/res/drawable-v26/ic_launcher_home.xml
deleted file mode 100644
index 7038775..0000000
--- a/res/drawable-v26/ic_launcher_home.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-        http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<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/drag_handle_indicator_no_shadow.xml b/res/drawable/drag_handle_indicator_no_shadow.xml
deleted file mode 100644
index 341e60c..0000000
--- a/res/drawable/drag_handle_indicator_no_shadow.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 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/vertical_drag_handle_width"
-    android:height="@dimen/vertical_drag_handle_height"
-    android:viewportWidth="18.0"
-    android:viewportHeight="6.0"
-    android:tint="?attr/workspaceTextColor" >
-
-    <path
-        android:pathData="M17,6c-0.15,0-0.3-0.03-0.45-0.11L9,2.12L1.45,5.89c-0.5,0.25-1.09,
-        0.05-1.34-0.45S0.06,4.35,0.55,4.11l8-4c0.28-0.14,0.61-0.14,0.89,0l8,4c0.49,0.25,0.69,
-        0.85,0.45,1.34C17.72,5.8,17.37,6,17,6z"
-        android:fillColor="@android:color/white" />
-</vector>
diff --git a/res/drawable-v24/ic_block_shadow.xml b/res/drawable/ic_block_shadow.xml
similarity index 100%
rename from res/drawable-v24/ic_block_shadow.xml
rename to res/drawable/ic_block_shadow.xml
diff --git a/res/drawable/ic_deepshortcut_placeholder.xml b/res/drawable/ic_deepshortcut_placeholder.xml
index 85a9694..3fa8506 100644
--- a/res/drawable/ic_deepshortcut_placeholder.xml
+++ b/res/drawable/ic_deepshortcut_placeholder.xml
@@ -1,5 +1,6 @@
 <?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.
@@ -13,10 +14,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-       android:shape="oval">
-    <solid android:color="?attr/popupColorSecondary" />
-    <size
-        android:height="32dp"
-        android:width="32dp" />
-</shape>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="?attr/popupColorSecondary"/>
+    <foreground android:drawable="?attr/popupColorSecondary"/>
+</adaptive-icon>
diff --git a/res/drawable/ic_launcher_home.xml b/res/drawable/ic_launcher_home.xml
index a6f2519..7038775 100644
--- a/res/drawable/ic_launcher_home.xml
+++ b/res/drawable/ic_launcher_home.xml
@@ -13,6 +13,9 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<bitmap
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:src="@mipmap/ic_launcher_home" />
+<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-v24/ic_remove_shadow.xml b/res/drawable/ic_remove_shadow.xml
similarity index 100%
rename from res/drawable-v24/ic_remove_shadow.xml
rename to res/drawable/ic_remove_shadow.xml
diff --git a/res/drawable-v24/ic_setup_shadow.xml b/res/drawable/ic_setup_shadow.xml
similarity index 100%
rename from res/drawable-v24/ic_setup_shadow.xml
rename to res/drawable/ic_setup_shadow.xml
diff --git a/res/drawable-v24/ic_uninstall_shadow.xml b/res/drawable/ic_uninstall_shadow.xml
similarity index 100%
rename from res/drawable-v24/ic_uninstall_shadow.xml
rename to res/drawable/ic_uninstall_shadow.xml
diff --git a/res/drawable-v24/drag_handle_indicator_shadow.xml b/res/layout/floating_surface_view.xml
similarity index 74%
rename from res/drawable-v24/drag_handle_indicator_shadow.xml
rename to res/layout/floating_surface_view.xml
index 774bc38..434e84f 100644
--- a/res/drawable-v24/drag_handle_indicator_shadow.xml
+++ b/res/layout/floating_surface_view.xml
@@ -5,7 +5,7 @@
      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,7 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<com.android.launcher3.graphics.ShadowDrawable
+<com.android.launcher3.views.FloatingSurfaceView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:src="@drawable/drag_handle_indicator_no_shadow"
-    android:elevation="@dimen/vertical_drag_handle_elevation" />
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content" />
diff --git a/res/layout/home_settings.xml b/res/layout/home_settings.xml
new file mode 100644
index 0000000..0f2461a
--- /dev/null
+++ b/res/layout/home_settings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <EditText
+        android:id="@+id/filter_box"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginHorizontal="@dimen/developer_options_filter_margins"
+        android:hint="@string/developer_options_filter_hint"
+        android:visibility="gone"
+        />
+
+    <FrameLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:id="@android:id/list_container"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/launcher.xml b/res/layout/launcher.xml
index a137908..0c18c8a 100644
--- a/res/layout/launcher.xml
+++ b/res/layout/launcher.xml
@@ -28,6 +28,12 @@
         android:clipToPadding="false"
         android:importantForAccessibility="no">
 
+        <com.android.launcher3.views.AccessibilityActionsView
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:contentDescription="@string/home_screen"
+            />
+
         <!-- The workspace contains 5 screens of cells -->
         <!-- DO NOT CHANGE THE ID -->
         <com.android.launcher3.Workspace
diff --git a/res/layout/launcher_preview_layout.xml b/res/layout/launcher_preview_layout.xml
index 3fd02e3..4a20c70 100644
--- a/res/layout/launcher_preview_layout.xml
+++ b/res/layout/launcher_preview_layout.xml
@@ -17,7 +17,8 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:launcher="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
-    android:layout_height="match_parent">
+    android:layout_height="match_parent"
+    android:focusable="false">
 
     <com.android.launcher3.CellLayout
         android:id="@+id/workspace"
diff --git a/res/layout/search_result_hero_app.xml b/res/layout/search_result_hero_app.xml
new file mode 100644
index 0000000..bd0e42b
--- /dev/null
+++ b/res/layout/search_result_hero_app.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.views.HeroSearchResultView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal"
+    android:gravity="center_vertical"
+    android:padding="@dimen/dynamic_grid_edge_margin">
+
+    <FrameLayout
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_gravity="center_vertical"
+        android:layout_weight="1">
+        <com.android.launcher3.BubbleTextView
+            android:id="@+id/bubble_text"
+            style="@style/BaseIcon"
+            android:drawablePadding="@dimen/dynamic_grid_icon_drawable_padding"
+            android:gravity="start|center_vertical"
+            android:textAlignment="viewStart"
+            android:textColor="?android:attr/textColorPrimary"
+            android:textSize="16sp"
+            android:layout_height="wrap_content"
+            launcher:iconDisplay="hero_app"
+            launcher:layoutHorizontal="true"/>
+
+        <View
+            android:id="@+id/icon"
+            android:layout_width="@dimen/deep_shortcut_icon_size"
+            android:layout_height="@dimen/deep_shortcut_icon_size"
+            android:layout_gravity="start|center_vertical"
+            android:background="@drawable/ic_deepshortcut_placeholder"/>
+    </FrameLayout>
+
+    <com.android.launcher3.BubbleTextView
+        android:id="@+id/shortcut_0"
+        style="@style/BaseIcon"
+        android:layout_width="@dimen/deep_shortcut_icon_size"
+        android:layout_height="match_parent"
+        android:gravity="start|center_vertical"
+        android:textAlignment="center"
+        launcher:iconDisplay="shortcut_popup"
+        android:textSize="12sp"
+        launcher:iconSizeOverride="@dimen/deep_shortcut_icon_size"
+        launcher:layoutHorizontal="false"/>
+
+    <com.android.launcher3.BubbleTextView
+        android:id="@+id/shortcut_1"
+        style="@style/BaseIcon"
+        android:layout_width="@dimen/deep_shortcut_icon_size"
+        android:layout_height="match_parent"
+        android:gravity="start|center_vertical"
+        android:textAlignment="center"
+        launcher:iconDisplay="shortcut_popup"
+        android:textSize="12sp"
+        launcher:iconSizeOverride="@dimen/deep_shortcut_icon_size"
+        launcher:layoutHorizontal="false"/>
+
+</com.android.launcher3.views.HeroSearchResultView>
\ No newline at end of file
diff --git a/res/layout/search_result_play_item.xml b/res/layout/search_result_play_item.xml
new file mode 100644
index 0000000..4e82eaf
--- /dev/null
+++ b/res/layout/search_result_play_item.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.views.SearchResultPlayItem xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:padding="8dp"
+    android:orientation="horizontal">
+    <View
+        android:id="@+id/icon"
+        android:layout_width="@dimen/deep_shortcut_icon_size"
+        android:layout_height="@dimen/deep_shortcut_icon_size"
+        android:layout_gravity="start|center_vertical"
+        android:background="@drawable/ic_deepshortcut_placeholder" />
+
+    <LinearLayout
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_gravity="start|center_vertical"
+        android:layout_weight="1"
+        android:orientation="vertical"
+        android:padding="8dp">
+
+        <TextView
+            android:id="@+id/title_view"
+            style="@style/TextHeadline"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textAlignment="viewStart"
+            android:textColor="?android:attr/textColorPrimary"
+            android:textSize="16sp" />
+
+        <TextView
+            android:id="@+id/detail_0"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textColor="?android:attr/textColorPrimary" />
+
+        <TextView
+            android:id="@+id/detail_1"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textColor="?android:attr/textColorPrimary"
+            android:visibility="gone" />
+
+        <TextView
+            android:id="@+id/detail_2"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textColor="?android:attr/textColorPrimary"
+            android:visibility="gone" />
+    </LinearLayout>
+    <Button
+        android:id="@+id/try_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="start|center_vertical"
+        android:background="?android:attr/selectableItemBackground"
+        android:text="@string/search_action_try_now">
+    </Button>
+
+
+</com.android.launcher3.views.SearchResultPlayItem>
diff --git a/res/layout/search_result_settings_row.xml b/res/layout/search_result_settings_row.xml
new file mode 100644
index 0000000..19daf34
--- /dev/null
+++ b/res/layout/search_result_settings_row.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2020 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.views.SearchSettingsRowView xmlns:android="http://schemas.android.com/apk/res/android"
+    style="@style/TextHeadline"
+    android:id="@+id/section_title"
+    android:background="?android:attr/selectableItemBackground"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:gravity="center_vertical"
+    android:padding="4dp"
+    android:minHeight="48dp"
+    android:textColor="?android:attr/textColorPrimary"
+    android:textSize="14sp">
+
+    <TextView
+        android:id="@+id/title"
+        style="@style/TextTitle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="4dp"
+        android:textColor="?android:attr/textColorPrimary"
+        android:textSize="16sp" />
+
+    <TextView
+        android:id="@+id/description"
+        style="@style/TextTitle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:visibility="gone"
+        android:textColor="?android:attr/textColorPrimary"
+        android:textSize="14sp" />
+
+    <TextView
+        android:id="@+id/breadcrumbs"
+        style="@style/TextTitle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:visibility="gone"
+        android:alpha=".7"
+        android:textColor="?android:attr/textColorSecondary"
+        android:textSize="14sp" />
+
+
+</com.android.launcher3.views.SearchSettingsRowView>
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/res/values/config.xml b/res/layout/search_result_slice.xml
similarity index 69%
rename from quickstep/recents_ui_overrides/res/values/config.xml
rename to res/layout/search_result_slice.xml
index 120e034..ea1d49a 100644
--- a/quickstep/recents_ui_overrides/res/values/config.xml
+++ b/res/layout/search_result_slice.xml
@@ -5,7 +5,7 @@
      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,6 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<resources>
-    <integer name="max_depth_blur_radius">150</integer>
-</resources>
\ No newline at end of file
+<androidx.slice.widget.SliceView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingHorizontal="4dp" />
\ No newline at end of file
diff --git a/res/layout/search_section_title.xml b/res/layout/search_section_title.xml
new file mode 100644
index 0000000..9419015
--- /dev/null
+++ b/res/layout/search_section_title.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.views.SearchSectionHeaderView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/section_title"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:fontFamily="@style/TextHeadline"
+    android:padding="4dp"
+    android:textColor="?android:attr/textColorPrimary"
+    android:textSize="14sp" />
\ No newline at end of file
diff --git a/res/mipmap-hdpi/ic_launcher_home.png b/res/mipmap-hdpi/ic_launcher_home.png
deleted file mode 100644
index d068d92..0000000
--- a/res/mipmap-hdpi/ic_launcher_home.png
+++ /dev/null
Binary files differ
diff --git a/res/mipmap-mdpi/ic_launcher_home.png b/res/mipmap-mdpi/ic_launcher_home.png
deleted file mode 100644
index 16c8ec2..0000000
--- a/res/mipmap-mdpi/ic_launcher_home.png
+++ /dev/null
Binary files differ
diff --git a/res/mipmap-xhdpi/ic_launcher_home.png b/res/mipmap-xhdpi/ic_launcher_home.png
deleted file mode 100644
index 8b2671b..0000000
--- a/res/mipmap-xhdpi/ic_launcher_home.png
+++ /dev/null
Binary files differ
diff --git a/res/mipmap-xxhdpi/ic_launcher_home.png b/res/mipmap-xxhdpi/ic_launcher_home.png
deleted file mode 100644
index 43d8b7d..0000000
--- a/res/mipmap-xxhdpi/ic_launcher_home.png
+++ /dev/null
Binary files differ
diff --git a/res/values-night-v26/styles.xml b/res/values-night/styles.xml
similarity index 100%
rename from res/values-night-v26/styles.xml
rename to res/values-night/styles.xml
diff --git a/res/values-v22/styles.xml b/res/values-v22/styles.xml
deleted file mode 100644
index f86db7a..0000000
--- a/res/values-v22/styles.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-* Copyright (C) 2018 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>
-
-    <style name="AppItemActivityTheme" parent="@android:style/Theme.DeviceDefault.Light.Dialog.Alert">
-        <item name="widgetsTheme">@style/WidgetContainerTheme</item>
-    </style>
-
-</resources>
\ No newline at end of file
diff --git a/res/values-v26/bools.xml b/res/values-v26/bools.xml
deleted file mode 100644
index ad8c7a1..0000000
--- a/res/values-v26/bools.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?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.
-*/
--->
-
-<resources>
-    <bool name="notification_dots_enabled">true</bool>
-
-    <bool name="enable_install_shortcut_api">false</bool>
-</resources>
\ No newline at end of file
diff --git a/res/values-v26/styles.xml b/res/values-v26/styles.xml
deleted file mode 100644
index d2f0802..0000000
--- a/res/values-v26/styles.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-* Copyright (C) 2017 The Android Open Source Project
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-*      http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
--->
-<resources>
-    <!-- Theme for the widget container. -->
-    <style name="WidgetContainerTheme" parent="@android:style/Theme.DeviceDefault.Settings">
-        <item name="android:colorPrimaryDark">#E8EAED</item>
-        <item name="android:textColorSecondary">?android:attr/textColorPrimary</item>
-        <item name="android:colorEdgeEffect">?android:attr/textColorSecondary</item>
-    </style>
-    <style name="WidgetContainerTheme.Dark" parent="AppTheme.Dark">
-        <item name="android:colorEdgeEffect">?android:attr/textColorSecondary</item>
-        <item name="android:colorPrimaryDark">#616161</item> <!-- Gray 700 -->
-    </style>
-
-</resources>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 26e6cba..acb8221 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -57,6 +57,7 @@
             <enum name="folder" value="2" />
             <enum name="widget_section" value="3" />
             <enum name="shortcut_popup" value="4" />
+            <enum name="hero_app" value="5" />
         </attr>
         <attr name="centerVertically" format="boolean" />
     </declare-styleable>
diff --git a/res/values/bools.xml b/res/values/bools.xml
deleted file mode 100644
index bc2c678..0000000
--- a/res/values/bools.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?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.
-*/
--->
-
-<resources>
-    <bool name="notification_dots_enabled">false</bool>
-
-    <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 043ad9a..f56fbaa 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -35,9 +35,6 @@
 
     <color name="icon_background">#E0E0E0</color> <!-- Gray 300 -->
 
-    <color name="all_apps_bg_hand_fill">#E5E5E5</color>
-    <color name="all_apps_bg_hand_fill_dark">#9AA0A6</color>
-
     <color name="gesture_tutorial_ripple_color">#A0C2F9</color> <!-- Light Blue -->
     <color name="gesture_tutorial_fake_task_view_color">#6DA1FF</color> <!-- Light Blue -->
     <color name="gesture_tutorial_action_button_label_color">#FFFFFFFF</color>
diff --git a/res/values/config.xml b/res/values/config.xml
index ca25325..fc0a5e1 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -69,7 +69,7 @@
     <string name="app_launch_tracker_class" translatable="false"></string>
     <string name="test_information_handler_class" translatable="false"></string>
     <string name="launcher_activity_logic_class" translatable="false"></string>
-    <string name="prediction_model_class" translatable="false"></string>
+    <string name="model_delegate_class" translatable="false"></string>
 
     <!-- View ID to use for QSB widget -->
     <item type="id" name="qsb_widget" />
@@ -142,7 +142,7 @@
 
     <item name="staggered_damping_ratio" type="dimen" format="float">0.7</item>
     <item name="staggered_stiffness" type="dimen" format="float">150</item>
-    <dimen name="unlock_staggered_velocity_dp_per_s">3dp</dimen>
+    <dimen name="unlock_staggered_velocity_dp_per_s">4dp</dimen>
 
     <item name="hint_scale_damping_ratio" type="dimen" format="float">0.7</item>
     <item name="hint_scale_stiffness" type="dimen" format="float">200</item>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 947e635..f59f02f 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -40,14 +40,6 @@
     <dimen name="workspace_page_indicator_line_height">1dp</dimen>
     <dimen name="workspace_page_indicator_overlap_workspace">0dp</dimen>
 
-    <!-- Hotseat/all-apps scrim -->
-    <dimen name="all_apps_scrim_blur">4dp</dimen>
-    <dimen name="vertical_drag_handle_width">18dp</dimen>
-    <dimen name="vertical_drag_handle_height">6dp</dimen>
-    <dimen name="vertical_drag_handle_elevation">1dp</dimen>
-    <dimen name="vertical_drag_handle_touch_size">48dp</dimen>
-    <dimen name="vertical_drag_handle_padding_in_vertical_bar_layout">16dp</dimen>
-
 <!-- Drop target bar -->
     <dimen name="dynamic_grid_drop_target_size">48dp</dimen>
     <dimen name="drop_target_vertical_gap">20dp</dimen>
@@ -247,6 +239,9 @@
     <dimen name="snackbar_min_text_size">12sp</dimen>
     <dimen name="snackbar_max_text_size">14sp</dimen>
 
+<!-- Developer Options -->
+    <dimen name="developer_options_filter_margins">10dp</dimen>
+
 <!-- Theming related -->
     <dimen name="default_dialog_corner_radius">8dp</dimen>
 
diff --git a/res/values/drawables.xml b/res/values/drawables.xml
deleted file mode 100644
index 7d63142..0000000
--- a/res/values/drawables.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-        http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<resources>
-    <drawable name="ic_setup_shadow">@drawable/ic_setting</drawable>
-    <drawable name="ic_remove_shadow">@drawable/ic_remove_no_shadow</drawable>
-    <drawable name="ic_uninstall_shadow">@drawable/ic_uninstall_no_shadow</drawable>
-    <drawable name="ic_block_shadow">@drawable/ic_block_no_shadow</drawable>
-    <drawable name="all_apps_arrow_shadow">@drawable/drag_handle_indicator_no_shadow</drawable>
-</resources>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 935bb40..ad3e2b7 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -36,7 +36,7 @@
     <!-- 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>
+    <string name="home_screen">Home</string>
     <!-- Label for showing custom action list of a shortcut or widget. [CHAR_LIMIT=30] -->
     <string name="custom_actions">Custom actions</string>
 
@@ -67,6 +67,12 @@
     <!-- Label for an icon representing any generic app. [CHAR_LIMIT=50] -->
     <string name="label_application">App</string>
 
+    <!--All apps Search-->
+    <!-- Section title for apps [CHAR_LIMIT=50] -->
+    <string name="search_corpus_apps">Apps</string>
+    <!-- try instant app action for play search result [CHAR_LIMIT=50 -->
+    <string name="search_action_try_now">Try Now</string>
+
     <!-- Popup items -->
     <!-- Text to display as the header above notifications. [CHAR_LIMIT=30] -->
     <string name="notifications_header">Notifications</string>
@@ -88,10 +94,6 @@
     <string name="all_apps_button_personal_label">Personal apps list</string>
     <string name="all_apps_button_work_label">Work apps list</string>
 
-    <!-- Label for button in all applications label to go back home (to the workspace / desktop)
-         for accessibilty (spoken when the button gets focus). -->
-    <string name="all_apps_home_button_label">Home</string>
-
     <!-- Label for remove drop target (from the homescreen only).
          May appear next to uninstall_drop_target_label [CHAR_LIMIT=20] -->
     <string name="remove_drop_target_label">Remove</string>
@@ -348,7 +350,8 @@
     <!-- content description for paused work apps list -->
     <string name="work_apps_paused_content_description">Work profile is paused. Work apps can\’t send you notifications, use your battery, or access your location</string>
 
-
+    <!-- A hint shown in launcher settings develop options filter box -->
+    <string name="developer_options_filter_hint">Filter</string>
 
     <!-- A tip shown pointing at work toggle -->
     <string name="work_switch_tip">Pause work apps and notifications</string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 25f21f3..fd3d873 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -143,12 +143,21 @@
     <style name="AppTheme.Dark.DarkMainColor" parent="@style/LauncherTheme.Dark.DarkMainColor" />
     <style name="AppTheme.Dark.DarkText" parent="@style/LauncherTheme.Dark.DarkText" />
 
-    <style name="AppItemActivityTheme" parent="@android:style/Theme.Material.Light.Dialog.Alert">
+    <style name="AppItemActivityTheme" parent="@android:style/Theme.DeviceDefault.Light.Dialog.Alert">
         <item name="widgetsTheme">@style/WidgetContainerTheme</item>
     </style>
 
     <style name="HomeSettingsTheme" parent="@android:style/Theme.DeviceDefault.Settings">
         <item name="android:navigationBarColor">@android:color/transparent</item>
+        <item name="preferenceTheme">@style/HomeSettingsPreferenceTheme</item>
+    </style>
+
+    <style name="HomeSettingsPreferenceTheme" parent="@style/PreferenceThemeOverlay.v14.Material">
+        <item name="preferenceFragmentCompatStyle">@style/HomeSettingsFragmentCompatStyle</item>
+    </style>
+
+    <style name="HomeSettingsFragmentCompatStyle" parent="@style/PreferenceFragment.Material">
+        <item name="android:layout">@layout/home_settings</item>
     </style>
 
     <!--
@@ -162,14 +171,16 @@
         <item name="android:textColorSecondary">?attr/workspaceTextColor</item>
     </style>
 
-    <!-- Theme for the widget container. Overridden on API 26. -->
+    <!-- Theme for the widget container. -->
     <style name="WidgetContainerTheme" parent="@android:style/Theme.DeviceDefault.Settings">
-        <item name="android:colorEdgeEffect">?android:attr/textColorSecondaryInverse</item>
-        <item name="android:textColorPrimary">?android:attr/textColorPrimaryInverse</item>
-        <item name="android:textColorSecondary">?android:attr/textColorSecondaryInverse</item>
+        <item name="android:colorPrimaryDark">#E8EAED</item>
+        <item name="android:textColorSecondary">?android:attr/textColorPrimary</item>
+        <item name="android:colorEdgeEffect">?android:attr/textColorSecondary</item>
     </style>
-
-    <style name="WidgetContainerTheme.Dark" />
+    <style name="WidgetContainerTheme.Dark" parent="AppTheme.Dark">
+        <item name="android:colorEdgeEffect">?android:attr/textColorSecondary</item>
+        <item name="android:colorPrimaryDark">#616161</item> <!-- Gray 700 -->
+    </style>
 
     <style name="FastScrollerPopup" parent="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle">
         <item name="android:layout_width">wrap_content</item>
diff --git a/res/xml/launcher_preferences.xml b/res/xml/launcher_preferences.xml
index 3455cb8..7e72208 100644
--- a/res/xml/launcher_preferences.xml
+++ b/res/xml/launcher_preferences.xml
@@ -44,12 +44,6 @@
         android:defaultValue="@bool/allow_rotation"
         android:persistent="true" />
 
-    <SwitchPreference
-        android:key="pref_grid_options"
-        android:title="Enable grid options"
-        android:defaultValue="false"
-        android:persistent="true" />
-
     <androidx.preference.PreferenceScreen
         android:key="pref_developer_options"
         android:persistent="false"
diff --git a/robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java b/robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
index 5610b0e..9ac3fe7 100644
--- a/robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
@@ -53,10 +53,10 @@
         mModelHelper = new LauncherModelHelper();
         mModelHelper.initializeData("/cache_data_updated_task_data.txt");
 
-        // Add dummy entries in the cache to simulate update
+        // Add placeholder entries in the cache to simulate update
         Context context = RuntimeEnvironment.application;
         IconCache iconCache = LauncherAppState.getInstance(context).getIconCache();
-        CachingLogic<ItemInfo> dummyLogic = new CachingLogic<ItemInfo>() {
+        CachingLogic<ItemInfo> placeholderLogic = new CachingLogic<ItemInfo>() {
             @Override
             public ComponentName getComponent(ItemInfo info) {
                 return info.getTargetComponent();
@@ -81,7 +81,7 @@
 
         UserManager um = context.getSystemService(UserManager.class);
         for (ItemInfo info : mModelHelper.getBgDataModel().itemsIdMap) {
-            iconCache.addIconToDBAndMemCache(info, dummyLogic, new PackageInfo(),
+            iconCache.addIconToDBAndMemCache(info, placeholderLogic, new PackageInfo(),
                     um.getSerialNumberForUser(info.user), true);
         }
     }
diff --git a/robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java b/robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java
index bbbe21e..be03c7d 100644
--- a/robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java
@@ -165,7 +165,7 @@
             @Override
             public void onOpen(SQLiteDatabase db) { }
         };
-        // Insert dummy data
+        // Insert mock data
         for (int i = 0; i < 10; i++) {
             ContentValues values = new ContentValues();
             values.put(Favorites._ID, i);
diff --git a/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java b/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
index 8f3a83e..655237d 100644
--- a/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
@@ -109,7 +109,7 @@
     public void testCustomProfileLoaded_with_widget() throws Exception {
         String pendingAppPkg = "com.test.pending";
 
-        // Add a dummy session info so that the widget exists
+        // Add a placeholder session info so that the widget exists
         SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
         params.setAppPackageName(pendingAppPkg);
 
@@ -120,7 +120,7 @@
         setField(sessionInfo, "appIcon", BitmapInfo.LOW_RES_ICON);
 
         writeLayoutAndLoad(new LauncherLayoutBuilder().atWorkspace(0, 1, 0)
-                .putWidget(pendingAppPkg, "DummyWidget", 2, 2));
+                .putWidget(pendingAppPkg, "PlaceholderWidget", 2, 2));
 
         // Verify widget
         assertEquals(1, mModelHelper.getBgDataModel().appWidgets.size());
diff --git a/robolectric_tests/src/com/android/launcher3/model/LoaderCursorTest.java b/robolectric_tests/src/com/android/launcher3/model/LoaderCursorTest.java
index 3a252dc..fb08c56 100644
--- a/robolectric_tests/src/com/android/launcher3/model/LoaderCursorTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/LoaderCursorTest.java
@@ -51,7 +51,7 @@
 
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.util.Executors;
@@ -92,9 +92,9 @@
                 SCREEN, CELLX, CELLY, RESTORED, INTENT
         });
 
-        mLoaderCursor = new LoaderCursor(mCursor, LauncherSettings.Favorites.CONTENT_URI, mApp,
-                new UserManagerState());
-        mLoaderCursor.allUsers.put(0, Process.myUserHandle());
+        UserManagerState ums = new UserManagerState();
+        mLoaderCursor = new LoaderCursor(mCursor, Favorites.CONTENT_URI, mApp, ums);
+        ums.allUsers.put(0, Process.myUserHandle());
     }
 
     private void initCursor(int itemType, String title) {
@@ -110,7 +110,7 @@
     public void getAppShortcutInfo_dontAllowMissing_invalidComponent() {
         initCursor(ITEM_TYPE_APPLICATION, "");
         assertTrue(mLoaderCursor.moveToNext());
-        ComponentName cn = new ComponentName(mContext.getPackageName(), "dummy-do");
+        ComponentName cn = new ComponentName(mContext.getPackageName(), "placeholder-do");
         assertNull(mLoaderCursor.getAppShortcutInfo(
                 new Intent().setComponent(cn), false /* allowMissingTarget */, true));
     }
@@ -136,7 +136,7 @@
         initCursor(ITEM_TYPE_APPLICATION, "");
         assertTrue(mLoaderCursor.moveToNext());
 
-        ComponentName cn = new ComponentName(mContext.getPackageName(), "dummy-do");
+        ComponentName cn = new ComponentName(mContext.getPackageName(), "placeholder-do");
         WorkspaceItemInfo info = Executors.MODEL_EXECUTOR.submit(() ->
                 mLoaderCursor.getAppShortcutInfo(
                         new Intent().setComponent(cn), true  /* allowMissingTarget */, true))
diff --git a/robolectric_tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java b/robolectric_tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
index ee73b82..4184d33 100644
--- a/robolectric_tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
+++ b/robolectric_tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
@@ -44,7 +44,7 @@
     @Test
     public void testMigrateProfileId() throws Exception {
         SQLiteDatabase db = new MyDatabaseHelper(42).getWritableDatabase();
-        // Add some dummy data
+        // Add some mock data
         for (int i = 0; i < 5; i++) {
             ContentValues values = new ContentValues();
             values.put(Favorites._ID, i);
@@ -64,7 +64,7 @@
     @Test
     public void testChangeDefaultColumn() throws Exception {
         SQLiteDatabase db = new MyDatabaseHelper(42).getWritableDatabase();
-        // Add some dummy data
+        // Add some mock data
         for (int i = 0; i < 5; i++) {
             ContentValues values = new ContentValues();
             values.put(Favorites._ID, i);
diff --git a/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java b/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java
index 0388087..f2b3071 100644
--- a/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java
+++ b/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java
@@ -81,7 +81,7 @@
     public static final int NO__ICON = -1;
     public static final String TEST_PACKAGE = "com.android.launcher3.validpackage";
 
-    // Authority for providing a dummy default-workspace-layout data.
+    // Authority for providing a test default-workspace-layout data.
     private static final String TEST_PROVIDER_AUTHORITY =
             LauncherModelHelper.class.getName().toLowerCase();
     private static final int DEFAULT_BITMAP_SIZE = 10;
@@ -252,7 +252,7 @@
     }
 
     /**
-     * Adds a dummy item in the DB.
+     * Adds a mock item in the DB.
      * @param type {@link #APP_ICON} or {@link #SHORTCUT} or >= 2 for
      *             folder (where the type represents the number of items in the folder).
      */
@@ -310,7 +310,7 @@
     }
 
     /**
-     * Initializes the DB with dummy elements to represent the provided grid structure.
+     * Initializes the DB with mock elements to represent the provided grid structure.
      * @param typeArray A 3d array of item types. {@see #addItem(int, long, long, int, int)} for
      *                  type definitions. The first dimension represents the screens and the next
      *                  two represent the workspace grid.
@@ -347,7 +347,7 @@
     }
 
     /**
-     * Sets up a dummy provider to load the provided layout by default, next time the layout loads
+     * Sets up a mock provider to load the provided layout by default, next time the layout loads
      */
     public LauncherModelHelper setupDefaultLayoutProvider(LauncherLayoutBuilder builder)
             throws Exception {
@@ -360,7 +360,7 @@
                 "launcher3.layout.provider", TEST_PROVIDER_AUTHORITY);
 
         shadowOf(context.getPackageManager())
-                .addProviderIfNotPresent(new ComponentName("com.test", "Dummy")).authority =
+                .addProviderIfNotPresent(new ComponentName("com.test", "Mock")).authority =
                 TEST_PROVIDER_AUTHORITY;
 
         ByteArrayOutputStream bos = new ByteArrayOutputStream();
diff --git a/robolectric_tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java b/robolectric_tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java
index 84c65b1..5ab3106 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java
@@ -129,7 +129,7 @@
         ShadowPackageManager spm = shadowOf(mContext.getPackageManager());
 
         for (int i = 0; i < num; i++) {
-            ComponentName cn = new ComponentName("com.dummy.apk" + i, "DummyWidet");
+            ComponentName cn = new ComponentName("com.placeholder.apk" + i, "PlaceholderWidet");
 
             AppWidgetProviderInfo widgetInfo = new AppWidgetProviderInfo();
             widgetInfo.provider = cn;
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index cd27a2d..3c34444 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3;
 
+import static android.animation.ValueAnimator.areAnimatorsEnabled;
 import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
 import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
 
@@ -35,8 +36,6 @@
 import androidx.annotation.IntDef;
 
 import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.views.BaseDragLayer;
@@ -62,7 +61,8 @@
             TYPE_ALL_APPS_EDU,
 
             TYPE_TASK_MENU,
-            TYPE_OPTIONS_POPUP
+            TYPE_OPTIONS_POPUP,
+            TYPE_ICON_SURFACE
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface FloatingViewType {}
@@ -80,16 +80,18 @@
     // Popups related to quickstep UI
     public static final int TYPE_TASK_MENU = 1 << 10;
     public static final int TYPE_OPTIONS_POPUP = 1 << 11;
+    public static final int TYPE_ICON_SURFACE = 1 << 12;
 
     public static final int TYPE_ALL = TYPE_FOLDER | TYPE_ACTION_POPUP
             | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGET_RESIZE_FRAME | TYPE_WIDGETS_FULL_SHEET
             | TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE | TYPE_TASK_MENU
-            | TYPE_OPTIONS_POPUP | TYPE_SNACKBAR | TYPE_LISTENER | TYPE_ALL_APPS_EDU;
+            | TYPE_OPTIONS_POPUP | TYPE_SNACKBAR | TYPE_LISTENER | TYPE_ALL_APPS_EDU
+            | TYPE_ICON_SURFACE;
 
     // Type of popups which should be kept open during launcher rebind
     public static final int TYPE_REBIND_SAFE = TYPE_WIDGETS_FULL_SHEET
             | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE
-            | TYPE_ALL_APPS_EDU;
+            | TYPE_ALL_APPS_EDU | TYPE_ICON_SURFACE;
 
     // Usually we show the back button when a floating view is open. Instead, hide for these types.
     public static final int TYPE_HIDE_BACK_BUTTON = TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE
@@ -123,10 +125,9 @@
     }
 
     public final void close(boolean animate) {
-        animate &= Utilities.areAnimationsEnabled(getContext());
+        animate &= areAnimatorsEnabled();
         if (mIsOpen) {
-            BaseActivity.fromContext(getContext()).getUserEventDispatcher()
-                    .resetElapsedContainerMillis("container closed");
+            // Add to WW logging
         }
         handleClose(animate);
         mIsOpen = false;
@@ -141,12 +142,6 @@
     public void addHintCloseAnim(
             float distanceToMove, Interpolator interpolator, PendingAnimation target) { }
 
-    public abstract void logActionCommand(int command);
-
-    public int getLogContainerType() {
-        return ContainerType.DEFAULT_CONTAINERTYPE;
-    }
-
     public final boolean isOpen() {
         return mIsOpen;
     }
@@ -155,7 +150,6 @@
 
     /** @return Whether the back is consumed. If false, Launcher will handle the back as well. */
     public boolean onBackPressed() {
-        logActionCommand(Action.Command.BACK);
         close(true);
         return true;
     }
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index 6f7b684..168d9c4 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -537,11 +537,6 @@
     }
 
     @Override
-    public void logActionCommand(int command) {
-        // TODO: Log this case.
-    }
-
-    @Override
     protected boolean isOfType(int type) {
         return (type & TYPE_WIDGET_RESIZE_FRAME) != 0;
     }
diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java
index 432073e..f61bc05 100644
--- a/src/com/android/launcher3/AutoInstallsLayout.java
+++ b/src/com/android/launcher3/AutoInstallsLayout.java
@@ -658,7 +658,7 @@
         }
     }
 
-    protected static void beginDocument(XmlPullParser parser, String firstElementName)
+    public static void beginDocument(XmlPullParser parser, String firstElementName)
             throws XmlPullParserException, IOException {
         int type;
         while ((type = parser.next()) != XmlPullParser.START_TAG
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index 9cb8cf2..0d90602 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -17,7 +17,7 @@
 package com.android.launcher3;
 
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
-import static com.android.launcher3.util.DefaultDisplay.CHANGE_ROTATION;
+import static com.android.launcher3.util.DisplayController.DisplayHolder.CHANGE_ROTATION;
 
 import android.app.ActivityOptions;
 import android.content.ActivityNotFoundException;
@@ -42,17 +42,17 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.Launcher.OnResumeCallback;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.logging.InstanceIdSequence;
-import com.android.launcher3.model.AppLaunchTracker;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.uioverrides.WallpaperColorInfo;
-import com.android.launcher3.util.DefaultDisplay;
-import com.android.launcher3.util.DefaultDisplay.DisplayInfoChangeListener;
-import com.android.launcher3.util.DefaultDisplay.Info;
+import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
+import com.android.launcher3.util.DisplayController.Info;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.TraceHelper;
@@ -82,9 +82,9 @@
         super.onCreate(savedInstanceState);
 
 
-        mIsSafeModeEnabled = TraceHelper.whitelistIpcs("isSafeMode",
+        mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode",
                 () -> getPackageManager().isSafeMode());
-        DefaultDisplay.INSTANCE.get(this).addChangeListener(this);
+        DisplayController.getDefaultDisplay(this).addChangeListener(this);
 
         // Update theme
         WallpaperColorInfo.INSTANCE.get(this).addOnChangeListener(this);
@@ -108,10 +108,20 @@
 
     private void updateTheme() {
         if (mThemeRes != Themes.getActivityThemeRes(this)) {
-            recreate();
+            // Workaround (b/162812884): The system currently doesn't allow recreating an activity
+            // when it is not resumed, in such a case defer recreation until it is possible
+            if (hasBeenResumed()) {
+                recreate();
+            } else {
+                addOnResumeCallback(this::recreate);
+            }
         }
     }
 
+    protected void addOnResumeCallback(OnResumeCallback callback) {
+        // To be overridden
+    }
+
     @Override
     public void onActionModeStarted(ActionMode mode) {
         super.onActionModeStarted(mode);
@@ -154,8 +164,7 @@
 
     public abstract ActivityOptions getActivityLaunchOptions(View v);
 
-    public boolean startActivitySafely(View v, Intent intent, @Nullable ItemInfo item,
-            @Nullable String sourceContainer) {
+    public boolean startActivitySafely(View v, Intent intent, @Nullable ItemInfo item) {
         if (mIsSafeModeEnabled && !PackageManagerHelper.isSystemApp(this, intent)) {
             Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
             return false;
@@ -176,19 +185,14 @@
                     && !((WorkspaceItemInfo) item).isPromise();
             if (isShortcut) {
                 // Shortcuts need some special checks due to legacy reasons.
-                startShortcutIntentSafely(intent, optsBundle, item, sourceContainer);
+                startShortcutIntentSafely(intent, optsBundle, item);
             } else if (user == null || user.equals(Process.myUserHandle())) {
                 // Could be launching some bookkeeping activity
                 startActivity(intent, optsBundle);
-                AppLaunchTracker.INSTANCE.get(this).onStartApp(intent.getComponent(),
-                        Process.myUserHandle(), sourceContainer);
             } else {
                 getSystemService(LauncherApps.class).startMainActivity(
                         intent.getComponent(), user, intent.getSourceBounds(), optsBundle);
-                AppLaunchTracker.INSTANCE.get(this).onStartApp(intent.getComponent(), user,
-                        sourceContainer);
             }
-            getUserEventDispatcher().logAppLaunch(v, intent, user);
             if (item != null) {
                 InstanceId instanceId = new InstanceIdSequence().newInstanceId();
                 logAppLaunch(item, instanceId);
@@ -206,8 +210,7 @@
                 .log(LAUNCHER_APP_LAUNCH_TAP);
     }
 
-    private void startShortcutIntentSafely(Intent intent, Bundle optsBundle, ItemInfo info,
-            @Nullable String sourceContainer) {
+    private void startShortcutIntentSafely(Intent intent, Bundle optsBundle, ItemInfo info) {
         try {
             StrictMode.VmPolicy oldPolicy = StrictMode.getVmPolicy();
             try {
@@ -221,8 +224,6 @@
                     String id = ((WorkspaceItemInfo) info).getDeepShortcutId();
                     String packageName = intent.getPackage();
                     startShortcut(packageName, id, intent.getSourceBounds(), optsBundle, info.user);
-                    AppLaunchTracker.INSTANCE.get(this).onStartShortcut(packageName, id, info.user,
-                            sourceContainer);
                 } else {
                     // Could be launching some bookkeeping activity
                     startActivity(intent, optsBundle);
@@ -255,7 +256,7 @@
     protected void onDestroy() {
         super.onDestroy();
         WallpaperColorInfo.INSTANCE.get(this).removeOnChangeListener(this);
-        DefaultDisplay.INSTANCE.get(this).removeChangeListener(this);
+        DisplayController.getDefaultDisplay(this).removeChangeListener(this);
     }
 
     public void runOnceOnStart(Runnable action) {
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index 8eceec0..c55b46b 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -16,11 +16,8 @@
 
 package com.android.launcher3;
 
-import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
-
 import android.content.Context;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
@@ -29,8 +26,6 @@
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.launcher3.compat.AccessibilityManagerCompat;
-import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.views.RecyclerViewFastScroller;
 
 
@@ -193,14 +188,4 @@
         super.onInitializeAccessibilityNodeInfo(info);
         if (isLayoutSuppressed()) info.setScrollable(false);
     }
-
-    @Override
-    public void setLayoutFrozen(boolean frozen) {
-        final boolean changing = frozen != isLayoutSuppressed();
-        super.setLayoutFrozen(frozen);
-        if (changing) {
-            ActivityContext.lookupContext(getContext()).getDragLayer()
-                    .sendAccessibilityEvent(TYPE_WINDOW_CONTENT_CHANGED);
-        }
-    }
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 60b6da6..06bb263 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -77,8 +77,9 @@
     private static final int DISPLAY_WORKSPACE = 0;
     private static final int DISPLAY_ALL_APPS = 1;
     private static final int DISPLAY_FOLDER = 2;
+    private static final int DISPLAY_HERO_APP = 5;
 
-    private static final int[] STATE_PRESSED = new int[] {android.R.attr.state_pressed};
+    private static final int[] STATE_PRESSED = new int[]{android.R.attr.state_pressed};
 
     private final PointF mTranslationForReorderBounce = new PointF(0, 0);
     private final PointF mTranslationForReorderPreview = new PointF(0, 0);
@@ -178,6 +179,8 @@
             setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.folderChildTextSizePx);
             setCompoundDrawablePadding(grid.folderChildDrawablePaddingPx);
             defaultIconSize = grid.folderChildIconSizePx;
+        } else if (mDisplay == DISPLAY_HERO_APP) {
+            defaultIconSize = grid.allAppsIconSizePx;
         } else {
             // widget_selection or shortcut_popup
             defaultIconSize = grid.iconSizePx;
@@ -277,7 +280,10 @@
         applyDotState(info, false /* animate */);
     }
 
-    public void applyFromPackageItemInfo(PackageItemInfo info) {
+    /**
+     * Apply label and tag using a generic {@link ItemInfoWithIcon}
+     */
+    public void applyFromItemInfoWithIcon(ItemInfoWithIcon info) {
         applyIconAndLabel(info);
         // We don't need to check the info since it's not a WorkspaceItemInfo
         super.setTag(info);
@@ -331,10 +337,7 @@
     public boolean onTouchEvent(MotionEvent event) {
         // ignore events if they happen in padding area
         if (event.getAction() == MotionEvent.ACTION_DOWN
-                && (event.getY() < getPaddingTop()
-                || event.getX() < getPaddingLeft()
-                || event.getY() > getHeight() - getPaddingBottom()
-                || event.getX() > getWidth() - getPaddingRight())) {
+                && shouldIgnoreTouchDown(event.getX(), event.getY())) {
             return false;
         }
         if (isLongClickable()) {
@@ -347,6 +350,16 @@
         }
     }
 
+    /**
+     * Returns true if the touch down at the provided position be ignored
+     */
+    protected boolean shouldIgnoreTouchDown(float x, float y) {
+        return y < getPaddingTop()
+                || x < getPaddingLeft()
+                || y > getHeight() - getPaddingBottom()
+                || x > getWidth() - getPaddingRight();
+    }
+
     void setStayPressed(boolean stayPressed) {
         mStayPressed = stayPressed;
         refreshDrawableState();
@@ -397,12 +410,14 @@
 
     /**
      * Draws the notification dot in the top right corner of the icon bounds.
+     *
      * @param canvas The canvas to draw to.
      */
     protected void drawDotIfNecessary(Canvas canvas) {
         if (!mForceHideDot && (hasDot() || mDotParams.scale > 0)) {
             getIconBounds(mDotParams.iconBounds);
-            Utilities.scaleRectAboutCenter(mDotParams.iconBounds, IconShape.getNormalizationScale());
+            Utilities.scaleRectAboutCenter(mDotParams.iconBounds,
+                    IconShape.getNormalizationScale());
             final int scrollX = getScrollX();
             final int scrollY = getScrollY();
             canvas.translate(scrollX, scrollY);
@@ -497,6 +512,7 @@
 
     /**
      * Creates an animator to fade the text in or out.
+     *
      * @param fadeIn Whether the text should fade in or fade out.
      */
     public ObjectAnimator createTextAlphaAnimator(boolean fadeIn) {
@@ -607,6 +623,9 @@
     @Override
     public void setIconVisible(boolean visible) {
         mIsIconVisible = visible;
+        if (!mIsIconVisible) {
+            resetIconScale();
+        }
         Drawable icon = visible ? mIcon : new ColorDrawable(Color.TRANSPARENT);
         applyCompoundDrawables(icon);
     }
@@ -650,7 +669,7 @@
                 applyFromWorkspaceItem((WorkspaceItemInfo) info);
                 mActivity.invalidateParent(info);
             } else if (info instanceof PackageItemInfo) {
-                applyFromPackageItemInfo((PackageItemInfo) info);
+                applyFromItemInfoWithIcon((PackageItemInfo) info);
             }
 
             mDisableRelayout = false;
@@ -746,11 +765,15 @@
 
     @Override
     public SafeCloseable prepareDrawDragView() {
-        if (getIcon() instanceof FastBitmapDrawable) {
-            FastBitmapDrawable icon = (FastBitmapDrawable) getIcon();
-            icon.setScale(1f);
-        }
+        resetIconScale();
         setForceHideDot(true);
-        return () -> { };
+        return () -> {
+        };
+    }
+
+    private void resetIconScale() {
+        if (mIcon instanceof FastBitmapDrawable) {
+            ((FastBitmapDrawable) mIcon).setScale(1f);
+        }
     }
 }
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 89d768c..1cd201f 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3;
 
+import static android.animation.ValueAnimator.areAnimatorsEnabled;
+
 import static com.android.launcher3.anim.Interpolators.DEACCEL_1_5;
 
 import android.animation.Animator;
@@ -2009,7 +2011,7 @@
             // Animations are disabled in power save mode, causing the repeated animation to jump
             // spastically between beginning and end states. Since this looks bad, we don't repeat
             // the animation in power save mode.
-            if (Utilities.areAnimationsEnabled(getContext())) {
+            if (areAnimatorsEnabled()) {
                 va.setRepeatMode(ValueAnimator.REVERSE);
                 va.setRepeatCount(ValueAnimator.INFINITE);
             }
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 49caa93..12ce9f3 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -28,7 +28,8 @@
 import com.android.launcher3.graphics.IconShape;
 import com.android.launcher3.icons.DotRenderer;
 import com.android.launcher3.icons.IconNormalizer;
-import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.DisplayController.Info;
 import com.android.launcher3.util.WindowBounds;
 
 public class DeviceProfile {
@@ -38,7 +39,7 @@
 
 
     public final InvariantDeviceProfile inv;
-    private final DefaultDisplay.Info mInfo;
+    private final Info mInfo;
 
     // Device properties
     public final boolean isTablet;
@@ -140,7 +141,7 @@
     public DotRenderer mDotRendererWorkSpace;
     public DotRenderer mDotRendererAllApps;
 
-    DeviceProfile(Context context, InvariantDeviceProfile inv, DefaultDisplay.Info info,
+    DeviceProfile(Context context, InvariantDeviceProfile inv, Info info,
             Point minSize, Point maxSize, int width, int height, boolean isLandscape,
             boolean isMultiWindowMode, boolean transposeLayoutWithOrientation,
             Point windowPosition) {
@@ -606,7 +607,7 @@
      */
     public boolean updateIsSeascape(Context context) {
         if (isVerticalBarLayout()) {
-            boolean isSeascape = DefaultDisplay.INSTANCE.get(context).getInfo().rotation
+            boolean isSeascape = DisplayController.getDefaultDisplay(context).getInfo().rotation
                     == Surface.ROTATION_270;
             if (mIsSeascape != isSeascape) {
                 mIsSeascape = isSeascape;
@@ -638,7 +639,7 @@
         }
     }
 
-    private static Context getContext(Context c, DefaultDisplay.Info info, int orientation) {
+    private static Context getContext(Context c, Info info, int orientation) {
         Configuration config = new Configuration(c.getResources().getConfiguration());
         config.orientation = orientation;
         config.densityDpi = info.metrics.densityDpi;
@@ -662,7 +663,7 @@
     public static class Builder {
         private Context mContext;
         private InvariantDeviceProfile mInv;
-        private DefaultDisplay.Info mInfo;
+        private Info mInfo;
 
         private final Point mWindowPosition = new Point();
         private Point mMinSize, mMaxSize;
@@ -672,7 +673,7 @@
         private boolean mIsMultiWindowMode = false;
         private boolean mTransposeLayoutWithOrientation;
 
-        public Builder(Context context, InvariantDeviceProfile inv, DefaultDisplay.Info info) {
+        public Builder(Context context, InvariantDeviceProfile inv, Info info) {
             mContext = context;
             mInv = inv;
             mInfo = info;
diff --git a/src/com/android/launcher3/DragSource.java b/src/com/android/launcher3/DragSource.java
index d4d7b99..ba227d4 100644
--- a/src/com/android/launcher3/DragSource.java
+++ b/src/com/android/launcher3/DragSource.java
@@ -19,12 +19,11 @@
 import android.view.View;
 
 import com.android.launcher3.DropTarget.DragObject;
-import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider;
 
 /**
  * Interface defining an object that can originate a drag.
  */
-public interface DragSource extends LogContainerProvider {
+public interface DragSource {
 
     /**
      * A callback made back to the source after an item from this source has been dropped on a
diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java
index b27abc4..fd4c30c 100644
--- a/src/com/android/launcher3/DropTarget.java
+++ b/src/com/android/launcher3/DropTarget.java
@@ -113,18 +113,6 @@
 
             return res;
         }
-
-
-        /**
-         * This is used to determine if an object is dropped at a different location than it was
-         * dragged from
-         */
-        public boolean isMoved() {
-            return dragInfo.cellX != originalDragInfo.cellX
-                    || dragInfo.cellY != originalDragInfo.cellY
-                    || dragInfo.screenId != originalDragInfo.screenId
-                    || dragInfo.container != originalDragInfo.container;
-        }
     }
 
     /**
diff --git a/src/com/android/launcher3/ExtendedEditText.java b/src/com/android/launcher3/ExtendedEditText.java
index d64967b..02c6162 100644
--- a/src/com/android/launcher3/ExtendedEditText.java
+++ b/src/com/android/launcher3/ExtendedEditText.java
@@ -24,6 +24,7 @@
 import android.view.inputmethod.InputMethodManager;
 import android.widget.EditText;
 
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.UiThreadHelper;
 
 
@@ -130,6 +131,10 @@
     public void reset() {
         if (!TextUtils.isEmpty(getText())) {
             setText("");
+        } else {
+            if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
+                return;
+            }
         }
         if (isFocused()) {
             View nextFocus = focusSearch(View.FOCUS_DOWN);
diff --git a/src/com/android/launcher3/FirstFrameAnimatorHelper.java b/src/com/android/launcher3/FirstFrameAnimatorHelper.java
index 6c5bc40..a199a57 100644
--- a/src/com/android/launcher3/FirstFrameAnimatorHelper.java
+++ b/src/com/android/launcher3/FirstFrameAnimatorHelper.java
@@ -15,7 +15,7 @@
  */
 package com.android.launcher3;
 
-import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
+import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
 
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
diff --git a/src/com/android/launcher3/GestureNavContract.java b/src/com/android/launcher3/GestureNavContract.java
new file mode 100644
index 0000000..2a7e629
--- /dev/null
+++ b/src/com/android/launcher3/GestureNavContract.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2020 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 static android.content.Intent.EXTRA_COMPONENT_NAME;
+import static android.content.Intent.EXTRA_USER;
+
+import android.annotation.TargetApi;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.graphics.RectF;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+import android.view.SurfaceControl;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Class to encapsulate the handshake protocol between Launcher and gestureNav.
+ */
+public class GestureNavContract {
+
+    private static final String TAG = "GestureNavContract";
+
+    public static final String EXTRA_GESTURE_CONTRACT = "gesture_nav_contract_v1";
+    public static final String EXTRA_ICON_POSITION = "gesture_nav_contract_icon_position";
+    public static final String EXTRA_ICON_SURFACE = "gesture_nav_contract_surface_control";
+    public static final String EXTRA_REMOTE_CALLBACK = "android.intent.extra.REMOTE_CALLBACK";
+
+    public final ComponentName componentName;
+    public final UserHandle user;
+
+    private final Message mCallback;
+
+    public GestureNavContract(ComponentName componentName, UserHandle user, Message callback) {
+        this.componentName = componentName;
+        this.user = user;
+        this.mCallback = callback;
+    }
+
+    /**
+     * Sends the position information to the receiver
+     */
+    @TargetApi(Build.VERSION_CODES.R)
+    public void sendEndPosition(RectF position, @Nullable SurfaceControl surfaceControl) {
+        Bundle result = new Bundle();
+        result.putParcelable(EXTRA_ICON_POSITION, position);
+        result.putParcelable(EXTRA_ICON_SURFACE, surfaceControl);
+
+        Message callback = Message.obtain();
+        callback.copyFrom(mCallback);
+        callback.setData(result);
+
+        try {
+            callback.replyTo.send(callback);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error sending icon position", e);
+        }
+    }
+
+    /**
+     * Clears and returns the GestureNavContract if it was present in the intent.
+     */
+    public static GestureNavContract fromIntent(Intent intent) {
+        if (!Utilities.ATLEAST_R) {
+            return null;
+        }
+        Bundle extras = intent.getBundleExtra(EXTRA_GESTURE_CONTRACT);
+        if (extras == null) {
+            return null;
+        }
+        intent.removeExtra(EXTRA_GESTURE_CONTRACT);
+
+        ComponentName componentName = extras.getParcelable(EXTRA_COMPONENT_NAME);
+        UserHandle userHandle = extras.getParcelable(EXTRA_USER);
+        Message callback = extras.getParcelable(EXTRA_REMOTE_CALLBACK);
+
+        if (componentName != null && userHandle != null && callback != null
+                && callback.replyTo != null) {
+            return new GestureNavContract(componentName, userHandle, callback);
+        }
+        return null;
+    }
+}
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
deleted file mode 100644
index 62c9b4d..0000000
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ /dev/null
@@ -1,672 +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 static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-
-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.SharedPreferences;
-import android.content.pm.ActivityInfo;
-import android.content.pm.LauncherActivityInfo;
-import android.content.pm.LauncherApps;
-import android.content.pm.PackageManager;
-import android.content.pm.ShortcutInfo;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-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 android.util.Pair;
-
-import androidx.annotation.Nullable;
-import androidx.annotation.WorkerThread;
-
-import com.android.launcher3.icons.BitmapInfo;
-import com.android.launcher3.icons.GraphicsUtils;
-import com.android.launcher3.icons.LauncherIcons;
-import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.LauncherAppWidgetInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.shortcuts.ShortcutKey;
-import com.android.launcher3.shortcuts.ShortcutRequest;
-import com.android.launcher3.util.PackageManagerHelper;
-import com.android.launcher3.util.Preconditions;
-import com.android.launcher3.util.Thunk;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.json.JSONStringer;
-
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Set;
-
-public class InstallShortcutReceiver extends BroadcastReceiver {
-
-    public static final int FLAG_ACTIVITY_PAUSED = 1;
-    public static final int FLAG_LOADER_RUNNING = 2;
-    public static final int FLAG_DRAG_AND_DROP = 4;
-
-    // Determines whether to defer installing shortcuts immediately until
-    // processAllPendingInstalls() is called.
-    private static int sInstallQueueDisabledFlags = 0;
-
-    private static final String TAG = "InstallShortcutReceiver";
-    private static final boolean DBG = false;
-
-    private static final String ACTION_INSTALL_SHORTCUT =
-            "com.android.launcher.action.INSTALL_SHORTCUT";
-
-    private static final String LAUNCH_INTENT_KEY = "intent.launch";
-    private static final String NAME_KEY = "name";
-    private static final String ICON_KEY = "icon";
-    private static final String ICON_RESOURCE_NAME_KEY = "iconResource";
-    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
-    private static final String APPS_PENDING_INSTALL = "apps_to_install";
-
-    public static final int NEW_SHORTCUT_BOUNCE_DURATION = 450;
-    public static final int NEW_SHORTCUT_STAGGER_DELAY = 85;
-
-    @WorkerThread
-    private static void addToQueue(Context context, PendingInstallShortcutInfo info) {
-        String encoded = info.encodeToString();
-        SharedPreferences prefs = Utilities.getPrefs(context);
-        Set<String> strings = prefs.getStringSet(APPS_PENDING_INSTALL, null);
-        strings = (strings != null) ? new HashSet<>(strings) : new HashSet<>(1);
-        strings.add(encoded);
-        prefs.edit().putStringSet(APPS_PENDING_INSTALL, strings).apply();
-    }
-
-    @WorkerThread
-    private static void flushQueueInBackground(Context context) {
-        if (Launcher.ACTIVITY_TRACKER.getCreatedActivity() == null) {
-            // Launcher not loaded
-            return;
-        }
-
-        ArrayList<Pair<ItemInfo, Object>> installQueue = new ArrayList<>();
-        SharedPreferences prefs = Utilities.getPrefs(context);
-        Set<String> strings = prefs.getStringSet(APPS_PENDING_INSTALL, null);
-        if (DBG) Log.d(TAG, "Getting and clearing APPS_PENDING_INSTALL: " + strings);
-        if (strings == null) {
-            return;
-        }
-
-        LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
-        for (String encoded : strings) {
-            PendingInstallShortcutInfo info = decode(encoded, context);
-            if (info == null) {
-                continue;
-            }
-
-            String pkg = getIntentPackage(info.launchIntent);
-            if (!TextUtils.isEmpty(pkg)
-                    && !launcherApps.isPackageEnabled(pkg, info.user)
-                    && !info.isActivity) {
-                if (DBG) {
-                    Log.d(TAG, "Ignoring shortcut for absent package: " + info.launchIntent);
-                }
-                continue;
-            }
-
-            // Generate a shortcut info to add into the model
-            installQueue.add(info.getItemInfo());
-        }
-        prefs.edit().remove(APPS_PENDING_INSTALL).apply();
-        if (!installQueue.isEmpty()) {
-            LauncherAppState.getInstance(context).getModel()
-                    .addAndBindAddedWorkspaceItems(installQueue);
-        }
-    }
-
-    public static void removeFromInstallQueue(Context context, HashSet<String> packageNames,
-            UserHandle user) {
-        if (packageNames.isEmpty()) {
-            return;
-        }
-        Preconditions.assertWorkerThread();
-
-        SharedPreferences sp = Utilities.getPrefs(context);
-        Set<String> strings = sp.getStringSet(APPS_PENDING_INSTALL, null);
-        if (DBG) {
-            Log.d(TAG, "APPS_PENDING_INSTALL: " + strings
-                    + ", removing packages: " + packageNames);
-        }
-        if (strings == null || ((Collection) strings).isEmpty()) {
-            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();
-    }
-
-    public void onReceive(Context context, Intent data) {
-        if (!ACTION_INSTALL_SHORTCUT.equals(data.getAction())) {
-            return;
-        }
-        PendingInstallShortcutInfo info = createPendingInfo(context, data);
-        if (info != null) {
-            if (!info.isLauncherActivity()) {
-                // Since its a custom shortcut, verify that it is safe to launch.
-                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;
-                }
-            }
-            queuePendingShortcutInfo(info, context);
-        }
-    }
-
-    /**
-     * @return true is the extra is either null or is of type {@param type}
-     */
-    private static boolean isValidExtraType(Intent intent, String key, Class type) {
-        Object extra = intent.getParcelableExtra(key);
-        return extra == null || type.isInstance(extra);
-    }
-
-    /**
-     * Verifies the intent and creates a {@link PendingInstallShortcutInfo}
-     */
-    private static PendingInstallShortcutInfo createPendingInfo(Context context, Intent data) {
-        if (!isValidExtraType(data, Intent.EXTRA_SHORTCUT_INTENT, Intent.class) ||
-                !(isValidExtraType(data, Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
-                        Intent.ShortcutIconResource.class)) ||
-                !(isValidExtraType(data, Intent.EXTRA_SHORTCUT_ICON, Bitmap.class))) {
-
-            if (DBG) Log.e(TAG, "Invalid install shortcut intent");
-            return null;
-        }
-
-        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;
-        }
-
-        return convertToLauncherActivityIfPossible(info);
-    }
-
-    public static WorkspaceItemInfo fromShortcutIntent(Context context, Intent data) {
-        PendingInstallShortcutInfo info = createPendingInfo(context, data);
-        return info == null ? null : (WorkspaceItemInfo) info.getItemInfo().first;
-    }
-
-    public static void queueShortcut(ShortcutInfo 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 queueApplication(Intent data, UserHandle user, Context context) {
-        queuePendingShortcutInfo(new PendingInstallShortcutInfo(data, context, user),
-                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 (strings == null || ((Collection) strings).isEmpty()) {
-            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
-        MODEL_EXECUTOR.post(() -> addToQueue(context, info));
-        flushInstallQueue(context);
-    }
-
-    public static void enableInstallQueue(int flag) {
-        sInstallQueueDisabledFlags |= flag;
-    }
-    public static void disableAndFlushInstallQueue(int flag, Context context) {
-        sInstallQueueDisabledFlags &= ~flag;
-        flushInstallQueue(context);
-    }
-
-    static void flushInstallQueue(Context context) {
-        if (sInstallQueueDisabledFlags != 0) {
-            return;
-        }
-        MODEL_EXECUTOR.post(() -> flushQueueInBackground(context));
-    }
-
-    /**
-     * Ensures that we have a valid, non-null name.  If the provided name is null, we will return
-     * the application name instead.
-     */
-    @Thunk static CharSequence ensureValidName(Context context, Intent intent, CharSequence name) {
-        if (name == null) {
-            try {
-                PackageManager pm = context.getPackageManager();
-                ActivityInfo info = pm.getActivityInfo(intent.getComponent(), 0);
-                name = info.loadLabel(pm);
-            } catch (PackageManager.NameNotFoundException nnfe) {
-                return "";
-            }
-        }
-        return name;
-    }
-
-    private static class PendingInstallShortcutInfo {
-
-        final boolean isActivity;
-        @Nullable final ShortcutInfo shortcutInfo;
-        @Nullable final AppWidgetProviderInfo providerInfo;
-
-        @Nullable final Intent data;
-        final Context mContext;
-        final Intent launchIntent;
-        final String label;
-        final UserHandle user;
-
-        /**
-         * Initializes a PendingInstallShortcutInfo received from a different app.
-         */
-        public PendingInstallShortcutInfo(Intent data, UserHandle user, Context context) {
-            isActivity = false;
-            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);
-        }
-
-        /**
-         * Initializes a PendingInstallShortcutInfo to represent a launcher target.
-         */
-        public PendingInstallShortcutInfo(LauncherActivityInfo info, Context context) {
-            isActivity = true;
-            shortcutInfo = null;
-            providerInfo = null;
-
-            String packageName = info.getComponentName().getPackageName();
-            data = new Intent();
-            data.putExtra(Intent.EXTRA_SHORTCUT_INTENT, new Intent().setComponent(
-                    new ComponentName(packageName, "")).setPackage(packageName));
-            data.putExtra(Intent.EXTRA_SHORTCUT_NAME, info.getLabel());
-
-            user = info.getUser();
-            mContext = context;
-
-            launchIntent = AppInfo.makeLaunchIntent(info);
-            label = info.getLabel().toString();
-        }
-
-        /**
-         * Initializes a PendingInstallShortcutInfo to represent a launcher target.
-         */
-        public PendingInstallShortcutInfo(Intent data, Context context, UserHandle user) {
-            isActivity = true;
-            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);
-        }
-
-        /**
-         * Initializes a PendingInstallShortcutInfo to represent a launcher target.
-         */
-        public PendingInstallShortcutInfo(ShortcutInfo info, Context context) {
-            isActivity = false;
-            shortcutInfo = info;
-            providerInfo = null;
-
-            data = null;
-            mContext = context;
-            user = info.getUserHandle();
-
-            launchIntent = ShortcutKey.makeIntent(info);
-            label = info.getShortLabel().toString();
-        }
-
-        /**
-         * Initializes a PendingInstallShortcutInfo to represent a launcher target.
-         */
-        public PendingInstallShortcutInfo(
-                AppWidgetProviderInfo info, int widgetId, Context context) {
-            isActivity = false;
-            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() {
-            try {
-                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(UserCache.INSTANCE.get(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(UserCache.INSTANCE.get(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);
-                }
-
-                // 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 == null ? null
-                        : data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
-                Intent.ShortcutIconResource iconResource = data == null ? null
-                    : data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
-
-                // Only encode the parameters which are supported by the API.
-                JSONStringer json = new JSONStringer()
-                    .object()
-                    .key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0))
-                    .key(NAME_KEY).value(name)
-                    .key(USER_HANDLE_KEY).value(
-                            UserCache.INSTANCE.get(mContext).getSerialNumberForUser(user))
-                    .key(APP_SHORTCUT_TYPE_KEY).value(isActivity);
-                if (icon != null) {
-                    byte[] iconByteArray = GraphicsUtils.flattenBitmap(icon);
-                    if (iconByteArray != null) {
-                        json = json.key(ICON_KEY).value(
-                                Base64.encodeToString(
-                                        iconByteArray, 0, iconByteArray.length, Base64.DEFAULT));
-                    }
-                }
-                if (iconResource != null) {
-                    json = json.key(ICON_RESOURCE_NAME_KEY).value(iconResource.resourceName);
-                    json = json.key(ICON_RESOURCE_PACKAGE_NAME_KEY)
-                            .value(iconResource.packageName);
-                }
-                return json.endObject().toString();
-            } catch (JSONException e) {
-                Log.d(TAG, "Exception when adding shortcut: " + e);
-                return null;
-            }
-        }
-
-        public Pair<ItemInfo, Object> getItemInfo() {
-            if (isActivity) {
-                WorkspaceItemInfo si = createWorkspaceItemInfo(data, user,
-                        LauncherAppState.getInstance(mContext));
-                si.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
-                si.status |= WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON;
-                return Pair.create(si, null);
-            } else if (shortcutInfo != null) {
-                WorkspaceItemInfo itemInfo = new WorkspaceItemInfo(shortcutInfo, mContext);
-                LauncherAppState.getInstance(mContext).getIconCache().getShortcutIcon(
-                        itemInfo, shortcutInfo);
-                return Pair.create(itemInfo, shortcutInfo);
-            } 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 Pair.create(widgetInfo, providerInfo);
-            } else {
-                WorkspaceItemInfo itemInfo =
-                        createWorkspaceItemInfo(data, user, LauncherAppState.getInstance(mContext));
-                return Pair.create(itemInfo, null);
-            }
-        }
-
-        public boolean isLauncherActivity() {
-            return isActivity;
-        }
-    }
-
-    private static String getIntentPackage(Intent intent) {
-        return intent.getComponent() == null
-                ? intent.getPackage() : intent.getComponent().getPackageName();
-    }
-
-    private static PendingInstallShortcutInfo decode(String encoded, Context context) {
-        try {
-            Decoder decoder = new Decoder(encoded, context);
-            if (decoder.optBoolean(APP_SHORTCUT_TYPE_KEY)) {
-                LauncherActivityInfo info = context.getSystemService(LauncherApps.class)
-                        .resolveActivity(decoder.launcherIntent, decoder.user);
-                if (info != null) {
-                    return new PendingInstallShortcutInfo(info, context);
-                }
-            } else if (decoder.optBoolean(DEEPSHORTCUT_TYPE_KEY)) {
-                List<ShortcutInfo> si = ShortcutKey.fromIntent(decoder.launcherIntent, decoder.user)
-                        .buildRequest(context)
-                        .query(ShortcutRequest.ALL);
-                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;
-                }
-                return new PendingInstallShortcutInfo(info, widgetId, context);
-            }
-
-            Intent data = new Intent();
-            data.putExtra(Intent.EXTRA_SHORTCUT_INTENT, decoder.launcherIntent);
-            data.putExtra(Intent.EXTRA_SHORTCUT_NAME, decoder.getString(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);
-                data.putExtra(Intent.EXTRA_SHORTCUT_ICON, b);
-            } else if (iconResourceName != null && !iconResourceName.isEmpty()) {
-                Intent.ShortcutIconResource iconResource =
-                    new Intent.ShortcutIconResource();
-                iconResource.resourceName = iconResourceName;
-                iconResource.packageName = iconResourcePackageName;
-                data.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource);
-            }
-
-            if (decoder.optBoolean(APP_SHORTCUT_TYPE_KEY)) {
-                return new PendingInstallShortcutInfo(data, context, decoder.user);
-            } else {
-                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) ? UserCache.INSTANCE.get(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.
-     * @return the newly created info or the original one.
-     */
-    private static PendingInstallShortcutInfo convertToLauncherActivityIfPossible(
-            PendingInstallShortcutInfo original) {
-        if (original.isLauncherActivity()) {
-            // Already an activity target
-            return original;
-        }
-        if (!PackageManagerHelper.isLauncherAppTarget(original.launchIntent)) {
-            return original;
-        }
-
-        LauncherActivityInfo info = original.mContext.getSystemService(LauncherApps.class)
-                .resolveActivity(original.launchIntent, original.user);
-        if (info == null) {
-            return original;
-        }
-        // Ignore any conflicts in the label name, as that can change based on locale.
-        return new PendingInstallShortcutInfo(info, original.mContext);
-    }
-
-    private static WorkspaceItemInfo createWorkspaceItemInfo(Intent data, UserHandle user,
-            LauncherAppState app) {
-        if (data == null) {
-            Log.e(TAG, "Can't construct WorkspaceItemInfo with null data");
-            return null;
-        }
-
-        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, return null as we can't construct a valid WorkspaceItemInfo
-            Log.e(TAG, "Can't construct WorkspaceItemInfo with null intent");
-            return null;
-        }
-
-        final WorkspaceItemInfo info = new WorkspaceItemInfo();
-        info.user = user;
-
-        BitmapInfo iconInfo = null;
-        LauncherIcons li = LauncherIcons.obtain(app.getContext());
-        if (bitmap instanceof Bitmap) {
-            iconInfo = li.createIconBitmap((Bitmap) bitmap);
-        } else {
-            Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
-            if (extra instanceof Intent.ShortcutIconResource) {
-                info.iconResource = (Intent.ShortcutIconResource) extra;
-                iconInfo = li.createIconBitmap(info.iconResource);
-            }
-        }
-        li.recycle();
-
-        if (iconInfo == null) {
-            iconInfo = app.getIconCache().getDefaultIcon(info.user);
-        }
-        info.bitmap = iconInfo;
-
-        info.title = Utilities.trim(name);
-        info.contentDescription = app.getContext().getPackageManager()
-                .getUserBadgedLabel(info.title, info.user);
-        info.intent = intent;
-        return info;
-    }
-
-}
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index e39e89c..370bd6f 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -48,8 +48,8 @@
 
 import com.android.launcher3.graphics.IconShape;
 import com.android.launcher3.util.ConfigMonitor;
-import com.android.launcher3.util.DefaultDisplay;
-import com.android.launcher3.util.DefaultDisplay.Info;
+import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.DisplayController.Info;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.Themes;
@@ -198,7 +198,7 @@
 
         // Get the display info based on default display and interpolate it to existing display
         DisplayOption defaultDisplayOption = invDistWeightedInterpolate(
-                DefaultDisplay.INSTANCE.get(context).getInfo(),
+                DisplayController.getDefaultDisplay(context).getInfo(),
                 getPredefinedDeviceProfiles(context, gridName));
 
         Info myInfo = new Info(context, display);
@@ -231,7 +231,7 @@
     }
 
     private String initGrid(Context context, String gridName) {
-        DefaultDisplay.Info displayInfo = DefaultDisplay.INSTANCE.get(context).getInfo();
+        Info displayInfo = DisplayController.getDefaultDisplay(context).getInfo();
         ArrayList<DisplayOption> allOptions = getPredefinedDeviceProfiles(context, gridName);
 
         DisplayOption displayOption = invDistWeightedInterpolate(displayInfo, allOptions);
@@ -240,7 +240,7 @@
     }
 
     private void initGrid(
-            Context context, DefaultDisplay.Info displayInfo, DisplayOption displayOption) {
+            Context context, Info displayInfo, DisplayOption displayOption) {
         GridOption closestProfile = displayOption.grid;
         numRows = closestProfile.numRows;
         numColumns = closestProfile.numColumns;
@@ -466,7 +466,7 @@
 
     @VisibleForTesting
     static DisplayOption invDistWeightedInterpolate(
-            DefaultDisplay.Info displayInfo, ArrayList<DisplayOption> points) {
+            Info displayInfo, ArrayList<DisplayOption> points) {
         Point smallestSize = new Point(displayInfo.smallestSize);
         Point largestSize = new Point(displayInfo.largestSize);
 
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 0970dae..cacd3fb 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -21,9 +21,9 @@
 import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
 
 import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
+import static com.android.launcher3.AbstractFloatingView.TYPE_ICON_SURFACE;
 import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
 import static com.android.launcher3.AbstractFloatingView.TYPE_SNACKBAR;
-import static com.android.launcher3.InstallShortcutReceiver.FLAG_DRAG_AND_DROP;
 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.FLAG_CLOSE_POPUPS;
@@ -33,7 +33,6 @@
 import static com.android.launcher3.LauncherState.NO_OFFSET;
 import static com.android.launcher3.LauncherState.NO_SCALE;
 import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.LauncherState.OVERVIEW_PEEK;
 import static com.android.launcher3.LauncherState.SPRING_LOADED;
 import static com.android.launcher3.Utilities.postAsyncCallback;
 import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_LAUNCHER_LOAD;
@@ -42,6 +41,9 @@
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ONRESUME;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ONSTOP;
 import static com.android.launcher3.logging.StatsLogManager.containerTypeToAtomState;
+import static com.android.launcher3.model.ItemInstallQueue.FLAG_ACTIVITY_PAUSED;
+import static com.android.launcher3.model.ItemInstallQueue.FLAG_DRAG_AND_DROP;
+import static com.android.launcher3.model.ItemInstallQueue.FLAG_LOADER_RUNNING;
 import static com.android.launcher3.popup.SystemShortcut.APP_INFO;
 import static com.android.launcher3.popup.SystemShortcut.INSTALL;
 import static com.android.launcher3.popup.SystemShortcut.WIDGETS;
@@ -108,7 +110,6 @@
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragView;
-import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderGridOrganizer;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.icons.IconCache;
@@ -118,8 +119,9 @@
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.logging.UserEventDispatcher;
-import com.android.launcher3.model.AppLaunchTracker;
 import com.android.launcher3.model.BgDataModel.Callbacks;
+import com.android.launcher3.model.ItemInstallQueue;
+import com.android.launcher3.model.ModelUtils;
 import com.android.launcher3.model.ModelWriter;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.FolderInfo;
@@ -168,6 +170,7 @@
 import com.android.launcher3.util.UiThreadHelper;
 import com.android.launcher3.util.ViewOnDrawExecutor;
 import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.views.FloatingSurfaceView;
 import com.android.launcher3.views.OptionsPopupView;
 import com.android.launcher3.views.ScrimView;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
@@ -449,12 +452,12 @@
                 float alpha = 1f - mCurrentAssistantVisibility;
                 if (finalState == NORMAL) {
                     mAppsView.getAlphaProperty(APPS_VIEW_ALPHA_CHANNEL_INDEX).setValue(alpha);
-                } else if (finalState == OVERVIEW || finalState == OVERVIEW_PEEK) {
+                } else if (finalState == OVERVIEW) {
                     mAppsView.getAlphaProperty(APPS_VIEW_ALPHA_CHANNEL_INDEX).setValue(alpha);
-                    mScrimView.getAlphaProperty(SCRIM_VIEW_ALPHA_CHANNEL_INDEX).setValue(alpha);
+                    mScrimView.setAlpha(alpha);
                 } else {
                     mAppsView.getAlphaProperty(APPS_VIEW_ALPHA_CHANNEL_INDEX).setValue(1f);
-                    mScrimView.getAlphaProperty(SCRIM_VIEW_ALPHA_CHANNEL_INDEX).setValue(1f);
+                    mScrimView.setAlpha(1f);
                 }
             }
         });
@@ -509,6 +512,7 @@
     public void onEnterAnimationComplete() {
         super.onEnterAnimationComplete();
         mRotationHelper.setCurrentTransitionRequest(REQUEST_NONE);
+        AbstractFloatingView.closeOpenViews(this, false, TYPE_ICON_SURFACE);
     }
 
     @Override
@@ -548,9 +552,9 @@
         LauncherState state = mStateManager.getState();
         if (state == NORMAL) {
             mAppsView.getAlphaProperty(APPS_VIEW_ALPHA_CHANNEL_INDEX).setValue(alpha);
-        } else if (state == OVERVIEW || state == OVERVIEW_PEEK) {
+        } else if (state == OVERVIEW) {
             mAppsView.getAlphaProperty(APPS_VIEW_ALPHA_CHANNEL_INDEX).setValue(alpha);
-            mScrimView.getAlphaProperty(SCRIM_VIEW_ALPHA_CHANNEL_INDEX).setValue(alpha);
+            mScrimView.setAlpha(alpha);
         }
     }
 
@@ -814,7 +818,7 @@
 
             if (grantResults.length > 0
                     && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
-                startActivitySafely(v, intent, null, null);
+                startActivitySafely(v, intent, null);
             } else {
                 // TODO: Show a snack bar with link to settings
                 Toast.makeText(this, getString(R.string.msg_no_phone_permission,
@@ -906,16 +910,13 @@
     @CallSuper
     protected void onDeferredResumed() {
         logStopAndResume(Action.Command.RESUME);
-        getUserEventDispatcher().startSession();
-
-        AppLaunchTracker.INSTANCE.get(this).onReturnedToHome();
 
         // Process any items that were added while Launcher was away.
-        InstallShortcutReceiver.disableAndFlushInstallQueue(
-                InstallShortcutReceiver.FLAG_ACTIVITY_PAUSED, this);
+        ItemInstallQueue.INSTANCE.get(this)
+                .resumeModelPush(FLAG_ACTIVITY_PAUSED);
 
         // Refresh shortcuts if the permission changed.
-        mModel.refreshShortcutsIfRequired();
+        mModel.validateModelDataOnResume();
 
         // Set the notification listener and fetch updated notifications when we resume
         NotificationListener.setNotificationsChangedListener(mPopupDataProvider);
@@ -923,8 +924,10 @@
         DiscoveryBounce.showForHomeIfNeeded(this);
     }
 
+    protected void handlePendingActivityRequest() { }
 
     private void logStopAndResume(int command) {
+        if (mPendingExecutor != null) return;
         int pageIndex = mWorkspace.isOverlayShown() ? -1 : mWorkspace.getCurrentPage();
         int containerType = mStateManager.getState().containerType;
 
@@ -1005,7 +1008,7 @@
         if (state == SPRING_LOADED) {
             // Prevent any Un/InstallShortcutReceivers from updating the db while we are
             // not on homescreen
-            InstallShortcutReceiver.enableInstallQueue(FLAG_DRAG_AND_DROP);
+            ItemInstallQueue.INSTANCE.get(this).pauseModelPush(FLAG_DRAG_AND_DROP);
             getRotationHelper().setCurrentStateRequest(REQUEST_LOCK);
 
             mWorkspace.showPageIndicatorAtCurrentScroll();
@@ -1030,7 +1033,8 @@
 
         if (state == NORMAL) {
             // Re-enable any Un/InstallShortcutReceiver and now process any queued items
-            InstallShortcutReceiver.disableAndFlushInstallQueue(FLAG_DRAG_AND_DROP, this);
+            ItemInstallQueue.INSTANCE.get(this)
+                    .resumeModelPush(FLAG_DRAG_AND_DROP);
 
             // Clear any rotation locks when going to normal state
             getRotationHelper().setCurrentStateRequest(REQUEST_NONE);
@@ -1064,7 +1068,7 @@
     @Override
     protected void onPause() {
         // Ensure that items added to Launcher are queued until Launcher returns
-        InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_ACTIVITY_PAUSED);
+        ItemInstallQueue.INSTANCE.get(this).pauseModelPush(FLAG_ACTIVITY_PAUSED);
 
         super.onPause();
         mDragController.cancelDrag();
@@ -1195,16 +1199,13 @@
         int[] cellXY = mTmpAddItemCellCoordinates;
         CellLayout layout = getCellLayout(container, screenId);
 
-        WorkspaceItemInfo info = null;
-        if (Utilities.ATLEAST_OREO) {
-            info = PinRequestHelper.createWorkspaceItemFromPinItemRequest(
+        WorkspaceItemInfo info = PinRequestHelper.createWorkspaceItemFromPinItemRequest(
                     this, PinRequestHelper.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;
+                    ? ModelUtils.fromLegacyShortcutIntent(this, data) : null;
 
             if (info == null) {
                 Log.e(TAG, "Unable to parse a valid custom shortcut result");
@@ -1423,7 +1424,8 @@
                 if (!isInState(NORMAL)) {
                     // Only change state, if not already the same. This prevents cancelling any
                     // animations running as part of resume
-                    mStateManager.goToState(NORMAL);
+                    mStateManager.goToState(NORMAL, mStateManager.shouldAnimateStateChange(),
+                            this::handlePendingActivityRequest);
                 }
 
                 // Reset the apps view
@@ -1448,13 +1450,30 @@
                 mLauncherCallbacks.onHomeIntent(internalStateHandled);
             }
             mOverlayManager.hideOverlay(isStarted() && !isForceInvisible());
+            handleGestureContract(intent);
         } else if (Intent.ACTION_ALL_APPS.equals(intent.getAction())) {
-            getStateManager().goToState(ALL_APPS, alreadyOnHome);
+            showAllAppsFromIntent(alreadyOnHome);
         }
 
         TraceHelper.INSTANCE.endSection(traceToken);
     }
 
+    protected void showAllAppsFromIntent(boolean alreadyOnHome) {
+        AbstractFloatingView.closeAllOpenViews(this);
+        getStateManager().goToState(ALL_APPS, alreadyOnHome);
+    }
+
+    /**
+     * Handles gesture nav contract
+     */
+    protected void handleGestureContract(Intent intent) {
+        GestureNavContract gnc = GestureNavContract.fromIntent(intent);
+        if (gnc != null) {
+            AbstractFloatingView.closeOpenViews(this, false, TYPE_ICON_SURFACE);
+            FloatingSurfaceView.show(this, gnc);
+        }
+    }
+
     /**
      * Hides the keyboard if visible
      */
@@ -1533,7 +1552,6 @@
         mOverlayManager.onActivityDestroyed(this);
         mAppTransitionManager.unregisterRemoteAnimations();
         mUserChangedCallbackCloseable.close();
-        mAllAppsController.onActivityDestroyed();
     }
 
     public LauncherAccessibilityDelegate getAccessibilityDelegate() {
@@ -1728,16 +1746,6 @@
     }
 
     /**
-     * Called when a workspace item is converted into a folder
-     */
-    public void folderCreatedFromItem(Folder folder, WorkspaceItemInfo itemInfo){}
-
-    /**
-     * Called when a folder is converted into a workspace item
-     */
-    public void folderConvertedToItem(Folder folder, WorkspaceItemInfo itemInfo) {}
-
-    /**
      * Unbinds the view for the specified item, and removes the item and all its children.
      *
      * @param v the view being removed.
@@ -1860,13 +1868,12 @@
     }
 
     @Override
-    public boolean startActivitySafely(View v, Intent intent, ItemInfo item,
-            @Nullable String sourceContainer) {
+    public boolean startActivitySafely(View v, Intent intent, ItemInfo item) {
         if (!hasBeenResumed()) {
             // Workaround an issue where the WM launch animation is clobbered when finishing the
             // recents animation into launcher. Defer launching the activity until Launcher is
             // next resumed.
-            addOnResumeCallback(() -> startActivitySafely(v, intent, item, sourceContainer));
+            addOnResumeCallback(() -> startActivitySafely(v, intent, item));
             if (mOnDeferredActivityLaunchCallback != null) {
                 mOnDeferredActivityLaunchCallback.run();
                 mOnDeferredActivityLaunchCallback = null;
@@ -1874,7 +1881,7 @@
             return true;
         }
 
-        boolean success = super.startActivitySafely(v, intent, item, sourceContainer);
+        boolean success = super.startActivitySafely(v, intent, item);
         if (success && v instanceof BubbleTextView) {
             // This is set to the view that launched the activity that navigated the user away
             // from launcher. Since there is no callback for when the activity has finished
@@ -1921,11 +1928,12 @@
         // Populate event with a fake title based on the current state.
         // TODO: When can workspace be null?
         text.add(mWorkspace == null
-                ? getString(R.string.all_apps_home_button_label)
+                ? getString(R.string.home_screen)
                 : mStateManager.getState().getDescription(this));
         return result;
     }
 
+    @Override
     public void addOnResumeCallback(OnResumeCallback callback) {
         mOnResumeCallbacks.add(callback);
     }
@@ -2174,9 +2182,6 @@
         workspace.requestLayout();
     }
 
-    @Override
-    public void bindPredictedItems(List<AppInfo> appInfos, IntArray ranks) { }
-
     /**
      * Add the views for a widget to the workspace.
      */
@@ -2405,8 +2410,8 @@
             mPendingActivityResult = null;
         }
 
-        InstallShortcutReceiver.disableAndFlushInstallQueue(
-                InstallShortcutReceiver.FLAG_LOADER_RUNNING, this);
+        ItemInstallQueue.INSTANCE.get(this)
+                .resumeModelPush(FLAG_LOADER_RUNNING);
 
         // When undoing the removal of the last item on a page, return to that page.
         // Since we are just resetting the current page without user interaction,
@@ -2433,8 +2438,8 @@
 
     private ValueAnimator createNewAppBounceAnimation(View v, int i) {
         ValueAnimator bounceAnim = new PropertyListBuilder().alpha(1).scale(1).build(v)
-                .setDuration(InstallShortcutReceiver.NEW_SHORTCUT_BOUNCE_DURATION);
-        bounceAnim.setStartDelay(i * InstallShortcutReceiver.NEW_SHORTCUT_STAGGER_DELAY);
+                .setDuration(ItemInstallQueue.NEW_SHORTCUT_BOUNCE_DURATION);
+        bounceAnim.setStartDelay(i * ItemInstallQueue.NEW_SHORTCUT_STAGGER_DELAY);
         bounceAnim.setInterpolator(new OvershootInterpolator(BOUNCE_ANIMATION_TENSION));
         return bounceAnim;
     }
@@ -2479,7 +2484,7 @@
      * @param updated list of shortcuts which have changed.
      */
     @Override
-    public void bindWorkspaceItemsChanged(ArrayList<WorkspaceItemInfo> updated) {
+    public void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) {
         if (!updated.isEmpty()) {
             mWorkspace.updateShortcuts(updated);
         }
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 53e5274..b278e81 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -33,7 +33,6 @@
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.icons.IconProvider;
 import com.android.launcher3.icons.LauncherIcons;
-import com.android.launcher3.model.PredictionModel;
 import com.android.launcher3.notification.NotificationListener;
 import com.android.launcher3.pm.InstallSessionHelper;
 import com.android.launcher3.pm.InstallSessionTracker;
@@ -58,7 +57,6 @@
     private final IconCache mIconCache;
     private final WidgetPreviewLoader mWidgetCache;
     private final InvariantDeviceProfile mInvariantDeviceProfile;
-    private final PredictionModel mPredictionModel;
 
     private SecureSettingsObserver mNotificationDotsObserver;
     private InstallSessionTracker mInstallSessionTracker;
@@ -109,15 +107,11 @@
         mInstallSessionTracker = InstallSessionHelper.INSTANCE.get(context)
                 .registerInstallTracker(mModel, MODEL_EXECUTOR);
 
-        if (!mContext.getResources().getBoolean(R.bool.notification_dots_enabled)) {
-            mNotificationDotsObserver = null;
-        } else {
-            // Register an observer to rebind the notification listener when dots are re-enabled.
-            mNotificationDotsObserver =
-                    newNotificationSettingsObserver(mContext, this::onNotificationSettingsChanged);
-            mNotificationDotsObserver.register();
-            mNotificationDotsObserver.dispatchOnChange();
-        }
+        // Register an observer to rebind the notification listener when dots are re-enabled.
+        mNotificationDotsObserver =
+                newNotificationSettingsObserver(mContext, this::onNotificationSettingsChanged);
+        mNotificationDotsObserver.register();
+        mNotificationDotsObserver.dispatchOnChange();
     }
 
     public LauncherAppState(Context context, @Nullable String iconCacheFileName) {
@@ -128,8 +122,7 @@
         mInvariantDeviceProfile = InvariantDeviceProfile.INSTANCE.get(context);
         mIconCache = new IconCache(mContext, mInvariantDeviceProfile, iconCacheFileName);
         mWidgetCache = new WidgetPreviewLoader(mContext, mIconCache);
-        mModel = new LauncherModel(this, mIconCache, AppFilter.newInstance(mContext));
-        mPredictionModel = PredictionModel.newInstance(mContext);
+        mModel = new LauncherModel(context, this, mIconCache, AppFilter.newInstance(mContext));
     }
 
     protected void onNotificationSettingsChanged(boolean areNotificationDotsEnabled) {
@@ -157,6 +150,7 @@
      * Call from Application.onTerminate(), which is not guaranteed to ever be called.
      */
     public void onTerminate() {
+        mModel.destroy();
         if (mModelChangeReceiver != null) {
             mContext.unregisterReceiver(mModelChangeReceiver);
         }
@@ -185,10 +179,6 @@
         return mModel;
     }
 
-    public PredictionModel getPredictionModel() {
-        return mPredictionModel;
-    }
-
     public WidgetPreviewLoader getWidgetCache() {
         return mWidgetCache;
     }
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index f434c91..8458152 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -20,7 +20,6 @@
 import static com.android.launcher3.config.FeatureFlags.IS_STUDIO_BUILD;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
 
 import android.content.Context;
 import android.content.Intent;
@@ -44,8 +43,10 @@
 import com.android.launcher3.model.BgDataModel;
 import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.model.CacheDataUpdatedTask;
+import com.android.launcher3.model.ItemInstallQueue;
 import com.android.launcher3.model.LoaderResults;
 import com.android.launcher3.model.LoaderTask;
+import com.android.launcher3.model.ModelDelegate;
 import com.android.launcher3.model.ModelWriter;
 import com.android.launcher3.model.PackageInstallStateChangedTask;
 import com.android.launcher3.model.PackageUpdatedTask;
@@ -58,9 +59,8 @@
 import com.android.launcher3.pm.PackageInstallInfo;
 import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.shortcuts.ShortcutRequest;
-import com.android.launcher3.util.IntSparseArrayMap;
+import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.ItemInfoMatcher;
-import com.android.launcher3.util.LooperExecutor;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.Preconditions;
 
@@ -85,7 +85,6 @@
 
     private final LauncherAppState mApp;
     private final Object mLock = new Object();
-    private final LooperExecutor mMainExecutor = MAIN_EXECUTOR;
 
     private LoaderTask mLoaderTask;
     private boolean mIsLoaderTaskRunning;
@@ -112,20 +111,22 @@
      */
     private final BgDataModel mBgDataModel = new BgDataModel();
 
+    private final ModelDelegate mModelDelegate;
+
     // Runnable to check if the shortcuts permission has changed.
-    private final Runnable mShortcutPermissionCheckRunnable = new Runnable() {
+    private final Runnable mDataValidationCheck = new Runnable() {
         @Override
         public void run() {
-            if (mModelLoaded && hasShortcutsPermission(mApp.getContext())
-                    != mBgAllAppsList.hasShortcutHostPermission()) {
-                forceReload();
+            if (mModelLoaded) {
+                mModelDelegate.validateData();
             }
         }
     };
 
-    LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
+    LauncherModel(Context context, LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
         mApp = app;
         mBgAllAppsList = new AllAppsList(iconCache, appFilter);
+        mModelDelegate = ModelDelegate.newInstance(context, app, mBgAllAppsList, mBgDataModel);
     }
 
     /**
@@ -217,6 +218,20 @@
         }
     }
 
+    /**
+     * Called when the workspace items have drastically changed
+     */
+    public void onWorkspaceUiChanged() {
+        MODEL_EXECUTOR.execute(mModelDelegate::workspaceLoadComplete);
+    }
+
+    /**
+     * Called when the model is destroyed
+     */
+    public void destroy() {
+        MODEL_EXECUTOR.execute(mModelDelegate::destroy);
+    }
+
     public void onBroadcastIntent(Intent intent) {
         if (DEBUG_RECEIVER) Log.d(TAG, "onReceive intent=" + intent);
         final String action = intent.getAction();
@@ -321,20 +336,21 @@
      */
     public boolean startLoader() {
         // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
-        InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_LOADER_RUNNING);
+        ItemInstallQueue.INSTANCE.get(mApp.getContext())
+                .pauseModelPush(ItemInstallQueue.FLAG_LOADER_RUNNING);
         synchronized (mLock) {
             // Don't bother to start the thread if we know it's not going to do anything
             final Callbacks[] callbacksList = getCallbacks();
             if (callbacksList.length > 0) {
                 // Clear any pending bind-runnables from the synchronized load process.
                 for (Callbacks cb : callbacksList) {
-                    mMainExecutor.execute(cb::clearPendingBinds);
+                    MAIN_EXECUTOR.execute(cb::clearPendingBinds);
                 }
 
                 // If there is already one running, tell it to stop.
                 stopLoader();
                 LoaderResults loaderResults = new LoaderResults(
-                        mApp, mBgDataModel, mBgAllAppsList, callbacksList, mMainExecutor);
+                        mApp, mBgDataModel, mBgAllAppsList, callbacksList);
                 if (mModelLoaded && !mIsLoaderTaskRunning) {
                     // Divide the set of loaded items into those that we are binding synchronously,
                     // and everything else that is to be bound normally (asynchronously).
@@ -372,7 +388,8 @@
     public void startLoaderForResults(LoaderResults results) {
         synchronized (mLock) {
             stopLoader();
-            mLoaderTask = new LoaderTask(mApp, mBgAllAppsList, mBgDataModel, results);
+            mLoaderTask = new LoaderTask(
+                    mApp, mBgAllAppsList, mBgDataModel, mModelDelegate, results);
 
             // Always post the loader task, instead of running directly (even on same thread) so
             // that we exit any nested synchronized blocks
@@ -410,7 +427,7 @@
         enqueueModelUpdateTask(new BaseModelUpdateTask() {
             @Override
             public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
-                final IntSparseArrayMap<Boolean> removedIds = new IntSparseArrayMap<>();
+                final IntSet removedIds = new IntSet();
                 synchronized (dataModel) {
                     for (ItemInfo info : dataModel.itemsIdMap) {
                         if (info instanceof WorkspaceItemInfo
@@ -418,13 +435,13 @@
                                 && user.equals(info.user)
                                 && info.getIntent() != null
                                 && TextUtils.equals(packageName, info.getIntent().getPackage())) {
-                            removedIds.put(info.id, true /* remove */);
+                            removedIds.add(info.id);
                         }
                     }
                 }
 
                 if (!removedIds.isEmpty()) {
-                    deleteAndBindComponentsRemoved(ItemInfoMatcher.ofItemIds(removedIds, false));
+                    deleteAndBindComponentsRemoved(ItemInfoMatcher.ofItemIds(removedIds));
                 }
             }
         });
@@ -491,9 +508,9 @@
      * Current implementation simply reloads the workspace, but it can be optimized to
      * use partial updates similar to {@link UserCache}
      */
-    public void refreshShortcutsIfRequired() {
-        MODEL_EXECUTOR.getHandler().removeCallbacks(mShortcutPermissionCheckRunnable);
-        MODEL_EXECUTOR.post(mShortcutPermissionCheckRunnable);
+    public void validateModelDataOnResume() {
+        MODEL_EXECUTOR.getHandler().removeCallbacks(mDataValidationCheck);
+        MODEL_EXECUTOR.post(mDataValidationCheck);
     }
 
     /**
@@ -520,7 +537,7 @@
     }
 
     public void enqueueModelUpdateTask(ModelUpdateTask task) {
-        task.init(mApp, this, mBgDataModel, mBgAllAppsList, mMainExecutor);
+        task.init(mApp, this, mBgDataModel, mBgAllAppsList, MAIN_EXECUTOR);
         MODEL_EXECUTOR.execute(task);
     }
 
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index e8b5568..2973cf7 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -100,10 +100,12 @@
     public static final int SCHEMA_VERSION = 28;
 
     public static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".settings";
+    public static final String KEY_LAYOUT_PROVIDER_AUTHORITY = "KEY_LAYOUT_PROVIDER_AUTHORITY";
 
     static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED";
 
     protected DatabaseHelper mOpenHelper;
+    protected String mProviderAuthority;
 
     private long mLastRestoreTimestamp = 0L;
 
@@ -367,7 +369,8 @@
             case LauncherSettings.Settings.METHOD_WAS_EMPTY_DB_CREATED : {
                 Bundle result = new Bundle();
                 result.putBoolean(LauncherSettings.Settings.EXTRA_VALUE,
-                        Utilities.getPrefs(getContext()).getBoolean(EMPTY_DATABASE_CREATED, false));
+                        Utilities.getPrefs(getContext()).getBoolean(
+                                mOpenHelper.getKey(EMPTY_DATABASE_CREATED), false));
                 return result;
             }
             case LauncherSettings.Settings.METHOD_DELETE_EMPTY_FOLDERS: {
@@ -437,6 +440,7 @@
                                             getContext(), true /* forMigration */)));
                     return result;
                 }
+                return null;
             }
             case LauncherSettings.Settings.METHOD_PREP_FOR_PREVIEW: {
                 if (MULTI_DB_GRID_MIRATION_ALGO.get()) {
@@ -450,6 +454,23 @@
                                     () -> mOpenHelper));
                     return result;
                 }
+                return null;
+            }
+            case LauncherSettings.Settings.METHOD_SWITCH_DATABASE: {
+                if (TextUtils.equals(arg, mOpenHelper.getDatabaseName())) return null;
+                final DatabaseHelper helper = mOpenHelper;
+                if (extras == null || !extras.containsKey(KEY_LAYOUT_PROVIDER_AUTHORITY)) {
+                    mProviderAuthority = null;
+                } else {
+                    mProviderAuthority = extras.getString(KEY_LAYOUT_PROVIDER_AUTHORITY);
+                }
+                mOpenHelper = DatabaseHelper.createDatabaseHelper(
+                        getContext(), arg, false /* forMigration */);
+                helper.close();
+                LauncherAppState app = LauncherAppState.getInstanceNoCreate();
+                if (app == null) return null;
+                app.getModel().forceReload();
+                return null;
             }
         }
         return null;
@@ -492,7 +513,8 @@
     }
 
     private void clearFlagEmptyDbCreated() {
-        Utilities.getPrefs(getContext()).edit().remove(EMPTY_DATABASE_CREATED).commit();
+        Utilities.getPrefs(getContext()).edit()
+                .remove(mOpenHelper.getKey(EMPTY_DATABASE_CREATED)).commit();
     }
 
     /**
@@ -505,7 +527,7 @@
     synchronized private void loadDefaultFavoritesIfNecessary() {
         SharedPreferences sp = Utilities.getPrefs(getContext());
 
-        if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) {
+        if (sp.getBoolean(mOpenHelper.getKey(EMPTY_DATABASE_CREATED), false)) {
             Log.d(TAG, "loading default workspace");
 
             AppWidgetHost widgetHost = mOpenHelper.newLauncherWidgetHost();
@@ -553,8 +575,13 @@
      */
     private AutoInstallsLayout createWorkspaceLoaderFromAppRestriction(AppWidgetHost widgetHost) {
         Context ctx = getContext();
-        String authority = Settings.Secure.getString(ctx.getContentResolver(),
-                "launcher3.layout.provider");
+        final String authority;
+        if (!TextUtils.isEmpty(mProviderAuthority)) {
+            authority = mProviderAuthority;
+        } else {
+            authority = Settings.Secure.getString(ctx.getContentResolver(),
+                    "launcher3.layout.provider");
+        }
         if (TextUtils.isEmpty(authority)) {
             return null;
         }
@@ -694,11 +721,25 @@
         }
 
         /**
+         * Re-composite given key in respect to database. If the current db is
+         * {@link LauncherFiles#LAUNCHER_DB}, return the key as-is. Otherwise append the db name to
+         * given key. e.g. consider key="EMPTY_DATABASE_CREATED", dbName="minimal.db", the returning
+         * string will be "EMPTY_DATABASE_CREATED@minimal.db".
+         */
+        String getKey(final String key) {
+            if (TextUtils.equals(getDatabaseName(), LauncherFiles.LAUNCHER_DB)) {
+                return key;
+            }
+            return key + "@" + getDatabaseName();
+        }
+
+        /**
          * Overriden in tests.
          */
         protected void onEmptyDbCreated() {
             // Set the flag for empty DB
-            Utilities.getPrefs(mContext).edit().putBoolean(EMPTY_DATABASE_CREATED, true).commit();
+            Utilities.getPrefs(mContext).edit().putBoolean(getKey(EMPTY_DATABASE_CREATED), true)
+                    .commit();
         }
 
         public long getSerialNumberForUser(UserHandle user) {
@@ -872,7 +913,6 @@
          * Removes widgets which are registered to the Launcher's host, but are not present
          * in our model.
          */
-        @TargetApi(Build.VERSION_CODES.O)
         public void removeGhostWidgets(SQLiteDatabase db) {
             // Get all existing widget ids.
             final AppWidgetHost host = newLauncherWidgetHost();
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index 5512654..58a418e 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -354,14 +354,20 @@
 
         public static final String METHOD_PREP_FOR_PREVIEW = "prep_for_preview";
 
+        public static final String METHOD_SWITCH_DATABASE = "switch_database";
+
         public static final String EXTRA_VALUE = "value";
 
         public static Bundle call(ContentResolver cr, String method) {
-            return call(cr, method, null);
+            return call(cr, method, null /* arg */);
         }
 
         public static Bundle call(ContentResolver cr, String method, String arg) {
-            return cr.call(CONTENT_URI, method, arg, null);
+            return call(cr, method, arg, null /* extras */);
+        }
+
+        public static Bundle call(ContentResolver cr, String method, String arg, Bundle extras) {
+            return cr.call(CONTENT_URI, method, arg, extras);
         }
     }
 }
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index c78df62..b6bc500 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -21,7 +21,6 @@
 import static com.android.launcher3.testing.TestProtocol.HINT_STATE_ORDINAL;
 import static com.android.launcher3.testing.TestProtocol.NORMAL_STATE_ORDINAL;
 import static com.android.launcher3.testing.TestProtocol.OVERVIEW_MODAL_TASK_STATE_ORDINAL;
-import static com.android.launcher3.testing.TestProtocol.OVERVIEW_PEEK_STATE_ORDINAL;
 import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
 import static com.android.launcher3.testing.TestProtocol.QUICK_SWITCH_STATE_ORDINAL;
 import static com.android.launcher3.testing.TestProtocol.SPRING_LOADED_STATE_ORDINAL;
@@ -117,8 +116,6 @@
     public static final LauncherState HINT_STATE = new HintState(HINT_STATE_ORDINAL);
 
     public static final LauncherState OVERVIEW = new OverviewState(OVERVIEW_STATE_ORDINAL);
-    public static final LauncherState OVERVIEW_PEEK =
-            OverviewState.newPeekState(OVERVIEW_PEEK_STATE_ORDINAL);
     public static final LauncherState OVERVIEW_MODAL_TASK = OverviewState.newModalTaskState(
             OVERVIEW_MODAL_TASK_STATE_ORDINAL);
     public static final LauncherState QUICK_SWITCH =
@@ -250,7 +247,8 @@
     }
 
     public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) {
-        if (this != NORMAL || !launcher.getDeviceProfile().shouldFadeAdjacentWorkspaceScreens()) {
+        if ((this != NORMAL && this != HINT_STATE)
+                || !launcher.getDeviceProfile().shouldFadeAdjacentWorkspaceScreens()) {
             return DEFAULT_ALPHA_PROVIDER;
         }
         final int centerPage = launcher.getWorkspace().getNextPage();
diff --git a/src/com/android/launcher3/MainProcessInitializer.java b/src/com/android/launcher3/MainProcessInitializer.java
index 5f6ecb5..f2a3de7 100644
--- a/src/com/android/launcher3/MainProcessInitializer.java
+++ b/src/com/android/launcher3/MainProcessInitializer.java
@@ -38,7 +38,6 @@
     protected void init(Context context) {
         FileLog.setDir(context.getApplicationContext().getFilesDir());
         FeatureFlags.initialize(context);
-        SessionCommitReceiver.applyDefaultUserPrefs(context);
         IconShape.init(context);
 
         if (BitmapCreationCheck.ENABLED) {
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index e29faac..4303dee 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -195,9 +195,7 @@
         mMinFlingVelocity = (int) (MIN_FLING_VELOCITY * density);
         mMinSnapVelocity = (int) (MIN_SNAP_VELOCITY * density);
 
-        if (Utilities.ATLEAST_OREO) {
-            setDefaultFocusHighlightEnabled(false);
-        }
+        setDefaultFocusHighlightEnabled(false);
     }
 
     protected void setDefaultInterpolator(Interpolator interpolator) {
@@ -1448,11 +1446,8 @@
         int minDistanceFromScreenCenterIndex = -1;
         final int childCount = getChildCount();
         for (int i = 0; i < childCount; ++i) {
-            View layout = getPageAt(i);
-            int childSize = mOrientationHandler.getMeasuredSize(layout);
-            int halfChildSize = (childSize / 2);
-            int childCenter = getChildOffset(i) + halfChildSize;
-            int distanceFromScreenCenter = Math.abs(childCenter - screenCenter);
+            int distanceFromScreenCenter = Math.abs(
+                    getDisplacementFromScreenCenter(i, screenCenter));
             if (distanceFromScreenCenter < minDistanceFromScreenCenter) {
                 minDistanceFromScreenCenter = distanceFromScreenCenter;
                 minDistanceFromScreenCenterIndex = i;
@@ -1461,6 +1456,20 @@
         return minDistanceFromScreenCenterIndex;
     }
 
+    private int getDisplacementFromScreenCenter(int childIndex, int screenCenter) {
+        View layout = getPageAt(childIndex);
+        int childSize = mOrientationHandler.getMeasuredSize(layout);
+        int halfChildSize = (childSize / 2);
+        int childCenter = getChildOffset(childIndex) + halfChildSize;
+        return childCenter - screenCenter;
+    }
+
+    protected int getDisplacementFromScreenCenter(int childIndex) {
+        int pageOrientationSize = mOrientationHandler.getMeasuredSize(this);
+        int screenCenter = mOrientationHandler.getPrimaryScroll(this) + (pageOrientationSize / 2);
+        return getDisplacementFromScreenCenter(childIndex, screenCenter);
+    }
+
     protected void snapToDestination() {
         snapToPage(getPageNearestToCenterOfScreen(), getPageSnapDuration());
     }
diff --git a/src/com/android/launcher3/ResourceUtils.java b/src/com/android/launcher3/ResourceUtils.java
index 403d779..c9fb75a 100644
--- a/src/com/android/launcher3/ResourceUtils.java
+++ b/src/com/android/launcher3/ResourceUtils.java
@@ -22,6 +22,7 @@
 
 public class ResourceUtils {
     public static final int DEFAULT_NAVBAR_VALUE = 48;
+    public static final int INVALID_RESOURCE_HANDLE = -1;
     public static final String NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE = "navigation_bar_width";
     public static final String NAVBAR_BOTTOM_GESTURE_SIZE = "navigation_bar_gesture_height";
 
@@ -51,7 +52,13 @@
         return val;
     }
 
+    public static int getIntegerByName(String resName, Resources res, int defaultValue) {
+        int resId = res.getIdentifier(resName, "integer", "android");
+        return resId != 0 ? res.getInteger(resId) : defaultValue;
+    }
+
     public static int pxFromDp(float size, DisplayMetrics metrics) {
-        return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, size, metrics));
+        return size < 0 ? INVALID_RESOURCE_HANDLE : Math.round(
+                TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, size, metrics));
     }
 }
diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java
index 499b54f..2df7f5a 100644
--- a/src/com/android/launcher3/SecondaryDropTarget.java
+++ b/src/com/android/launcher3/SecondaryDropTarget.java
@@ -39,7 +39,7 @@
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.logging.LoggerUtils;
 import com.android.launcher3.logging.StatsLogManager;
-import com.android.launcher3.model.AppLaunchTracker;
+import com.android.launcher3.logging.StatsLogManager.StatsLogger;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -49,7 +49,6 @@
 import com.android.launcher3.util.Themes;
 
 import java.net.URISyntaxException;
-import java.util.ArrayList;
 
 /**
  * Drop target which provides a secondary option for an item.
@@ -218,13 +217,16 @@
     public void onDrop(DragObject d, DragOptions options) {
         // Defer onComplete
         d.dragSource = new DeferredOnComplete(d.dragSource, getContext());
+
         super.onDrop(d, options);
+        StatsLogger logger = mStatsLogManager.logger().withInstanceId(d.logInstanceId);
+        if (d.originalDragInfo != null) {
+            logger.withItemInfo(d.originalDragInfo);
+        }
         if (mCurrentAccessibilityAction == UNINSTALL) {
-            mStatsLogManager.logger().withInstanceId(d.logInstanceId)
-                    .log(LAUNCHER_ITEM_DROPPED_ON_UNINSTALL);
+            logger.log(LAUNCHER_ITEM_DROPPED_ON_UNINSTALL);
         } else if (mCurrentAccessibilityAction == DISMISS_PREDICTION) {
-            mStatsLogManager.logger().withInstanceId(d.logInstanceId)
-                    .log(LAUNCHER_ITEM_DROPPED_ON_DONT_SUGGEST);
+            logger.log(LAUNCHER_ITEM_DROPPED_ON_DONT_SUGGEST);
         }
     }
 
@@ -283,8 +285,7 @@
             return null;
         }
         if (mCurrentAccessibilityAction == DISMISS_PREDICTION) {
-            AppLaunchTracker.INSTANCE.get(getContext()).onDismissApp(info.getTargetComponent(),
-                    info.user, AppLaunchTracker.CONTAINER_PREDICTIONS);
+            // We sent the log event, nothing else left to do
             return null;
         }
         // else: mCurrentAccessibilityAction == UNINSTALL
@@ -338,12 +339,6 @@
         }
 
         @Override
-        public void fillInLogContainerData(ItemInfo childInfo, Target child,
-                ArrayList<Target> parents) {
-            mOriginal.fillInLogContainerData(childInfo, child, parents);
-        }
-
-        @Override
         public void onLauncherResume() {
             // We use MATCH_UNINSTALLED_PACKAGES as the app can be on SD card as well.
             if (new PackageManagerHelper(mContext).getApplicationInfo(mPackageName,
diff --git a/src/com/android/launcher3/SessionCommitReceiver.java b/src/com/android/launcher3/SessionCommitReceiver.java
index 89f0a3d..007e5f5 100644
--- a/src/com/android/launcher3/SessionCommitReceiver.java
+++ b/src/com/android/launcher3/SessionCommitReceiver.java
@@ -16,55 +16,29 @@
 
 package com.android.launcher3;
 
-import static com.android.launcher3.pm.InstallSessionHelper.getUserHandle;
-
-import android.annotation.TargetApi;
 import android.content.BroadcastReceiver;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.pm.LauncherActivityInfo;
-import android.content.pm.LauncherApps;
 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.graphics.Bitmap;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Build;
 import android.os.UserHandle;
-import android.provider.Settings;
 import android.text.TextUtils;
-import android.util.Log;
 
+import com.android.launcher3.model.ItemInstallQueue;
 import com.android.launcher3.pm.InstallSessionHelper;
-import com.android.launcher3.util.Executors;
-
-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.ATLEAST_OREO) {
+        if (!isEnabled(context)) {
             // User has decided to not add icons on homescreen.
             return;
         }
@@ -86,105 +60,11 @@
             return;
         }
 
-        queueAppIconAddition(context, info.getAppPackageName(), user);
-    }
-
-    public static void queuePromiseAppIconAddition(Context context, SessionInfo sessionInfo) {
-        String packageName = sessionInfo.getAppPackageName();
-        if (context.getSystemService(LauncherApps.class)
-                .getActivityList(packageName, getUserHandle(sessionInfo)).isEmpty()) {
-            // Ensure application isn't already installed.
-            queueAppIconAddition(context, packageName, sessionInfo.getAppLabel(),
-                    sessionInfo.getAppIcon(), getUserHandle(sessionInfo));
-        }
-    }
-
-    public static void queueAppIconAddition(Context context, String packageName, UserHandle user) {
-        List<LauncherActivityInfo> activities = context.getSystemService(LauncherApps.class)
-                .getActivityList(packageName, user);
-        if (activities.isEmpty()) {
-            // no activity found
-            return;
-        }
-        queueAppIconAddition(context, packageName, activities.get(0).getLabel(), null, user);
-    }
-
-    private static void queueAppIconAddition(Context context, String packageName,
-            CharSequence label, Bitmap icon, UserHandle user) {
-        Intent data = new Intent();
-        data.putExtra(Intent.EXTRA_SHORTCUT_INTENT, new Intent().setComponent(
-                new ComponentName(packageName, "")).setPackage(packageName));
-        data.putExtra(Intent.EXTRA_SHORTCUT_NAME, label);
-        data.putExtra(Intent.EXTRA_SHORTCUT_ICON, icon);
-
-        InstallShortcutReceiver.queueApplication(data, user, context);
+        ItemInstallQueue.INSTANCE.get(context)
+                .queueItem(info.getAppPackageName(), user);
     }
 
     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.ATLEAST_OREO) {
-            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(Executors.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/Utilities.java b/src/com/android/launcher3/Utilities.java
index bf63788..dea2a8d 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -18,7 +18,6 @@
 
 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ICON_BADGED;
 
-import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
 import android.app.ActivityManager;
 import android.app.Person;
@@ -26,6 +25,7 @@
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.LauncherApps;
@@ -48,7 +48,6 @@
 import android.os.DeadObjectException;
 import android.os.Handler;
 import android.os.Message;
-import android.os.PowerManager;
 import android.os.TransactionTooLargeException;
 import android.provider.Settings;
 import android.text.Spannable;
@@ -65,7 +64,6 @@
 
 import androidx.core.os.BuildCompat;
 
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
 import com.android.launcher3.graphics.GridOptionsProvider;
 import com.android.launcher3.graphics.TintedDrawableSpan;
@@ -113,12 +111,6 @@
     public static final boolean ATLEAST_P =
             Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
 
-    public static final boolean ATLEAST_OREO_MR1 =
-            Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1;
-
-    public static final boolean ATLEAST_OREO =
-            Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
-
     /**
      * Set on a motion event dispatched from the nav bar. See {@link MotionEvent#setEdgeFlags(int)}.
      */
@@ -494,21 +486,6 @@
                 LauncherFiles.DEVICE_PREFERENCES_KEY, Context.MODE_PRIVATE);
     }
 
-    /**
-     * @return {@link SharedPreferences} that backs {@link FeatureFlags}
-     */
-    public static SharedPreferences getFeatureFlagsPrefs(Context context) {
-        // Use application context for shared preferences, so that we use a single cached instance
-        return context.getApplicationContext().getSharedPreferences(
-            FeatureFlags.FLAGS_PREF_NAME, Context.MODE_PRIVATE);
-    }
-
-    public static boolean areAnimationsEnabled(Context context) {
-        return ATLEAST_OREO
-                ? ValueAnimator.areAnimatorsEnabled()
-                : !context.getSystemService(PowerManager.class).isPowerSaveMode();
-    }
-
     public static boolean isWallpaperAllowed(Context context) {
         return context.getSystemService(WallpaperManager.class).isSetWallpaperAllowed();
     }
@@ -663,6 +640,14 @@
         }
     }
 
+    /**
+     * @return true is the extra is either null or is of type {@param type}
+     */
+    public static boolean isValidExtraType(Intent intent, String key, Class type) {
+        Object extra = intent.getParcelableExtra(key);
+        return extra == null || type.isInstance(extra);
+    }
+
     public static float squaredHypot(float x, float y) {
         return x * x + y * y;
     }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 1441e0b..8da81ac 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -22,12 +22,12 @@
 import static com.android.launcher3.LauncherState.FLAG_MULTI_PAGE;
 import static com.android.launcher3.LauncherState.FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED;
 import static com.android.launcher3.LauncherState.FLAG_WORKSPACE_INACCESSIBLE;
+import static com.android.launcher3.LauncherState.HINT_STATE;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.LauncherState.SPRING_LOADED;
 import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
 import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_OVERLAY;
-import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SWIPELEFT;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SWIPERIGHT;
@@ -98,7 +98,6 @@
 import com.android.launcher3.touch.WorkspaceTouchListener;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSparseArrayMap;
@@ -115,6 +114,7 @@
 
 import java.util.ArrayList;
 import java.util.HashSet;
+import java.util.List;
 import java.util.function.Predicate;
 
 /**
@@ -309,7 +309,9 @@
             // In portrait, we want the pages spaced such that there is no
             // overhang of the previous / next page into the current page viewport.
             // We assume symmetrical padding in portrait mode.
-            setPageSpacing(Math.max(grid.edgeMarginPx, padding.left + 1));
+            int maxInsets = Math.max(insets.left, insets.right);
+            int maxPadding = Math.max(grid.edgeMarginPx, padding.left + 1);
+            setPageSpacing(Math.max(maxInsets, maxPadding));
         }
 
 
@@ -940,7 +942,10 @@
         super.onScrollChanged(l, t, oldl, oldt);
 
         // Update the page indicator progress.
-        boolean isTransitioning = mIsSwitchingState
+        // Unlike from other states, we show the page indicator when transitioning from HINT_STATE.
+        boolean isSwitchingState = mIsSwitchingState
+                && mLauncher.getStateManager().getCurrentStableState() != HINT_STATE;
+        boolean isTransitioning = isSwitchingState
                 || (getLayoutTransition() != null && getLayoutTransition().isRunning());
         if (!isTransitioning) {
             showPageIndicatorAtCurrentScroll();
@@ -1499,7 +1504,6 @@
                     .showForIcon((BubbleTextView) child);
             if (popupContainer != null) {
                 dragOptions.preDragCondition = popupContainer.createPreDragCondition();
-                mLauncher.getUserEventDispatcher().resetElapsedContainerMillis("dragging started");
             }
         }
 
@@ -1699,7 +1703,6 @@
                 fi.addItem(destInfo);
                 fi.addItem(sourceInfo);
             }
-            mLauncher.folderCreatedFromItem(fi.getFolder(), destInfo);
             return true;
         }
         return false;
@@ -1716,7 +1719,7 @@
         if (dropOverView instanceof FolderIcon) {
             FolderIcon fi = (FolderIcon) dropOverView;
             if (fi.acceptDrop(d.dragInfo)) {
-                mStatsLogManager.logger().withItemInfo(fi.mInfo).withInstanceId(d.logInstanceId)
+                mStatsLogManager.logger().withItemInfo(d.dragInfo).withInstanceId(d.logInstanceId)
                         .log(LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED);
                 fi.onDrop(d, false /* itemReturnedOnFailedDrop */);
 
@@ -3085,7 +3088,7 @@
         return false;
     }
 
-    void updateShortcuts(ArrayList<WorkspaceItemInfo> shortcuts) {
+    void updateShortcuts(List<WorkspaceItemInfo> shortcuts) {
         final HashSet<WorkspaceItemInfo> updates = new HashSet<>(shortcuts);
         ItemOperator op = (info, v) -> {
             if (v instanceof BubbleTextView && updates.contains(info)) {
@@ -3260,29 +3263,11 @@
         }
         if (nScreens == 0) {
             // When the workspace is not loaded, we do not know how many screen will be bound.
-            return getContext().getString(R.string.all_apps_home_button_label);
+            return getContext().getString(R.string.home_screen);
         }
         return getContext().getString(R.string.workspace_scroll_format, page + 1, nScreens);
     }
 
-    @Override
-    public void fillInLogContainerData(ItemInfo childInfo, Target child,
-            ArrayList<Target> parents) {
-        if (childInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
-                || childInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
-            getHotseat().fillInLogContainerData(childInfo, child, parents);
-            return;
-        } else if (childInfo.container >= 0) {
-            FolderIcon icon = (FolderIcon) getHomescreenIconByItemId(childInfo.container);
-            icon.getFolder().fillInLogContainerData(childInfo, child, parents);
-            return;
-        }
-        child.gridX = childInfo.cellX;
-        child.gridY = childInfo.cellY;
-        child.pageIndex = getCurrentPage();
-        parents.add(newContainerTarget(ContainerType.WORKSPACE));
-    }
-
     /**
      * Used as a workaround to ensure that the AppWidgetService receives the
      * PACKAGE_ADDED broadcast before updating widgets.
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index c989e7b..1f51566 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -15,7 +15,8 @@
  */
 package com.android.launcher3.allapps;
 
-import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
+import static com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
+import static com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
@@ -42,6 +43,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.StringRes;
+import androidx.core.os.BuildCompat;
 import androidx.recyclerview.widget.DefaultItemAnimator;
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
@@ -55,11 +57,10 @@
 import com.android.launcher3.InsettableFrameLayout;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.keyboard.FocusedItemDecorator;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.ItemInfo;
-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.MultiValueAlpha;
 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
@@ -67,8 +68,6 @@
 import com.android.launcher3.views.RecyclerViewFastScroller;
 import com.android.launcher3.views.SpringRelativeLayout;
 
-import java.util.ArrayList;
-
 /**
  * The all apps view container.
  */
@@ -83,7 +82,7 @@
     protected final BaseDraggingActivity mLauncher;
     protected final AdapterHolder[] mAH;
     private final ItemInfoMatcher mPersonalMatcher = ItemInfoMatcher.ofUser(Process.myUserHandle());
-    private final ItemInfoMatcher mWorkMatcher = ItemInfoMatcher.not(mPersonalMatcher);
+    private final ItemInfoMatcher mWorkMatcher = mPersonalMatcher.negate();
     private final AllAppsStore mAllAppsStore = new AllAppsStore();
 
     private final Paint mNavBarScrimPaint;
@@ -107,7 +106,7 @@
 
     private final MultiValueAlpha mMultiValueAlpha;
 
-    Rect mInsets = new Rect();
+    private Rect mInsets = new Rect();
 
     public AllAppsContainerView(Context context) {
         this(context, null);
@@ -203,6 +202,17 @@
         mAH[AdapterHolder.WORK].applyPadding();
     }
 
+    private void hideInput() {
+        if (!BuildCompat.isAtLeastR() || !FeatureFlags.ENABLE_DEVICE_SEARCH.get()) return;
+
+        WindowInsets insets = getRootWindowInsets();
+        if (insets == null) return;
+
+        if (insets.isVisible(WindowInsets.Type.ime())) {
+            getWindowInsetsController().hide(WindowInsets.Type.ime());
+        }
+    }
+
     /**
      * Returns whether the view itself will handle the touch event or not.
      */
@@ -218,9 +228,14 @@
         }
         if (rv.getScrollbar().getThumbOffsetY() >= 0 &&
                 mLauncher.getDragLayer().isEventOverView(rv.getScrollbar(), ev)) {
+            hideInput();
             return false;
         }
-        return rv.shouldContainerScroll(ev, mLauncher.getDragLayer());
+        boolean shouldScroll = rv.shouldContainerScroll(ev, mLauncher.getDragLayer());
+        if (shouldScroll) {
+            hideInput();
+        }
+        return shouldScroll;
     }
 
     @Override
@@ -335,13 +350,6 @@
     }
 
     @Override
-    public void fillInLogContainerData(ItemInfo childInfo, Target child,
-            ArrayList<Target> parents) {
-        parents.add(newContainerTarget(
-                getApps().hasFilter() ? ContainerType.SEARCHRESULT : ContainerType.ALLAPPS));
-    }
-
-    @Override
     public void setInsets(Rect insets) {
         mInsets.set(insets);
         DeviceProfile grid = mLauncher.getDeviceProfile();
@@ -486,7 +494,7 @@
         if (mWorkModeSwitch != null) {
             mWorkModeSwitch.setWorkTabVisible(pos == AdapterHolder.WORK
                     && mAllAppsStore.hasModelFlag(
-                            FLAG_HAS_SHORTCUT_PERMISSION | FLAG_QUIET_MODE_CHANGE_PERMISSION));
+                    FLAG_HAS_SHORTCUT_PERMISSION | FLAG_QUIET_MODE_CHANGE_PERMISSION));
         }
     }
 
@@ -526,6 +534,41 @@
         return mViewPager == null ? getActiveRecyclerView() : mViewPager;
     }
 
+    /**
+     * Handles selection on focused view and returns success
+     */
+    public boolean selectFocusedView(View v) {
+        ItemInfo itemInfo = getHighlightedItemInfo();
+        if (itemInfo != null) {
+            return mLauncher.startActivitySafely(v, itemInfo.getIntent(), itemInfo);
+        }
+        AdapterItem focusedItem = getActiveRecyclerView().getApps().getFocusedChild();
+        if (focusedItem instanceof AdapterItemWithPayload) {
+            Runnable onSelection = ((AdapterItemWithPayload) focusedItem).getSelectionHandler();
+            if (onSelection != null) {
+                onSelection.run();
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns the ItemInfo of a view that is in focus, ready to be launched by an IME.
+     */
+    public ItemInfo getHighlightedItemInfo() {
+        View view = getFloatingHeaderView().getFocusedChild();
+        if (view != null && view.getTag() instanceof ItemInfo) {
+            return ((ItemInfo) view.getTag());
+        }
+        if (getActiveRecyclerView().getApps().getFocusedChild() != null) {
+            // TODO: when new pipelines are included, getSearchResults
+            // should be supported at recycler view level and not apps list level.
+            return getActiveRecyclerView().getApps().getFocusedChild().appInfo;
+        }
+        return null;
+    }
+
     public RecyclerViewFastScroller getScrollBar() {
         AllAppsRecyclerView rv = getActiveRecyclerView();
         return rv == null ? null : rv.getScrollbar();
@@ -538,6 +581,10 @@
         int padding = mHeader.getMaxTranslation();
         for (int i = 0; i < mAH.length; i++) {
             mAH[i].padding.top = padding;
+            if (FeatureFlags.ENABLE_DEVICE_SEARCH.get() && mUsingTabs) {
+                //add extra space between tabs and recycler view
+                mAH[i].padding.top += mLauncher.getDeviceProfile().edgeMarginPx;
+            }
             mAH[i].applyPadding();
         }
     }
@@ -652,6 +699,10 @@
             applyVerticalFadingEdgeEnabled(verticalFadingEdge);
             applyPadding();
             setupOverlay();
+            if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
+                recyclerView.addItemDecoration(new AllAppsSectionDecorator(
+                        AllAppsContainerView.this));
+            }
         }
 
         void setupOverlay() {
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 8ec4d27..da161ac 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
+import android.net.Uri;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -30,18 +31,27 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.TextView;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.core.view.accessibility.AccessibilityEventCompat;
 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
 import androidx.core.view.accessibility.AccessibilityRecordCompat;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleRegistry;
+import androidx.lifecycle.LiveData;
 import androidx.recyclerview.widget.GridLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
+import androidx.slice.Slice;
+import androidx.slice.widget.SliceLiveData;
+import androidx.slice.widget.SliceView;
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.R;
-import com.android.launcher3.allapps.AlphabeticalAppsList.AdapterItem;
-import com.android.launcher3.model.AppLaunchTracker;
+import com.android.launcher3.allapps.search.AllAppsSearchBarController.PayloadResultHandler;
+import com.android.launcher3.allapps.search.SearchSectionInfo;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.util.PackageManagerHelper;
 
@@ -50,7 +60,9 @@
 /**
  * The grid view adapter of all the apps.
  */
-public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHolder> {
+public class AllAppsGridAdapter extends
+        RecyclerView.Adapter<AllAppsGridAdapter.ViewHolder> implements
+        LifecycleOwner {
 
     public static final String TAG = "AppsGridAdapter";
 
@@ -67,10 +79,22 @@
     // A divider that separates the apps list and the search market button
     public static final int VIEW_TYPE_ALL_APPS_DIVIDER = 1 << 4;
 
+    public static final int VIEW_TYPE_SEARCH_CORPUS_TITLE = 1 << 5;
+
+    public static final int VIEW_TYPE_SEARCH_HERO_APP = 1 << 6;
+
+    public static final int VIEW_TYPE_SEARCH_ROW_WITH_BUTTON = 1 << 7;
+
+    public static final int VIEW_TYPE_SEARCH_ROW = 1 << 8;
+
+    public static final int VIEW_TYPE_SEARCH_SLICE = 1 << 9;
+
     // Common view type masks
     public static final int VIEW_TYPE_MASK_DIVIDER = VIEW_TYPE_ALL_APPS_DIVIDER;
     public static final int VIEW_TYPE_MASK_ICON = VIEW_TYPE_ICON;
 
+    private final LifecycleRegistry mLifecycleRegistry;
+
     /**
      * ViewHolder for each icon.
      */
@@ -82,6 +106,111 @@
     }
 
     /**
+     * Info about a particular adapter item (can be either section or app)
+     */
+    public static class AdapterItem {
+        /** Common properties */
+        // The index of this adapter item in the list
+        public int position;
+        // The type of this item
+        public int viewType;
+
+        /** 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 row that this item shows up on
+        public int rowIndex;
+        // The index of this app in the row
+        public int rowAppIndex;
+        // The associated AppInfo for the app
+        public AppInfo appInfo = null;
+        // The index of this app not including sections
+        public int appIndex = -1;
+        // Search section associated to result
+        public SearchSectionInfo searchSectionInfo = null;
+
+        /**
+         * Factory method for AppIcon AdapterItem
+         */
+        public static AdapterItem asApp(int pos, String sectionName, AppInfo appInfo,
+                int appIndex) {
+            AdapterItem item = new AdapterItem();
+            item.viewType = VIEW_TYPE_ICON;
+            item.position = pos;
+            item.sectionName = sectionName;
+            item.appInfo = appInfo;
+            item.appIndex = appIndex;
+            return item;
+        }
+
+        /**
+         * Factory method for empty search results view
+         */
+        public static AdapterItem asEmptySearch(int pos) {
+            AdapterItem item = new AdapterItem();
+            item.viewType = VIEW_TYPE_EMPTY_SEARCH;
+            item.position = pos;
+            return item;
+        }
+
+        /**
+         * Factory method for a dividerView in AllAppsSearch
+         */
+        public static AdapterItem asAllAppsDivider(int pos) {
+            AdapterItem item = new AdapterItem();
+            item.viewType = VIEW_TYPE_ALL_APPS_DIVIDER;
+            item.position = pos;
+            return item;
+        }
+
+        /**
+         * Factory method for a market search button
+         */
+        public static AdapterItem asMarketSearch(int pos) {
+            AdapterItem item = new AdapterItem();
+            item.viewType = VIEW_TYPE_SEARCH_MARKET;
+            item.position = pos;
+            return item;
+        }
+
+        boolean isCountedForAccessibility() {
+            return viewType == VIEW_TYPE_ICON
+                    || viewType == VIEW_TYPE_SEARCH_HERO_APP
+                    || viewType == VIEW_TYPE_SEARCH_ROW_WITH_BUTTON
+                    || viewType == VIEW_TYPE_SEARCH_SLICE
+                    || viewType == VIEW_TYPE_SEARCH_ROW;
+        }
+    }
+
+    /**
+     * Extension of AdapterItem that contains an extra payload specific to item
+     *
+     * @param <T> Play load Type
+     */
+    public static class AdapterItemWithPayload<T> extends AdapterItem {
+        private T mPayload;
+        private Runnable mSelectionHandler;
+
+        public AdapterItemWithPayload(T payload, int type) {
+            mPayload = payload;
+            viewType = type;
+        }
+
+        public void setSelectionHandler(Runnable runnable) {
+            mSelectionHandler = runnable;
+        }
+
+        public Runnable getSelectionHandler() {
+            return mSelectionHandler;
+        }
+
+        public T getPayload() {
+            return mPayload;
+        }
+    }
+
+    /**
      * A subclass of GridLayoutManager that overrides accessibility values during app search.
      */
     public class AppsGridLayoutManager extends GridLayoutManager {
@@ -202,6 +331,12 @@
         mOnIconClickListener = launcher.getItemOnClickListener();
 
         setAppsPerRow(mLauncher.getDeviceProfile().inv.numAllAppsColumns);
+        if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
+            mLifecycleRegistry = new LifecycleRegistry(this);
+            mLifecycleRegistry.setCurrentState(Lifecycle.State.STARTED);
+        } else {
+            mLifecycleRegistry = null;
+        }
     }
 
     public void setAppsPerRow(int appsPerRow) {
@@ -270,11 +405,27 @@
                 View searchMarketView = mLayoutInflater.inflate(R.layout.all_apps_search_market,
                         parent, false);
                 searchMarketView.setOnClickListener(v -> mLauncher.startActivitySafely(
-                        v, mMarketSearchIntent, null, AppLaunchTracker.CONTAINER_SEARCH));
+                        v, mMarketSearchIntent, null));
                 return new ViewHolder(searchMarketView);
             case VIEW_TYPE_ALL_APPS_DIVIDER:
                 return new ViewHolder(mLayoutInflater.inflate(
                         R.layout.all_apps_divider, parent, false));
+            case VIEW_TYPE_SEARCH_CORPUS_TITLE:
+                return new ViewHolder(
+                        mLayoutInflater.inflate(R.layout.search_section_title, parent, false));
+
+            case VIEW_TYPE_SEARCH_HERO_APP:
+                return new ViewHolder(mLayoutInflater.inflate(
+                        R.layout.search_result_hero_app, parent, false));
+            case VIEW_TYPE_SEARCH_ROW_WITH_BUTTON:
+                return new ViewHolder(mLayoutInflater.inflate(
+                        R.layout.search_result_play_item, parent, false));
+            case VIEW_TYPE_SEARCH_ROW:
+                return new ViewHolder(mLayoutInflater.inflate(
+                        R.layout.search_result_settings_row, parent, false));
+            case VIEW_TYPE_SEARCH_SLICE:
+                return new ViewHolder(mLayoutInflater.inflate(
+                        R.layout.search_result_slice, parent, false));
             default:
                 throw new RuntimeException("Unexpected view type");
         }
@@ -303,6 +454,24 @@
                     searchView.setVisibility(View.GONE);
                 }
                 break;
+            case VIEW_TYPE_SEARCH_SLICE:
+                SliceView sliceView = (SliceView) holder.itemView;
+                Uri uri = ((AdapterItemWithPayload<Uri>) mApps.getAdapterItems().get(position))
+                        .getPayload();
+                try {
+                    LiveData<Slice> liveData = SliceLiveData.fromUri(mLauncher, uri);
+                    liveData.observe(this::getLifecycle, sliceView);
+                } catch (Exception ignored) {
+                }
+                break;
+            case VIEW_TYPE_SEARCH_CORPUS_TITLE:
+            case VIEW_TYPE_SEARCH_ROW_WITH_BUTTON:
+            case VIEW_TYPE_SEARCH_HERO_APP:
+            case VIEW_TYPE_SEARCH_ROW:
+                PayloadResultHandler payloadResultView = (PayloadResultHandler) holder.itemView;
+                payloadResultView.applyAdapterInfo(
+                        (AdapterItemWithPayload) mApps.getAdapterItems().get(position));
+                break;
             case VIEW_TYPE_ALL_APPS_DIVIDER:
                 // nothing to do
                 break;
@@ -322,8 +491,13 @@
 
     @Override
     public int getItemViewType(int position) {
-        AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(position);
+        AdapterItem item = mApps.getAdapterItems().get(position);
         return item.viewType;
     }
 
+    @NonNull
+    @Override
+    public Lifecycle getLifecycle() {
+        return mLifecycleRegistry;
+    }
 }
diff --git a/src/com/android/launcher3/allapps/AllAppsInsetTransitionController.java b/src/com/android/launcher3/allapps/AllAppsInsetTransitionController.java
new file mode 100644
index 0000000..d7af5f1
--- /dev/null
+++ b/src/com/android/launcher3/allapps/AllAppsInsetTransitionController.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2020 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.graphics.Insets;
+import android.os.Build;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowInsets;
+import android.view.WindowInsetsAnimationControlListener;
+import android.view.WindowInsetsAnimationController;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.core.os.BuildCompat;
+
+/**
+ * Handles IME over all apps to be synchronously transitioning along with the passed in
+ * root inset.
+ */
+public class AllAppsInsetTransitionController {
+
+    private static final boolean DEBUG = false;
+    private static final String TAG = "AllAppsInsetTransitionController";
+    private static final Interpolator LINEAR = new LinearInterpolator();
+
+    private WindowInsetsAnimationController mAnimationController;
+    private WindowInsetsAnimationControlListener mCurrentRequest;
+
+    private float mAllAppsHeight;
+
+    private int mDownInsetBottom;
+    private boolean mShownAtDown;
+
+    private int mHiddenInsetBottom;
+    private int mShownInsetBottom;
+
+    private float mDown, mCurrent;
+    private View mApps;
+
+    public AllAppsInsetTransitionController(float allAppsHeight, View appsView) {
+        mAllAppsHeight = allAppsHeight;
+        mApps = appsView;
+    }
+
+    public void hide() {
+        if (!BuildCompat.isAtLeastR()) return;
+
+        WindowInsets insets = mApps.getRootWindowInsets();
+        if (insets == null) return;
+
+        if (insets.isVisible(WindowInsets.Type.ime())) {
+            mApps.getWindowInsetsController().hide(WindowInsets.Type.ime());
+        }
+    }
+
+    /**
+     * Initializes member variables and requests for the {@link WindowInsetsAnimationController}
+     * object.
+     *
+     * @param progress value between 0..1
+     */
+    @RequiresApi(api = Build.VERSION_CODES.R)
+    public void onDragStart(float progress) {
+        if (!BuildCompat.isAtLeastR()) return;
+        onAnimationEnd(progress);
+
+        mDown = progress * mAllAppsHeight;
+
+        // Below two values are sometimes incorrect. Possibly a platform bug
+        mDownInsetBottom = mApps.getRootWindowInsets().getInsets(WindowInsets.Type.ime()).bottom;
+        mShownAtDown = mApps.getRootWindowInsets().isVisible(WindowInsets.Type.ime());
+
+        // override this value based on what it should actually be.
+        mShownAtDown = Float.compare(progress, 1f) == 0 ? false : true;
+        mDownInsetBottom = mShownAtDown ? mShownInsetBottom : mHiddenInsetBottom;
+        if (DEBUG) {
+            Log.d(TAG, "\nonDragStart mDownInsets=" + mDownInsetBottom
+                    + " mShownAtDown =" + mShownAtDown);
+        }
+
+        mApps.getWindowInsetsController().controlWindowInsetsAnimation(
+                WindowInsets.Type.ime(), -1 /* no predetermined duration */, LINEAR, null,
+                mCurrentRequest = new WindowInsetsAnimationControlListener() {
+
+                    @Override
+                    public void onReady(WindowInsetsAnimationController controller, int types) {
+                        if (DEBUG) {
+                            Log.d(TAG, "Listener.onReady " + (mCurrentRequest == this));
+                        }
+                        if (mCurrentRequest == this) {
+                            mAnimationController = controller;
+                        } else {
+                            controller.finish(mShownAtDown);
+                        }
+                    }
+
+                    @Override
+                    public void onFinished(WindowInsetsAnimationController controller) {
+                        // when screen lock happens, then this method get called
+                        mAnimationController.finish(false);
+                        mAnimationController = null;
+                        if (DEBUG) {
+                            Log.d(TAG, "Listener.onFinished ctrl=" + controller);
+                        }
+                    }
+
+                    @Override
+                    public void onCancelled(@Nullable WindowInsetsAnimationController controller) {
+                        mAnimationController = null;
+                        if (controller != null) {
+                            controller.finish(mShownAtDown);
+                        }
+                        if (DEBUG) {
+                            Log.d(TAG, "Listener.onCancelled ctrl=" + controller);
+                        }
+                    }
+                });
+    }
+
+    /**
+     * Handles the translation using the progress.
+     *
+     * @param progress value between 0..1
+     */
+    @RequiresApi(api = 30)
+    public void setProgress(float progress) {
+        if (!BuildCompat.isAtLeastR()) return;
+        // progress that equals to 0 or 1 is error prone. Do not use them.
+        // Instead use onDragStart and onAnimationEnd
+        if (mAnimationController == null || progress <= 0f || progress >= 1f) return;
+
+        mCurrent = progress * mAllAppsHeight;
+        mHiddenInsetBottom = mAnimationController.getHiddenStateInsets().bottom; // 0
+        mShownInsetBottom = mAnimationController.getShownStateInsets().bottom; // 1155
+
+        int shift = mShownAtDown ? 0 : (int) (mAllAppsHeight - mShownInsetBottom);
+
+        int inset = (int) (mDownInsetBottom + (mDown - mCurrent) - shift);
+
+        if (DEBUG) {
+            Log.d(TAG, "updateInset mCurrent=" + mCurrent + " mDown="
+                    + mDown + " hidden=" + mHiddenInsetBottom
+                    + " shown=" + mShownInsetBottom
+                    + " mDownInsets.bottom=" + mDownInsetBottom + " inset:" + inset
+                    + " shift: " + shift);
+        }
+        final int start = mShownAtDown ? mShownInsetBottom : mHiddenInsetBottom;
+        final int end = mShownAtDown ? mHiddenInsetBottom : mShownInsetBottom;
+        inset = Math.max(inset, mHiddenInsetBottom);
+        inset = Math.min(inset, mShownInsetBottom);
+        Log.d(TAG, "updateInset inset:" + inset);
+
+        mAnimationController.setInsetsAndAlpha(
+                Insets.of(0, 0, 0, inset),
+                1f, (inset - start) / (float) (end - start));
+    }
+
+    /**
+     * Report to the animation controller that we no longer plan to translate anymore.
+     *
+     * @param progress value between 0..1
+     */
+    @RequiresApi(api = 30)
+    public void onAnimationEnd(float progress) {
+        if (DEBUG) {
+            Log.d(TAG, "endTranslation progress=" + progress
+                    + " mAnimationController=" + mAnimationController);
+        }
+
+        if (mAnimationController == null) return;
+
+        if (Float.compare(progress, 1f) == 0 /* bottom */) {
+            mAnimationController.finish(false /* gone */);
+        }
+        if (Float.compare(progress, 0f) == 0 /* top */) {
+            mAnimationController.finish(true /* show */);
+        }
+        mAnimationController = null;
+        mCurrentRequest = null;
+    }
+}
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index cbf02b7..13a93ff 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -107,6 +107,7 @@
         pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_ALL_APPS_DIVIDER, 1);
         pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET, 1);
         pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_ICON, approxRows * mNumAppsPerRow);
+        pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_SEARCH_CORPUS_TITLE, 1);
 
         mViewHeights.clear();
         mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_ICON, grid.allAppsCellHeightPx);
@@ -277,7 +278,7 @@
         if (mApps == null) {
             return;
         }
-        List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
+        List<AllAppsGridAdapter.AdapterItem> items = mApps.getAdapterItems();
 
         // Skip early if there are no items or we haven't been measured
         if (items.isEmpty() || mNumAppsPerRow == 0) {
@@ -351,7 +352,7 @@
     @Override
     public int getCurrentScrollY() {
         // Return early if there are no items or we haven't been measured
-        List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
+        List<AllAppsGridAdapter.AdapterItem> items = mApps.getAdapterItems();
         if (items.isEmpty() || mNumAppsPerRow == 0 || getChildCount() == 0) {
             return -1;
         }
@@ -367,14 +368,14 @@
     }
 
     public int getCurrentScrollY(int position, int offset) {
-        List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
-        AlphabeticalAppsList.AdapterItem posItem = position < items.size() ?
-                items.get(position) : null;
+        List<AllAppsGridAdapter.AdapterItem> items = mApps.getAdapterItems();
+        AllAppsGridAdapter.AdapterItem posItem = position < items.size()
+                ? items.get(position) : null;
         int y = mCachedScrollPositions.get(position, -1);
         if (y < 0) {
             y = 0;
             for (int i = 0; i < position; i++) {
-                AlphabeticalAppsList.AdapterItem item = items.get(i);
+                AllAppsGridAdapter.AdapterItem item = items.get(i);
                 if (AllAppsGridAdapter.isIconViewType(item.viewType)) {
                     // Break once we reach the desired row
                     if (posItem != null && posItem.viewType == item.viewType &&
diff --git a/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java b/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java
new file mode 100644
index 0000000..0214c35
--- /dev/null
+++ b/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2020 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.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+import androidx.core.graphics.ColorUtils;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.launcher3.R;
+import com.android.launcher3.allapps.search.SearchSectionInfo;
+import com.android.launcher3.util.Themes;
+
+import java.util.List;
+
+/**
+ * ItemDecoration class that groups items in {@link AllAppsRecyclerView}
+ */
+public class AllAppsSectionDecorator extends RecyclerView.ItemDecoration {
+
+    private final AllAppsContainerView mAppsView;
+
+    AllAppsSectionDecorator(AllAppsContainerView appsContainerView) {
+        mAppsView = appsContainerView;
+    }
+
+    @Override
+    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
+        // Iterate through views in recylerview and draw bounds around views in the same section.
+        // Since views in the same section will follow each other, we can skip to a last view in
+        // a section to get the bounds of the section without having to iterate on every item.
+        int itemCount = parent.getChildCount();
+        List<AllAppsGridAdapter.AdapterItem> adapterItems = mAppsView.getApps().getAdapterItems();
+        SectionDecorationHandler lastDecorationHandler = null;
+        int i = 0;
+        while (i < itemCount) {
+            View view = parent.getChildAt(i);
+            int position = parent.getChildAdapterPosition(view);
+            AllAppsGridAdapter.AdapterItem adapterItem = adapterItems.get(position);
+            if (adapterItem.searchSectionInfo != null) {
+                SearchSectionInfo sectionInfo = adapterItem.searchSectionInfo;
+                int endIndex = Math.min(i + sectionInfo.getPosEnd() - position, itemCount - 1);
+                SectionDecorationHandler decorationHandler = sectionInfo.getDecorationHandler();
+                if (decorationHandler != lastDecorationHandler && lastDecorationHandler != null) {
+                    drawDecoration(c, lastDecorationHandler, parent);
+                }
+                lastDecorationHandler = decorationHandler;
+                if (decorationHandler != null) {
+                    decorationHandler.extendBounds(view);
+                }
+
+                if (endIndex > i) {
+                    i = endIndex;
+                    continue;
+                }
+            }
+            i++;
+        }
+        if (lastDecorationHandler != null) {
+            drawDecoration(c, lastDecorationHandler, parent);
+        }
+    }
+
+    private void drawDecoration(Canvas c, SectionDecorationHandler decorationHandler,
+            RecyclerView parent) {
+        if (decorationHandler == null) return;
+        if (decorationHandler.mIsFullWidth) {
+            decorationHandler.mBounds.left = parent.getPaddingLeft();
+            decorationHandler.mBounds.right = parent.getWidth() - parent.getPaddingRight();
+        }
+        decorationHandler.onDraw(c);
+        if (mAppsView.getFloatingHeaderView().getFocusedChild() == null
+                && mAppsView.getApps().getFocusedChild() != null) {
+            int index = mAppsView.getApps().getFocusedChildIndex();
+            if (index >= 0 && index < parent.getChildCount()) {
+                decorationHandler.onFocusDraw(c, parent.getChildAt(index));
+            }
+        }
+        decorationHandler.reset();
+    }
+
+    /**
+     * Handles grouping and drawing of items in the same all apps sections.
+     */
+    public static class SectionDecorationHandler {
+        private static final int FILL_ALPHA = (int) (.3f * 255);
+        private static final int FOCUS_ALPHA = (int) (.8f * 255);
+
+        protected RectF mBounds = new RectF();
+        private final boolean mIsFullWidth;
+        private final float mRadius;
+
+        protected int mFocusColor;
+        protected int mFillcolor;
+        private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+
+
+        public SectionDecorationHandler(Context context, boolean isFullWidth) {
+            mIsFullWidth = isFullWidth;
+            int endScrim = Themes.getAttrColor(context, R.attr.allAppsScrimColor);
+            mFillcolor = ColorUtils.setAlphaComponent(endScrim, FILL_ALPHA);
+            mFocusColor = ColorUtils.setAlphaComponent(endScrim, FOCUS_ALPHA);
+            mRadius = Themes.getDialogCornerRadius(context);
+        }
+
+        /**
+         * Extends current bounds to include the view.
+         */
+        public void extendBounds(View view) {
+            if (mBounds.isEmpty()) {
+                mBounds.set(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
+            } else {
+                mBounds.set(
+                        Math.min(mBounds.left, view.getLeft()),
+                        Math.min(mBounds.top, view.getTop()),
+                        Math.max(mBounds.right, view.getRight()),
+                        Math.max(mBounds.bottom, view.getBottom())
+                );
+            }
+        }
+
+        /**
+         * Draw bounds onto canvas.
+         */
+        public void onDraw(Canvas canvas) {
+            mPaint.setColor(mFillcolor);
+            canvas.drawRoundRect(mBounds, mRadius, mRadius, mPaint);
+        }
+
+        /**
+         * Draw the bound of the view to the canvas.
+         */
+        public void onFocusDraw(Canvas canvas, @Nullable View view) {
+            if (view == null) {
+                return;
+            }
+            mPaint.setColor(mFocusColor);
+            canvas.drawRoundRect(view.getLeft(), view.getTop(),
+                    view.getRight(), view.getBottom(), mRadius, mRadius, mPaint);
+        }
+
+        /**
+         * Reset view bounds to empty.
+         */
+        public void reset() {
+            mBounds.setEmpty();
+        }
+    }
+
+}
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index a9b030e..0268b96 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -1,10 +1,24 @@
+/*
+ * 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 static com.android.launcher3.LauncherState.ALL_APPS_CONTENT;
 import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA;
 import static com.android.launcher3.LauncherState.APPS_VIEW_ITEM_MASK;
 import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.LauncherState.VERTICAL_SWIPE_INDICATOR;
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
 import static com.android.launcher3.anim.Interpolators.INSTANT;
@@ -14,31 +28,29 @@
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_HEADER_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
+import static com.android.launcher3.util.SystemUiController.UI_STATE_ALLAPPS;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
-import android.content.Context;
 import android.util.FloatProperty;
 import android.view.View;
-import android.view.ViewGroup;
 import android.view.animation.Interpolator;
 import android.widget.EditText;
 
+import androidx.core.os.BuildCompat;
+
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.R;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.anim.PropertySetter;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.statemanager.StateManager.StateHandler;
 import com.android.launcher3.states.StateAnimationConfig;
-import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.views.ScrimView;
-import com.android.systemui.plugins.AllAppsSearchPlugin;
-import com.android.systemui.plugins.PluginListener;
 
 /**
  * Handles AllApps view transition.
@@ -51,7 +63,7 @@
  * closer to top or closer to the page indicator.
  */
 public class AllAppsTransitionController implements StateHandler<LauncherState>,
-        OnDeviceProfileChangeListener, PluginListener<AllAppsSearchPlugin> {
+        OnDeviceProfileChangeListener {
 
     public static final FloatProperty<AllAppsTransitionController> ALL_APPS_PROGRESS =
             new FloatProperty<AllAppsTransitionController>("allAppsProgress") {
@@ -85,10 +97,7 @@
     private float mProgress;        // [0, 1], mShiftRange * mProgress = shiftCurrent
 
     private float mScrollRangeDelta = 0;
-
-    // plugin related variables
-    private AllAppsSearchPlugin mPlugin;
-    private View mPluginContent;
+    private AllAppsInsetTransitionController mInsetController;
 
     public AllAppsTransitionController(Launcher l) {
         mLauncher = l;
@@ -103,6 +112,10 @@
         return mShiftRange;
     }
 
+    public AllAppsInsetTransitionController getInsetController() {
+        return mInsetController;
+    }
+
     @Override
     public void onDeviceProfileChanged(DeviceProfile dp) {
         mIsVerticalLayout = dp.isVerticalBarLayout();
@@ -130,8 +143,8 @@
         float shiftCurrent = progress * mShiftRange;
 
         mAppsView.setTranslationY(shiftCurrent);
-        if (mPlugin != null) {
-            mPlugin.setProgress(progress);
+        if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
+            mInsetController.setProgress(progress);
         }
     }
 
@@ -201,20 +214,13 @@
         Interpolator allAppsFade = config.getInterpolator(ANIM_ALL_APPS_FADE, LINEAR);
         Interpolator headerFade = config.getInterpolator(ANIM_ALL_APPS_HEADER_FADE, allAppsFade);
 
-        if (mPlugin == null) {
-            setter.setViewAlpha(mAppsView.getContentView(), hasAllAppsContent ? 1 : 0, allAppsFade);
-            setter.setViewAlpha(mAppsView.getScrollBar(), hasAllAppsContent ? 1 : 0, allAppsFade);
-            mAppsView.getFloatingHeaderView().setContentVisibility(hasHeaderExtra,
-                    hasAllAppsContent, setter, headerFade, allAppsFade);
-        } else {
-            setter.setViewAlpha(mPluginContent, hasAllAppsContent ? 1 : 0, allAppsFade);
-            setter.setViewAlpha(mAppsView.getContentView(), 0, allAppsFade);
-            setter.setViewAlpha(mAppsView.getScrollBar(), 0, allAppsFade);
-        }
-        mAppsView.getSearchUiManager().setContentVisibility(visibleElements, setter, allAppsFade);
 
-        setter.setInt(mScrimView, ScrimView.DRAG_HANDLE_ALPHA,
-                (visibleElements & VERTICAL_SWIPE_INDICATOR) != 0 ? 255 : 0, allAppsFade);
+        setter.setViewAlpha(mAppsView.getContentView(), hasAllAppsContent ? 1 : 0, allAppsFade);
+        setter.setViewAlpha(mAppsView.getScrollBar(), hasAllAppsContent ? 1 : 0, allAppsFade);
+        mAppsView.getFloatingHeaderView().setContentVisibility(hasHeaderExtra,
+                hasAllAppsContent, setter, headerFade, allAppsFade);
+
+        mAppsView.getSearchUiManager().setContentVisibility(visibleElements, setter, allAppsFade);
 
         // Set visibility of the container at the very beginning or end of the transition.
         setter.setViewAlpha(mAppsView, hasAnyVisibleItem ? 1 : 0,
@@ -228,8 +234,12 @@
     public void setupViews(AllAppsContainerView appsView, ScrimView scrimView) {
         mAppsView = appsView;
         mScrimView = scrimView;
-        PluginManagerWrapper.INSTANCE.get(mLauncher)
-                .addPluginListener(this, AllAppsSearchPlugin.class, false);
+        if (FeatureFlags.ENABLE_DEVICE_SEARCH.get() && BuildCompat.isAtLeastR()) {
+            mInsetController = new AllAppsInsetTransitionController(mShiftRange, mAppsView);
+            mLauncher.getSystemUiController().updateUiState(UI_STATE_ALLAPPS,
+                    View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+                    | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+        }
     }
 
     /**
@@ -252,47 +262,19 @@
         if (Float.compare(mProgress, 1f) == 0) {
             mAppsView.reset(false /* animate */);
         }
-        updatePluginAnimationEnd();
-    }
-
-    @Override
-    public void onPluginConnected(AllAppsSearchPlugin plugin, Context context) {
-        mPlugin = plugin;
-        mPluginContent = mLauncher.getLayoutInflater().inflate(
-                R.layout.all_apps_content_layout, mAppsView, false);
-        mAppsView.addView(mPluginContent);
-        mPluginContent.setAlpha(0f);
-        mPlugin.setup((ViewGroup) mPluginContent, mLauncher, mShiftRange);
-    }
-
-    @Override
-    public void onPluginDisconnected(AllAppsSearchPlugin plugin) {
-        mPlugin = null;
-        mAppsView.removeView(mPluginContent);
-    }
-
-    public void onActivityDestroyed() {
-        PluginManagerWrapper.INSTANCE.get(mLauncher).removePluginListener(this);
-    }
-
-    /** Used for the plugin to signal when drag starts happens
-     * @param toAllApps*/
-    public void onDragStart(boolean toAllApps) {
-        if (mPlugin == null) return;
-
-        if (toAllApps) {
-            EditText editText = mAppsView.getSearchUiManager().setTextSearchEnabled(true);
-            mPlugin.setEditText(editText);
-        }
-        mPlugin.onDragStart(toAllApps ? 1f : 0f);
-    }
-
-    private void updatePluginAnimationEnd() {
-        if (mPlugin == null) return;
-        mPlugin.onAnimationEnd(mProgress);
-        if (Float.compare(mProgress, 1f) == 0) {
-            mAppsView.getSearchUiManager().setTextSearchEnabled(false);
-            mPlugin.setEditText(null);
+        if (FeatureFlags.ENABLE_DEVICE_SEARCH.get() && BuildCompat.isAtLeastR()) {
+            mInsetController.onAnimationEnd(mProgress);
+            if (Float.compare(mProgress, 0f) == 0) {
+                EditText editText = mAppsView.getSearchUiManager().getEditText();
+                if (editText != null) {
+                    editText.requestFocus();
+                }
+            }
+            if (Float.compare(mProgress, 1f) == 0) {
+                // Called when home gesture closes all apps container.
+                // TODO: should make the controller hide synchronously
+                mInsetController.hide();
+            }
         }
     }
 }
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 06209bb..8c059d5 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -19,8 +19,10 @@
 import android.content.Context;
 
 import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
+import com.android.launcher3.allapps.search.SearchSectionInfo;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.LabelComparator;
 
@@ -60,61 +62,6 @@
         }
     }
 
-    /**
-     * Info about a particular adapter item (can be either section or app)
-     */
-    public static class AdapterItem {
-        /** Common properties */
-        // The index of this adapter item in the list
-        public int position;
-        // The type of this item
-        public int viewType;
-
-        /** 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 row that this item shows up on
-        public int rowIndex;
-        // The index of this app in the row
-        public int rowAppIndex;
-        // The associated AppInfo for the app
-        public AppInfo appInfo = null;
-        // The index of this app not including sections
-        public int appIndex = -1;
-
-        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.sectionName = sectionName;
-            item.appInfo = appInfo;
-            item.appIndex = appIndex;
-            return item;
-        }
-
-        public static AdapterItem asEmptySearch(int pos) {
-            AdapterItem item = new AdapterItem();
-            item.viewType = AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH;
-            item.position = pos;
-            return item;
-        }
-
-        public static AdapterItem asAllAppsDivider(int pos) {
-            AdapterItem item = new AdapterItem();
-            item.viewType = AllAppsGridAdapter.VIEW_TYPE_ALL_APPS_DIVIDER;
-            item.position = pos;
-            return item;
-        }
-
-        public static AdapterItem asMarketSearch(int pos) {
-            AdapterItem item = new AdapterItem();
-            item.viewType = AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET;
-            item.position = pos;
-            return item;
-        }
-    }
 
     private final BaseDraggingActivity mLauncher;
 
@@ -122,8 +69,8 @@
     private final List<AppInfo> mApps = new ArrayList<>();
     private final AllAppsStore mAllAppsStore;
 
-    // The set of filtered apps with the current filter
-    private final List<AppInfo> mFilteredApps = new ArrayList<>();
+    // The number of results in current adapter
+    private int mAccessibilityResultsCount = 0;
     // The current set of adapter items
     private final ArrayList<AdapterItem> mAdapterItems = new ArrayList<>();
     // The set of sections that we allow fast-scrolling to (includes non-merged sections)
@@ -132,7 +79,7 @@
     private final boolean mIsWork;
 
     // The of ordered component names as a result of a search query
-    private ArrayList<ComponentKey> mSearchResults;
+    private ArrayList<AdapterItem> mSearchResults;
     private AllAppsGridAdapter mAdapter;
     private AppInfoComparator mAppNameComparator;
     private final int mNumAppsPerRow;
@@ -182,6 +129,25 @@
     }
 
     /**
+     * Returns the child adapter item with IME launch focus.
+     */
+    public AdapterItem getFocusedChild() {
+        return mAdapterItems.get(getFocusedChildIndex());
+    }
+
+    /**
+     * Returns the index of the child with IME launch focus.
+     */
+    public int getFocusedChildIndex() {
+        for (AdapterItem item : mAdapterItems) {
+            if (item.isCountedForAccessibility()) {
+                return mAdapterItems.indexOf(item);
+            }
+        }
+        return -1;
+    }
+
+    /**
      * Returns the number of rows of applications
      */
     public int getNumAppRows() {
@@ -192,7 +158,7 @@
      * Returns the number of applications in this list.
      */
     public int getNumFilteredApps() {
-        return mFilteredApps.size();
+        return mAccessibilityResultsCount;
     }
 
     /**
@@ -206,14 +172,14 @@
      * Returns whether there are no filtered results.
      */
     public boolean hasNoFilteredResults() {
-        return (mSearchResults != null) && mFilteredApps.isEmpty();
+        return (mSearchResults != null) && mAccessibilityResultsCount == 0;
     }
 
     /**
-     * Sets the sorted list of filtered components.
+     * Sets results list for search
      */
-    public boolean setOrderedFilter(ArrayList<ComponentKey> f) {
-        if (mSearchResults != f) {
+    public boolean setSearchResults(ArrayList<AdapterItem> f) {
+        if (f == null || mSearchResults != f) {
             boolean same = mSearchResults != null && mSearchResults.equals(f);
             mSearchResults = f;
             onAppsUpdated();
@@ -292,41 +258,75 @@
         int appIndex = 0;
 
         // Prepare to update the list of sections, filtered apps, etc.
-        mFilteredApps.clear();
+        mAccessibilityResultsCount = 0;
         mFastScrollerSections.clear();
         mAdapterItems.clear();
 
+        SearchSectionInfo appSection = new SearchSectionInfo();
+        appSection.setDecorationHandler(
+                new AllAppsSectionDecorator.SectionDecorationHandler(mLauncher, true));
+
         // Recreate the filtered and sectioned apps (for convenience for the grid layout) from the
         // ordered set of sections
-        for (AppInfo info : getFiltersAppInfos()) {
-            String sectionName = info.sectionName;
 
-            // Create a new section if the section names do not match
-            if (!sectionName.equals(lastSectionName)) {
-                lastSectionName = sectionName;
-                lastFastScrollerSectionInfo = new FastScrollSectionInfo(sectionName);
-                mFastScrollerSections.add(lastFastScrollerSectionInfo);
-            }
+        if (!hasFilter()) {
+            mAccessibilityResultsCount = mApps.size();
+            appSection.setPosStart(position);
+            for (AppInfo info : mApps) {
+                String sectionName = info.sectionName;
 
-            // Create an app item
-            AdapterItem appItem = AdapterItem.asApp(position++, sectionName, info, appIndex++);
-            if (lastFastScrollerSectionInfo.fastScrollToItem == null) {
-                lastFastScrollerSectionInfo.fastScrollToItem = appItem;
+                // Create a new section if the section names do not match
+                if (!sectionName.equals(lastSectionName)) {
+                    lastSectionName = sectionName;
+                    lastFastScrollerSectionInfo = new FastScrollSectionInfo(sectionName);
+                    mFastScrollerSections.add(lastFastScrollerSectionInfo);
+                }
+
+                // Create an app item
+                AdapterItem appItem = AdapterItem.asApp(position++, sectionName, info, appIndex++);
+                if (lastFastScrollerSectionInfo.fastScrollToItem == null) {
+                    lastFastScrollerSectionInfo.fastScrollToItem = appItem;
+                }
+                if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
+                    appItem.searchSectionInfo = appSection;
+                }
+                mAdapterItems.add(appItem);
             }
-            mAdapterItems.add(appItem);
-            mFilteredApps.add(info);
+            appSection.setPosEnd(mApps.isEmpty() ? appSection.getPosStart() : position - 1);
+        } else {
+            List<AppInfo> appInfos = new ArrayList<>();
+            SearchSectionInfo lastSection = null;
+            for (int i = 0; i < mSearchResults.size(); i++) {
+                AdapterItem adapterItem = mSearchResults.get(i);
+                adapterItem.position = i;
+                mAdapterItems.add(adapterItem);
+                if (adapterItem.searchSectionInfo != lastSection) {
+                    if (adapterItem.searchSectionInfo != null) {
+                        adapterItem.searchSectionInfo.setPosStart(i);
+                    }
+                    if (lastSection != null) {
+                        lastSection.setPosEnd(i - 1);
+                    }
+                    lastSection = adapterItem.searchSectionInfo;
+                }
+                if (AllAppsGridAdapter.isIconViewType(adapterItem.viewType)) {
+                    appInfos.add(adapterItem.appInfo);
+                }
+                if (adapterItem.isCountedForAccessibility()) {
+                    mAccessibilityResultsCount++;
+                }
+            }
+            if (!FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
+                // Append the search market item
+                if (hasNoFilteredResults()) {
+                    mAdapterItems.add(AdapterItem.asEmptySearch(position++));
+                } else {
+                    mAdapterItems.add(AdapterItem.asAllAppsDivider(position++));
+                }
+                mAdapterItems.add(AdapterItem.asMarketSearch(position++));
+
+            }
         }
-
-        if (hasFilter()) {
-            // Append the search market item
-            if (hasNoFilteredResults()) {
-                mAdapterItems.add(AdapterItem.asEmptySearch(position++));
-            } else {
-                mAdapterItems.add(AdapterItem.asAllAppsDivider(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
             // would have to shift the values again)
@@ -381,18 +381,4 @@
             }
         }
     }
-
-    private List<AppInfo> getFiltersAppInfos() {
-        if (mSearchResults == null) {
-            return mApps;
-        }
-        ArrayList<AppInfo> result = new ArrayList<>();
-        for (ComponentKey key : mSearchResults) {
-            AppInfo match = mAllAppsStore.getApp(key);
-            if (match != null) {
-                result.add(match);
-            }
-        }
-        return result;
-    }
 }
diff --git a/src/com/android/launcher3/allapps/DiscoveryBounce.java b/src/com/android/launcher3/allapps/DiscoveryBounce.java
index b4ff5ea..14595ca 100644
--- a/src/com/android/launcher3/allapps/DiscoveryBounce.java
+++ b/src/com/android/launcher3/allapps/DiscoveryBounce.java
@@ -116,11 +116,6 @@
     }
 
     @Override
-    public void logActionCommand(int command) {
-        // Since this is on-boarding popup, it is not a user controlled action.
-    }
-
-    @Override
     protected boolean isOfType(int type) {
         return (type & TYPE_DISCOVERY_BOUNCE) != 0;
     }
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderRow.java b/src/com/android/launcher3/allapps/FloatingHeaderRow.java
index f899587..e357f61 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderRow.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderRow.java
@@ -16,6 +16,7 @@
 package com.android.launcher3.allapps;
 
 import android.graphics.Rect;
+import android.view.View;
 import android.view.animation.Interpolator;
 
 import com.android.launcher3.DeviceProfile;
@@ -55,4 +56,9 @@
     void setVerticalScroll(int scroll, boolean isScrolledOut);
 
     Class<? extends FloatingHeaderRow> getTypeClass();
+
+    /**
+     * Returns a child that has focus to be launched by the IME.
+     */
+    View getFocusedChild();
 }
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index 81e1b94..11d3fb9 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderView.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -38,6 +38,7 @@
 import com.android.launcher3.Insettable;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.PropertySetter;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.systemui.plugins.AllAppsRow;
 import com.android.systemui.plugins.AllAppsRow.OnHeightUpdatedListener;
@@ -194,6 +195,19 @@
         onHeightUpdated();
     }
 
+    @Override
+    public View getFocusedChild() {
+        if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
+            for (FloatingHeaderRow row : mAllRows) {
+                if (row.hasVisibleContent() && row.shouldDraw()) {
+                    return row.getFocusedChild();
+                }
+            }
+            return null;
+        }
+        return super.getFocusedChild();
+    }
+
     public void setup(AllAppsContainerView.AdapterHolder[] mAH, boolean tabsHidden) {
         for (FloatingHeaderRow row : mAllRows) {
             row.setup(this, mAllRows, tabsHidden);
diff --git a/src/com/android/launcher3/allapps/PluginHeaderRow.java b/src/com/android/launcher3/allapps/PluginHeaderRow.java
index 3089b18..cf7142c 100644
--- a/src/com/android/launcher3/allapps/PluginHeaderRow.java
+++ b/src/com/android/launcher3/allapps/PluginHeaderRow.java
@@ -83,4 +83,9 @@
     public Class<PluginHeaderRow> getTypeClass() {
         return PluginHeaderRow.class;
     }
+
+    @Override
+    public View getFocusedChild() {
+        return null;
+    }
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/allapps/SearchUiManager.java b/src/com/android/launcher3/allapps/SearchUiManager.java
index 7d5363f..aa056a0 100644
--- a/src/com/android/launcher3/allapps/SearchUiManager.java
+++ b/src/com/android/launcher3/allapps/SearchUiManager.java
@@ -66,12 +66,8 @@
     }
 
     /**
-     * Called to control how the search UI result should be handled.
-     *
-     * @param isEnabled when {@code true}, the search is all handled inside AOSP
-     *                  and is not overlayable.
-     * @return the searchbox edit text object
+     * @return the edit text object
      */
     @Nullable
-    EditText setTextSearchEnabled(boolean isEnabled);
+    EditText getEditText();
 }
diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
index ed45749..2e5ed3e 100644
--- a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.allapps.search;
 
+import android.os.Bundle;
 import android.text.Editable;
 import android.text.TextUtils;
 import android.text.TextWatcher;
@@ -27,12 +28,17 @@
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.ExtendedEditText;
+import com.android.launcher3.Launcher;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.model.AppLaunchTracker;
-import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.allapps.AllAppsGridAdapter;
+import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.PackageManagerHelper;
+import com.android.systemui.plugins.AllAppsSearchPlugin;
 
 import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
 
 /**
  * An interface to a search box that AllApps can command.
@@ -51,12 +57,13 @@
     public void setVisibility(int visibility) {
         mInput.setVisibility(visibility);
     }
+
     /**
      * Sets the references to the apps model and the search result callback.
      */
     public final void initialize(
             SearchAlgorithm searchAlgorithm, ExtendedEditText input,
-            BaseDraggingActivity launcher, Callbacks cb) {
+            BaseDraggingActivity launcher, Callbacks cb, Consumer<List<Bundle>> secondaryCb) {
         mCb = cb;
         mLauncher = launcher;
 
@@ -69,8 +76,11 @@
     }
 
     @Override
-    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-        // Do nothing
+    public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+        if (mSearchAlgorithm instanceof PluginWrapper) {
+            ((PluginWrapper) mSearchAlgorithm).runOnPluginIfConnected(
+                    AllAppsSearchPlugin::startedTyping);
+        }
     }
 
     @Override
@@ -101,6 +111,14 @@
 
     @Override
     public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+        if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
+            if (actionId == EditorInfo.IME_ACTION_SEARCH) {
+                if (Launcher.getLauncher(mLauncher).getAppsView().selectFocusedView(v)) {
+                    return true;
+                }
+            }
+        }
+
         // Skip if it's not the right action
         if (actionId != EditorInfo.IME_ACTION_SEARCH) {
             return false;
@@ -112,8 +130,8 @@
             return false;
         }
         return mLauncher.startActivitySafely(v,
-                PackageManagerHelper.getMarketSearchIntent(mLauncher, query), null,
-                AppLaunchTracker.CONTAINER_SEARCH);
+                PackageManagerHelper.getMarketSearchIntent(mLauncher, query), null
+        );
     }
 
     @Override
@@ -158,16 +176,26 @@
     }
 
     /**
+     * A wrapper setup for running essential calls to plugin from search controller
+     */
+    public interface PluginWrapper {
+        /**
+         * executes call if plugin is connected
+         */
+        void runOnPluginIfConnected(Consumer<AllAppsSearchPlugin> plugin);
+    }
+
+    /**
      * Callback for getting search results.
      */
     public interface Callbacks {
 
         /**
-         * Called when the search is complete.
+         * Called when the search from primary source is complete.
          *
-         * @param apps sorted list of matching components or null if in case of failure.
+         * @param items sorted list of search result adapter items.
          */
-        void onSearchResult(String query, ArrayList<ComponentKey> apps);
+        void onSearchResult(String query, ArrayList<AllAppsGridAdapter.AdapterItem> items);
 
         /**
          * Called when the search results should be cleared.
@@ -175,4 +203,15 @@
         void clearSearchResult();
     }
 
+    /**
+     * An interface for supporting dynamic search results
+     *
+     * @param <T> Type of payload
+     */
+    public interface PayloadResultHandler<T> {
+        /**
+         * Updates View using Adapter's payload
+         */
+        void applyAdapterInfo(AdapterItemWithPayload<T> adapterItemWithPayload);
+    }
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
index 356c52c..7518521 100644
--- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
+++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
@@ -24,6 +24,7 @@
 
 import android.content.Context;
 import android.graphics.Rect;
+import android.os.Bundle;
 import android.text.Selection;
 import android.text.SpannableStringBuilder;
 import android.text.method.TextKeyListener;
@@ -38,22 +39,25 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.ExtendedEditText;
 import com.android.launcher3.Insettable;
+import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.AllAppsContainerView;
+import com.android.launcher3.allapps.AllAppsGridAdapter;
 import com.android.launcher3.allapps.AllAppsStore;
 import com.android.launcher3.allapps.AlphabeticalAppsList;
 import com.android.launcher3.allapps.SearchUiManager;
 import com.android.launcher3.anim.PropertySetter;
-import com.android.launcher3.util.ComponentKey;
 
 import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
 
 /**
  * Layout to contain the All-apps search UI.
  */
 public class AppsSearchContainerLayout extends ExtendedEditText
         implements SearchUiManager, AllAppsSearchBarController.Callbacks,
-        AllAppsStore.OnUpdateListener, Insettable {
+        AllAppsStore.OnUpdateListener, Insettable, Consumer<List<Bundle>> {
 
     private final BaseDraggingActivity mLauncher;
     private final AllAppsSearchBarController mSearchBarController;
@@ -135,7 +139,8 @@
         mApps = appsView.getApps();
         mAppsView = appsView;
         mSearchBarController.initialize(
-                new DefaultAppSearchAlgorithm(mApps.getApps()), this, mLauncher, this);
+                new DefaultAppSearchAlgorithm(mLauncher, LauncherAppState.getInstance(mLauncher)),
+                this, mLauncher, this, this);
     }
 
     @Override
@@ -168,9 +173,9 @@
     }
 
     @Override
-    public void onSearchResult(String query, ArrayList<ComponentKey> apps) {
-        if (apps != null) {
-            mApps.setOrderedFilter(apps);
+    public void onSearchResult(String query, ArrayList<AllAppsGridAdapter.AdapterItem> items) {
+        if (items != null) {
+            mApps.setSearchResults(items);
             notifyResultChanged();
             mAppsView.setLastSearchQuery(query);
         }
@@ -178,7 +183,7 @@
 
     @Override
     public void clearSearchResult() {
-        if (mApps.setOrderedFilter(null)) {
+        if (mApps.setSearchResults(null)) {
             notifyResultChanged();
         }
 
@@ -216,7 +221,12 @@
     }
 
     @Override
-    public EditText setTextSearchEnabled(boolean isEnabled) {
+    public EditText getEditText() {
         return this;
     }
+
+    @Override
+    public void accept(List<Bundle> bundles) {
+        // TODO: Render the result on mAppsView object
+    }
 }
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java b/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java
new file mode 100644
index 0000000..dc9c155
--- /dev/null
+++ b/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2020 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.search;
+
+import android.content.Context;
+
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
+import com.android.launcher3.allapps.AllAppsSectionDecorator.SectionDecorationHandler;
+import com.android.launcher3.model.AllAppsList;
+import com.android.launcher3.model.BaseModelUpdateTask;
+import com.android.launcher3.model.BgDataModel;
+import com.android.launcher3.model.data.AppInfo;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * A device search section for handling app searches
+ */
+public class AppsSearchPipeline implements SearchPipeline {
+
+    private static final int MAX_RESULTS_COUNT = 5;
+
+    private final SearchSectionInfo mSearchSectionInfo;
+    private final LauncherAppState mLauncherAppState;
+
+    public AppsSearchPipeline(Context context, LauncherAppState launcherAppState) {
+        mLauncherAppState = launcherAppState;
+        mSearchSectionInfo = new SearchSectionInfo();
+        mSearchSectionInfo.setDecorationHandler(
+                new SectionDecorationHandler(context, true));
+    }
+
+    @Override
+    public void performSearch(String query, Consumer<ArrayList<AdapterItem>> callback) {
+        mLauncherAppState.getModel().enqueueModelUpdateTask(new BaseModelUpdateTask() {
+            @Override
+            public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+                List<AppInfo> matchingResults = getTitleMatchResult(apps.data, query);
+                callback.accept(getAdapterItems(matchingResults));
+            }
+        });
+    }
+
+    /**
+     * Filters {@link AppInfo}s matching specified query
+     */
+    public static ArrayList<AppInfo> getTitleMatchResult(List<AppInfo> apps, String query) {
+        // Do an intersection of the words in the query and each title, and filter out all the
+        // apps that don't match all of the words in the query.
+        final String queryTextLower = query.toLowerCase();
+        final ArrayList<AppInfo> result = new ArrayList<>();
+        DefaultAppSearchAlgorithm.StringMatcher matcher =
+                DefaultAppSearchAlgorithm.StringMatcher.getInstance();
+        for (AppInfo info : apps) {
+            if (DefaultAppSearchAlgorithm.matches(info, queryTextLower, matcher)) {
+                result.add(info);
+            }
+        }
+        return result;
+    }
+
+    private ArrayList<AdapterItem> getAdapterItems(List<AppInfo> matchingApps) {
+        ArrayList<AdapterItem> items = new ArrayList<>();
+        for (int i = 0; i < matchingApps.size() && i < MAX_RESULTS_COUNT; i++) {
+            AdapterItem appItem = AdapterItem.asApp(i, "", matchingApps.get(i), i);
+            appItem.searchSectionInfo = mSearchSectionInfo;
+            items.add(appItem);
+        }
+
+        return items;
+    }
+}
diff --git a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
index f72a988..5ed7de5 100644
--- a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
+++ b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
@@ -15,26 +15,25 @@
  */
 package com.android.launcher3.allapps.search;
 
+import android.content.Context;
 import android.os.Handler;
 
+import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.util.ComponentKey;
 
 import java.text.Collator;
-import java.util.ArrayList;
-import java.util.List;
 
 /**
  * The default search implementation.
  */
 public class DefaultAppSearchAlgorithm implements SearchAlgorithm {
 
-    private final List<AppInfo> mApps;
     protected final Handler mResultHandler;
+    private final AppsSearchPipeline mAppsSearchPipeline;
 
-    public DefaultAppSearchAlgorithm(List<AppInfo> apps) {
-        mApps = apps;
+    public DefaultAppSearchAlgorithm(Context context, LauncherAppState launcherAppState) {
         mResultHandler = new Handler();
+        mAppsSearchPipeline = new AppsSearchPipeline(context, launcherAppState);
     }
 
     @Override
@@ -47,28 +46,8 @@
     @Override
     public void doSearch(final String query,
             final AllAppsSearchBarController.Callbacks callback) {
-        final ArrayList<ComponentKey> result = getTitleMatchResult(query);
-        mResultHandler.post(new Runnable() {
-
-            @Override
-            public void run() {
-                callback.onSearchResult(query, result);
-            }
-        });
-    }
-
-    private ArrayList<ComponentKey> getTitleMatchResult(String query) {
-        // Do an intersection of the words in the query and each title, and filter out all the
-        // apps that don't match all of the words in the query.
-        final String queryTextLower = query.toLowerCase();
-        final ArrayList<ComponentKey> result = new ArrayList<>();
-        StringMatcher matcher = StringMatcher.getInstance();
-        for (AppInfo info : mApps) {
-            if (matches(info, queryTextLower, matcher)) {
-                result.add(info.toComponentKey());
-            }
-        }
-        return result;
+        mAppsSearchPipeline.performSearch(query,
+                results -> mResultHandler.post(() -> callback.onSearchResult(query, results)));
     }
 
     public static boolean matches(AppInfo info, String query, StringMatcher matcher) {
diff --git a/src/com/android/launcher3/allapps/search/SearchPipeline.java b/src/com/android/launcher3/allapps/search/SearchPipeline.java
new file mode 100644
index 0000000..545f0e3
--- /dev/null
+++ b/src/com/android/launcher3/allapps/search/SearchPipeline.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2020 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.search;
+
+import com.android.launcher3.allapps.AllAppsGridAdapter;
+
+import java.util.ArrayList;
+import java.util.function.Consumer;
+
+/**
+ * An interface for handling search within pipeline
+ */
+public interface SearchPipeline {
+
+    /**
+     * Perform query
+     */
+    void performSearch(String query, Consumer<ArrayList<AllAppsGridAdapter.AdapterItem>> cb);
+}
diff --git a/src/com/android/launcher3/allapps/search/SearchSectionInfo.java b/src/com/android/launcher3/allapps/search/SearchSectionInfo.java
new file mode 100644
index 0000000..e026e84
--- /dev/null
+++ b/src/com/android/launcher3/allapps/search/SearchSectionInfo.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2020 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.search;
+
+import com.android.launcher3.allapps.AllAppsSectionDecorator.SectionDecorationHandler;
+
+/**
+ * Info class for a search section
+ */
+public class SearchSectionInfo {
+
+    private String mTitle;
+    private SectionDecorationHandler mDecorationHandler;
+
+    public int getPosStart() {
+        return mPosStart;
+    }
+
+    public void setPosStart(int posStart) {
+        mPosStart = posStart;
+    }
+
+    public int getPosEnd() {
+        return mPosEnd;
+    }
+
+    public void setPosEnd(int posEnd) {
+        mPosEnd = posEnd;
+    }
+
+    private int mPosStart;
+    private int mPosEnd;
+
+    public SearchSectionInfo() {
+        this(null);
+    }
+
+    public SearchSectionInfo(String title) {
+        mTitle = title;
+    }
+
+    public void setDecorationHandler(SectionDecorationHandler sectionDecorationHandler) {
+        mDecorationHandler = sectionDecorationHandler;
+    }
+
+
+    public SectionDecorationHandler getDecorationHandler() {
+        return mDecorationHandler;
+    }
+
+    /**
+     * Returns the section's title
+     */
+    public String getTitle() {
+        return mTitle == null ? "" : mTitle;
+    }
+}
diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
index ea0ff8b..dcdfb6e 100644
--- a/src/com/android/launcher3/anim/AnimatorPlaybackController.java
+++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
@@ -19,7 +19,7 @@
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.clampToProgress;
 import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
-import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
+import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
 
 import android.animation.Animator;
 import android.animation.Animator.AnimatorListener;
diff --git a/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java
index 860cceb..8016b2d 100644
--- a/src/com/android/launcher3/anim/Interpolators.java
+++ b/src/com/android/launcher3/anim/Interpolators.java
@@ -16,7 +16,7 @@
 
 package com.android.launcher3.anim;
 
-import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
+import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
 
 import android.content.Context;
 import android.graphics.Path;
@@ -198,6 +198,7 @@
         public OvershootParams(float startProgress, float overshootPastProgress,
                 float endProgress, float velocityPxPerMs, int totalDistancePx, Context context) {
             velocityPxPerMs = Math.abs(velocityPxPerMs);
+            overshootPastProgress = Math.max(overshootPastProgress, startProgress);
             start = startProgress;
             int startPx = (int) (start * totalDistancePx);
             // Overshoot by about half a frame.
diff --git a/src/com/android/launcher3/anim/PendingAnimation.java b/src/com/android/launcher3/anim/PendingAnimation.java
index 4195933..5362575 100644
--- a/src/com/android/launcher3/anim/PendingAnimation.java
+++ b/src/com/android/launcher3/anim/PendingAnimation.java
@@ -149,7 +149,7 @@
             mProgressAnimator = null;
         }
         if (mAnimHolders.isEmpty()) {
-            // Add a dummy animation to that the duration is respected
+            // Add a placeholder animation to that the duration is respected
             add(ValueAnimator.ofFloat(0, 1).setDuration(mDuration));
         }
         return mAnim;
diff --git a/src/com/android/launcher3/anim/SpringAnimationBuilder.java b/src/com/android/launcher3/anim/SpringAnimationBuilder.java
index a9702b4..bd52158 100644
--- a/src/com/android/launcher3/anim/SpringAnimationBuilder.java
+++ b/src/com/android/launcher3/anim/SpringAnimationBuilder.java
@@ -25,7 +25,7 @@
 import androidx.annotation.FloatRange;
 import androidx.dynamicanimation.animation.SpringForce;
 
-import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.DisplayController;
 
 /**
  * Utility class to build an object animator which follows the same path as a spring animation for
@@ -134,7 +134,7 @@
     }
 
     public SpringAnimationBuilder computeParams() {
-        int singleFrameMs = DefaultDisplay.getSingleFrameMs(mContext);
+        int singleFrameMs = DisplayController.getSingleFrameMs(mContext);
         double naturalFreq = Math.sqrt(mStiffness);
         double dampedFreq = naturalFreq * Math.sqrt(1 - mDampingRatio * mDampingRatio);
 
diff --git a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
index 1d32d1d..30c3417 100644
--- a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
+++ b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
@@ -70,7 +70,8 @@
         final Bundle parcel = new Bundle();
         parcel.putInt(TestProtocol.STATE_FIELD, stateOrdinal);
 
-        sendEventToTest(accessibilityManager, TestProtocol.SWITCHED_TO_STATE_MESSAGE, parcel);
+        sendEventToTest(
+                accessibilityManager, context, TestProtocol.SWITCHED_TO_STATE_MESSAGE, parcel);
         Log.d(TestProtocol.PERMANENT_DIAG_TAG, "sendStateEventToTest: " + stateOrdinal);
     }
 
@@ -78,22 +79,24 @@
         final AccessibilityManager accessibilityManager = getAccessibilityManagerForTest(context);
         if (accessibilityManager == null) return;
 
-        sendEventToTest(accessibilityManager, TestProtocol.SCROLL_FINISHED_MESSAGE, null);
+        sendEventToTest(accessibilityManager, context, TestProtocol.SCROLL_FINISHED_MESSAGE, null);
     }
 
     public static void sendPauseDetectedEventToTest(Context context) {
         final AccessibilityManager accessibilityManager = getAccessibilityManagerForTest(context);
         if (accessibilityManager == null) return;
 
-        sendEventToTest(accessibilityManager, TestProtocol.PAUSE_DETECTED_MESSAGE, null);
+        sendEventToTest(accessibilityManager, context, TestProtocol.PAUSE_DETECTED_MESSAGE, null);
     }
 
     private static void sendEventToTest(
-            AccessibilityManager accessibilityManager, String eventTag, Bundle data) {
+            AccessibilityManager accessibilityManager,
+            Context context, String eventTag, Bundle data) {
         final AccessibilityEvent e = AccessibilityEvent.obtain(
                 AccessibilityEvent.TYPE_ANNOUNCEMENT);
         e.setClassName(eventTag);
         e.setParcelableData(data);
+        e.setPackageName(context.getApplicationContext().getPackageName());
         accessibilityManager.sendAccessibilityEvent(e);
     }
 
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 5c4a492..d8fca1d 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -92,7 +92,11 @@
 
     // Keep as DeviceFlag to allow remote disable in emergency.
     public static final BooleanFlag ENABLE_SUGGESTED_ACTIONS_OVERVIEW = new DeviceFlag(
-            "ENABLE_SUGGESTED_ACTIONS_OVERVIEW", false, "Show chip hints on the overview screen");
+            "ENABLE_SUGGESTED_ACTIONS_OVERVIEW", true, "Show chip hints on the overview screen");
+
+
+    public static final BooleanFlag ENABLE_DEVICE_SEARCH = getDebugFlag(
+            "ENABLE_DEVICE_SEARCH", false, "Allows on device search in all apps");
 
     public static final BooleanFlag FOLDER_NAME_SUGGEST = new DeviceFlag(
             "FOLDER_NAME_SUGGEST", true,
@@ -138,10 +142,6 @@
     public static final BooleanFlag ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER = getDebugFlag(
             "ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER", true, "Show launcher preview in grid picker");
 
-    public static final BooleanFlag ENABLE_OVERVIEW_ACTIONS = getDebugFlag(
-            "ENABLE_OVERVIEW_ACTIONS", true, "Show app actions instead of the shelf in Overview."
-            + " As part of this decoupling, also distinguish swipe up from nav bar vs above it.");
-
     // Keep as DeviceFlag for remote disable in emergency.
     public static final BooleanFlag ENABLE_OVERVIEW_SELECTIONS = new DeviceFlag(
             "ENABLE_OVERVIEW_SELECTIONS", true, "Show Select Mode button in Overview Actions");
@@ -157,9 +157,9 @@
             "ENABLE_UNIVERSAL_SMARTSPACE", false,
             "Replace Smartspace with a version rendered by System UI.");
 
-    public static final BooleanFlag ENABLE_LSQ_VELOCITY_PROVIDER = getDebugFlag(
-            "ENABLE_LSQ_VELOCITY_PROVIDER", true,
-            "Use Least Square algorithm for motion pause detection.");
+    public static final BooleanFlag ENABLE_SYSTEM_VELOCITY_PROVIDER = getDebugFlag(
+            "ENABLE_SYSTEM_VELOCITY_PROVIDER", true,
+            "Use system VelocityTracker's algorithm for motion pause detection.");
 
     public static final BooleanFlag ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS =
             getDebugFlag(
@@ -174,8 +174,9 @@
             "SEPARATE_RECENTS_ACTIVITY", false,
             "Uses a separate recents activity instead of using the integrated recents+Launcher UI");
 
-    public static final BooleanFlag USER_EVENT_DISPATCHER = new DeviceFlag(
-            "USER_EVENT_DISPATCHER", true, "User event dispatcher collects logs.");
+    public static final BooleanFlag ENABLE_MINIMAL_DEVICE = new DeviceFlag(
+            "ENABLE_MINIMAL_DEVICE", false,
+            "Allow user to toggle minimal device mode in launcher.");
 
     public static void initialize(Context context) {
         synchronized (sDebugFlags) {
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index 0df6713..2d625c5 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -43,13 +43,13 @@
 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.LauncherAppWidgetHost;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.R;
+import com.android.launcher3.model.ItemInstallQueue;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.pm.PinRequestHelper;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
@@ -249,7 +249,7 @@
      */
     public void onPlaceAutomaticallyClick(View v) {
         if (mRequest.getRequestType() == PinItemRequest.REQUEST_TYPE_SHORTCUT) {
-            InstallShortcutReceiver.queueShortcut(mRequest.getShortcutInfo(), this);
+            ItemInstallQueue.INSTANCE.get(this).queueItem(mRequest.getShortcutInfo());
             logCommand(Action.Command.CONFIRM);
             mRequest.accept();
             finish();
@@ -270,7 +270,8 @@
     }
 
     private void acceptWidget(int widgetId) {
-        InstallShortcutReceiver.queueWidget(mRequest.getAppWidgetProviderInfo(this), widgetId, this);
+        ItemInstallQueue.INSTANCE.get(this)
+                .queueItem(mRequest.getAppWidgetProviderInfo(this), widgetId);
         mWidgetOptions.putInt(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
         mRequest.accept(mWidgetOptions);
         logCommand(Action.Command.CONFIRM);
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 03028d3..ef666f0 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -463,10 +463,10 @@
     }
 
     public void forceTouchMove() {
-        int[] dummyCoordinates = mCoordinatesTemp;
-        DropTarget dropTarget = findDropTarget(mLastTouch.x, mLastTouch.y, dummyCoordinates);
-        mDragObject.x = dummyCoordinates[0];
-        mDragObject.y = dummyCoordinates[1];
+        int[] placeholderCoordinates = mCoordinatesTemp;
+        DropTarget dropTarget = findDropTarget(mLastTouch.x, mLastTouch.y, placeholderCoordinates);
+        mDragObject.x = placeholderCoordinates[0];
+        mDragObject.y = placeholderCoordinates[1];
         checkTouchMove(dropTarget);
     }
 
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index de0fa1a..86b93d0 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -187,9 +187,6 @@
      */
     @TargetApi(Build.VERSION_CODES.O)
     public void setItemInfo(final ItemInfo info) {
-        if (!Utilities.ATLEAST_OREO) {
-            return;
-        }
         if (info.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION &&
                 info.itemType != LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT &&
                 info.itemType != LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
diff --git a/src/com/android/launcher3/dragndrop/PinItemDragListener.java b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
index bf3aa7f..6104d80 100644
--- a/src/com/android/launcher3/dragndrop/PinItemDragListener.java
+++ b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
@@ -16,7 +16,6 @@
 
 package com.android.launcher3.dragndrop;
 
-import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
 
 import android.annotation.TargetApi;
 import android.appwidget.AppWidgetManager;
@@ -33,15 +32,11 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.PendingAddItemInfo;
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
 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.ArrayList;
-
 /**
  * {@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.
@@ -107,12 +102,6 @@
     }
 
     @Override
-    public void fillInLogContainerData(ItemInfo childInfo, LauncherLogProto.Target child,
-            ArrayList<LauncherLogProto.Target> parents) {
-        parents.add(newContainerTarget(LauncherLogProto.ContainerType.PINITEM));
-    }
-
-    @Override
     protected void postCleanup() {
         super.postCleanup();
         mCancelSignal.cancel();
diff --git a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
index 9982b39..f543e47 100644
--- a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
+++ b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
@@ -49,14 +49,14 @@
 
     // 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 static final String STUB_COMPONENT_CLASS = "pinned-shortcut";
 
     private final PinItemRequest mRequest;
     private final ShortcutInfo mInfo;
     private final Context mContext;
 
     public PinShortcutRequestActivityInfo(PinItemRequest request, Context context) {
-        super(new ComponentName(request.getShortcutInfo().getPackage(), DUMMY_COMPONENT_CLASS),
+        super(new ComponentName(request.getShortcutInfo().getPackage(), STUB_COMPONENT_CLASS),
                 request.getShortcutInfo().getUserHandle());
         mRequest = request;
         mInfo = request.getShortcutInfo();
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index d01e189..f1606ea 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -19,11 +19,11 @@
 import static android.text.TextUtils.isEmpty;
 
 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
-import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
 import static com.android.launcher3.config.FeatureFlags.ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS;
 import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_CONVERTED_TO_ICON;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_LABEL_UPDATED;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED;
 
@@ -241,9 +241,9 @@
         mFolderName.setSelectAllOnFocus(true);
         mFolderName.setInputType(mFolderName.getInputType()
                 & ~InputType.TYPE_TEXT_FLAG_AUTO_CORRECT
-                & ~InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
+                | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
                 | InputType.TYPE_TEXT_FLAG_CAP_WORDS);
-        mFolderName.forceDisableSuggestions(!FeatureFlags.FOLDER_NAME_SUGGEST.get());
+        mFolderName.forceDisableSuggestions(true);
 
         mFooter = findViewById(R.id.folder_footer);
 
@@ -741,7 +741,8 @@
 
     @Override
     protected View getAccessibilityInitialFocusView() {
-        return mContent.getFirstItem();
+        View firstItem = mContent.getFirstItem();
+        return firstItem != null ? firstItem : super.getAccessibilityInitialFocusView();
     }
 
     private void closeComplete(boolean wasAnimated) {
@@ -1134,6 +1135,9 @@
      * Rearranges the children based on their rank.
      */
     public void rearrangeChildren() {
+        if (!mContent.areViewsBound()) {
+            return;
+        }
         mContent.arrangeChildren(getIconsInReadingOrder());
         mItemsInvalidated = true;
     }
@@ -1179,7 +1183,8 @@
                         newIcon.requestFocus();
                     }
                     if (finalItem != null) {
-                        mLauncher.folderConvertedToItem(mFolderIcon.getFolder(), finalItem);
+                        mStatsLogManager.logger().withItemInfo(finalItem)
+                                .log(LAUNCHER_FOLDER_CONVERTED_TO_ICON);
                     }
                 }
             }
@@ -1477,27 +1482,6 @@
         outRect.right += mScrollAreaOffset;
     }
 
-    @Override
-    public void fillInLogContainerData(ItemInfo childInfo, LauncherLogProto.Target child,
-            ArrayList<LauncherLogProto.Target> targets) {
-        child.gridX = childInfo.cellX;
-        child.gridY = childInfo.cellY;
-        child.pageIndex = mContent.getCurrentPage();
-
-        LauncherLogProto.Target target = newContainerTarget(LauncherLogProto.ContainerType.FOLDER);
-        target.pageIndex = mInfo.screenId;
-        target.gridX = mInfo.cellX;
-        target.gridY = mInfo.cellY;
-        targets.add(target);
-
-        // continue to parent
-        if (mInfo.container == CONTAINER_HOTSEAT) {
-            mLauncher.getHotseat().fillInLogContainerData(mInfo, target, targets);
-        } else {
-            mLauncher.getWorkspace().fillInLogContainerData(mInfo, target, targets);
-        }
-    }
-
     private class OnScrollHintListener implements OnAlarmListener {
 
         private final DragObject mDragObject;
@@ -1585,17 +1569,6 @@
         return getOpenView(launcher, TYPE_FOLDER);
     }
 
-    @Override
-    public void logActionCommand(int command) {
-        mLauncher.getUserEventDispatcher().logActionCommand(
-                command, getFolderIcon(), getLogContainerType());
-    }
-
-    @Override
-    public int getLogContainerType() {
-        return LauncherLogProto.ContainerType.FOLDER;
-    }
-
     /**
      * Navigation bar back key or hardware input back key has been issued.
      */
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 75275b2..32d061c 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -129,6 +129,8 @@
     private float mDotScale;
     private Animator mDotScaleAnim;
 
+    private Rect mTouchArea = new Rect();
+
     private final PointF mTranslationForReorderBounce = new PointF(0, 0);
     private final PointF mTranslationForReorderPreview = new PointF(0, 0);
     private float mScaleForReorderBounce = 1f;
@@ -711,6 +713,11 @@
 
     @Override
     public boolean onTouchEvent(MotionEvent event) {
+        if (event.getAction() == MotionEvent.ACTION_DOWN
+                && shouldIgnoreTouchDown(event.getX(), event.getY())) {
+            return false;
+        }
+
         // Call the superclass onTouchEvent first, because sometimes it changes the state to
         // isPressed() on an ACTION_UP
         super.onTouchEvent(event);
@@ -719,6 +726,15 @@
         return true;
     }
 
+    /**
+     * Returns true if the touch down at the provided position be ignored
+     */
+    protected boolean shouldIgnoreTouchDown(float x, float y) {
+        mTouchArea.set(getPaddingLeft(), getPaddingTop(), getWidth() - getPaddingRight(),
+                getHeight() - getPaddingBottom());
+        return !mTouchArea.contains((int) x, (int) y);
+    }
+
     @Override
     public void cancelLongPress() {
         super.cancelLongPress();
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index 32531c0..a08dd30 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -500,6 +500,9 @@
      * Reorders the items such that the {@param empty} spot moves to {@param target}
      */
     public void realTimeReorder(int empty, int target) {
+        if (!mViewsBound) {
+            return;
+        }
         completePendingPageChanges();
         int delay = 0;
         float delayAmount = START_VIEW_REORDER_DELAY;
diff --git a/src/com/android/launcher3/graphics/IconShape.java b/src/com/android/launcher3/graphics/IconShape.java
index 4369385..b208a40 100644
--- a/src/com/android/launcher3/graphics/IconShape.java
+++ b/src/com/android/launcher3/graphics/IconShape.java
@@ -43,8 +43,9 @@
 import android.view.View;
 import android.view.ViewOutlineProvider;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
 import com.android.launcher3.icons.GraphicsUtils;
 import com.android.launcher3.icons.IconNormalizer;
@@ -59,8 +60,6 @@
 import java.util.ArrayList;
 import java.util.List;
 
-import androidx.annotation.Nullable;
-
 /**
  * Abstract representation of the shape of an icon shape
  */
@@ -381,9 +380,6 @@
      * Initializes the shape which is closest to the {@link AdaptiveIconDrawable}
      */
     public static void init(Context context) {
-        if (!Utilities.ATLEAST_OREO) {
-            return;
-        }
         pickBestShape(context);
     }
 
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 885fb66..0f1432a 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -19,6 +19,7 @@
 import static android.view.View.MeasureSpec.makeMeasureSpec;
 import static android.view.View.VISIBLE;
 
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER;
 import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
 import static com.android.launcher3.model.ModelUtils.getMissingHotseatRanks;
@@ -28,6 +29,7 @@
 import android.annotation.TargetApi;
 import android.app.Fragment;
 import android.appwidget.AppWidgetHostView;
+import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.Intent;
@@ -56,6 +58,7 @@
 import com.android.launcher3.InsettableFrameLayout;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherSettings.Favorites;
@@ -70,11 +73,12 @@
 import com.android.launcher3.model.AllAppsList;
 import com.android.launcher3.model.BgDataModel;
 import com.android.launcher3.model.BgDataModel.Callbacks;
+import com.android.launcher3.model.BgDataModel.FixedContainerItems;
 import com.android.launcher3.model.LoaderResults;
 import com.android.launcher3.model.LoaderTask;
+import com.android.launcher3.model.ModelDelegate;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.model.WidgetsModel;
-import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -83,6 +87,7 @@
 import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.uioverrides.PredictedAppIconInflater;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
+import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.views.ActivityContext;
@@ -91,6 +96,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -113,7 +119,8 @@
  *   4) Measure and draw the view on a canvas
  */
 @TargetApi(Build.VERSION_CODES.O)
-public class LauncherPreviewRenderer {
+public class LauncherPreviewRenderer extends ContextThemeWrapper
+        implements ActivityContext, WorkspaceLayoutManager, LayoutInflater.Factory2 {
 
     private static final String TAG = "LauncherPreviewRenderer";
 
@@ -123,7 +130,7 @@
      */
     public static class PreviewContext extends ContextWrapper {
 
-        private static final Set<MainThreadInitializedObject> WHITELIST = new HashSet<>(
+        private final Set<MainThreadInitializedObject> mAllowedObjects = new HashSet<>(
                 Arrays.asList(UserCache.INSTANCE, InstallSessionHelper.INSTANCE,
                         LauncherAppState.INSTANCE, InvariantDeviceProfile.INSTANCE,
                         CustomWidgetManager.INSTANCE, PluginManagerWrapper.INSTANCE));
@@ -157,7 +164,7 @@
          */
         public <T> T getObject(MainThreadInitializedObject<T> mainThreadInitializedObject,
                 MainThreadInitializedObject.ObjectProvider<T> provider) {
-            if (!WHITELIST.contains(mainThreadInitializedObject)) {
+            if (!mAllowedObjects.contains(mainThreadInitializedObject)) {
                 throw new IllegalStateException("Leaking unknown objects");
             }
             if (mainThreadInitializedObject == LauncherAppState.INSTANCE) {
@@ -207,10 +214,14 @@
     private final DeviceProfile mDp;
     private final boolean mMigrated;
     private final Rect mInsets;
-
     private final WorkspaceItemInfo mWorkspaceItemInfo;
+    private final LayoutInflater mHomeElementInflater;
+    private final InsettableFrameLayout mRootView;
+    private final Hotseat mHotseat;
+    private final CellLayout mWorkspace;
 
     public LauncherPreviewRenderer(Context context, InvariantDeviceProfile idp, boolean migrated) {
+        super(context, R.style.AppTheme);
         mUiHandler = new Handler(Looper.getMainLooper());
         mContext = context;
         mIdp = idp;
@@ -235,267 +246,288 @@
         mWorkspaceItemInfo.intent = new Intent();
         mWorkspaceItemInfo.contentDescription = mWorkspaceItemInfo.title =
                 context.getString(R.string.label_application);
+
+        mHomeElementInflater = LayoutInflater.from(
+                new ContextThemeWrapper(this, R.style.HomeScreenElementTheme));
+        mHomeElementInflater.setFactory2(this);
+
+        mRootView = (InsettableFrameLayout) mHomeElementInflater.inflate(
+                R.layout.launcher_preview_layout, null, false);
+        mRootView.setInsets(mInsets);
+        measureView(mRootView, mDp.widthPx, mDp.heightPx);
+
+        mHotseat = mRootView.findViewById(R.id.hotseat);
+        mHotseat.resetLayout(false);
+
+        mWorkspace = mRootView.findViewById(R.id.workspace);
+        mWorkspace.setPadding(mDp.workspacePadding.left + mDp.cellLayoutPaddingLeftRightPx,
+                mDp.workspacePadding.top,
+                mDp.workspacePadding.right + mDp.cellLayoutPaddingLeftRightPx,
+                mDp.workspacePadding.bottom);
     }
 
     /** Populate preview and render it. */
     public View getRenderedView() {
-        MainThreadRenderer renderer = new MainThreadRenderer(mContext);
-        renderer.populate();
-        return renderer.mRootView;
+        populate();
+        return mRootView;
     }
 
-    private class MainThreadRenderer extends ContextThemeWrapper
-            implements ActivityContext, WorkspaceLayoutManager, LayoutInflater.Factory2 {
+    public boolean shouldShowRealLauncherPreview() {
+        return ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER.get();
+    }
 
-        private final LayoutInflater mHomeElementInflater;
-        private final InsettableFrameLayout mRootView;
+    public boolean shouldShowQsb() {
+        return FeatureFlags.QSB_ON_FIRST_SCREEN;
+    }
 
-        private final Hotseat mHotseat;
-        private final CellLayout mWorkspace;
+    @Override
+    public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
+        if ("TextClock".equals(name)) {
+            // Workaround for TextClock accessing handler for unregistering ticker.
+            return new TextClock(context, attrs) {
 
-        MainThreadRenderer(Context context) {
-            super(context, R.style.AppTheme);
-
-            mHomeElementInflater = LayoutInflater.from(
-                    new ContextThemeWrapper(this, R.style.HomeScreenElementTheme));
-            mHomeElementInflater.setFactory2(this);
-
-            mRootView = (InsettableFrameLayout) mHomeElementInflater.inflate(
-                    R.layout.launcher_preview_layout, null, false);
-            mRootView.setInsets(mInsets);
-            measureView(mRootView, mDp.widthPx, mDp.heightPx);
-
-            mHotseat = mRootView.findViewById(R.id.hotseat);
-            mHotseat.resetLayout(false);
-
-            mWorkspace = mRootView.findViewById(R.id.workspace);
-            mWorkspace.setPadding(mDp.workspacePadding.left + mDp.cellLayoutPaddingLeftRightPx,
-                    mDp.workspacePadding.top,
-                    mDp.workspacePadding.right + mDp.cellLayoutPaddingLeftRightPx,
-                    mDp.workspacePadding.bottom);
+                @Override
+                public Handler getHandler() {
+                    return mUiHandler;
+                }
+            };
+        } else if (!"fragment".equals(name)) {
+            return null;
         }
 
-        @Override
-        public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
-            if ("TextClock".equals(name)) {
-                // Workaround for TextClock accessing handler for unregistering ticker.
-                return new TextClock(context, attrs) {
+        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.PreviewFragment);
+        FragmentWithPreview f = (FragmentWithPreview) Fragment.instantiate(
+                context, ta.getString(R.styleable.PreviewFragment_android_name));
+        f.enterPreviewMode(context);
+        f.onInit(null);
 
-                    @Override
-                    public Handler getHandler() {
-                        return mUiHandler;
-                    }
-                };
-            } else if (!"fragment".equals(name)) {
-                return null;
-            }
+        View view = f.onCreateView(LayoutInflater.from(context), (ViewGroup) parent, null);
+        view.setId(ta.getInt(R.styleable.PreviewFragment_android_id, View.NO_ID));
+        return view;
+    }
 
-            TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.PreviewFragment);
-            FragmentWithPreview f = (FragmentWithPreview) Fragment.instantiate(
-                    context, ta.getString(R.styleable.PreviewFragment_android_name));
-            f.enterPreviewMode(context);
-            f.onInit(null);
+    @Override
+    public View onCreateView(String name, Context context, AttributeSet attrs) {
+        return onCreateView(null, name, context, attrs);
+    }
 
-            View view = f.onCreateView(LayoutInflater.from(context), (ViewGroup) parent, null);
-            view.setId(ta.getInt(R.styleable.PreviewFragment_android_id, View.NO_ID));
-            return view;
+    @Override
+    public BaseDragLayer getDragLayer() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public DeviceProfile getDeviceProfile() {
+        return mDp;
+    }
+
+    @Override
+    public Hotseat getHotseat() {
+        return mHotseat;
+    }
+
+    @Override
+    public CellLayout getScreenWithId(int screenId) {
+        return mWorkspace;
+    }
+
+    private void inflateAndAddIcon(WorkspaceItemInfo info) {
+        BubbleTextView icon = (BubbleTextView) mHomeElementInflater.inflate(
+                R.layout.app_icon, mWorkspace, false);
+        icon.applyFromWorkspaceItem(info);
+        addInScreenFromBind(icon, info);
+    }
+
+    private void inflateAndAddFolder(FolderInfo info) {
+        FolderIcon folderIcon = FolderIcon.inflateIcon(R.layout.folder_icon, this, mWorkspace,
+                info);
+        addInScreenFromBind(folderIcon, info);
+    }
+
+    private void inflateAndAddWidgets(
+            LauncherAppWidgetInfo info,
+            Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap) {
+        if (widgetProviderInfoMap == null) {
+            return;
         }
-
-        @Override
-        public View onCreateView(String name, Context context, AttributeSet attrs) {
-            return onCreateView(null, name, context, attrs);
+        AppWidgetProviderInfo providerInfo = widgetProviderInfoMap.get(
+                new ComponentKey(info.providerName, info.user));
+        if (providerInfo == null) {
+            return;
         }
+        inflateAndAddWidgets(info, LauncherAppWidgetProviderInfo.fromProviderInfo(
+                getApplicationContext(), providerInfo));
+    }
 
-        @Override
-        public BaseDragLayer getDragLayer() {
-            throw new UnsupportedOperationException();
+    private void inflateAndAddWidgets(LauncherAppWidgetInfo info, WidgetsModel widgetsModel) {
+        WidgetItem widgetItem = widgetsModel.getWidgetProviderInfoByProviderName(
+                info.providerName);
+        if (widgetItem == null) {
+            return;
         }
+        inflateAndAddWidgets(info, widgetItem.widgetInfo);
+    }
 
-        @Override
-        public DeviceProfile getDeviceProfile() {
-            return mDp;
-        }
+    private void inflateAndAddWidgets(
+            LauncherAppWidgetInfo info, LauncherAppWidgetProviderInfo providerInfo) {
+        AppWidgetHostView view = new AppWidgetHostView(mContext);
+        view.setAppWidget(-1, providerInfo);
+        view.updateAppWidget(null);
+        view.setTag(info);
+        addInScreenFromBind(view, info);
+    }
 
-        @Override
-        public Hotseat getHotseat() {
-            return mHotseat;
-        }
-
-        @Override
-        public CellLayout getScreenWithId(int screenId) {
-            return mWorkspace;
-        }
-
-        private void inflateAndAddIcon(WorkspaceItemInfo info) {
-            BubbleTextView icon = (BubbleTextView) mHomeElementInflater.inflate(
-                    R.layout.app_icon, mWorkspace, false);
-            icon.applyFromWorkspaceItem(info);
-            addInScreenFromBind(icon, info);
-        }
-
-        private void inflateAndAddFolder(FolderInfo info) {
-            FolderIcon folderIcon = FolderIcon.inflateIcon(R.layout.folder_icon, this, mWorkspace,
-                    info);
-            addInScreenFromBind(folderIcon, info);
-        }
-
-        private void inflateAndAddWidgets(LauncherAppWidgetInfo info, WidgetsModel widgetsModel) {
-            WidgetItem widgetItem = widgetsModel.getWidgetProviderInfoByProviderName(
-                    info.providerName);
-            if (widgetItem == null) {
-                return;
-            }
-            AppWidgetHostView view = new AppWidgetHostView(mContext);
-            view.setAppWidget(-1, widgetItem.widgetInfo);
-            view.updateAppWidget(null);
-            view.setTag(info);
+    private void inflateAndAddPredictedIcon(WorkspaceItemInfo info) {
+        View view = PredictedAppIconInflater.inflate(mHomeElementInflater, mWorkspace, info);
+        if (view != null) {
             addInScreenFromBind(view, info);
         }
+    }
 
-        private void inflateAndAddPredictedIcon(WorkspaceItemInfo info) {
-            View view = PredictedAppIconInflater.inflate(mHomeElementInflater, mWorkspace, info);
-            if (view != null) {
-                addInScreenFromBind(view, info);
-            }
+    private void dispatchVisibilityAggregated(View view, boolean isVisible) {
+        // Similar to View.dispatchVisibilityAggregated implementation.
+        final boolean thisVisible = view.getVisibility() == VISIBLE;
+        if (thisVisible || !isVisible) {
+            view.onVisibilityAggregated(isVisible);
         }
 
-        private void dispatchVisibilityAggregated(View view, boolean isVisible) {
-            // Similar to View.dispatchVisibilityAggregated implementation.
-            final boolean thisVisible = view.getVisibility() == VISIBLE;
-            if (thisVisible || !isVisible) {
-                view.onVisibilityAggregated(isVisible);
-            }
+        if (view instanceof ViewGroup) {
+            isVisible = thisVisible && isVisible;
+            ViewGroup vg = (ViewGroup) view;
+            int count = vg.getChildCount();
 
-            if (view instanceof ViewGroup) {
-                isVisible = thisVisible && isVisible;
-                ViewGroup vg = (ViewGroup) view;
-                int count = vg.getChildCount();
-
-                for (int i = 0; i < count; i++) {
-                    dispatchVisibilityAggregated(vg.getChildAt(i), isVisible);
-                }
+            for (int i = 0; i < count; i++) {
+                dispatchVisibilityAggregated(vg.getChildAt(i), isVisible);
             }
         }
+    }
 
-        private void populate() {
-            if (ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER.get()) {
-                WorkspaceFetcher fetcher;
-                PreviewContext previewContext = null;
-                if (mMigrated) {
-                    previewContext = new PreviewContext(mContext, mIdp);
-                    LauncherAppState appForPreview = new LauncherAppState(
-                            previewContext, null /* iconCacheFileName */);
-                    fetcher = new WorkspaceItemsInfoFromPreviewFetcher(appForPreview);
-                    MODEL_EXECUTOR.execute(fetcher);
-                } else {
-                    fetcher = new WorkspaceItemsInfoFetcher();
-                    LauncherAppState.getInstance(mContext).getModel().enqueueModelUpdateTask(
-                            (LauncherModel.ModelUpdateTask) fetcher);
+    private void populate() {
+        if (shouldShowRealLauncherPreview()) {
+            WorkspaceFetcher fetcher;
+            PreviewContext previewContext = null;
+            if (mMigrated) {
+                previewContext = new PreviewContext(mContext, mIdp);
+                LauncherAppState appForPreview = new LauncherAppState(
+                        previewContext, null /* iconCacheFileName */);
+                fetcher = new WorkspaceItemsInfoFromPreviewFetcher(appForPreview);
+                MODEL_EXECUTOR.execute(fetcher);
+            } else {
+                fetcher = new WorkspaceItemsInfoFetcher();
+                LauncherAppState.getInstance(mContext).getModel().enqueueModelUpdateTask(
+                        (LauncherModel.ModelUpdateTask) fetcher);
+            }
+            WorkspaceResult workspaceResult = fetcher.get();
+            if (previewContext != null) {
+                previewContext.onDestroy();
+            }
+
+            if (workspaceResult == null) {
+                return;
+            }
+
+            // Separate the items that are on the current screen, and the other remaining items.
+            ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>();
+            ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>();
+            ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>();
+            ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>();
+            filterCurrentWorkspaceItems(0 /* currentScreenId */,
+                    workspaceResult.mWorkspaceItems, currentWorkspaceItems,
+                    otherWorkspaceItems);
+            filterCurrentWorkspaceItems(0 /* currentScreenId */, workspaceResult.mAppWidgets,
+                    currentAppWidgets, otherAppWidgets);
+            sortWorkspaceItemsSpatially(mIdp, currentWorkspaceItems);
+            for (ItemInfo itemInfo : currentWorkspaceItems) {
+                switch (itemInfo.itemType) {
+                    case Favorites.ITEM_TYPE_APPLICATION:
+                    case Favorites.ITEM_TYPE_SHORTCUT:
+                    case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
+                        inflateAndAddIcon((WorkspaceItemInfo) itemInfo);
+                        break;
+                    case Favorites.ITEM_TYPE_FOLDER:
+                        inflateAndAddFolder((FolderInfo) itemInfo);
+                        break;
+                    default:
+                        break;
                 }
-                WorkspaceResult workspaceResult = fetcher.get();
-                if (previewContext != null) {
-                    previewContext.onDestroy();
-                }
-
-                if (workspaceResult == null) {
-                    return;
-                }
-
-                // Separate the items that are on the current screen, and all the other remaining
-                // items
-                ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>();
-                ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>();
-                ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>();
-                ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>();
-
-                filterCurrentWorkspaceItems(0 /* currentScreenId */,
-                        workspaceResult.mWorkspaceItems, currentWorkspaceItems,
-                        otherWorkspaceItems);
-                filterCurrentWorkspaceItems(0 /* currentScreenId */, workspaceResult.mAppWidgets,
-                        currentAppWidgets, otherAppWidgets);
-                sortWorkspaceItemsSpatially(mIdp, currentWorkspaceItems);
-
-                for (ItemInfo itemInfo : currentWorkspaceItems) {
-                    switch (itemInfo.itemType) {
-                        case Favorites.ITEM_TYPE_APPLICATION:
-                        case Favorites.ITEM_TYPE_SHORTCUT:
-                        case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
-                            inflateAndAddIcon((WorkspaceItemInfo) itemInfo);
-                            break;
-                        case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
-                            inflateAndAddFolder((FolderInfo) itemInfo);
-                            break;
-                        default:
-                            break;
-                    }
-                }
-                for (ItemInfo itemInfo : currentAppWidgets) {
-                    switch (itemInfo.itemType) {
-                        case Favorites.ITEM_TYPE_APPWIDGET:
-                        case Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
+            }
+            for (ItemInfo itemInfo : currentAppWidgets) {
+                switch (itemInfo.itemType) {
+                    case Favorites.ITEM_TYPE_APPWIDGET:
+                    case Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
+                        if (mMigrated) {
+                            inflateAndAddWidgets((LauncherAppWidgetInfo) itemInfo,
+                                    workspaceResult.mWidgetProvidersMap);
+                        } else {
                             inflateAndAddWidgets((LauncherAppWidgetInfo) itemInfo,
                                     workspaceResult.mWidgetsModel);
-                            break;
-                        default:
-                            break;
-                    }
-                }
-
-                IntArray ranks = getMissingHotseatRanks(currentWorkspaceItems,
-                        mIdp.numHotseatIcons);
-                int count = Math.min(ranks.size(), workspaceResult.mCachedPredictedItems.size());
-                for (int i = 0; i < count; i++) {
-                    AppInfo appInfo = workspaceResult.mCachedPredictedItems.get(i);
-                    int rank = ranks.get(i);
-                    WorkspaceItemInfo itemInfo = new WorkspaceItemInfo(appInfo);
-                    itemInfo.container = LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
-                    itemInfo.rank = rank;
-                    itemInfo.cellX = mHotseat.getCellXFromOrder(rank);
-                    itemInfo.cellY = mHotseat.getCellYFromOrder(rank);
-                    itemInfo.screenId = rank;
-                    inflateAndAddPredictedIcon(itemInfo);
-                }
-            } else {
-                // Add hotseat icons
-                for (int i = 0; i < mIdp.numHotseatIcons; i++) {
-                    WorkspaceItemInfo info = new WorkspaceItemInfo(mWorkspaceItemInfo);
-                    info.container = Favorites.CONTAINER_HOTSEAT;
-                    info.screenId = i;
-                    inflateAndAddIcon(info);
-                }
-                // Add workspace icons
-                for (int i = 0; i < mIdp.numColumns; i++) {
-                    WorkspaceItemInfo info = new WorkspaceItemInfo(mWorkspaceItemInfo);
-                    info.container = Favorites.CONTAINER_DESKTOP;
-                    info.screenId = 0;
-                    info.cellX = i;
-                    info.cellY = mIdp.numRows - 1;
-                    inflateAndAddIcon(info);
+                        }
+                        break;
+                    default:
+                        break;
                 }
             }
-
-            // Add first page QSB
-            if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
-                View qsb = mHomeElementInflater.inflate(
-                        R.layout.search_container_workspace, mWorkspace, false);
-                CellLayout.LayoutParams lp =
-                        new CellLayout.LayoutParams(0, 0, mWorkspace.getCountX(), 1);
-                lp.canReorder = false;
-                mWorkspace.addViewToCellLayout(qsb, 0, R.id.search_container_workspace, lp, true);
+            IntArray ranks = getMissingHotseatRanks(currentWorkspaceItems,
+                    mIdp.numHotseatIcons);
+            List<ItemInfo> predictions = workspaceResult.mHotseatPredictions == null
+                    ? Collections.emptyList() : workspaceResult.mHotseatPredictions.items;
+            int count = Math.min(ranks.size(), predictions.size());
+            for (int i = 0; i < count; i++) {
+                int rank = ranks.get(i);
+                WorkspaceItemInfo itemInfo =
+                        new WorkspaceItemInfo((WorkspaceItemInfo) predictions.get(i));
+                itemInfo.container = CONTAINER_HOTSEAT_PREDICTION;
+                itemInfo.rank = rank;
+                itemInfo.cellX = mHotseat.getCellXFromOrder(rank);
+                itemInfo.cellY = mHotseat.getCellYFromOrder(rank);
+                itemInfo.screenId = rank;
+                inflateAndAddPredictedIcon(itemInfo);
             }
-
-            // Setup search view
-            SearchUiManager searchUiManager =
-                    mRootView.findViewById(R.id.search_container_all_apps);
-            mRootView.findViewById(R.id.apps_view).setTranslationY(
-                    mDp.heightPx - searchUiManager.getScrollRangeDelta(mInsets));
-
-            measureView(mRootView, mDp.widthPx, mDp.heightPx);
-            dispatchVisibilityAggregated(mRootView, true);
-            measureView(mRootView, mDp.widthPx, mDp.heightPx);
-            // Additional measure for views which use auto text size API
-            measureView(mRootView, mDp.widthPx, mDp.heightPx);
+        } else {
+            // Add hotseat icons
+            for (int i = 0; i < mIdp.numHotseatIcons; i++) {
+                WorkspaceItemInfo info = new WorkspaceItemInfo(mWorkspaceItemInfo);
+                info.container = Favorites.CONTAINER_HOTSEAT;
+                info.screenId = i;
+                inflateAndAddIcon(info);
+            }
+            // Add workspace icons
+            for (int i = 0; i < mIdp.numColumns; i++) {
+                WorkspaceItemInfo info = new WorkspaceItemInfo(mWorkspaceItemInfo);
+                info.container = Favorites.CONTAINER_DESKTOP;
+                info.screenId = 0;
+                info.cellX = i;
+                info.cellY = mIdp.numRows - 1;
+                inflateAndAddIcon(info);
+            }
         }
+
+        // Add first page QSB
+        if (shouldShowQsb()) {
+            View qsb = mHomeElementInflater.inflate(
+                    R.layout.search_container_workspace, mWorkspace, false);
+            CellLayout.LayoutParams lp =
+                    new CellLayout.LayoutParams(0, 0, mWorkspace.getCountX(), 1);
+            lp.canReorder = false;
+            mWorkspace.addViewToCellLayout(qsb, 0, R.id.search_container_workspace, lp, true);
+        }
+
+        // Setup search view
+        SearchUiManager searchUiManager =
+                mRootView.findViewById(R.id.search_container_all_apps);
+        mRootView.findViewById(R.id.apps_view).setTranslationY(
+                mDp.heightPx - searchUiManager.getScrollRangeDelta(mInsets));
+        ViewGroup searchView = (ViewGroup) searchUiManager;
+        searchView.setEnabled(false);
+        for (int i = 0; i < searchView.getChildCount(); i++) {
+            searchView.getChildAt(i).setEnabled(false);
+        }
+
+        measureView(mRootView, mDp.widthPx, mDp.heightPx);
+        dispatchVisibilityAggregated(mRootView, true);
+        measureView(mRootView, mDp.widthPx, mDp.heightPx);
+        // Additional measure for views which use auto text size API
+        measureView(mRootView, mDp.widthPx, mDp.heightPx);
     }
 
     private static void measureView(View view, int width, int height) {
@@ -541,8 +573,7 @@
                 return null;
             }
 
-            return new WorkspaceResult(mBgDataModel.workspaceItems, mBgDataModel.appWidgets,
-                    mBgDataModel.cachedPredictedItems, mBgDataModel.widgetsModel);
+            return new WorkspaceResult(mBgDataModel, mBgDataModel.widgetsModel, null);
         }
     }
 
@@ -552,7 +583,7 @@
         private final FutureTask<WorkspaceResult> mTask = new FutureTask<>(this);
 
         WorkspaceItemsInfoFromPreviewFetcher(LauncherAppState app) {
-            super(app, null, new BgDataModel(), null);
+            super(app, null, new BgDataModel(), new ModelDelegate(), null);
         }
 
         @Override
@@ -566,12 +597,10 @@
         }
 
         @Override
-        public WorkspaceResult call() throws Exception {
+        public WorkspaceResult call() {
             List<ShortcutInfo> allShortcuts = new ArrayList<>();
             loadWorkspace(allShortcuts, LauncherSettings.Favorites.PREVIEW_CONTENT_URI);
-            mBgDataModel.widgetsModel.update(mApp, null);
-            return new WorkspaceResult(mBgDataModel.workspaceItems, mBgDataModel.appWidgets,
-                    mBgDataModel.cachedPredictedItems, mBgDataModel.widgetsModel);
+            return new WorkspaceResult(mBgDataModel, null, mWidgetProvidersMap);
         }
     }
 
@@ -591,16 +620,18 @@
     private static class WorkspaceResult {
         private final ArrayList<ItemInfo> mWorkspaceItems;
         private final ArrayList<LauncherAppWidgetInfo> mAppWidgets;
-        private final ArrayList<AppInfo> mCachedPredictedItems;
+        private final FixedContainerItems mHotseatPredictions;
         private final WidgetsModel mWidgetsModel;
+        private final Map<ComponentKey, AppWidgetProviderInfo> mWidgetProvidersMap;
 
-        private WorkspaceResult(ArrayList<ItemInfo> workspaceItems,
-                ArrayList<LauncherAppWidgetInfo> appWidgets,
-                ArrayList<AppInfo> cachedPredictedItems, WidgetsModel widgetsModel) {
-            mWorkspaceItems = workspaceItems;
-            mAppWidgets = appWidgets;
-            mCachedPredictedItems = cachedPredictedItems;
+        private WorkspaceResult(BgDataModel dataModel,
+                WidgetsModel widgetsModel,
+                Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap) {
+            mWorkspaceItems = dataModel.workspaceItems;
+            mAppWidgets = dataModel.appWidgets;
+            mHotseatPredictions = dataModel.extraItems.get(CONTAINER_HOTSEAT_PREDICTION);
             mWidgetsModel = widgetsModel;
+            mWidgetProvidersMap = widgetProviderInfoMap;
         }
     }
 }
diff --git a/src/com/android/launcher3/graphics/OverviewScrim.java b/src/com/android/launcher3/graphics/OverviewScrim.java
index 94acbfd..c0c3e5e 100644
--- a/src/com/android/launcher3/graphics/OverviewScrim.java
+++ b/src/com/android/launcher3/graphics/OverviewScrim.java
@@ -22,6 +22,7 @@
 import static com.android.launcher3.LauncherState.OVERVIEW;
 
 import android.graphics.Rect;
+import android.util.FloatProperty;
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -33,10 +34,25 @@
  */
 public class OverviewScrim extends Scrim {
 
+    public static final FloatProperty<OverviewScrim> SCRIM_MULTIPLIER =
+            new FloatProperty<OverviewScrim>("scrimMultiplier") {
+                @Override
+                public Float get(OverviewScrim scrim) {
+                    return scrim.mScrimMultiplier;
+                }
+
+                @Override
+                public void setValue(OverviewScrim scrim, float v) {
+                    scrim.setScrimMultiplier(v);
+                }
+            };
+
     private @NonNull View mStableScrimmedView;
     // Might be higher up if mStableScrimmedView is invisible.
     private @Nullable View mCurrentScrimmedView;
 
+    private float mScrimMultiplier = 1f;
+
     public OverviewScrim(View view) {
         super(view);
         mStableScrimmedView = mCurrentScrimmedView = mLauncher.getOverviewPanel();
@@ -68,4 +84,16 @@
     public @Nullable View getScrimmedView() {
         return mCurrentScrimmedView;
     }
+
+    private void setScrimMultiplier(float scrimMultiplier) {
+        if (Float.compare(mScrimMultiplier, scrimMultiplier) != 0) {
+            mScrimMultiplier = scrimMultiplier;
+            invalidate();
+        }
+    }
+
+    @Override
+    protected int getScrimAlpha() {
+        return Math.round(super.getScrimAlpha() * mScrimMultiplier);
+    }
 }
diff --git a/src/com/android/launcher3/graphics/Scrim.java b/src/com/android/launcher3/graphics/Scrim.java
index f90962d..a151cba 100644
--- a/src/com/android/launcher3/graphics/Scrim.java
+++ b/src/com/android/launcher3/graphics/Scrim.java
@@ -61,7 +61,11 @@
     }
 
     public void draw(Canvas canvas) {
-        canvas.drawColor(setColorAlphaBound(mScrimColor, mScrimAlpha));
+        canvas.drawColor(setColorAlphaBound(mScrimColor, getScrimAlpha()));
+    }
+
+    protected int getScrimAlpha() {
+        return mScrimAlpha;
     }
 
     private void setScrimProgress(float progress) {
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index ff0f773..8013557 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -67,8 +67,8 @@
 
     private static final String TAG = "Launcher.IconCache";
 
-    private final Predicate<ItemInfoWithIcon> mIsUsingFallbackIconCheck = w -> w.bitmap != null
-            && w.bitmap.isNullOrLowRes() && !isDefaultIcon(w.bitmap, w.user);
+    private final Predicate<ItemInfoWithIcon> mIsUsingFallbackOrNonDefaultIconCheck = w ->
+            w.bitmap != null && (w.bitmap.isNullOrLowRes() || !isDefaultIcon(w.bitmap, w.user));
 
     private final CachingLogic<ComponentWithLabel> mComponentWithLabelCachingLogic;
     private final CachingLogic<LauncherActivityInfo> mLauncherActivityInfoCachingLogic;
@@ -192,14 +192,14 @@
      * Fill in {@param info} with the icon for {@param si}
      */
     public void getShortcutIcon(ItemInfoWithIcon info, ShortcutInfo si) {
-        getShortcutIcon(info, si, true, mIsUsingFallbackIconCheck);
+        getShortcutIcon(info, si, true, mIsUsingFallbackOrNonDefaultIconCheck);
     }
 
     /**
      * Fill in {@param info} with an unbadged icon for {@param si}
      */
     public void getUnbadgedShortcutIcon(ItemInfoWithIcon info, ShortcutInfo si) {
-        getShortcutIcon(info, si, false, mIsUsingFallbackIconCheck);
+        getShortcutIcon(info, si, false, mIsUsingFallbackOrNonDefaultIconCheck);
     }
 
     /**
@@ -260,14 +260,6 @@
     }
 
     /**
-     * Fill in info with the icon and label for deep shortcut.
-     */
-    public synchronized CacheEntry getDeepShortcutTitleAndIcon(ShortcutInfo info) {
-        return cacheLocked(ShortcutKey.fromInfo(info).componentName, info.getUserHandle(),
-                () -> info, mShortcutCachingLogic, false, false);
-    }
-
-    /**
      * Fill in {@param info} with the icon and label. If the
      * corresponding activity is not found, it reverts to the package icon.
      */
@@ -295,7 +287,7 @@
     /**
      * Fill in {@param mWorkspaceItemInfo} with the icon and label for {@param info}
      */
-    private synchronized void getTitleAndIcon(
+    public synchronized void getTitleAndIcon(
             @NonNull ItemInfoWithIcon infoInOut,
             @NonNull Supplier<LauncherActivityInfo> activityInfoProvider,
             boolean usePkgIcon, boolean useLowResIcon) {
diff --git a/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java b/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java
index c50189c..ae7ad10 100644
--- a/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java
+++ b/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java
@@ -32,6 +32,8 @@
 import android.view.View.OnFocusChangeListener;
 
 import com.android.launcher3.R;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.util.Themes;
 
 /**
  * A helper class to draw background of a focused view.
@@ -93,6 +95,7 @@
 
     private ObjectAnimator mCurrentAnimation;
     private float mAlpha;
+    private float mRadius;
 
     public FocusIndicatorHelper(View container) {
         mContainer = container;
@@ -104,6 +107,9 @@
 
         setAlpha(0);
         mShift = 0;
+        if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
+            mRadius = Themes.getDialogCornerRadius(container.getContext());
+        }
     }
 
     protected void setAlpha(float alpha) {
@@ -129,13 +135,15 @@
     }
 
     public void draw(Canvas c) {
-        if (mAlpha > 0) {
-            Rect newRect = getDrawRect();
-            if (newRect != null) {
-                mDirtyRect.set(newRect);
-                c.drawRect(mDirtyRect, mPaint);
-                mIsDirty = true;
-            }
+        if (mAlpha <= 0) return;
+
+        Rect newRect = getDrawRect();
+        if (newRect != null) {
+            mDirtyRect.set(newRect);
+            c.drawRoundRect((float) mDirtyRect.left, (float) mDirtyRect.top,
+                    (float) mDirtyRect.right, (float) mDirtyRect.bottom,
+                    mRadius, mRadius, mPaint);
+            mIsDirty = true;
         }
     }
 
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 8e23b65..ec1c3ef 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -15,7 +15,6 @@
  */
 package com.android.launcher3.logging;
 
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_CLOSE_DOWN;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_OPEN_UP;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
@@ -23,8 +22,6 @@
 
 import android.content.Context;
 
-import androidx.annotation.Nullable;
-
 import com.android.launcher3.R;
 import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
 import com.android.launcher3.logger.LauncherAtom.FromState;
@@ -33,11 +30,16 @@
 import com.android.launcher3.userevent.LauncherLogProto;
 import com.android.launcher3.util.ResourceBasedOverride;
 
+import java.util.List;
+
 /**
  * Handles the user event logging in R+.
+ *
+ * <pre>
  * All of the event ids are defined here.
- * Most of the methods are dummy methods for Launcher3
+ * Most of the methods are placeholder methods for Launcher3
  * Actual call happens only for Launcher variant that implements QuickStep.
+ * </pre>
  */
 public class StatsLogManager implements ResourceBasedOverride {
 
@@ -49,8 +51,8 @@
     public static final int LAUNCHER_STATE_UNCHANGED = 5;
 
     /**
-     * Returns proper launcher state enum for {@link StatsLogManager}
-     * (to be removed during UserEventDispatcher cleanup)
+     * Returns proper launcher state enum for {@link StatsLogManager}(to be removed during
+     * UserEventDispatcher cleanup)
      */
     public static int containerTypeToAtomState(int containerType) {
         switch (containerType) {
@@ -67,9 +69,8 @@
     }
 
     /**
-     * Returns event enum based on the two {@link ContainerType} transition information when
-     * swipe gesture happens.
-     * (to be removed during UserEventDispatcher cleanup)
+     * Returns event enum based on the two {@link ContainerType} transition information when swipe
+     * gesture happens(to be removed during UserEventDispatcher cleanup).
      */
     public static EventEnum getLauncherAtomEvent(int startContainerType,
             int targetContainerType, EventEnum fallbackEvent) {
@@ -273,7 +274,55 @@
         LAUNCHER_SELECT_MODE_CLOSE(583),
 
         @UiEvent(doc = "User tapped on the highlight items in select mode")
-        LAUNCHER_SELECT_MODE_ITEM(584);
+        LAUNCHER_SELECT_MODE_ITEM(584),
+
+        @UiEvent(doc = "Notification dot on app icon enabled.")
+        LAUNCHER_NOTIFICATION_DOT_ENABLED(611),
+
+        @UiEvent(doc = "Notification dot on app icon disabled.")
+        LAUNCHER_NOTIFICATION_DOT_DISABLED(612),
+
+        @UiEvent(doc = "For new apps, add app icons to home screen enabled.")
+        LAUNCHER_ADD_NEW_APPS_TO_HOME_SCREEN_ENABLED(613),
+
+        @UiEvent(doc = "For new apps, add app icons to home screen disabled.")
+        LAUNCHER_ADD_NEW_APPS_TO_HOME_SCREEN_DISABLED(614),
+
+        @UiEvent(doc = "Home screen rotation is enabled when phone is rotated.")
+        LAUNCHER_HOME_SCREEN_ROTATION_ENABLED(615),
+
+        @UiEvent(doc = "Home screen rotation is disabled when phone is rotated.")
+        LAUNCHER_HOME_SCREEN_ROTATION_DISABLED(616),
+
+        @UiEvent(doc = "Suggestions in all apps list enabled.")
+        LAUNCHER_ALL_APPS_SUGGESTIONS_ENABLED(619),
+
+        @UiEvent(doc = "Suggestions in all apps list disabled.")
+        LAUNCHER_ALL_APPS_SUGGESTIONS_DISABLED(620),
+
+        @UiEvent(doc = "Suggestions on home screen is enabled.")
+        LAUNCHER_HOME_SCREEN_SUGGESTIONS_ENABLED(621),
+
+        @UiEvent(doc = "Suggestions on home screen is disabled.")
+        LAUNCHER_HOME_SCREEN_SUGGESTIONS_DISABLED(622),
+
+        @UiEvent(doc = "System navigation is 3 button mode.")
+        LAUNCHER_NAVIGATION_MODE_3_BUTTON(623),
+
+        @UiEvent(doc = "System navigation mode is 2 button mode.")
+        LAUNCHER_NAVIGATION_MODE_2_BUTTON(624),
+
+        @UiEvent(doc = "System navigation mode is 0 button mode/gesture navigation mode .")
+        LAUNCHER_NAVIGATION_MODE_GESTURE_BUTTON(625),
+
+        @UiEvent(doc = "User tapped on image content in Overview Select mode.")
+        LAUNCHER_SELECT_MODE_IMAGE(627),
+
+        @UiEvent(doc = "A folder was replaced by a single item")
+        LAUNCHER_FOLDER_CONVERTED_TO_ICON(628),
+
+        @UiEvent(doc = "A hotseat prediction item was pinned")
+        LAUNCHER_HOTSEAT_PREDICTION_PINNED(629);
 
         // ADD MORE
 
@@ -404,21 +453,8 @@
     }
 
     /**
-     * Log an event with ranked-choice information along with package. Does nothing if event.getId()
-     * <= 0.
-     *
-     * @param rankingEvent an enum implementing EventEnum interface.
-     * @param instanceId An identifier obtained from an InstanceIdSequence.
-     * @param packageName the package name of the relevant app, if known (null otherwise).
-     * @param position the position picked.
+     * Logs impression of the current workspace with additional launcher events.
      */
-    public void log(EventEnum rankingEvent, InstanceId instanceId, @Nullable String packageName,
-            int position) {
-    }
-
-    /**
-     * Logs snapshot, or impression of the current workspace.
-     */
-    public void logSnapshot() {
+    public void logSnapshot(List<EventEnum> additionalEvents) {
     }
 }
diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java
index e094cab..a40cc26 100644
--- a/src/com/android/launcher3/logging/UserEventDispatcher.java
+++ b/src/com/android/launcher3/logging/UserEventDispatcher.java
@@ -19,26 +19,21 @@
 import static com.android.launcher3.logging.LoggerUtils.newAction;
 import static com.android.launcher3.logging.LoggerUtils.newCommandAction;
 import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
-import static com.android.launcher3.logging.LoggerUtils.newControlTarget;
 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;
-import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
 import static com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
 import static com.android.launcher3.userevent.nano.LauncherLogProto.TipType;
 
 import static java.util.Optional.ofNullable;
 
-import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
-import android.os.Process;
 import android.os.SystemClock;
-import android.os.UserHandle;
 import android.util.Log;
 import android.view.View;
 
@@ -54,7 +49,6 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 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.InstantAppResolver;
 import com.android.launcher3.util.LogConfig;
 import com.android.launcher3.util.ResourceBasedOverride;
@@ -101,7 +95,7 @@
      *
      * @return whether container data was added.
      */
-    public boolean fillLogContainer(@Nullable View v, Target child,
+    private boolean fillLogContainer(@Nullable View v, Target child,
             @Nullable ArrayList<Target> targets) {
         LogContainerProvider firstParent = StatsLogUtils.getLaunchProviderRecursive(v);
         if (v == null || !(v.getTag() instanceof ItemInfo) || firstParent == null) {
@@ -125,55 +119,6 @@
     private boolean mAppOrTaskLaunch;
     private boolean mPreviousHomeGesture;
 
-    //                      APP_ICON    SHORTCUT    WIDGET
-    // --------------------------------------------------------------
-    // packageNameHash      required    optional    required
-    // componentNameHash    required                required
-    // intentHash                       required
-    // --------------------------------------------------------------
-
-    @Deprecated
-    public void logAppLaunch(View v, Intent intent, @Nullable UserHandle userHandle) {
-        Target itemTarget = newItemTarget(v, mInstantAppResolver);
-        Action action = newTouchAction(Action.Touch.TAP);
-        ArrayList<Target> targets = makeTargetsList(itemTarget);
-        if (fillLogContainer(v, itemTarget, targets)) {
-            onFillInLogContainerData((ItemInfo) v.getTag(), itemTarget, targets);
-            fillIntentInfo(itemTarget, intent, userHandle);
-        }
-        LauncherEvent event = newLauncherEvent(action,  targets);
-        dispatchUserEvent(event, intent);
-        mAppOrTaskLaunch = true;
-    }
-
-    /**
-     * Dummy method.
-     */
-    public void logActionTip(int actionType, int viewType) {
-    }
-
-    @Deprecated
-    public void logTaskLaunchOrDismiss(int action, int direction, int taskIndex,
-            ComponentKey componentKey) {
-        LauncherEvent event = newLauncherEvent(newTouchAction(action), // TAP or SWIPE or FLING
-                newTarget(Target.Type.ITEM));
-        if (action == Action.Touch.SWIPE || action == Action.Touch.FLING) {
-            // Direction DOWN means the task was launched, UP means it was dismissed.
-            event.action.dir = direction;
-        }
-        event.srcTarget[0].itemType = ItemType.TASK;
-        event.srcTarget[0].pageIndex = taskIndex;
-        fillComponentInfo(event.srcTarget[0], componentKey.componentName);
-        dispatchUserEvent(event, null);
-        mAppOrTaskLaunch = true;
-    }
-
-    protected void fillIntentInfo(Target target, Intent intent, @Nullable UserHandle userHandle) {
-        target.intentHash = intent.hashCode();
-        target.isWorkApp = userHandle != null && !userHandle.equals(Process.myUserHandle());
-        fillComponentInfo(target, intent.getComponent());
-    }
-
     private void fillComponentInfo(Target target, ComponentName cn) {
         if (cn != null) {
             target.packageNameHash = (mUuidStr + cn.getPackageName()).hashCode();
@@ -181,22 +126,6 @@
         }
     }
 
-    public void logNotificationLaunch(View v, PendingIntent intent) {
-        LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.TAP),
-                newItemTarget(v, mInstantAppResolver), newTarget(Target.Type.CONTAINER));
-        Target itemTarget = newItemTarget(v, mInstantAppResolver);
-        ArrayList<Target> targets = makeTargetsList(itemTarget);
-
-        if (fillLogContainer(v, itemTarget, targets)) {
-            itemTarget.packageNameHash = (mUuidStr + intent.getCreatorPackage()).hashCode();
-        }
-        dispatchUserEvent(event, null);
-    }
-
-    public void logActionCommand(int command, Target srcTarget) {
-        logActionCommand(command, srcTarget, null);
-    }
-
     public void logActionCommand(int command, int srcContainerType, int dstContainerType) {
         logActionCommand(command, newContainerTarget(srcContainerType),
                 dstContainerType >= 0 ? newContainerTarget(dstContainerType) : null);
@@ -227,25 +156,6 @@
         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 srcContainerType) {
-        LauncherEvent event = newLauncherEvent(newCommandAction(command),
-                newItemTarget(itemView, mInstantAppResolver), newTarget(Target.Type.CONTAINER));
-
-        Target itemTarget = newItemTarget(itemView, mInstantAppResolver);
-        ArrayList<Target> targets = makeTargetsList(itemTarget);
-
-        if (fillLogContainer(itemView, itemTarget, targets)) {
-            // TODO: Remove the following two lines once fillInLogContainerData can take in a
-            // container view.
-            itemTarget.type = Target.Type.CONTAINER;
-            itemTarget.containerType = srcContainerType;
-        }
-        dispatchUserEvent(event, null);
-    }
-
     public void logActionOnControl(int action, int controlType) {
         logActionOnControl(action, controlType, null);
     }
@@ -332,7 +242,6 @@
         event.srcTarget[0].spanX = downX;
         event.srcTarget[0].spanY = downY;
         dispatchUserEvent(event, null);
-        resetElapsedContainerMillis("state changed");
     }
 
     public void logActionOnItem(int action, int dir, int itemType) {
@@ -386,7 +295,6 @@
         ArrayList<Target> targets = makeTargetsList(child);
         fillLogContainer(icon, child, targets);
         dispatchUserEvent(newLauncherEvent(newTouchAction(Action.Touch.TAP), targets), null);
-        resetElapsedContainerMillis("deep shortcut open");
     }
 
     public void logDragNDrop(DropTarget.DragObject dragObj, View dropTargetAsView) {
@@ -397,7 +305,7 @@
         Target destChild = newItemTarget(dragObj.originalDragInfo, mInstantAppResolver);
         ArrayList<Target> destTargets = makeTargetsList(destChild);
 
-        dragObj.dragSource.fillInLogContainerData(dragObj.originalDragInfo, srcChild, srcTargets);
+        //dragObj.dragSource.fillInLogContainerData(dragObj.originalDragInfo, srcChild, srcTargets);
         if (dropTargetAsView instanceof LogContainerProvider) {
             ((LogContainerProvider) dropTargetAsView).fillInLogContainerData(dragObj.dragInfo,
                     destChild, destTargets);
@@ -414,35 +322,6 @@
         dispatchUserEvent(event, null);
     }
 
-    public void logActionBack(boolean completed, int downX, int downY, boolean isButton,
-            boolean gestureSwipeLeft, int containerType) {
-        int actionTouch = isButton ? Action.Touch.TAP : Action.Touch.SWIPE;
-        Action action = newCommandAction(actionTouch);
-        action.command = Action.Command.BACK;
-        action.dir = isButton ? Action.Direction.NONE :
-                gestureSwipeLeft ? Action.Direction.LEFT : Action.Direction.RIGHT;
-        Target target = newControlTarget(isButton ? ControlType.BACK_BUTTON :
-                ControlType.BACK_GESTURE);
-        target.spanX = downX;
-        target.spanY = downY;
-        target.cardinality = completed ? 1 : 0;
-        LauncherEvent event = newLauncherEvent(action, target, newContainerTarget(containerType));
-
-        dispatchUserEvent(event, null);
-    }
-
-    /**
-     * Currently logs following containers: workspace, allapps, widget tray.
-     */
-    public final void resetElapsedContainerMillis(String reason) {
-        mElapsedContainerMillis = SystemClock.uptimeMillis();
-        if (!IS_VERBOSE) {
-            return;
-        }
-        Log.d(TAG, "resetElapsedContainerMillis reason=" + reason);
-
-    }
-
     public final void startSession() {
         mSessionStarted = true;
         mElapsedSessionMillis = SystemClock.uptimeMillis();
diff --git a/src/com/android/launcher3/model/AllAppsList.java b/src/com/android/launcher3/model/AllAppsList.java
index eb5d106..2695e66 100644
--- a/src/com/android/launcher3/model/AllAppsList.java
+++ b/src/com/android/launcher3/model/AllAppsList.java
@@ -305,7 +305,7 @@
      *
      * @return the corresponding AppInfo or null
      */
-    private @Nullable AppInfo findAppInfo(@NonNull ComponentName componentName,
+    public @Nullable AppInfo findAppInfo(@NonNull ComponentName componentName,
                                           @NonNull UserHandle user) {
         for (AppInfo info: data) {
             if (componentName.equals(info.componentName) && user.equals(info.user)) {
diff --git a/src/com/android/launcher3/model/AppLaunchTracker.java b/src/com/android/launcher3/model/AppLaunchTracker.java
deleted file mode 100644
index 629a0ee..0000000
--- a/src/com/android/launcher3/model/AppLaunchTracker.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2019 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 static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
-
-import android.content.ComponentName;
-import android.os.UserHandle;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.R;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.launcher3.util.ResourceBasedOverride;
-
-/**
- * Callback for receiving various app launch events
- */
-public class AppLaunchTracker implements ResourceBasedOverride {
-
-    /**
-     * Derived from LauncherEvent proto.
-     * TODO: Use proper descriptive constants
-     */
-    public static final String CONTAINER_DEFAULT = Integer.toString(ContainerType.WORKSPACE);
-    public static final String CONTAINER_ALL_APPS = Integer.toString(ContainerType.ALLAPPS);
-    public static final String CONTAINER_PREDICTIONS = Integer.toString(ContainerType.PREDICTION);
-    public static final String CONTAINER_SEARCH = Integer.toString(ContainerType.SEARCHRESULT);
-    public static final String CONTAINER_OVERVIEW = Integer.toString(ContainerType.OVERVIEW);
-
-
-    public static final MainThreadInitializedObject<AppLaunchTracker> INSTANCE =
-            forOverride(AppLaunchTracker.class, R.string.app_launch_tracker_class);
-
-    public void onStartShortcut(String packageName, String shortcutId, UserHandle user,
-            @Nullable String container) { }
-
-    public void onStartApp(ComponentName componentName, UserHandle user,
-            @Nullable String container) { }
-
-    public void onDismissApp(ComponentName componentName, UserHandle user,
-             @Nullable String container){}
-
-    public void onReturnedToHome() { }
-}
diff --git a/src/com/android/launcher3/model/BaseLoaderResults.java b/src/com/android/launcher3/model/BaseLoaderResults.java
index 8b0ef7b..5c85bab 100644
--- a/src/com/android/launcher3/model/BaseLoaderResults.java
+++ b/src/com/android/launcher3/model/BaseLoaderResults.java
@@ -17,7 +17,6 @@
 package com.android.launcher3.model;
 
 import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
-import static com.android.launcher3.model.ModelUtils.getMissingHotseatRanks;
 import static com.android.launcher3.model.ModelUtils.sortWorkspaceItemsSpatially;
 
 import android.util.Log;
@@ -27,6 +26,7 @@
 import com.android.launcher3.LauncherModel.CallbackTask;
 import com.android.launcher3.PagedView;
 import com.android.launcher3.model.BgDataModel.Callbacks;
+import com.android.launcher3.model.BgDataModel.FixedContainerItems;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -76,18 +76,20 @@
         ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
         ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
         final IntArray orderedScreenIds = new IntArray();
+        ArrayList<FixedContainerItems> extraItems = new ArrayList<>();
 
         synchronized (mBgDataModel) {
             workspaceItems.addAll(mBgDataModel.workspaceItems);
             appWidgets.addAll(mBgDataModel.appWidgets);
             orderedScreenIds.addAll(mBgDataModel.collectWorkspaceScreens());
+            mBgDataModel.extraItems.forEach(extraItems::add);
             mBgDataModel.lastBindId++;
             mMyBindingId = mBgDataModel.lastBindId;
         }
 
         for (Callbacks cb : mCallbacksList) {
             new WorkspaceBinder(cb, mUiExecutor, mApp, mBgDataModel, mMyBindingId,
-                    workspaceItems, appWidgets, orderedScreenIds).bind();
+                    workspaceItems, appWidgets, extraItems, orderedScreenIds).bind();
         }
     }
 
@@ -135,7 +137,7 @@
         private final ArrayList<ItemInfo> mWorkspaceItems;
         private final ArrayList<LauncherAppWidgetInfo> mAppWidgets;
         private final IntArray mOrderedScreenIds;
-
+        private final ArrayList<FixedContainerItems> mExtraItems;
 
         WorkspaceBinder(Callbacks callbacks,
                 Executor uiExecutor,
@@ -144,6 +146,7 @@
                 int myBindingId,
                 ArrayList<ItemInfo> workspaceItems,
                 ArrayList<LauncherAppWidgetInfo> appWidgets,
+                ArrayList<FixedContainerItems> extraItems,
                 IntArray orderedScreenIds) {
             mCallbacks = callbacks;
             mUiExecutor = uiExecutor;
@@ -152,6 +155,7 @@
             mMyBindingId = myBindingId;
             mWorkspaceItems = workspaceItems;
             mAppWidgets = appWidgets;
+            mExtraItems = extraItems;
             mOrderedScreenIds = orderedScreenIds;
         }
 
@@ -198,15 +202,15 @@
             // Load items on the current page.
             bindWorkspaceItems(currentWorkspaceItems, mainExecutor);
             bindAppWidgets(currentAppWidgets, mainExecutor);
+            mExtraItems.forEach(item ->
+                    executeCallbacksTask(c -> c.bindExtraContainerItems(item), mainExecutor));
 
-            // Locate available spots for prediction using currentWorkspaceItems
-            IntArray gaps = getMissingHotseatRanks(currentWorkspaceItems, idp.numHotseatIcons);
-            bindPredictedItems(gaps, mainExecutor);
             // In case of validFirstPage, only bind the first screen, and defer binding the
             // remaining screens after first onDraw (and an optional the fade animation whichever
             // happens later).
             // This ensures that the first screen is immediately visible (eg. during rotation)
             // In case of !validFirstPage, bind all pages one after other.
+
             final Executor deferredExecutor =
                     validFirstPage ? new ViewOnDrawExecutor() : mainExecutor;
 
@@ -253,11 +257,6 @@
             }
         }
 
-        private void bindPredictedItems(IntArray ranks, final Executor executor) {
-            ArrayList<AppInfo> items = new ArrayList<>(mBgDataModel.cachedPredictedItems);
-            executeCallbacksTask(c -> c.bindPredictedItems(items, ranks), executor);
-        }
-
         protected void executeCallbacksTask(CallbackTask task, Executor executor) {
             executor.execute(() -> {
                 if (mMyBindingId != mBgDataModel.lastBindId) {
diff --git a/src/com/android/launcher3/model/BaseModelUpdateTask.java b/src/com/android/launcher3/model/BaseModelUpdateTask.java
index 9013cba..d1e5017 100644
--- a/src/com/android/launcher3/model/BaseModelUpdateTask.java
+++ b/src/com/android/launcher3/model/BaseModelUpdateTask.java
@@ -22,7 +22,9 @@
 import com.android.launcher3.LauncherModel.CallbackTask;
 import com.android.launcher3.LauncherModel.ModelUpdateTask;
 import com.android.launcher3.model.BgDataModel.Callbacks;
+import com.android.launcher3.model.BgDataModel.FixedContainerItems;
 import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.ItemInfoMatcher;
@@ -30,7 +32,10 @@
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.Executor;
+import java.util.stream.Collectors;
 
 /**
  * Extension of {@link ModelUpdateTask} with some utility methods
@@ -88,11 +93,27 @@
         return mModel.getWriter(false /* hasVerticalHotseat */, false /* verifyChanges */);
     }
 
-
-    public void bindUpdatedWorkspaceItems(final ArrayList<WorkspaceItemInfo> updatedShortcuts) {
-        if (!updatedShortcuts.isEmpty()) {
-            scheduleCallbackTask(c -> c.bindWorkspaceItemsChanged(updatedShortcuts));
+    public void bindUpdatedWorkspaceItems(List<WorkspaceItemInfo> allUpdates) {
+        // Bind workspace items
+        List<WorkspaceItemInfo> workspaceUpdates = allUpdates.stream()
+                .filter(info -> info.id != ItemInfo.NO_ID)
+                .collect(Collectors.toList());
+        if (!workspaceUpdates.isEmpty()) {
+            scheduleCallbackTask(c -> c.bindWorkspaceItemsChanged(workspaceUpdates));
         }
+
+        // Bind extra items if any
+        allUpdates.stream()
+                .mapToInt(info -> info.container)
+                .distinct()
+                .mapToObj(mDataModel.extraItems::get)
+                .filter(Objects::nonNull)
+                .forEach(this::bindExtraContainerItems);
+    }
+
+    public void bindExtraContainerItems(FixedContainerItems item) {
+        FixedContainerItems copy = item.clone();
+        scheduleCallbackTask(c -> c.bindExtraContainerItems(copy));
     }
 
     public void bindDeepShortcuts(BgDataModel dataModel) {
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 9bef847..49b40ed 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -15,19 +15,24 @@
  */
 package com.android.launcher3.model;
 
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY;
+
 import static com.android.launcher3.model.WidgetsModel.GO_DISABLE_WIDGETS;
 import static com.android.launcher3.shortcuts.ShortcutRequest.PINNED;
 
+import static java.util.stream.Collectors.groupingBy;
+import static java.util.stream.Collectors.mapping;
+
 import android.content.Context;
 import android.content.pm.LauncherApps;
 import android.content.pm.ShortcutInfo;
 import android.os.UserHandle;
 import android.text.TextUtils;
+import android.util.ArraySet;
 import android.util.Log;
-import android.util.MutableInt;
 
-import com.android.launcher3.InstallShortcutReceiver;
 import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.data.AppInfo;
@@ -36,8 +41,10 @@
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.model.data.PromiseAppInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.shortcuts.ShortcutRequest;
+import com.android.launcher3.shortcuts.ShortcutRequest.QueryResult;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSet;
@@ -50,13 +57,16 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.function.BiConsumer;
+import java.util.Set;
+import java.util.function.Consumer;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * All the data stored in-memory and managed by the LauncherModel
@@ -88,21 +98,9 @@
     public final IntSparseArrayMap<FolderInfo> folders = new IntSparseArrayMap<>();
 
     /**
-     * Map of ShortcutKey to the number of times it is pinned.
+     * Extra container based items
      */
-    public final Map<ShortcutKey, MutableInt> pinnedShortcutCounts = new HashMap<>();
-
-    /**
-     * List of all cached predicted items visible on home screen
-     */
-    public final ArrayList<AppInfo> cachedPredictedItems = new ArrayList<>();
-
-    /**
-     * @see Callbacks#FLAG_HAS_SHORTCUT_PERMISSION
-     * @see Callbacks#FLAG_QUIET_MODE_ENABLED
-     * @see Callbacks#FLAG_QUIET_MODE_CHANGE_PERMISSION
-     */
-    public int flags;
+    public final IntSparseArrayMap<FixedContainerItems> extraItems = new IntSparseArrayMap<>();
 
     /**
      * Maps all launcher activities to counts of their shortcuts.
@@ -127,8 +125,8 @@
         appWidgets.clear();
         folders.clear();
         itemsIdMap.clear();
-        pinnedShortcutCounts.clear();
         deepShortcutMap.clear();
+        extraItems.clear();
     }
 
     /**
@@ -181,6 +179,7 @@
     }
 
     public synchronized void removeItem(Context context, Iterable<? extends ItemInfo> items) {
+        ArraySet<UserHandle> updatedDeepShortcuts = new ArraySet<>();
         for (ItemInfo item : items) {
             switch (item.itemType) {
                 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
@@ -199,14 +198,7 @@
                     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)) {
-                        unpinShortcut(context, pinnedShortcut);
-                    }
+                    updatedDeepShortcuts.add(item.user);
                     // Fall through.
                 }
                 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
@@ -220,6 +212,7 @@
             }
             itemsIdMap.remove(item.id);
         }
+        updatedDeepShortcuts.forEach(user -> updateShortcutPinnedState(context, user));
     }
 
     public synchronized void addItem(Context context, ItemInfo item, boolean newItem) {
@@ -229,23 +222,7 @@
                 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) {
-                    updatePinnedShortcuts(context, pinnedShortcut, List::add);
-                }
-                // Fall through
-            }
+            case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
             case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
             case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
                 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
@@ -270,36 +247,86 @@
                 appWidgets.add((LauncherAppWidgetInfo) item);
                 break;
         }
+        if (newItem && item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+            updateShortcutPinnedState(context, item.user);
+        }
     }
 
     /**
-     * Removes the given shortcut from the current list of pinned shortcuts.
-     * (Runs on background thread)
+     * Updates the deep shortucts state in system to match out internal model, pinning any missing
+     * shortcuts and unpinning any extra shortcuts.
      */
-    public void unpinShortcut(Context context, ShortcutKey key) {
-        updatePinnedShortcuts(context, key, List::remove);
+    public void updateShortcutPinnedState(Context context) {
+        for (UserHandle user : UserCache.INSTANCE.get(context).getUserProfiles()) {
+            updateShortcutPinnedState(context, user);
+        }
     }
 
-    private void updatePinnedShortcuts(Context context, ShortcutKey key,
-            BiConsumer<List<String>, String> idOp) {
+    /**
+     * Updates the deep shortucts state in system to match out internal model, pinning any missing
+     * shortcuts and unpinning any extra shortcuts.
+     */
+    public synchronized void updateShortcutPinnedState(Context context, UserHandle user) {
         if (GO_DISABLE_WIDGETS) {
             return;
         }
-        String packageName = key.componentName.getPackageName();
-        String id = key.getId();
-        UserHandle user = key.user;
-        List<String> pinnedIds = new ShortcutRequest(context, user)
-                .forPackage(packageName)
-                .query(PINNED)
-                .stream()
-                .map(ShortcutInfo::getId)
-                .collect(Collectors.toCollection(ArrayList::new));
-        idOp.accept(pinnedIds, id);
-        try {
-            context.getSystemService(LauncherApps.class).pinShortcuts(packageName, pinnedIds, user);
-        } catch (SecurityException | IllegalStateException e) {
-            Log.w(TAG, "Failed to pin shortcut", e);
+
+        // Collect all system shortcuts
+        QueryResult result = new ShortcutRequest(context, user)
+                .query(PINNED | FLAG_GET_KEY_FIELDS_ONLY);
+        if (!result.wasSuccess()) {
+            return;
         }
+        // Map of packageName to shortcutIds that are currently in the system
+        Map<String, Set<String>> systemMap = result.stream()
+                .collect(groupingBy(ShortcutInfo::getPackage,
+                        mapping(ShortcutInfo::getId, Collectors.toSet())));
+
+        // Collect all model shortcuts
+        Stream.Builder<WorkspaceItemInfo> itemStream = Stream.builder();
+        forAllWorkspaceItemInfos(user, itemStream::accept);
+        // Map of packageName to shortcutIds that are currently in our model
+        Map<String, Set<String>> modelMap = Stream.concat(
+                    // Model shortcuts
+                    itemStream.build()
+                        .filter(wi -> wi.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT)
+                        .map(ShortcutKey::fromItemInfo),
+                    // Pending shortcuts
+                    ItemInstallQueue.INSTANCE.get(context).getPendingShortcuts(user))
+                .collect(groupingBy(ShortcutKey::getPackageName,
+                        mapping(ShortcutKey::getId, Collectors.toSet())));
+
+        // Check for diff
+        for (Map.Entry<String, Set<String>> entry : modelMap.entrySet()) {
+            Set<String> modelShortcuts = entry.getValue();
+            Set<String> systemShortcuts = systemMap.remove(entry.getKey());
+            if (systemShortcuts == null) {
+                systemShortcuts = Collections.emptySet();
+            }
+
+            // Do not use .equals as it can vary based on the type of set
+            if (systemShortcuts.size() != modelShortcuts.size()
+                    || !systemShortcuts.containsAll(modelShortcuts)) {
+                // Update system state for this package
+                try {
+                    context.getSystemService(LauncherApps.class).pinShortcuts(
+                            entry.getKey(), new ArrayList<>(modelShortcuts), user);
+                } catch (SecurityException | IllegalStateException e) {
+                    Log.w(TAG, "Failed to pin shortcut", e);
+                }
+            }
+        }
+
+        // If there are any extra pinned shortcuts, remove them
+        systemMap.keySet().forEach(packageName -> {
+            // Update system state
+            try {
+                context.getSystemService(LauncherApps.class).pinShortcuts(
+                        packageName, Collections.emptyList(), user);
+            } catch (SecurityException | IllegalStateException e) {
+                Log.w(TAG, "Failed to unpin shortcut", e);
+            }
+        });
     }
 
     /**
@@ -348,6 +375,59 @@
         }
     }
 
+    /**
+     * Calls the provided {@code op} for all workspaceItems in the in-memory model (both persisted
+     * items and dynamic/predicted items for the provided {@code userHandle}.
+     * Note the call is not synchronized over the model, that should be handled by the called.
+     */
+    public void forAllWorkspaceItemInfos(UserHandle userHandle, Consumer<WorkspaceItemInfo> op) {
+        for (ItemInfo info : itemsIdMap) {
+            if (info instanceof WorkspaceItemInfo && userHandle.equals(info.user)) {
+                op.accept((WorkspaceItemInfo) info);
+            }
+        }
+
+        for (int i = extraItems.size() - 1; i >= 0; i--) {
+            for (ItemInfo info : extraItems.valueAt(i).items) {
+                if (info instanceof WorkspaceItemInfo && userHandle.equals(info.user)) {
+                    op.accept((WorkspaceItemInfo) info);
+                }
+            }
+        }
+    }
+
+    /**
+     * An object containing items corresponding to a fixed container
+     */
+    public static class FixedContainerItems {
+
+        public final int containerId;
+        public final List<ItemInfo> items;
+
+        public FixedContainerItems(int containerId) {
+            this(containerId, new ArrayList<>());
+        }
+
+        public FixedContainerItems(int containerId, List<ItemInfo> items) {
+            this.containerId = containerId;
+            this.items = items;
+        }
+
+        @Override
+        public FixedContainerItems clone() {
+            return new FixedContainerItems(containerId, new ArrayList<>(items));
+        }
+
+        public void setItems(List<ItemInfo> newItems) {
+            items.clear();
+            newItems.forEach(item -> {
+                item.container = containerId;
+                items.add(item);
+            });
+        }
+    }
+
+
     public interface Callbacks {
         // If the launcher has permission to access deep shortcuts.
         int FLAG_HAS_SHORTCUT_PERMISSION = 1 << 0;
@@ -370,7 +450,7 @@
         void bindAppsAdded(IntArray newScreens,
                 ArrayList<ItemInfo> addNotAnimated, ArrayList<ItemInfo> addAnimated);
         void bindPromiseAppProgressUpdated(PromiseAppInfo app);
-        void bindWorkspaceItemsChanged(ArrayList<WorkspaceItemInfo> updated);
+        void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated);
         void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
         void bindRestoreItemsChange(HashSet<ItemInfo> updates);
         void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher);
@@ -379,11 +459,11 @@
         void executeOnNextDraw(ViewOnDrawExecutor executor);
         void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap);
 
-        void bindAllApplications(AppInfo[] apps, int flags);
-
         /**
-         * Binds predicted appInfos at at available prediction slots.
+         * Binds extra item provided any external source
          */
-        void bindPredictedItems(List<AppInfo> appInfos, IntArray ranks);
+        default void bindExtraContainerItems(FixedContainerItems item) { }
+
+        void bindAllApplications(AppInfo[] apps, int flags);
     }
 }
diff --git a/src/com/android/launcher3/model/CacheDataUpdatedTask.java b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
index 8e6b064..f644d49 100644
--- a/src/com/android/launcher3/model/CacheDataUpdatedTask.java
+++ b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
@@ -21,7 +21,6 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 
 import java.util.ArrayList;
@@ -48,23 +47,18 @@
     @Override
     public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
         IconCache iconCache = app.getIconCache();
-
-
         ArrayList<WorkspaceItemInfo> updatedShortcuts = new ArrayList<>();
 
         synchronized (dataModel) {
-            for (ItemInfo info : dataModel.itemsIdMap) {
-                if (info instanceof WorkspaceItemInfo && mUser.equals(info.user)) {
-                    WorkspaceItemInfo si = (WorkspaceItemInfo) 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);
-                    }
+            dataModel.forAllWorkspaceItemInfos(mUser, si -> {
+                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);
         }
         bindUpdatedWorkspaceItems(updatedShortcuts);
diff --git a/src/com/android/launcher3/model/FirstScreenBroadcast.java b/src/com/android/launcher3/model/FirstScreenBroadcast.java
index 5112304..70d1b48 100644
--- a/src/com/android/launcher3/model/FirstScreenBroadcast.java
+++ b/src/com/android/launcher3/model/FirstScreenBroadcast.java
@@ -17,25 +17,30 @@
 
 import static android.os.Process.myUserHandle;
 
+import static com.android.launcher3.pm.InstallSessionHelper.getUserHandle;
+
+import static java.util.stream.Collectors.groupingBy;
+import static java.util.stream.Collectors.mapping;
+
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageInstaller.SessionInfo;
+import android.os.UserHandle;
 import android.util.Log;
 
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
-import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.PackageUserKey;
 
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * Helper class to send broadcasts to package installers that have:
@@ -61,26 +66,10 @@
 
     private static final String VERIFICATION_TOKEN_EXTRA = "verificationToken";
 
-    private final MultiHashMap<String, String> mPackagesForInstaller;
+    private final HashMap<PackageUserKey, SessionInfo> mSessionInfoForPackage;
 
     public FirstScreenBroadcast(HashMap<PackageUserKey, SessionInfo> sessionInfoForPackage) {
-        mPackagesForInstaller = getPackagesForInstaller(sessionInfoForPackage);
-    }
-
-    /**
-     * @return Map where the key is the package name of the installer, and the value is a list
-     *         of packages with active sessions for that installer.
-     */
-    private MultiHashMap<String, String> getPackagesForInstaller(
-            HashMap<PackageUserKey, SessionInfo> sessionInfoForPackage) {
-        MultiHashMap<String, String> packagesForInstaller = new MultiHashMap<>();
-        for (Map.Entry<PackageUserKey, SessionInfo> entry : sessionInfoForPackage.entrySet()) {
-            if (myUserHandle().equals(entry.getKey().mUser)) {
-                packagesForInstaller.addToList(entry.getValue().getInstallerPackageName(),
-                        entry.getKey().mPackageName);
-            }
-        }
-        return packagesForInstaller;
+        mSessionInfoForPackage = sessionInfoForPackage;
     }
 
     /**
@@ -88,9 +77,15 @@
      * first screen.
      */
     public void sendBroadcasts(Context context, List<ItemInfo> firstScreenItems) {
-        for (Map.Entry<String, ArrayList<String>> entry : mPackagesForInstaller.entrySet()) {
-            sendBroadcastToInstaller(context, entry.getKey(), entry.getValue(), firstScreenItems);
-        }
+        UserHandle myUser = myUserHandle();
+        mSessionInfoForPackage
+                .values()
+                .stream()
+                .filter(info -> myUser.equals(getUserHandle(info)))
+                .collect(groupingBy(SessionInfo::getInstallerPackageName,
+                        mapping(SessionInfo::getAppPackageName, Collectors.toSet())))
+                .forEach((installer, packages) ->
+                    sendBroadcastToInstaller(context, installer, packages, firstScreenItems));
     }
 
     /**
@@ -99,7 +94,7 @@
      * @param firstScreenItems List of items on the first screen.
      */
     private void sendBroadcastToInstaller(Context context, String installerPackageName,
-            List<String> packages, List<ItemInfo> firstScreenItems) {
+            Set<String> packages, List<ItemInfo> firstScreenItems) {
         Set<String> folderItems = new HashSet<>();
         Set<String> workspaceItems = new HashSet<>();
         Set<String> hotseatItems = new HashSet<>();
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
index ebdfa8c..79467d3 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
@@ -406,7 +406,7 @@
          * to speed up the search.
          */
         private boolean findPlacement(DbEntry entry) {
-            for (int y = mNextStartY; y > 0; y--) {
+            for (int y = mNextStartY; y >= (mScreenId == 0 ? 1 /* smartspace */ : 0); y--) {
                 for (int x = mNextStartX; x < mTrgX; x++) {
                     boolean fits = mOccupied.isRegionVacant(x, y, entry.spanX, entry.spanY);
                     boolean minFits = mOccupied.isRegionVacant(x, y, entry.minSpanX,
diff --git a/src/com/android/launcher3/model/ItemInstallQueue.java b/src/com/android/launcher3/model/ItemInstallQueue.java
new file mode 100644
index 0000000..5e48a0f
--- /dev/null
+++ b/src/com/android/launcher3/model/ItemInstallQueue.java
@@ -0,0 +1,342 @@
+/*
+ * 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.model;
+
+import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID;
+
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+import static com.android.launcher3.model.data.AppInfo.makeLaunchIntent;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.Pair;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.shortcuts.ShortcutRequest;
+import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.PersistedItemArray;
+import com.android.launcher3.util.Preconditions;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * Class to maintain a queue of pending items to be added to the workspace.
+ */
+public class ItemInstallQueue {
+
+    public static final int FLAG_ACTIVITY_PAUSED = 1;
+    public static final int FLAG_LOADER_RUNNING = 2;
+    public static final int FLAG_DRAG_AND_DROP = 4;
+
+    private static final String TAG = "InstallShortcutReceiver";
+
+    // The set of shortcuts that are pending install
+    private static final String APPS_PENDING_INSTALL = "apps_to_install";
+
+    public static final int NEW_SHORTCUT_BOUNCE_DURATION = 450;
+    public static final int NEW_SHORTCUT_STAGGER_DELAY = 85;
+
+    public static MainThreadInitializedObject<ItemInstallQueue> INSTANCE =
+            new MainThreadInitializedObject<>(ItemInstallQueue::new);
+
+    private final PersistedItemArray<PendingInstallShortcutInfo> mStorage =
+            new PersistedItemArray<>(APPS_PENDING_INSTALL);
+    private final Context mContext;
+
+    // Determines whether to defer installing shortcuts immediately until
+    // processAllPendingInstalls() is called.
+    private int mInstallQueueDisabledFlags = 0;
+
+    // Only accessed on worker thread
+    private List<PendingInstallShortcutInfo> mItems;
+
+    private ItemInstallQueue(Context context) {
+        mContext = context;
+    }
+
+    @WorkerThread
+    private void ensureQueueLoaded() {
+        Preconditions.assertWorkerThread();
+        if (mItems == null) {
+            mItems = mStorage.read(mContext, this::decode);
+        }
+    }
+
+    @WorkerThread
+    private void addToQueue(PendingInstallShortcutInfo info) {
+        ensureQueueLoaded();
+        mItems.add(info);
+        mStorage.write(mContext, mItems);
+    }
+
+    @WorkerThread
+    private void flushQueueInBackground() {
+        Launcher launcher = Launcher.ACTIVITY_TRACKER.getCreatedActivity();
+        if (launcher == null) {
+            // Launcher not loaded
+            return;
+        }
+        ensureQueueLoaded();
+        if (mItems.isEmpty()) {
+            return;
+        }
+
+        List<Pair<ItemInfo, Object>> installQueue = mItems.stream()
+                .map(info -> info.getItemInfo(mContext))
+                .collect(Collectors.toList());
+
+        // Add the items and clear queue
+        if (!installQueue.isEmpty()) {
+            launcher.getModel().addAndBindAddedWorkspaceItems(installQueue);
+        }
+        mItems.clear();
+        mStorage.getFile(mContext).delete();
+    }
+
+    /**
+     * Removes previously added items from the queue.
+     */
+    @WorkerThread
+    public void removeFromInstallQueue(HashSet<String> packageNames, UserHandle user) {
+        if (packageNames.isEmpty()) {
+            return;
+        }
+        ensureQueueLoaded();
+        if (mItems.removeIf(item ->
+                item.user.equals(user) && packageNames.contains(getIntentPackage(item.intent)))) {
+            mStorage.write(mContext, mItems);
+        }
+    }
+
+    /**
+     * Adds an item to the install queue
+     */
+    public void queueItem(ShortcutInfo info) {
+        queuePendingShortcutInfo(new PendingInstallShortcutInfo(info));
+    }
+
+    /**
+     * Adds an item to the install queue
+     */
+    public void queueItem(AppWidgetProviderInfo info, int widgetId) {
+        queuePendingShortcutInfo(new PendingInstallShortcutInfo(info, widgetId));
+    }
+
+    /**
+     * Adds an item to the install queue
+     */
+    public void queueItem(String packageName, UserHandle userHandle) {
+        queuePendingShortcutInfo(new PendingInstallShortcutInfo(packageName, userHandle));
+    }
+
+    /**
+     * Returns a stream of all pending shortcuts in the queue
+     */
+    @WorkerThread
+    public Stream<ShortcutKey> getPendingShortcuts(UserHandle user) {
+        ensureQueueLoaded();
+        return mItems.stream()
+                .filter(item -> item.itemType == ITEM_TYPE_DEEP_SHORTCUT && user.equals(item.user))
+                .map(item -> ShortcutKey.fromIntent(item.intent, user));
+    }
+
+    private void queuePendingShortcutInfo(PendingInstallShortcutInfo info) {
+        // Queue the item up for adding if launcher has not loaded properly yet
+        MODEL_EXECUTOR.post(() -> addToQueue(info));
+        flushInstallQueue();
+    }
+
+    /**
+     * Pauses the push-to-model flow until unpaused. All items are held in the queue and
+     * not added to the model.
+     */
+    public void pauseModelPush(int flag) {
+        mInstallQueueDisabledFlags |= flag;
+    }
+
+    /**
+     * Adds all the queue items to the model if the use is completely resumed.
+     */
+    public void resumeModelPush(int flag) {
+        mInstallQueueDisabledFlags &= ~flag;
+        flushInstallQueue();
+    }
+
+    private void flushInstallQueue() {
+        if (mInstallQueueDisabledFlags != 0) {
+            return;
+        }
+        MODEL_EXECUTOR.post(this::flushQueueInBackground);
+    }
+
+    private static class PendingInstallShortcutInfo extends ItemInfo {
+
+        final Intent intent;
+
+        @Nullable ShortcutInfo shortcutInfo;
+        @Nullable AppWidgetProviderInfo providerInfo;
+
+        /**
+         * Initializes a PendingInstallShortcutInfo to represent a pending launcher target.
+         */
+        public PendingInstallShortcutInfo(String packageName, UserHandle userHandle) {
+            itemType = Favorites.ITEM_TYPE_APPLICATION;
+            intent = new Intent().setPackage(packageName);
+            user = userHandle;
+        }
+
+        /**
+         * Initializes a PendingInstallShortcutInfo to represent a deep shortcut.
+         */
+        public PendingInstallShortcutInfo(ShortcutInfo info) {
+            itemType = Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+            intent = ShortcutKey.makeIntent(info);
+            user = info.getUserHandle();
+
+            shortcutInfo = info;
+        }
+
+        /**
+         * Initializes a PendingInstallShortcutInfo to represent an app widget.
+         */
+        public PendingInstallShortcutInfo(AppWidgetProviderInfo info, int widgetId) {
+            itemType = Favorites.ITEM_TYPE_APPWIDGET;
+            intent = new Intent()
+                    .setComponent(info.provider)
+                    .putExtra(EXTRA_APPWIDGET_ID, widgetId);
+            user = info.getProfile();
+
+            providerInfo = info;
+        }
+
+        @Override
+        public Intent getIntent() {
+            return intent;
+        }
+
+        public Pair<ItemInfo, Object> getItemInfo(Context context) {
+            switch (itemType) {
+                case ITEM_TYPE_APPLICATION: {
+                    String packageName = intent.getPackage();
+                    List<LauncherActivityInfo> laiList =
+                            context.getSystemService(LauncherApps.class)
+                                    .getActivityList(packageName, user);
+
+                    final WorkspaceItemInfo si = new WorkspaceItemInfo();
+                    si.user = user;
+                    si.itemType = ITEM_TYPE_APPLICATION;
+
+                    LauncherActivityInfo lai;
+                    boolean usePackageIcon = laiList.isEmpty();
+                    if (usePackageIcon) {
+                        lai = null;
+                        si.intent = makeLaunchIntent(new ComponentName(packageName, ""))
+                                .setPackage(packageName);
+                        si.status |= WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON;
+                    } else {
+                        lai = laiList.get(0);
+                        si.intent = makeLaunchIntent(lai);
+                    }
+                    LauncherAppState.getInstance(context).getIconCache()
+                            .getTitleAndIcon(si, () -> lai, usePackageIcon, false);
+                    return Pair.create(si, null);
+                }
+                case ITEM_TYPE_DEEP_SHORTCUT: {
+                    WorkspaceItemInfo itemInfo = new WorkspaceItemInfo(shortcutInfo, context);
+                    LauncherAppState.getInstance(context).getIconCache()
+                            .getShortcutIcon(itemInfo, shortcutInfo);
+                    return Pair.create(itemInfo, shortcutInfo);
+                }
+                case ITEM_TYPE_APPWIDGET: {
+                    LauncherAppWidgetProviderInfo info = LauncherAppWidgetProviderInfo
+                            .fromProviderInfo(context, providerInfo);
+                    LauncherAppWidgetInfo widgetInfo = new LauncherAppWidgetInfo(
+                            intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 0),
+                            info.provider);
+                    InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
+                    widgetInfo.minSpanX = info.minSpanX;
+                    widgetInfo.minSpanY = info.minSpanY;
+                    widgetInfo.spanX = Math.min(info.spanX, idp.numColumns);
+                    widgetInfo.spanY = Math.min(info.spanY, idp.numRows);
+                    widgetInfo.user = user;
+                    return Pair.create(widgetInfo, providerInfo);
+                }
+            }
+            return null;
+        }
+    }
+
+    private static String getIntentPackage(Intent intent) {
+        return intent.getComponent() == null
+                ? intent.getPackage() : intent.getComponent().getPackageName();
+    }
+
+    private PendingInstallShortcutInfo decode(int itemType, UserHandle user, Intent intent) {
+        switch (itemType) {
+            case Favorites.ITEM_TYPE_APPLICATION:
+                return new PendingInstallShortcutInfo(intent.getPackage(), user);
+            case Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
+                List<ShortcutInfo> si = ShortcutKey.fromIntent(intent, user)
+                        .buildRequest(mContext)
+                        .query(ShortcutRequest.ALL);
+                if (si.isEmpty()) {
+                    return null;
+                } else {
+                    return new PendingInstallShortcutInfo(si.get(0));
+                }
+            }
+            case Favorites.ITEM_TYPE_APPWIDGET: {
+                int widgetId = intent.getIntExtra(EXTRA_APPWIDGET_ID, 0);
+                AppWidgetProviderInfo info =
+                        AppWidgetManager.getInstance(mContext).getAppWidgetInfo(widgetId);
+                if (info == null || !info.provider.equals(intent.getComponent())
+                        || !info.getProfile().equals(user)) {
+                    return null;
+                }
+                return new PendingInstallShortcutInfo(info, widgetId);
+            }
+            default:
+                Log.e(TAG, "Unknown item type");
+        }
+        return null;
+    }
+}
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 165d1ea..a27ac23 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -65,7 +65,7 @@
 
     private static final String TAG = "LoaderCursor";
 
-    public final LongSparseArray<UserHandle> allUsers;
+    private final LongSparseArray<UserHandle> allUsers;
 
     private final Uri mContentUri;
     private final Context mContext;
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 102ec31..a9e385f 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -46,12 +46,8 @@
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.LongSparseArray;
-import android.util.MutableInt;
 import android.util.TimingLogger;
 
-import androidx.annotation.WorkerThread;
-
-import com.android.launcher3.InstallShortcutReceiver;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherSettings;
@@ -85,7 +81,6 @@
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.IOUtils;
 import com.android.launcher3.util.LooperIdleLock;
-import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.TraceHelper;
@@ -97,6 +92,7 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.CancellationException;
 
 /**
@@ -112,6 +108,7 @@
     protected final LauncherAppState mApp;
     private final AllAppsList mBgAllAppsList;
     protected final BgDataModel mBgDataModel;
+    private final ModelDelegate mModelDelegate;
 
     private FirstScreenBroadcast mFirstScreenBroadcast;
 
@@ -126,13 +123,18 @@
 
     private final UserManagerState mUserManagerState = new UserManagerState();
 
+    protected Map<ComponentKey, AppWidgetProviderInfo> mWidgetProvidersMap;
+
     private boolean mStopped;
 
+    private boolean mItemsDeleted = false;
+
     public LoaderTask(LauncherAppState app, AllAppsList bgAllAppsList, BgDataModel dataModel,
-            LoaderResults results) {
+            ModelDelegate modelDelegate, LoaderResults results) {
         mApp = app;
         mBgAllAppsList = bgAllAppsList;
         mBgDataModel = dataModel;
+        mModelDelegate = modelDelegate;
         mResults = results;
 
         mLauncherApps = mApp.getContext().getSystemService(LauncherApps.class);
@@ -187,13 +189,13 @@
         try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
             List<ShortcutInfo> allShortcuts = new ArrayList<>();
             loadWorkspace(allShortcuts);
-            loadCachedPredictions();
             logger.addSplit("loadWorkspace");
 
             verifyNotStopped();
             mResults.bindWorkspace();
             logger.addSplit("bindWorkspace");
 
+            mModelDelegate.workspaceLoadComplete();
             // Notify the installer packages of packages with active installs on the first screen.
             sendFirstScreenActiveInstallsBroadcast();
             logger.addSplit("sendFirstScreenActiveInstallsBroadcast");
@@ -270,6 +272,7 @@
             if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
                 loadFolderNames();
             }
+            sanitizeData();
 
             verifyNotStopped();
             updateHandler.finish();
@@ -300,7 +303,7 @@
         final PackageManagerHelper pmHelper = new PackageManagerHelper(context);
         final boolean isSafeMode = pmHelper.isSafeMode();
         final boolean isSdCardReady = Utilities.isBootCompleted();
-        final MultiHashMap<UserHandle, String> pendingPackages = new MultiHashMap<>();
+        final Set<PackageUserKey> pendingPackages = new HashSet<>();
 
         boolean clearDb = false;
         try {
@@ -342,8 +345,6 @@
                     contentResolver.query(contentUri, null, null, null, null), contentUri, mApp,
                     mUserManagerState);
 
-            Map<ComponentKey, AppWidgetProviderInfo> widgetProvidersMap = null;
-
             try {
                 final int appWidgetIdIndex = c.getColumnIndexOrThrow(
                         LauncherSettings.Favorites.APPWIDGET_ID);
@@ -358,15 +359,12 @@
                 final int optionsIndex = c.getColumnIndexOrThrow(
                         LauncherSettings.Favorites.OPTIONS);
 
-                final LongSparseArray<UserHandle> allUsers = c.allUsers;
                 final LongSparseArray<Boolean> unlockedUsers = new LongSparseArray<>();
 
                 mUserManagerState.init(mUserCache, mUserManager);
 
                 for (UserHandle user : mUserCache.getUserProfiles()) {
                     long serialNo = mUserCache.getSerialNumberForUser(user);
-                    allUsers.put(serialNo, user);
-
                     boolean userUnlocked = mUserManager.isUserUnlocked(user);
 
                     // We can only query for shortcuts when the user is unlocked.
@@ -417,16 +415,6 @@
                             ComponentName cn = intent.getComponent();
                             targetPkg = cn == null ? intent.getPackage() : cn.getPackageName();
 
-                            if (allUsers.indexOfValue(c.user) < 0) {
-                                if (c.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
-                                    c.markDeleted("Legacy shortcuts are only allowed for current users");
-                                    continue;
-                                } else if (c.restoreFlag != 0) {
-                                    // Don't restore items for other profiles.
-                                    c.markDeleted("Restore from other profiles not supported");
-                                    continue;
-                                }
-                            }
                             if (TextUtils.isEmpty(targetPkg) &&
                                     c.itemType != LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
                                 c.markDeleted("Only legacy shortcuts can have null package");
@@ -496,7 +484,7 @@
                                     // 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);
+                                    pendingPackages.add(new PackageUserKey(targetPkg, c.user));
                                     // Add the icon on the workspace anyway.
                                     allowMissingTarget = true;
                                 } else {
@@ -650,10 +638,11 @@
                             final boolean wasProviderReady = !c.hasRestoreFlag(
                                     LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY);
 
-                            if (widgetProvidersMap == null) {
-                                widgetProvidersMap = WidgetManagerHelper.getAllProvidersMap(context);
+                            if (mWidgetProvidersMap == null) {
+                                mWidgetProvidersMap = WidgetManagerHelper.getAllProvidersMap(
+                                        context);
                             }
-                            final AppWidgetProviderInfo provider = widgetProvidersMap.get(
+                            final AppWidgetProviderInfo provider = mWidgetProvidersMap.get(
                                     new ComponentKey(component, c.user));
 
                             final boolean isProviderReady = isValidProvider(provider);
@@ -769,6 +758,9 @@
                 IOUtils.closeSilently(c);
             }
 
+            // Load delegate items
+            mModelDelegate.loadItems(mUserManagerState, shortcutKeyToPinnedShortcuts);
+
             // Break early if we've stopped loading
             if (mStopped) {
                 mBgDataModel.clear();
@@ -788,21 +780,7 @@
                     mBgDataModel.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 = mBgDataModel.pinnedShortcutCounts.get(key);
-                if ((numTimesPinned == null || numTimesPinned.value == 0)
-                        && !pendingShortcuts.contains(key)) {
-                    // Shortcut is pinned but doesn't exist on the workspace; unpin it.
-                    mBgDataModel.unpinShortcut(context, key);
-                }
+                mItemsDeleted = true;
             }
 
             // Sort the folder items, update ranks, and make sure all preview items are high res.
@@ -860,22 +838,16 @@
         }
     }
 
-    @WorkerThread
-    private void loadCachedPredictions() {
-        synchronized (mBgDataModel) {
-            List<ComponentKey> componentKeys =
-                    mApp.getPredictionModel().getPredictionComponentKeys();
-            List<LauncherActivityInfo> l;
-            mBgDataModel.cachedPredictedItems.clear();
-            for (ComponentKey key : componentKeys) {
-                l = mLauncherApps.getActivityList(key.componentName.getPackageName(), key.user);
-                if (l.size() == 0) continue;
-                AppInfo info = new AppInfo(l.get(0), key.user,
-                        mUserManagerState.isUserQuiet(key.user));
-                mBgDataModel.cachedPredictedItems.add(info);
-                mIconCache.getTitleAndIcon(info, false);
-            }
+    private void sanitizeData() {
+        Context context = mApp.getContext();
+        if (mItemsDeleted) {
+            // Remove any ghost widgets
+            LauncherSettings.Settings.call(context.getContentResolver(),
+                    LauncherSettings.Settings.METHOD_REMOVE_GHOST_WIDGETS);
         }
+
+        // Update pinned state of model shortcuts
+        mBgDataModel.updateShortcutPinnedState(context);
     }
 
     private List<LauncherActivityInfo> loadAllApps() {
@@ -909,14 +881,6 @@
                         PackageInstallInfo.fromInstallingState(info));
             }
         }
-        for (AppInfo item : mBgDataModel.cachedPredictedItems) {
-            List<LauncherActivityInfo> l = mLauncherApps.getActivityList(
-                    item.componentName.getPackageName(), item.user);
-            for (LauncherActivityInfo info : l) {
-                boolean quietMode = mUserManagerState.isUserQuiet(item.user);
-                mBgAllAppsList.add(new AppInfo(info, item.user, quietMode), info);
-            }
-        }
 
         mBgAllAppsList.setFlags(FLAG_QUIET_MODE_ENABLED,
                 mUserManagerState.isAnyProfileQuietModeEnabled());
diff --git a/src/com/android/launcher3/model/ModelDelegate.java b/src/com/android/launcher3/model/ModelDelegate.java
new file mode 100644
index 0000000..3ed8809
--- /dev/null
+++ b/src/com/android/launcher3/model/ModelDelegate.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2020 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 static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
+
+import android.content.Context;
+import android.content.pm.ShortcutInfo;
+
+import androidx.annotation.WorkerThread;
+
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.R;
+import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.util.ResourceBasedOverride;
+
+import java.util.Map;
+
+/**
+ * Class to extend LauncherModel functionality to provide extra data
+ */
+public class ModelDelegate implements ResourceBasedOverride {
+
+    /**
+     * Creates and initializes a new instance of the delegate
+     */
+    public static ModelDelegate newInstance(
+            Context context, LauncherAppState app, AllAppsList appsList, BgDataModel dataModel) {
+        ModelDelegate delegate = Overrides.getObject(
+                ModelDelegate.class, context, R.string.model_delegate_class);
+
+        delegate.mApp = app;
+        delegate.mAppsList = appsList;
+        delegate.mDataModel = dataModel;
+        return delegate;
+    }
+
+    protected LauncherAppState mApp;
+    protected AllAppsList mAppsList;
+    protected BgDataModel mDataModel;
+
+    public ModelDelegate() { }
+
+    /**
+     * Called periodically to validate and update any data
+     */
+    @WorkerThread
+    public void validateData() {
+        if (hasShortcutsPermission(mApp.getContext())
+                != mAppsList.hasShortcutHostPermission()) {
+            mApp.getModel().forceReload();
+        }
+    }
+
+    /**
+     * Load delegate items if any in the data model
+     */
+    @WorkerThread
+    public void loadItems(UserManagerState ums, Map<ShortcutKey, ShortcutInfo> pinnedShortcuts) { }
+
+    /**
+     * Called during loader after workspace loading is complete
+     */
+    @WorkerThread
+    public void workspaceLoadComplete() { }
+
+    /**
+     * Called when the delegate is no loner needed
+     */
+    @WorkerThread
+    public void destroy() { }
+
+}
diff --git a/src/com/android/launcher3/model/ModelUtils.java b/src/com/android/launcher3/model/ModelUtils.java
index 4efeba5..9b5fac8 100644
--- a/src/com/android/launcher3/model/ModelUtils.java
+++ b/src/com/android/launcher3/model/ModelUtils.java
@@ -15,17 +15,29 @@
  */
 package com.android.launcher3.model;
 
+import static com.android.launcher3.Utilities.isValidExtraType;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.os.Process;
+import android.util.Log;
+
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSet;
 
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.Iterator;
 import java.util.List;
+import java.util.Objects;
 import java.util.stream.IntStream;
 
 /**
@@ -33,6 +45,8 @@
  */
 public class ModelUtils {
 
+    private static final String TAG = "ModelUtils";
+
     /**
      * Filters the set of items who are directly or indirectly (via another container) on the
      * specified screen.
@@ -42,13 +56,7 @@
             ArrayList<T> currentScreenItems,
             ArrayList<T> otherScreenItems) {
         // Purge any null ItemInfos
-        Iterator<T> iter = allWorkspaceItems.iterator();
-        while (iter.hasNext()) {
-            ItemInfo i = iter.next();
-            if (i == null) {
-                iter.remove();
-            }
-        }
+        allWorkspaceItems.removeIf(Objects::isNull);
         // Order the set of items by their containers first, this allows use to walk through the
         // list sequentially, build up a list of containers that are in the specified screen,
         // as well as all items in those containers.
@@ -125,4 +133,52 @@
         IntStream.range(0, len).filter(i -> !seen.contains(i)).forEach(result::add);
         return result;
     }
+
+
+    /**
+     * Creates a workspace item info for the legacy shortcut intent
+     */
+    @SuppressWarnings("deprecation")
+    public static WorkspaceItemInfo fromLegacyShortcutIntent(Context context, Intent data) {
+        if (!isValidExtraType(data, Intent.EXTRA_SHORTCUT_INTENT, Intent.class)
+                || !(isValidExtraType(data, Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
+                        Intent.ShortcutIconResource.class))
+                || !(isValidExtraType(data, Intent.EXTRA_SHORTCUT_ICON, Bitmap.class))) {
+
+            Log.e(TAG, "Invalid install shortcut intent");
+            return null;
+        }
+
+        Intent launchIntent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
+        String label = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
+        if (launchIntent == null || label == null) {
+            Log.e(TAG, "Invalid install shortcut intent");
+            return null;
+        }
+
+        final WorkspaceItemInfo info = new WorkspaceItemInfo();
+        info.user = Process.myUserHandle();
+
+        BitmapInfo iconInfo = null;
+        try (LauncherIcons li = LauncherIcons.obtain(context)) {
+            Bitmap bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
+            if (bitmap != null) {
+                iconInfo = li.createIconBitmap(bitmap);
+            } else {
+                info.iconResource = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
+                if (info.iconResource != null) {
+                    iconInfo = li.createIconBitmap(info.iconResource);
+                }
+            }
+        }
+
+        if (iconInfo == null) {
+            Log.e(TAG, "Invalid icon by the app");
+            return null;
+        }
+        info.bitmap = iconInfo;
+        info.contentDescription = info.title = Utilities.trim(label);
+        info.intent = launchIntent;
+        return info;
+    }
 }
diff --git a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
index 203f1ca..8369c48 100644
--- a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
+++ b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
@@ -20,8 +20,6 @@
 import android.content.pm.PackageManager;
 
 import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherModel.CallbackTask;
-import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.model.data.PromiseAppInfo;
@@ -70,21 +68,18 @@
 
         synchronized (dataModel) {
             final HashSet<ItemInfo> updates = new HashSet<>();
-            for (ItemInfo info : dataModel.itemsIdMap) {
-                if (info instanceof WorkspaceItemInfo) {
-                    WorkspaceItemInfo si = (WorkspaceItemInfo) info;
-                    ComponentName cn = si.getTargetComponent();
-                    if (si.hasPromiseIconUi() && (cn != null)
-                            && mInstallInfo.packageName.equals(cn.getPackageName())) {
-                        si.setInstallProgress(mInstallInfo.progress);
-                        if (mInstallInfo.state == PackageInstallInfo.STATUS_FAILED) {
-                            // Mark this info as broken.
-                            si.status &= ~WorkspaceItemInfo.FLAG_INSTALL_SESSION_ACTIVE;
-                        }
-                        updates.add(si);
+            dataModel.forAllWorkspaceItemInfos(mInstallInfo.user, si -> {
+                ComponentName cn = si.getTargetComponent();
+                if (si.hasPromiseIconUi() && (cn != null)
+                        && mInstallInfo.packageName.equals(cn.getPackageName())) {
+                    si.setInstallProgress(mInstallInfo.progress);
+                    if (mInstallInfo.state == PackageInstallInfo.STATUS_FAILED) {
+                        // Mark this info as broken.
+                        si.status &= ~WorkspaceItemInfo.FLAG_INSTALL_SESSION_ACTIVE;
                     }
+                    updates.add(si);
                 }
-            }
+            });
 
             for (LauncherAppWidgetInfo widget : dataModel.appWidgets) {
                 if (widget.providerName.getPackageName().equals(mInstallInfo.packageName)) {
@@ -94,12 +89,7 @@
             }
 
             if (!updates.isEmpty()) {
-                scheduleCallbackTask(new CallbackTask() {
-                    @Override
-                    public void execute(Callbacks callbacks) {
-                        callbacks.bindRestoreItemsChange(updates);
-                    }
-                });
+                scheduleCallbackTask(callbacks -> callbacks.bindRestoreItemsChange(updates));
             }
         }
     }
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 7cd467e..896bfb6 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -24,16 +24,12 @@
 import android.content.Intent;
 import android.content.pm.LauncherApps;
 import android.content.pm.ShortcutInfo;
-import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Log;
 
-import com.android.launcher3.InstallShortcutReceiver;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.SessionCommitReceiver;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.icons.IconCache;
@@ -45,7 +41,7 @@
 import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.shortcuts.ShortcutRequest;
 import com.android.launcher3.util.FlagOp;
-import com.android.launcher3.util.IntSparseArrayMap;
+import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.PackageUserKey;
@@ -92,9 +88,11 @@
 
         final String[] packages = mPackages;
         final int N = packages.length;
-        FlagOp flagOp = FlagOp.NO_OP;
+        final FlagOp flagOp;
         final HashSet<String> packageSet = new HashSet<>(Arrays.asList(packages));
-        ItemInfoMatcher matcher = ItemInfoMatcher.ofPackages(packageSet, mUser);
+        final ItemInfoMatcher matcher = mOp == OP_USER_AVAILABILITY_CHANGE
+                ? ItemInfoMatcher.ofUser(mUser) // We want to update all packages for this user
+                : ItemInfoMatcher.ofPackages(packageSet, mUser);
         final HashSet<ComponentName> removedComponents = new HashSet<>();
 
         switch (mOp) {
@@ -106,11 +104,6 @@
                         appsList.removePackage(packages[i], mUser);
                     }
                     appsList.addPackage(context, packages[i], mUser);
-
-                    // Automatically add homescreen icon for work profile apps for below O device.
-                    if (!Utilities.ATLEAST_OREO && !Process.myUserHandle().equals(mUser)) {
-                        SessionCommitReceiver.queueAppIconAddition(context, packages[i], mUser);
-                    }
                 }
                 flagOp = FlagOp.removeFlag(WorkspaceItemInfo.FLAG_DISABLED_NOT_AVAILABLE);
                 break;
@@ -158,19 +151,22 @@
                 flagOp = ums.isUserQuiet(mUser)
                         ? FlagOp.addFlag(WorkspaceItemInfo.FLAG_DISABLED_QUIET_USER)
                         : FlagOp.removeFlag(WorkspaceItemInfo.FLAG_DISABLED_QUIET_USER);
-                // We want to update all packages for this user.
-                matcher = ItemInfoMatcher.ofUser(mUser);
                 appsList.updateDisabledFlags(matcher, flagOp);
 
                 // We are not synchronizing here, as int operations are atomic
                 appsList.setFlags(FLAG_QUIET_MODE_ENABLED, ums.isAnyProfileQuietModeEnabled());
                 break;
             }
+            default:
+                flagOp = FlagOp.NO_OP;
+                break;
         }
 
         bindApplicationsIfNeeded();
 
-        final IntSparseArrayMap<Boolean> removedShortcuts = new IntSparseArrayMap<>();
+        final IntSet removedShortcuts = new IntSet();
+        // Shortcuts to keep even if the corresponding app was removed
+        final IntSet forceKeepShortcuts = new IntSet();
 
         // Update shortcut infos
         if (mOp == OP_ADD || flagOp != FlagOp.NO_OP) {
@@ -180,118 +176,118 @@
             // For system apps, package manager send OP_UPDATE when an app is enabled.
             final boolean isNewApkAvailable = mOp == OP_ADD || mOp == OP_UPDATE;
             synchronized (dataModel) {
-                for (ItemInfo info : dataModel.itemsIdMap) {
-                    if (info instanceof WorkspaceItemInfo && mUser.equals(info.user)) {
-                        WorkspaceItemInfo si = (WorkspaceItemInfo) info;
-                        boolean infoUpdated = false;
-                        boolean shortcutUpdated = false;
+                dataModel.forAllWorkspaceItemInfos(mUser, si -> {
 
-                        // Update shortcuts which use iconResource.
-                        if ((si.iconResource != null)
-                                && packageSet.contains(si.iconResource.packageName)) {
-                            LauncherIcons li = LauncherIcons.obtain(context);
-                            BitmapInfo iconInfo = li.createIconBitmap(si.iconResource);
-                            li.recycle();
-                            if (iconInfo != null) {
-                                si.bitmap = iconInfo;
-                                infoUpdated = true;
+                    boolean infoUpdated = false;
+                    boolean shortcutUpdated = false;
+
+                    // Update shortcuts which use iconResource.
+                    if ((si.iconResource != null)
+                            && packageSet.contains(si.iconResource.packageName)) {
+                        LauncherIcons li = LauncherIcons.obtain(context);
+                        BitmapInfo iconInfo = li.createIconBitmap(si.iconResource);
+                        li.recycle();
+                        if (iconInfo != null) {
+                            si.bitmap = iconInfo;
+                            infoUpdated = true;
+                        }
+                    }
+
+                    ComponentName cn = si.getTargetComponent();
+                    if (cn != null && matcher.matches(si, cn)) {
+                        String packageName = cn.getPackageName();
+
+                        if (si.hasStatusFlag(WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI)) {
+                            forceKeepShortcuts.add(si.id);
+                            if (mOp == OP_REMOVE) {
+                                return;
                             }
                         }
 
-                        ComponentName cn = si.getTargetComponent();
-                        if (cn != null && matcher.matches(si, cn)) {
-                            String packageName = cn.getPackageName();
-
-                            if (si.hasStatusFlag(WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI)) {
-                                removedShortcuts.put(si.id, false);
-                                if (mOp == OP_REMOVE) {
-                                    continue;
-                                }
-                            }
-
-                            if (si.isPromise() && isNewApkAvailable) {
-                                boolean isTargetValid = true;
-                                if (si.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
-                                    List<ShortcutInfo> shortcut =
-                                            new ShortcutRequest(context, mUser)
-                                                    .forPackage(cn.getPackageName(),
-                                                            si.getDeepShortcutId())
-                                                    .query(ShortcutRequest.PINNED);
-                                    if (shortcut.isEmpty()) {
-                                        isTargetValid = false;
-                                    } else {
-                                        si.updateFromDeepShortcutInfo(shortcut.get(0), context);
-                                        infoUpdated = true;
-                                    }
-                                } else if (!cn.getClassName().equals(IconCache.EMPTY_CLASS_NAME)) {
-                                    isTargetValid = context.getSystemService(LauncherApps.class)
-                                            .isActivityEnabled(cn, mUser);
-                                }
-                                if (si.hasStatusFlag(FLAG_RESTORED_ICON | FLAG_AUTOINSTALL_ICON)) {
-                                    if (updateWorkspaceItemIntent(context, si, packageName)) {
-                                        infoUpdated = true;
-                                    } else if (si.hasPromiseIconUi()) {
-                                        removedShortcuts.put(si.id, true);
-                                        continue;
-                                    }
-                                } else if (!isTargetValid) {
-                                    removedShortcuts.put(si.id, true);
-                                    FileLog.e(TAG, "Restored shortcut no longer valid "
-                                            + si.getIntent());
-                                    continue;
+                        if (si.isPromise() && isNewApkAvailable) {
+                            boolean isTargetValid = true;
+                            if (si.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+                                List<ShortcutInfo> shortcut =
+                                        new ShortcutRequest(context, mUser)
+                                                .forPackage(cn.getPackageName(),
+                                                        si.getDeepShortcutId())
+                                                .query(ShortcutRequest.PINNED);
+                                if (shortcut.isEmpty()) {
+                                    isTargetValid = false;
                                 } else {
-                                    si.status = WorkspaceItemInfo.DEFAULT;
+                                    si.updateFromDeepShortcutInfo(shortcut.get(0), context);
                                     infoUpdated = true;
                                 }
-                            } else if (isNewApkAvailable && removedComponents.contains(cn)) {
+                            } else if (!cn.getClassName().equals(IconCache.EMPTY_CLASS_NAME)) {
+                                isTargetValid = context.getSystemService(LauncherApps.class)
+                                        .isActivityEnabled(cn, mUser);
+                            }
+                            if (si.hasStatusFlag(FLAG_RESTORED_ICON | FLAG_AUTOINSTALL_ICON)) {
                                 if (updateWorkspaceItemIntent(context, si, packageName)) {
                                     infoUpdated = true;
+                                } else if (si.hasPromiseIconUi()) {
+                                    removedShortcuts.add(si.id);
+                                    return;
                                 }
-                            }
-
-                            if (isNewApkAvailable &&
-                                    si.itemType == Favorites.ITEM_TYPE_APPLICATION) {
-                                iconCache.getTitleAndIcon(si, si.usingLowResIcon());
+                            } else if (!isTargetValid) {
+                                removedShortcuts.add(si.id);
+                                FileLog.e(TAG, "Restored shortcut no longer valid "
+                                        + si.getIntent());
+                                return;
+                            } else {
+                                si.status = WorkspaceItemInfo.DEFAULT;
                                 infoUpdated = true;
                             }
-
-                            int oldRuntimeFlags = si.runtimeStatusFlags;
-                            si.runtimeStatusFlags = flagOp.apply(si.runtimeStatusFlags);
-                            if (si.runtimeStatusFlags != oldRuntimeFlags) {
-                                shortcutUpdated = true;
+                        } else if (isNewApkAvailable && removedComponents.contains(cn)) {
+                            if (updateWorkspaceItemIntent(context, si, packageName)) {
+                                infoUpdated = true;
                             }
                         }
 
-                        if (infoUpdated || shortcutUpdated) {
-                            updatedWorkspaceItems.add(si);
+                        if (isNewApkAvailable
+                                && si.itemType == Favorites.ITEM_TYPE_APPLICATION) {
+                            iconCache.getTitleAndIcon(si, si.usingLowResIcon());
+                            infoUpdated = true;
                         }
-                        if (infoUpdated) {
-                            getModelWriter().updateItemInDatabase(si);
-                        }
-                    } else if (info instanceof LauncherAppWidgetInfo && isNewApkAvailable) {
-                        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);
+                        int oldRuntimeFlags = si.runtimeStatusFlags;
+                        si.runtimeStatusFlags = flagOp.apply(si.runtimeStatusFlags);
+                        if (si.runtimeStatusFlags != oldRuntimeFlags) {
+                            shortcutUpdated = true;
                         }
                     }
+
+                    if (infoUpdated || shortcutUpdated) {
+                        updatedWorkspaceItems.add(si);
+                    }
+                    if (infoUpdated && si.id != ItemInfo.NO_ID) {
+                        getModelWriter().updateItemInDatabase(si);
+                    }
+                });
+
+                for (LauncherAppWidgetInfo widgetInfo : dataModel.appWidgets) {
+                    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);
+                    }
                 }
             }
 
             bindUpdatedWorkspaceItems(updatedWorkspaceItems);
             if (!removedShortcuts.isEmpty()) {
-                deleteAndBindComponentsRemoved(ItemInfoMatcher.ofItemIds(removedShortcuts, false));
+                deleteAndBindComponentsRemoved(ItemInfoMatcher.ofItemIds(removedShortcuts));
             }
 
             if (!widgets.isEmpty()) {
@@ -319,14 +315,15 @@
         if (!removedPackages.isEmpty() || !removedComponents.isEmpty()) {
             ItemInfoMatcher removeMatch = ItemInfoMatcher.ofPackages(removedPackages, mUser)
                     .or(ItemInfoMatcher.ofComponents(removedComponents, mUser))
-                    .and(ItemInfoMatcher.ofItemIds(removedShortcuts, true));
+                    .and(ItemInfoMatcher.ofItemIds(forceKeepShortcuts).negate());
             deleteAndBindComponentsRemoved(removeMatch);
 
             // Remove any queued items from the install queue
-            InstallShortcutReceiver.removeFromInstallQueue(context, removedPackages, mUser);
+            ItemInstallQueue.INSTANCE.get(context)
+                    .removeFromInstallQueue(removedPackages, mUser);
         }
 
-        if (Utilities.ATLEAST_OREO && mOp == OP_ADD) {
+        if (mOp == OP_ADD) {
             // Load widgets for the new package. Changes due to app updates are handled through
             // AppWidgetHost events, this is just to initialize the long-press options.
             for (int i = 0; i < N; i++) {
diff --git a/src/com/android/launcher3/model/PredictionModel.java b/src/com/android/launcher3/model/PredictionModel.java
deleted file mode 100644
index 1429843..0000000
--- a/src/com/android/launcher3/model/PredictionModel.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2020 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 static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.os.UserHandle;
-
-import androidx.annotation.AnyThread;
-import androidx.annotation.WorkerThread;
-
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.Preconditions;
-import com.android.launcher3.util.ResourceBasedOverride;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Model Helper for app predictions
- */
-public class PredictionModel implements ResourceBasedOverride {
-
-    private static final String CACHED_ITEMS_KEY = "predicted_item_keys";
-    private static final int MAX_CACHE_ITEMS = 5;
-
-    protected Context mContext;
-    private SharedPreferences mDevicePrefs;
-    private UserCache mUserCache;
-
-
-    /**
-     * Retrieve instance of this object that can be overridden in runtime based on the build
-     * variant of the application.
-     */
-    public static PredictionModel newInstance(Context context) {
-        PredictionModel model = Overrides.getObject(PredictionModel.class, context,
-                R.string.prediction_model_class);
-        model.init(context);
-        return model;
-    }
-
-    protected void init(Context context) {
-        mContext = context;
-        mDevicePrefs = Utilities.getDevicePrefs(mContext);
-        mUserCache = UserCache.INSTANCE.get(mContext);
-
-    }
-    /**
-     * Formats and stores a list of component key in device preferences.
-     */
-    @AnyThread
-    public void cachePredictionComponentKeys(List<ComponentKey> componentKeys) {
-        MODEL_EXECUTOR.execute(() -> {
-            StringBuilder builder = new StringBuilder();
-            int count = Math.min(componentKeys.size(), MAX_CACHE_ITEMS);
-            for (int i = 0; i < count; i++) {
-                builder.append(serializeComponentKeyToString(componentKeys.get(i)));
-                builder.append("\n");
-            }
-            mDevicePrefs.edit().putString(CACHED_ITEMS_KEY, builder.toString()).apply();
-        });
-    }
-
-    /**
-     * parses and returns ComponentKeys saved by
-     * {@link PredictionModel#cachePredictionComponentKeys(List)}
-     */
-    @WorkerThread
-    public List<ComponentKey> getPredictionComponentKeys() {
-        Preconditions.assertWorkerThread();
-        ArrayList<ComponentKey> items = new ArrayList<>();
-        String cachedBlob = mDevicePrefs.getString(CACHED_ITEMS_KEY, "");
-        for (String line : cachedBlob.split("\n")) {
-            ComponentKey key = getComponentKeyFromSerializedString(line);
-            if (key != null) {
-                items.add(key);
-            }
-
-        }
-        return items;
-    }
-
-    private String serializeComponentKeyToString(ComponentKey componentKey) {
-        long userSerialNumber = mUserCache.getSerialNumberForUser(componentKey.user);
-        return componentKey.componentName.flattenToString() + "#" + userSerialNumber;
-    }
-
-    private ComponentKey getComponentKeyFromSerializedString(String str) {
-        int sep = str.indexOf('#');
-        if (sep < 0 || (sep + 1) >= str.length()) {
-            return null;
-        }
-        ComponentName componentName = ComponentName.unflattenFromString(str.substring(0, sep));
-        if (componentName == null) {
-            return null;
-        }
-        try {
-            long serialNumber = Long.parseLong(str.substring(sep + 1));
-            UserHandle userHandle = mUserCache.getUserForSerialNumber(serialNumber);
-            return userHandle != null ? new ComponentKey(componentName, userHandle) : null;
-        } catch (NumberFormatException ex) {
-            return null;
-        }
-    }
-}
diff --git a/src/com/android/launcher3/model/SdCardAvailableReceiver.java b/src/com/android/launcher3/model/SdCardAvailableReceiver.java
index eb3cb52..3798575 100644
--- a/src/com/android/launcher3/model/SdCardAvailableReceiver.java
+++ b/src/com/android/launcher3/model/SdCardAvailableReceiver.java
@@ -24,12 +24,11 @@
 
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel;
-import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.util.PackageUserKey;
 
 import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Map.Entry;
+import java.util.Set;
 
 /**
  * Helper class to re-query app status when SD-card becomes available.
@@ -42,10 +41,9 @@
 
     private final LauncherModel mModel;
     private final Context mContext;
-    private final MultiHashMap<UserHandle, String> mPackages;
+    private final Set<PackageUserKey> mPackages;
 
-    public SdCardAvailableReceiver(LauncherAppState app,
-            MultiHashMap<UserHandle, String> packages) {
+    public SdCardAvailableReceiver(LauncherAppState app, Set<PackageUserKey> packages) {
         mModel = app.getModel();
         mContext = app.getContext();
         mPackages = packages;
@@ -55,19 +53,17 @@
     public void onReceive(Context context, Intent intent) {
         final LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
         final PackageManagerHelper pmHelper = new PackageManagerHelper(context);
-        for (Entry<UserHandle, ArrayList<String>> entry : mPackages.entrySet()) {
-            UserHandle user = entry.getKey();
+        for (PackageUserKey puk : mPackages) {
+            UserHandle user = puk.mUser;
 
             final ArrayList<String> packagesRemoved = new ArrayList<>();
             final ArrayList<String> packagesUnavailable = new ArrayList<>();
 
-            for (String pkg : new HashSet<>(entry.getValue())) {
-                if (!launcherApps.isPackageEnabled(pkg, user)) {
-                    if (pmHelper.isAppOnSdcard(pkg, user)) {
-                        packagesUnavailable.add(pkg);
-                    } else {
-                        packagesRemoved.add(pkg);
-                    }
+            if (!launcherApps.isPackageEnabled(puk.mPackageName, user)) {
+                if (pmHelper.isAppOnSdcard(puk.mPackageName, user)) {
+                    packagesUnavailable.add(puk.mPackageName);
+                } else {
+                    packagesRemoved.add(puk.mPackageName);
                 }
             }
             if (!packagesRemoved.isEmpty()) {
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java
index 1cbe5c2..6fedad1 100644
--- a/src/com/android/launcher3/model/ShortcutsChangedTask.java
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java
@@ -21,16 +21,16 @@
 
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.shortcuts.ShortcutRequest;
 import com.android.launcher3.util.ItemInfoMatcher;
-import com.android.launcher3.util.MultiHashMap;
 
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * Handles changes due to shortcut manager updates (deep shortcut changes)
@@ -54,54 +54,53 @@
     public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
         final Context context = app.getContext();
         // Find WorkspaceItemInfo's that have changed on the workspace.
-        HashSet<ShortcutKey> removedKeys = new HashSet<>();
-        MultiHashMap<ShortcutKey, WorkspaceItemInfo> keyToShortcutInfo = new MultiHashMap<>();
-        HashSet<String> allIds = new HashSet<>();
+        ArrayList<WorkspaceItemInfo> matchingWorkspaceItems = new ArrayList<>();
 
-        for (ItemInfo itemInfo : dataModel.itemsIdMap) {
-            if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
-                WorkspaceItemInfo si = (WorkspaceItemInfo) itemInfo;
-                if (mPackageName.equals(si.getIntent().getPackage()) && si.user.equals(mUser)) {
-                    keyToShortcutInfo.addToList(ShortcutKey.fromItemInfo(si), si);
-                    allIds.add(si.getDeepShortcutId());
+        synchronized (dataModel) {
+            dataModel.forAllWorkspaceItemInfos(mUser, si -> {
+                if ((si.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT)
+                        && mPackageName.equals(si.getIntent().getPackage())) {
+                    matchingWorkspaceItems.add(si);
                 }
-            }
+            });
         }
 
-        final ArrayList<WorkspaceItemInfo> updatedWorkspaceItemInfos = new ArrayList<>();
-        if (!keyToShortcutInfo.isEmpty()) {
+        if (!matchingWorkspaceItems.isEmpty()) {
             // Update the workspace to reflect the changes to updated shortcuts residing on it.
+            List<String> allLauncherKnownIds = matchingWorkspaceItems.stream()
+                    .map(WorkspaceItemInfo::getDeepShortcutId)
+                    .distinct()
+                    .collect(Collectors.toList());
             List<ShortcutInfo> shortcuts = new ShortcutRequest(context, mUser)
-                    .forPackage(mPackageName, new ArrayList<>(allIds))
+                    .forPackage(mPackageName, allLauncherKnownIds)
                     .query(ShortcutRequest.ALL);
+
+            Set<String> nonPinnedIds = new HashSet<>(allLauncherKnownIds);
+            ArrayList<WorkspaceItemInfo> updatedWorkspaceItemInfos = new ArrayList<>();
             for (ShortcutInfo fullDetails : shortcuts) {
-                ShortcutKey key = ShortcutKey.fromInfo(fullDetails);
-                List<WorkspaceItemInfo> workspaceItemInfos = keyToShortcutInfo.remove(key);
                 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.
-                    removedKeys.add(key);
                     continue;
                 }
-                for (final WorkspaceItemInfo workspaceItemInfo : workspaceItemInfos) {
-                    workspaceItemInfo.updateFromDeepShortcutInfo(fullDetails, context);
-                    app.getIconCache().getShortcutIcon(workspaceItemInfo, fullDetails);
-                    updatedWorkspaceItemInfos.add(workspaceItemInfo);
-                }
+
+                String sid = fullDetails.getId();
+                nonPinnedIds.remove(sid);
+                matchingWorkspaceItems
+                        .stream()
+                        .filter(itemInfo -> sid.equals(itemInfo.getDeepShortcutId()))
+                        .forEach(workspaceItemInfo -> {
+                            workspaceItemInfo.updateFromDeepShortcutInfo(fullDetails, context);
+                            app.getIconCache().getShortcutIcon(workspaceItemInfo, fullDetails);
+                            updatedWorkspaceItemInfos.add(workspaceItemInfo);
+                        });
             }
-        }
 
-        // If there are still entries in keyToShortcutInfo, that means that
-        // the corresponding shortcuts weren't passed in onShortcutsChanged(). This
-        // means they were cleared, so we remove and unpin them now.
-        removedKeys.addAll(keyToShortcutInfo.keySet());
-
-        bindUpdatedWorkspaceItems(updatedWorkspaceItemInfos);
-        if (!keyToShortcutInfo.isEmpty()) {
-            deleteAndBindComponentsRemoved(ItemInfoMatcher.ofShortcutKeys(removedKeys));
+            bindUpdatedWorkspaceItems(updatedWorkspaceItemInfos);
+            if (!nonPinnedIds.isEmpty()) {
+                deleteAndBindComponentsRemoved(ItemInfoMatcher.ofShortcutKeys(
+                        nonPinnedIds.stream()
+                                .map(id -> new ShortcutKey(mPackageName, mUser, id))
+                                .collect(Collectors.toSet())));
+            }
         }
 
         if (mUpdateIdMap) {
diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java
index 7ec884f..5048e13 100644
--- a/src/com/android/launcher3/model/UserLockStateChangedTask.java
+++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java
@@ -23,7 +23,6 @@
 
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.shortcuts.ShortcutRequest;
@@ -73,27 +72,27 @@
         ArrayList<WorkspaceItemInfo> updatedWorkspaceItemInfos = new ArrayList<>();
         HashSet<ShortcutKey> removedKeys = new HashSet<>();
 
-        for (ItemInfo itemInfo : dataModel.itemsIdMap) {
-            if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
-                    && mUser.equals(itemInfo.user)) {
-                WorkspaceItemInfo si = (WorkspaceItemInfo) itemInfo;
-                if (mIsUserUnlocked) {
-                    ShortcutKey key = ShortcutKey.fromItemInfo(si);
-                    ShortcutInfo shortcut = pinnedShortcuts.get(key);
-                    // 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) {
-                        removedKeys.add(key);
-                        continue;
+        synchronized (dataModel) {
+            dataModel.forAllWorkspaceItemInfos(mUser, si -> {
+                if (si.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+                    if (mIsUserUnlocked) {
+                        ShortcutKey key = ShortcutKey.fromItemInfo(si);
+                        ShortcutInfo shortcut = pinnedShortcuts.get(key);
+                        // 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) {
+                            removedKeys.add(key);
+                            return;
+                        }
+                        si.runtimeStatusFlags &= ~FLAG_DISABLED_LOCKED_USER;
+                        si.updateFromDeepShortcutInfo(shortcut, context);
+                        app.getIconCache().getShortcutIcon(si, shortcut);
+                    } else {
+                        si.runtimeStatusFlags |= FLAG_DISABLED_LOCKED_USER;
                     }
-                    si.runtimeStatusFlags &= ~FLAG_DISABLED_LOCKED_USER;
-                    si.updateFromDeepShortcutInfo(shortcut, context);
-                    app.getIconCache().getShortcutIcon(si, shortcut);
-                } else {
-                    si.runtimeStatusFlags |= FLAG_DISABLED_LOCKED_USER;
+                    updatedWorkspaceItemInfos.add(si);
                 }
-                updatedWorkspaceItemInfos.add(si);
-            }
+            });
         }
         bindUpdatedWorkspaceItems(updatedWorkspaceItemInfos);
         if (!removedKeys.isEmpty()) {
diff --git a/src/com/android/launcher3/model/data/AppInfo.java b/src/com/android/launcher3/model/data/AppInfo.java
index b17b062..aee1f2a 100644
--- a/src/com/android/launcher3/model/data/AppInfo.java
+++ b/src/com/android/launcher3/model/data/AppInfo.java
@@ -138,8 +138,7 @@
         info.runtimeStatusFlags |= (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0
                 ? FLAG_SYSTEM_NO : FLAG_SYSTEM_YES;
 
-        if (Utilities.ATLEAST_OREO
-                && appInfo.targetSdkVersion >= Build.VERSION_CODES.O
+        if (appInfo.targetSdkVersion >= Build.VERSION_CODES.O
                 && Process.myUserHandle().equals(lai.getUser())) {
             // The icon for a non-primary user is badged, hence it's not exactly an adaptive icon.
             info.runtimeStatusFlags |= FLAG_ADAPTIVE_ICON;
diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java
index 0d3ddad..e03fd72 100644
--- a/src/com/android/launcher3/model/data/ItemInfo.java
+++ b/src/com/android/launcher3/model/data/ItemInfo.java
@@ -53,6 +53,7 @@
 import com.android.launcher3.logger.LauncherAtom.ShortcutsContainer;
 import com.android.launcher3.logger.LauncherAtom.TaskSwitcherContainer;
 import com.android.launcher3.model.ModelWriter;
+import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.util.ContentWriter;
 
 import java.util.Optional;
@@ -257,12 +258,6 @@
     }
 
     /**
-     * Can be overridden by inherited classes to fill in {@link LauncherAtom.ItemInfo}
-     */
-    public void setItemBuilder(LauncherAtom.ItemInfo.Builder builder) {
-    }
-
-    /**
      * Creates {@link LauncherAtom.ItemInfo} with important fields and parent container info.
      */
     public LauncherAtom.ItemInfo buildProto() {
@@ -285,6 +280,13 @@
                                 .orElse(LauncherAtom.Application.newBuilder()));
                 break;
             case ITEM_TYPE_DEEP_SHORTCUT:
+                itemBuilder
+                        .setShortcut(nullableComponent
+                                .map(component -> LauncherAtom.Shortcut.newBuilder()
+                                        .setShortcutName(component.flattenToShortString())
+                                        .setShortcutId(ShortcutKey.fromItemInfo(this).getId()))
+                                .orElse(LauncherAtom.Shortcut.newBuilder()));
+                break;
             case ITEM_TYPE_SHORTCUT:
                 itemBuilder
                         .setShortcut(nullableComponent
diff --git a/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java b/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java
index b0d19a6..c04b7f0 100644
--- a/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java
+++ b/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java
@@ -26,7 +26,6 @@
 import com.android.launcher3.AppWidgetResizeFrame;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.util.ContentWriter;
 
 /**
@@ -195,13 +194,4 @@
     public final boolean hasOptionFlag(int option) {
         return (options & option) != 0;
     }
-
-    @Override
-    public void setItemBuilder(LauncherAtom.ItemInfo.Builder builder) {
-        builder.setWidget(LauncherAtom.Widget.newBuilder()
-                .setSpanX(spanX)
-                .setSpanY(spanY)
-                .setComponentName(providerName.toString())
-                .setPackageName(providerName.getPackageName()));
-    }
 }
diff --git a/src/com/android/launcher3/model/data/WorkspaceItemInfo.java b/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
index a7bf1f3..1e1d093 100644
--- a/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
+++ b/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
@@ -216,7 +216,7 @@
         if (cn == null && (itemType == Favorites.ITEM_TYPE_SHORTCUT || hasStatusFlag(
                 FLAG_SUPPORTS_WEB_UI | FLAG_AUTOINSTALL_ICON | FLAG_RESTORED_ICON))) {
             // Legacy shortcuts and promise icons with web UI may not have a componentName but just
-            // a packageName. In that case create a dummy componentName instead of adding additional
+            // a packageName. In that case create a empty componentName instead of adding additional
             // check everywhere.
             String pkg = intent.getPackage();
             return pkg == null ? null : new ComponentName(pkg, IconCache.EMPTY_CLASS_NAME);
diff --git a/src/com/android/launcher3/notification/NotificationInfo.java b/src/com/android/launcher3/notification/NotificationInfo.java
index 835f72d..80eeb22 100644
--- a/src/com/android/launcher3/notification/NotificationInfo.java
+++ b/src/com/android/launcher3/notification/NotificationInfo.java
@@ -106,7 +106,6 @@
                 view, 0, 0, view.getWidth(), view.getHeight()).toBundle();
         try {
             intent.send(null, 0, null, null, null, null, activityOptions);
-            launcher.getUserEventDispatcher().logNotificationLaunch(view, intent);
             launcher.getStatsLogManager().logger().withItemInfo(mItemInfo)
                     .log(LAUNCHER_NOTIFICATION_LAUNCH_TAP);
         } catch (PendingIntent.CanceledException e) {
diff --git a/src/com/android/launcher3/notification/NotificationKeyData.java b/src/com/android/launcher3/notification/NotificationKeyData.java
index a1917ec..1dda3df 100644
--- a/src/com/android/launcher3/notification/NotificationKeyData.java
+++ b/src/com/android/launcher3/notification/NotificationKeyData.java
@@ -30,9 +30,9 @@
 
 /**
  * The key data associated with the notification, used to determine what to include
- * in dots and dummy popup views before they are populated.
+ * in dots and stub popup views before they are populated.
  *
- * @see NotificationInfo for the full data used when populating the dummy views.
+ * @see NotificationInfo for the full data used when populating the stub views.
  */
 public class NotificationKeyData {
     public final String notificationKey;
diff --git a/src/com/android/launcher3/notification/NotificationMainView.java b/src/com/android/launcher3/notification/NotificationMainView.java
index b03aa9c..32f060b 100644
--- a/src/com/android/launcher3/notification/NotificationMainView.java
+++ b/src/com/android/launcher3/notification/NotificationMainView.java
@@ -137,7 +137,7 @@
             setOnClickListener(mNotificationInfo);
         }
         setContentTranslation(0);
-        // Add a dummy ItemInfo so that logging populates the correct container and item types
+        // Add a stub ItemInfo so that logging populates the correct container and item types
         // instead of DEFAULT_CONTAINERTYPE and DEFAULT_ITEMTYPE, respectively.
         setTag(NOTIFICATION_ITEM_INFO);
         if (animate) {
diff --git a/src/com/android/launcher3/pm/InstallSessionHelper.java b/src/com/android/launcher3/pm/InstallSessionHelper.java
index 901d27f..753a6dd 100644
--- a/src/com/android/launcher3/pm/InstallSessionHelper.java
+++ b/src/com/android/launcher3/pm/InstallSessionHelper.java
@@ -36,6 +36,7 @@
 import com.android.launcher3.SessionCommitReceiver;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.model.ItemInstallQueue;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.LooperExecutor;
@@ -167,7 +168,7 @@
      * Attempt to restore workspace layout if the session is triggered due to device restore.
      */
     public boolean restoreDbIfApplicable(@NonNull final SessionInfo info) {
-        if (!Utilities.ATLEAST_OREO || !FeatureFlags.ENABLE_DATABASE_RESTORE.get()) {
+        if (!FeatureFlags.ENABLE_DATABASE_RESTORE.get()) {
             return false;
         }
         if (isRestore(info)) {
@@ -203,7 +204,7 @@
      * - A promise icon for the session has not already been created
      */
     void tryQueuePromiseAppIcon(PackageInstaller.SessionInfo sessionInfo) {
-        if (Utilities.ATLEAST_OREO && FeatureFlags.PROMISE_APPS_NEW_INSTALLS.get()
+        if (FeatureFlags.PROMISE_APPS_NEW_INSTALLS.get()
                 && SessionCommitReceiver.isEnabled(mAppContext)
                 && verify(sessionInfo) != null
                 && sessionInfo.getInstallReason() == PackageManager.INSTALL_REASON_USER
@@ -212,7 +213,9 @@
                 && !mPromiseIconIds.contains(sessionInfo.getSessionId())
                 && new PackageManagerHelper(mAppContext).getApplicationInfo(
                         sessionInfo.getAppPackageName(), getUserHandle(sessionInfo), 0) == null) {
-            SessionCommitReceiver.queuePromiseAppIconAddition(mAppContext, sessionInfo);
+            ItemInstallQueue.INSTANCE.get(mAppContext)
+                    .queueItem(sessionInfo.getAppPackageName(), getUserHandle(sessionInfo));
+
             mPromiseIconIds.add(sessionInfo.getSessionId());
             updatePromiseIconPrefs();
         }
diff --git a/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java b/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java
index 40d7031..7af14c6 100644
--- a/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java
+++ b/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java
@@ -27,7 +27,6 @@
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.LauncherApps;
 import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.Process;
@@ -39,13 +38,13 @@
 
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.icons.ComponentWithLabelAndIcon;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.util.PackageUserKey;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -175,37 +174,23 @@
         List<ShortcutConfigActivityInfo> result = new ArrayList<>();
         UserHandle myUser = Process.myUserHandle();
 
-        if (Utilities.ATLEAST_OREO) {
-            final List<UserHandle> users;
-            final String packageName;
-            if (packageUser == null) {
-                users = UserCache.INSTANCE.get(context).getUserProfiles();
-                packageName = null;
-            } else {
-                users = new ArrayList<>(1);
-                users.add(packageUser.mUser);
-                packageName = packageUser.mPackageName;
-            }
-            LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
-            for (UserHandle user : users) {
-                boolean ignoreTargetSdk = myUser.equals(user);
-                for (LauncherActivityInfo activityInfo :
-                        launcherApps.getShortcutConfigActivityList(packageName, user)) {
-                    if (ignoreTargetSdk || activityInfo.getApplicationInfo().targetSdkVersion
-                            >= Build.VERSION_CODES.O) {
-                        result.add(new ShortcutConfigActivityInfoVO(activityInfo));
-                    }
-                }
-            }
+        final List<UserHandle> users;
+        final String packageName;
+        if (packageUser == null) {
+            users = UserCache.INSTANCE.get(context).getUserProfiles();
+            packageName = null;
         } else {
-            if (packageUser == null || packageUser.mUser.equals(myUser)) {
-                Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
-                if (packageUser != null) {
-                    intent.setPackage(packageUser.mPackageName);
-                }
-                for (ResolveInfo info :
-                        context.getPackageManager().queryIntentActivities(intent, 0)) {
-                    result.add(new ShortcutConfigActivityInfoVL(info.activityInfo));
+            users = Collections.singletonList(packageUser.mUser);
+            packageName = packageUser.mPackageName;
+        }
+        LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
+        for (UserHandle user : users) {
+            boolean ignoreTargetSdk = myUser.equals(user);
+            for (LauncherActivityInfo activityInfo :
+                    launcherApps.getShortcutConfigActivityList(packageName, user)) {
+                if (ignoreTargetSdk || activityInfo.getApplicationInfo().targetSdkVersion
+                        >= Build.VERSION_CODES.O) {
+                    result.add(new ShortcutConfigActivityInfoVO(activityInfo));
                 }
             }
         }
diff --git a/src/com/android/launcher3/pm/UserCache.java b/src/com/android/launcher3/pm/UserCache.java
index 5aab41a..2d7d6b0 100644
--- a/src/com/android/launcher3/pm/UserCache.java
+++ b/src/com/android/launcher3/pm/UserCache.java
@@ -21,8 +21,10 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.ArrayMap;
+import android.util.Log;
 import android.util.LongSparseArray;
 
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.util.SimpleBroadcastReceiver;
@@ -102,6 +104,9 @@
                 mUsers = null;
                 mUserToSerialMap = null;
             }
+            if (TestProtocol.sDebugTracing) {
+                Log.d(TestProtocol.WORK_PROFILE_REMOVED, "Work profile removed", new Exception());
+            }
         }
     }
 
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 614cf14..26b32b8 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -20,12 +20,9 @@
 import static com.android.launcher3.Utilities.squaredHypot;
 import static com.android.launcher3.Utilities.squaredTouchSlop;
 import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
-import static com.android.launcher3.notification.NotificationMainView.NOTIFICATION_ITEM_INFO;
 import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS;
 import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS_IF_NOTIFICATIONS;
 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;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
 import android.animation.AnimatorSet;
@@ -52,7 +49,6 @@
 import com.android.launcher3.DropTarget.DragObject;
 import com.android.launcher3.Launcher;
 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.dot.DotInfo;
@@ -148,17 +144,6 @@
         return (type & TYPE_ACTION_POPUP) != 0;
     }
 
-    @Override
-    public void logActionCommand(int command) {
-        mLauncher.getUserEventDispatcher().logActionCommand(
-                command, mOriginalIcon, getLogContainerType());
-    }
-
-    @Override
-    public int getLogContainerType() {
-        return ContainerType.DEEPSHORTCUTS;
-    }
-
     public OnClickListener getItemClickListener() {
         return (view) -> {
             mLauncher.getItemOnClickListener().onClick(view);
@@ -400,9 +385,7 @@
         } else if (view instanceof ImageView) {
             // Only the system shortcut icon shows on a gray background header.
             info.setIconAndContentDescriptionFor((ImageView) view);
-            if (Utilities.ATLEAST_OREO) {
-                view.setTooltipText(view.getContentDescription());
-            }
+            view.setTooltipText(view.getContentDescription());
         }
         view.setTag(info);
         view.setOnClickListener(info);
@@ -499,18 +482,6 @@
     }
 
     @Override
-    public void fillInLogContainerData(ItemInfo childInfo, Target child,
-            ArrayList<Target> parents) {
-        if (childInfo == NOTIFICATION_ITEM_INFO) {
-            child.itemType = ItemType.NOTIFICATION;
-        } else {
-            child.itemType = ItemType.DEEPSHORTCUT;
-            child.rank = childInfo.rank;
-        }
-        parents.add(newContainerTarget(ContainerType.DEEPSHORTCUTS));
-    }
-
-    @Override
     protected void onCreateCloseAnimation(AnimatorSet anim) {
         // Animate original icon's text back in.
         anim.play(mOriginalIcon.createTextAlphaAnimator(true /* fadeIn */));
diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java
index 5a5f668..76048ba 100644
--- a/src/com/android/launcher3/popup/PopupDataProvider.java
+++ b/src/com/android/launcher3/popup/PopupDataProvider.java
@@ -38,7 +38,6 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.function.Consumer;
@@ -202,20 +201,11 @@
     }
 
     public List<WidgetItem> getWidgetsForPackageUser(PackageUserKey packageUserKey) {
-        for (WidgetListRowEntry entry : mAllWidgets) {
-            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 null;
+        return mAllWidgets.stream()
+                .filter(row -> row.pkgItem.packageName.equals(packageUserKey.mPackageName))
+                .flatMap(row -> row.widgets.stream())
+                .filter(widget -> packageUserKey.mUser.equals(widget.user))
+                .collect(Collectors.toList());
     }
 
     /**
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index fd292a3..81302ac 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -99,7 +99,7 @@
         final List<WidgetItem> widgets =
                 launcher.getPopupDataProvider().getWidgetsForPackageUser(new PackageUserKey(
                         itemInfo.getTargetComponent().getPackageName(), itemInfo.user));
-        if (widgets == null) {
+        if (widgets.isEmpty()) {
             return null;
         }
         return new Widgets(launcher, itemInfo);
@@ -174,7 +174,7 @@
         public void onClick(View view) {
             Intent intent = new PackageManagerHelper(view.getContext()).getMarketIntent(
                     mItemInfo.getTargetComponent().getPackageName());
-            mTarget.startActivitySafely(view, intent, mItemInfo, null);
+            mTarget.startActivitySafely(view, intent, mItemInfo);
             AbstractFloatingView.closeAllOpenViews(mTarget);
         }
     }
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
index 21ad275..2b04365 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
@@ -15,13 +15,12 @@
  */
 package com.android.launcher3.secondarydisplay;
 
-import static com.android.launcher3.model.AppLaunchTracker.CONTAINER_ALL_APPS;
-
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.app.ActivityOptions;
 import android.content.Intent;
 import android.os.Bundle;
+import android.view.Display;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.ViewAnimationUtils;
@@ -170,7 +169,9 @@
 
     @Override
     public ActivityOptions getActivityLaunchOptions(View v) {
-        return null;
+        final Display display = getWindow().getDecorView().getDisplay();
+        return display != null ? ActivityOptions.makeBasic().setLaunchDisplayId(
+                       display.getDisplayId()) : null;
     }
 
     @Override
@@ -196,9 +197,6 @@
     public void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons) { }
 
     @Override
-    public void bindPredictedItems(List<AppInfo> appInfos, IntArray ranks) { }
-
-    @Override
     public void bindScreens(IntArray orderedScreenIds) { }
 
     @Override
@@ -224,7 +222,7 @@
     }
 
     @Override
-    public void bindWorkspaceItemsChanged(ArrayList<WorkspaceItemInfo> updated) { }
+    public void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) { }
 
     @Override
     public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets) { }
@@ -327,7 +325,7 @@
             if (intent == null) {
                 throw new IllegalArgumentException("Input must have a valid intent");
             }
-            startActivitySafely(v, intent, item, CONTAINER_ALL_APPS);
+            startActivitySafely(v, intent, item);
         }
     }
 }
diff --git a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
index b12d04f..4baecb7 100644
--- a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
+++ b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
@@ -34,17 +34,23 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.provider.Settings;
+import android.text.Editable;
+import android.text.TextWatcher;
 import android.util.ArrayMap;
 import android.util.Pair;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
 import android.view.View;
+import android.widget.EditText;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceCategory;
 import androidx.preference.PreferenceDataStore;
 import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.PreferenceGroup;
 import androidx.preference.PreferenceScreen;
 import androidx.preference.PreferenceViewHolder;
 import androidx.preference.SwitchPreference;
@@ -81,6 +87,7 @@
     private PreferenceCategory mPluginsCategory;
     private FlagTogglerPrefUi mFlagTogglerPrefUi;
 
+
     @Override
     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
         IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
@@ -99,6 +106,50 @@
         maybeAddSandboxCategory();
     }
 
+    private void filterPreferences(String query, PreferenceGroup pg) {
+        int count = pg.getPreferenceCount();
+        int hidden = 0;
+        for (int i = 0; i < count; i++) {
+            Preference preference = pg.getPreference(i);
+            if (preference instanceof PreferenceGroup) {
+                filterPreferences(query, (PreferenceGroup) preference);
+            } else {
+                String title = preference.getTitle().toString().toLowerCase().replace("_", " ");
+                if (query.isEmpty() || title.contains(query)) {
+                    preference.setVisible(true);
+                } else {
+                    preference.setVisible(false);
+                    hidden++;
+                }
+            }
+        }
+        pg.setVisible(hidden != count);
+    }
+
+    @Override
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+        EditText filterBox = view.findViewById(R.id.filter_box);
+        filterBox.setVisibility(View.VISIBLE);
+        filterBox.addTextChangedListener(new TextWatcher() {
+            @Override
+            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+
+            }
+
+            @Override
+            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+
+            }
+
+            @Override
+            public void afterTextChanged(Editable editable) {
+                String query = editable.toString().toLowerCase().replace("_", " ");
+                filterPreferences(query, mPreferenceScreen);
+            }
+        });
+    }
+
     @Override
     public void onDestroy() {
         super.onDestroy();
diff --git a/src/com/android/launcher3/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java
index d3213a1..922425f 100644
--- a/src/com/android/launcher3/settings/SettingsActivity.java
+++ b/src/com/android/launcher3/settings/SettingsActivity.java
@@ -18,7 +18,6 @@
 
 import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS;
 
-import static com.android.launcher3.SessionCommitReceiver.ADD_ICON_PREFERENCE_KEY;
 import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY;
 import static com.android.launcher3.states.RotationHelper.getAllowRotationDefaultValue;
 import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver;
@@ -45,6 +44,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.util.SecureSettingsObserver;
 
@@ -172,8 +172,7 @@
         protected boolean initPreference(Preference preference) {
             switch (preference.getKey()) {
                 case NOTIFICATION_DOTS_PREFERENCE_KEY:
-                    if (!Utilities.ATLEAST_OREO ||
-                            !getResources().getBoolean(R.bool.notification_dots_enabled)) {
+                    if (WidgetsModel.GO_DISABLE_NOTIFICATION_DOTS) {
                         return false;
                     }
 
@@ -188,9 +187,6 @@
                     mNotificationDotsObserver.dispatchOnChange();
                     return true;
 
-                case ADD_ICON_PREFERENCE_KEY:
-                    return Utilities.ATLEAST_OREO;
-
                 case ALLOW_ROTATION_PREFERENCE_KEY:
                     if (getResources().getBoolean(R.bool.allow_rotation)) {
                         // Launcher supports rotation by default. No need to show this setting.
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java b/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java
index 2daa2fe..eb68592 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java
@@ -23,7 +23,6 @@
 import android.graphics.drawable.Drawable;
 import android.text.TextUtils;
 import android.util.AttributeSet;
-import android.view.MotionEvent;
 import android.widget.Toast;
 
 import com.android.launcher3.BubbleTextView;
@@ -106,12 +105,12 @@
     }
 
     @Override
-    public boolean onTouchEvent(MotionEvent ev) {
-        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-            // Show toast if user touches the drag handle (long clicks still start the drag).
-            mShowInstructionToast = mDragHandleBounds.contains((int) ev.getX(), (int) ev.getY());
-        }
-        return super.onTouchEvent(ev);
+    protected boolean shouldIgnoreTouchDown(float x, float y) {
+        // Show toast if user touches the drag handle (long clicks still start the drag).
+        mShowInstructionToast = mDragHandleBounds.contains((int) x, (int) y);
+
+        // assume the whole view as clickable
+        return false;
     }
 
     @Override
diff --git a/src/com/android/launcher3/shortcuts/ShortcutKey.java b/src/com/android/launcher3/shortcuts/ShortcutKey.java
index 3ca9490..0c6d675 100644
--- a/src/com/android/launcher3/shortcuts/ShortcutKey.java
+++ b/src/com/android/launcher3/shortcuts/ShortcutKey.java
@@ -30,6 +30,10 @@
         return componentName.getClassName();
     }
 
+    public String getPackageName() {
+        return componentName.getPackageName();
+    }
+
     /**
      * Creates a {@link ShortcutRequest} for this key
      */
diff --git a/src/com/android/launcher3/statemanager/StateManager.java b/src/com/android/launcher3/statemanager/StateManager.java
index 60b87d9..beb5b68 100644
--- a/src/com/android/launcher3/statemanager/StateManager.java
+++ b/src/com/android/launcher3/statemanager/StateManager.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.statemanager;
 
+import static android.animation.ValueAnimator.areAnimatorsEnabled;
+
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_COMPONENTS;
 
 import android.animation.Animator;
@@ -26,7 +28,6 @@
 import android.os.Looper;
 import android.util.Log;
 
-import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
@@ -179,7 +180,7 @@
 
     private void goToState(STATE_TYPE state, boolean animated, long delay,
             final Runnable onCompleteRunnable) {
-        animated &= Utilities.areAnimationsEnabled(mActivity);
+        animated &= areAnimatorsEnabled();
         if (mActivity.isInState(state)) {
             if (mConfig.currentAnimation == null) {
                 // Run any queued runnable
@@ -311,7 +312,13 @@
                 handler.setStateWithAnimation(state, mConfig, builder);
             }
         }
-        builder.addListener(new AnimationSuccessListener() {
+        builder.addListener(createStateAnimationListener(state));
+        mConfig.setAnimation(builder.buildAnim(), state);
+        return builder;
+    }
+
+    private AnimatorListener createStateAnimationListener(STATE_TYPE state) {
+        return new AnimationSuccessListener() {
 
             @Override
             public void onAnimationStart(Animator animation) {
@@ -326,9 +333,7 @@
                 }
                 onStateTransitionEnd(state);
             }
-        });
-        mConfig.setAnimation(builder.buildAnim(), state);
-        return builder;
+        };
     }
 
     private void onStateTransitionStart(STATE_TYPE state) {
@@ -396,6 +401,19 @@
     }
 
     /**
+     * @see #setCurrentAnimation(AnimatorSet, Animator...). Using this method tells the StateManager
+     * that this is a custom animation to the given state, and thus the StateManager will add an
+     * animation listener to call {@link #onStateTransitionStart} and {@link #onStateTransitionEnd}.
+     * @param anim The custom animation to the given state.
+     * @param toState The state we are animating towards.
+     */
+    public void setCurrentAnimation(AnimatorSet anim, STATE_TYPE toState) {
+        cancelAnimation();
+        setCurrentAnimation(anim);
+        anim.addListener(createStateAnimationListener(toState));
+    }
+
+    /**
      * Sets the animation as the current state animation, i.e., canceled when
      * starting another animation and may block some launcher interactions while running.
      *
diff --git a/src/com/android/launcher3/states/StateAnimationConfig.java b/src/com/android/launcher3/states/StateAnimationConfig.java
index f90ad3c..8b72177 100644
--- a/src/com/android/launcher3/states/StateAnimationConfig.java
+++ b/src/com/android/launcher3/states/StateAnimationConfig.java
@@ -71,6 +71,7 @@
             ANIM_ALL_APPS_HEADER_FADE,
             ANIM_OVERVIEW_MODAL,
             ANIM_DEPTH,
+            ANIM_OVERVIEW_ACTIONS_FADE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface AnimType {}
@@ -89,10 +90,11 @@
     public static final int ANIM_ALL_APPS_HEADER_FADE = 12; // e.g. predictions
     public static final int ANIM_OVERVIEW_MODAL = 13;
     public static final int ANIM_DEPTH = 14;
+    public static final int ANIM_OVERVIEW_ACTIONS_FADE = 15;
 
-    private static final int ANIM_TYPES_COUNT = 15;
+    private static final int ANIM_TYPES_COUNT = 16;
 
-    private final Interpolator[] mInterpolators = new Interpolator[ANIM_TYPES_COUNT];
+    protected final Interpolator[] mInterpolators = new Interpolator[ANIM_TYPES_COUNT];
 
     public StateAnimationConfig() { }
 
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index 38b3712..d4a132e 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -33,6 +33,7 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
 import com.android.launcher3.util.ResourceBasedOverride;
+import com.android.launcher3.widget.WidgetsFullSheet;
 
 import java.util.concurrent.ExecutionException;
 import java.util.function.Function;
@@ -92,6 +93,11 @@
                         l -> l.getAppsView().getActiveRecyclerView().getCurrentScrollY());
             }
 
+            case TestProtocol.REQUEST_WIDGETS_SCROLL_Y: {
+                return getLauncherUIProperty(Bundle::putInt,
+                        l -> WidgetsFullSheet.getWidgetsView(l).getCurrentScrollY());
+            }
+
             case TestProtocol.REQUEST_WINDOW_INSETS: {
                 return getUIProperty(Bundle::putParcelable, a -> {
                     WindowInsets insets = a.getWindow()
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 3ca08fd..30f8fb0 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -27,12 +27,11 @@
     public static final int NORMAL_STATE_ORDINAL = 0;
     public static final int SPRING_LOADED_STATE_ORDINAL = 1;
     public static final int OVERVIEW_STATE_ORDINAL = 2;
-    public static final int OVERVIEW_PEEK_STATE_ORDINAL = 3;
-    public static final int OVERVIEW_MODAL_TASK_STATE_ORDINAL = 4;
-    public static final int QUICK_SWITCH_STATE_ORDINAL = 5;
-    public static final int ALL_APPS_STATE_ORDINAL = 6;
-    public static final int BACKGROUND_APP_STATE_ORDINAL = 7;
-    public static final int HINT_STATE_ORDINAL = 8;
+    public static final int OVERVIEW_MODAL_TASK_STATE_ORDINAL = 3;
+    public static final int QUICK_SWITCH_STATE_ORDINAL = 4;
+    public static final int ALL_APPS_STATE_ORDINAL = 5;
+    public static final int BACKGROUND_APP_STATE_ORDINAL = 6;
+    public static final int HINT_STATE_ORDINAL = 7;
     public static final String TAPL_EVENTS_TAG = "TaplEvents";
     public static final String SEQUENCE_MAIN = "Main";
     public static final String SEQUENCE_TIS = "TIS";
@@ -46,8 +45,6 @@
                 return "SpringLoaded";
             case OVERVIEW_STATE_ORDINAL:
                 return "Overview";
-            case OVERVIEW_PEEK_STATE_ORDINAL:
-                return "OverviewPeek";
             case OVERVIEW_MODAL_TASK_STATE_ORDINAL:
                 return "OverviewModal";
             case QUICK_SWITCH_STATE_ORDINAL:
@@ -81,6 +78,7 @@
     public static final String REQUEST_UNFREEZE_APP_LIST = "unfreeze-app-list";
     public static final String REQUEST_APP_LIST_FREEZE_FLAGS = "app-list-freeze-flags";
     public static final String REQUEST_APPS_LIST_SCROLL_Y = "apps-list-scroll-y";
+    public static final String REQUEST_WIDGETS_SCROLL_Y = "widgets-scroll-y";
     public static final String REQUEST_WINDOW_INSETS = "window-insets";
     public static final String REQUEST_PID = "pid";
     public static final String REQUEST_TOTAL_PSS_KB = "total_pss";
@@ -97,7 +95,7 @@
     public static final String REQUEST_ENABLE_DEBUG_TRACING = "enable-debug-tracing";
     public static final String REQUEST_DISABLE_DEBUG_TRACING = "disable-debug-tracing";
 
-    public static final String REQUEST_OVERVIEW_ACTIONS_ENABLED = "overview-actions-enabled";
+    public static final String REQUEST_OVERVIEW_SHARE_ENABLED = "overview-share-enabled";
 
     public static boolean sDisableSensorRotation;
     public static final String REQUEST_MOCK_SENSOR_ROTATION = "mock-sensor-rotation";
@@ -106,4 +104,5 @@
     public static final String PAUSE_NOT_DETECTED = "b/139891609";
     public static final String OVERIEW_NOT_ALLAPPS = "b/156095088";
     public static final String NO_SWIPE_TO_HOME = "b/158017601";
+    public static final String WORK_PROFILE_REMOVED = "b/159671700";
 }
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index 3c78b08..8ee5a6e 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -26,7 +26,7 @@
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_COMPONENTS;
 import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_SCALE;
 import static com.android.launcher3.states.StateAnimationConfig.PLAY_NON_ATOMIC;
-import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
+import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -37,6 +37,8 @@
 import android.view.HapticFeedbackConstants;
 import android.view.MotionEvent;
 
+import androidx.core.os.BuildCompat;
+
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.LauncherState;
@@ -44,6 +46,7 @@
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.states.StateAnimationConfig;
@@ -265,8 +268,10 @@
         mCanBlockFling = mFromState == NORMAL;
         mFlingBlockCheck.unblockFling();
         // Must be called after all the animation controllers have been paused
-        if (mToState == ALL_APPS || mToState == NORMAL) {
-            mLauncher.getAllAppsController().onDragStart(mToState == ALL_APPS);
+        if (FeatureFlags.ENABLE_DEVICE_SEARCH.get() && BuildCompat.isAtLeastR()
+                && (mToState == ALL_APPS || mToState == NORMAL)) {
+            mLauncher.getAllAppsController().getInsetController().onDragStart(
+                    mToState == ALL_APPS ? 0 : 1);
         }
     }
 
@@ -508,15 +513,7 @@
         if (mAtomicAnim == null) {
             return 0;
         }
-        if (Utilities.ATLEAST_OREO) {
-            return mAtomicAnim.getTotalDuration() - mAtomicAnim.getCurrentPlayTime();
-        } else {
-            long remainingDuration = 0;
-            for (Animator anim : mAtomicAnim.getChildAnimations()) {
-                remainingDuration = Math.max(remainingDuration, anim.getDuration());
-            }
-            return remainingDuration;
-        }
+        return mAtomicAnim.getTotalDuration() - mAtomicAnim.getCurrentPlayTime();
     }
 
     protected void updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration,
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index 8486666..61d6f7d 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -18,7 +18,6 @@
 import static com.android.launcher3.Launcher.REQUEST_BIND_PENDING_APPWIDGET;
 import static com.android.launcher3.Launcher.REQUEST_RECONFIGURE_APPWIDGET;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_OPEN;
-import static com.android.launcher3.model.AppLaunchTracker.CONTAINER_ALL_APPS;
 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_BY_PUBLISHER;
 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_QUIET_USER;
@@ -37,8 +36,6 @@
 import android.view.View.OnClickListener;
 import android.widget.Toast;
 
-import androidx.annotation.Nullable;
-
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
@@ -72,13 +69,9 @@
     /**
      * Instance used for click handling on items
      */
-    public static final OnClickListener INSTANCE = getInstance(null);
+    public static final OnClickListener INSTANCE = ItemClickHandler::onClick;
 
-    public static final OnClickListener getInstance(String sourceContainer) {
-        return v -> onClick(v, sourceContainer);
-    }
-
-    private static void onClick(View v, String sourceContainer) {
+    private static void onClick(View v) {
         // Make sure that rogue clicks don't get through while allapps is launching, or after the
         // view has detached (it's possible for this to happen if the view is removed mid touch).
         if (v.getWindowToken() == null) return;
@@ -88,14 +81,14 @@
 
         Object tag = v.getTag();
         if (tag instanceof WorkspaceItemInfo) {
-            onClickAppShortcut(v, (WorkspaceItemInfo) tag, launcher, sourceContainer);
+            onClickAppShortcut(v, (WorkspaceItemInfo) tag, launcher);
         } else if (tag instanceof FolderInfo) {
             if (v instanceof FolderIcon) {
                 onClickFolderIcon(v);
             }
         } else if (tag instanceof AppInfo) {
-            startAppShortcutOrInfoActivity(v, (AppInfo) tag, launcher,
-                    sourceContainer == null ? CONTAINER_ALL_APPS: sourceContainer);
+            startAppShortcutOrInfoActivity(v, (AppInfo) tag, launcher
+            );
         } else if (tag instanceof LauncherAppWidgetInfo) {
             if (v instanceof PendingAppWidgetHostView) {
                 onClickPendingWidget((PendingAppWidgetHostView) v, launcher);
@@ -191,7 +184,7 @@
 
         // Fallback to using custom market intent.
         Intent intent = new PackageManagerHelper(launcher).getMarketIntent(packageName);
-        launcher.startActivitySafely(v, intent, item, null);
+        launcher.startActivitySafely(v, intent, item);
     }
 
     /**
@@ -199,8 +192,7 @@
      *
      * @param v The view that was clicked. Must be a tagged with a {@link WorkspaceItemInfo}.
      */
-    public static void onClickAppShortcut(View v, WorkspaceItemInfo shortcut, Launcher launcher,
-            @Nullable String sourceContainer) {
+    public static void onClickAppShortcut(View v, WorkspaceItemInfo shortcut, Launcher launcher) {
         if (shortcut.isDisabled()) {
             final int disabledFlags = shortcut.runtimeStatusFlags
                     & WorkspaceItemInfo.FLAG_DISABLED_MASK;
@@ -241,11 +233,10 @@
         }
 
         // Start activities
-        startAppShortcutOrInfoActivity(v, shortcut, launcher, sourceContainer);
+        startAppShortcutOrInfoActivity(v, shortcut, launcher);
     }
 
-    private static void startAppShortcutOrInfoActivity(View v, ItemInfo item, Launcher launcher,
-            @Nullable String sourceContainer) {
+    private static void startAppShortcutOrInfoActivity(View v, ItemInfo item, Launcher launcher) {
         TestLogging.recordEvent(
                 TestProtocol.SEQUENCE_MAIN, "start: startAppShortcutOrInfoActivity");
         Intent intent;
@@ -274,6 +265,6 @@
             // Preload the icon to reduce latency b/w swapping the floating view with the original.
             FloatingIconView.fetchIcon(launcher, v, item, true /* isOpening */);
         }
-        launcher.startActivitySafely(v, intent, item, sourceContainer);
+        launcher.startActivitySafely(v, intent, item);
     }
 }
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index 48c7734..fb02f79 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -17,6 +17,7 @@
 package com.android.launcher3.touch;
 
 import static android.widget.ListPopupWindow.WRAP_CONTENT;
+
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
 import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL;
@@ -33,6 +34,7 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.LinearLayout;
 
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.PagedView;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.OverScroller;
@@ -70,6 +72,7 @@
         out.halfPageSize = view.getNormalChildHeight() / 2;
         out.halfScreenSize = view.getMeasuredHeight() / 2;
         out.screenCenter = insets.top + view.getPaddingTop() + out.scroll + out.halfPageSize;
+        out.pageParentScale = view.getScaleY();
     }
 
     @Override
@@ -105,6 +108,11 @@
     }
 
     @Override
+    public <T> void setSecondary(T target, Float2DAction<T> action, float param) {
+        action.call(target, param, 0);
+    }
+
+    @Override
     public float getPrimaryDirection(MotionEvent event, int pointerIndex) {
         return event.getY(pointerIndex);
     }
@@ -145,8 +153,9 @@
     }
 
     @Override
-    public void setPrimaryAndResetSecondaryTranslate(View view, float translation) {
-        view.setTranslationX(0);
+    public void setPrimaryAndResetSecondaryTranslate(
+            View view, float translation, float defaultTranslationX, float defaultTranslationY) {
+        view.setTranslationX(defaultTranslationX);
         view.setTranslationY(translation);
     }
 
@@ -212,7 +221,11 @@
     }
 
     @Override
-    public int getTaskDismissDirectionFactor() {
+    public int getPrimaryTranslationDirectionFactor() {
+        return -1;
+    }
+
+    public int getSecondaryTranslationDirectionFactor() {
         return 1;
     }
 
@@ -237,7 +250,8 @@
     }
 
     @Override
-    public int getTaskMenuLayoutOrientation(LinearLayout taskMenuLayout) {
+    public int getTaskMenuLayoutOrientation(boolean canRecentsActivityRotate,
+        LinearLayout taskMenuLayout) {
         return LinearLayout.HORIZONTAL;
     }
 
@@ -260,4 +274,10 @@
         }
         return new ChildBounds(childHeight, childWidth, childBottom, childLeft);
     }
+
+    @SuppressWarnings("SuspiciousNameCombination")
+    @Override
+    public int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect) {
+        return rect.left;
+    }
 }
diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java
index 65b1a7a..354d78d 100644
--- a/src/com/android/launcher3/touch/PagedOrientationHandler.java
+++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java
@@ -57,6 +57,7 @@
 
     <T> void set(T target, Int2DAction<T> action, int param);
     <T> void set(T target, Float2DAction<T> action, float param);
+    <T> void setSecondary(T target, Float2DAction<T> action, float param);
     float getPrimaryDirection(MotionEvent event, int pointerIndex);
     float getPrimaryVelocity(VelocityTracker velocityTracker, int pointerId);
     int getMeasuredSize(View view);
@@ -65,7 +66,8 @@
     int getSecondaryDimension(View view);
     FloatProperty<View> getPrimaryViewTranslate();
     FloatProperty<View> getSecondaryViewTranslate();
-    void setPrimaryAndResetSecondaryTranslate(View view, float translation);
+    void setPrimaryAndResetSecondaryTranslate(
+            View view, float translation, float defaultTranslationX, float defaultTranslationY);
     int getPrimaryScroll(View view);
     float getPrimaryScale(View view);
     int getChildStart(View view);
@@ -74,7 +76,8 @@
     int getScrollOffsetStart(View view, Rect insets);
     int getScrollOffsetEnd(View view, Rect insets);
     SingleAxisSwipeDetector.Direction getOppositeSwipeDirection();
-    int getTaskDismissDirectionFactor();
+    int getPrimaryTranslationDirectionFactor();
+    int getSecondaryTranslationDirectionFactor();
     int getTaskDragDisplacementFactor(boolean isRtl);
     ChildBounds getChildBounds(View child, int childStart, int pageCenter, boolean layoutChild);
     void setMaxScroll(AccessibilityEvent event, int maxScroll);
@@ -94,8 +97,9 @@
     float getTaskMenuX(float x, View thumbnailView);
     float getTaskMenuY(float y, View thumbnailView);
     int getTaskMenuWidth(View view);
-    int getTaskMenuLayoutOrientation(LinearLayout taskMenuLayout);
+    int getTaskMenuLayoutOrientation(boolean canRecentsActivityRotate, LinearLayout taskMenuLayout);
     void setLayoutParamsForTaskMenuOptionItem(LinearLayout.LayoutParams lp);
+    int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect);
 
     /**
      * Maps the velocity from the coordinate plane of the foreground app to that
@@ -108,6 +112,7 @@
         public int halfPageSize;
         public int screenCenter;
         public int halfScreenSize;
+        public float pageParentScale;
     }
 
     class ChildBounds {
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index 79e5c87..06479e6 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -32,6 +32,7 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.LinearLayout;
 
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.PagedView;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.OverScroller;
@@ -69,6 +70,7 @@
         out.halfPageSize = view.getNormalChildWidth() / 2;
         out.halfScreenSize = view.getMeasuredWidth() / 2;
         out.screenCenter = insets.left + view.getPaddingLeft() + out.scroll + out.halfPageSize;
+        out.pageParentScale = view.getScaleX();
     }
 
     @Override
@@ -103,6 +105,11 @@
     }
 
     @Override
+    public <T> void setSecondary(T target, Float2DAction<T> action, float param) {
+        action.call(target, 0, param);
+    }
+
+    @Override
     public float getPrimaryDirection(MotionEvent event, int pointerIndex) {
         return event.getX(pointerIndex);
     }
@@ -143,9 +150,10 @@
     }
 
     @Override
-    public void setPrimaryAndResetSecondaryTranslate(View view, float translation) {
+    public void setPrimaryAndResetSecondaryTranslate(
+            View view, float translation, float defaultTranslationX, float defaultTranslationY) {
         view.setTranslationX(translation);
-        view.setTranslationY(0);
+        view.setTranslationY(defaultTranslationY);
     }
 
     @Override
@@ -210,7 +218,11 @@
     }
 
     @Override
-    public int getTaskDismissDirectionFactor() {
+    public int getPrimaryTranslationDirectionFactor() {
+        return 1;
+    }
+
+    public int getSecondaryTranslationDirectionFactor() {
         return -1;
     }
 
@@ -236,8 +248,9 @@
     }
 
     @Override
-    public int getTaskMenuLayoutOrientation(LinearLayout taskMenuLayout) {
-        return taskMenuLayout.getOrientation();
+    public int getTaskMenuLayoutOrientation(boolean canRecentsActivityRotate,
+        LinearLayout taskMenuLayout) {
+        return canRecentsActivityRotate ? taskMenuLayout.getOrientation() : LinearLayout.VERTICAL;
     }
 
     @Override
@@ -257,4 +270,9 @@
         }
         return new ChildBounds(childWidth, childHeight, childRight, childTop);
     }
+
+    @Override
+    public int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect) {
+        return dp.heightPx - rect.bottom;
+    }
 }
diff --git a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
index d5ae2dc..60d19d9 100644
--- a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
@@ -18,15 +18,17 @@
 
 import android.content.res.Resources;
 import android.graphics.PointF;
+import android.graphics.Rect;
 import android.view.Surface;
 import android.view.View;
 
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Utilities;
 
 public class SeascapePagedViewHandler extends LandscapePagedViewHandler {
 
     @Override
-    public int getTaskDismissDirectionFactor() {
+    public int getSecondaryTranslationDirectionFactor() {
         return -1;
     }
 
@@ -73,8 +75,7 @@
     }
 
     @Override
-    public void setPrimaryAndResetSecondaryTranslate(View view, float translation) {
-        view.setTranslationX(0);
-        view.setTranslationY(translation);
+    public int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect) {
+        return dp.widthPx - rect.right;
     }
 }
diff --git a/src/com/android/launcher3/util/ConfigMonitor.java b/src/com/android/launcher3/util/ConfigMonitor.java
index 0f81520..b3b69f6 100644
--- a/src/com/android/launcher3/util/ConfigMonitor.java
+++ b/src/com/android/launcher3/util/ConfigMonitor.java
@@ -26,14 +26,16 @@
 import android.graphics.Point;
 import android.util.Log;
 
+import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
+import com.android.launcher3.util.DisplayController.Info;
+
 import java.util.function.Consumer;
 
 /**
  * {@link BroadcastReceiver} which watches configuration changes and
  * notifies the callback in case changes which affect the device profile occur.
  */
-public class ConfigMonitor extends BroadcastReceiver implements
-        DefaultDisplay.DisplayInfoChangeListener {
+public class ConfigMonitor extends BroadcastReceiver implements DisplayInfoChangeListener {
 
     private static final String TAG = "ConfigMonitor";
 
@@ -57,9 +59,9 @@
         mFontScale = config.fontScale;
         mDensity = config.densityDpi;
 
-        DefaultDisplay display = DefaultDisplay.INSTANCE.get(context);
+        DisplayController.DisplayHolder display = DisplayController.getDefaultDisplay(context);
         display.addChangeListener(this);
-        DefaultDisplay.Info displayInfo = display.getInfo();
+        Info displayInfo = display.getInfo();
         mDisplayId = displayInfo.id;
 
         mRealSize = new Point(displayInfo.realSize);
@@ -82,7 +84,7 @@
     }
 
     @Override
-    public void onDisplayInfoChanged(DefaultDisplay.Info info, int flags) {
+    public void onDisplayInfoChanged(Info info, int flags) {
         if (info.id != mDisplayId) {
             return;
         }
@@ -113,8 +115,7 @@
     public void unregister() {
         try {
             mContext.unregisterReceiver(this);
-            DefaultDisplay display = DefaultDisplay.INSTANCE.get(mContext);
-            display.removeChangeListener(this);
+            DisplayController.getDefaultDisplay(mContext).removeChangeListener(this);
         } catch (Exception e) {
             Log.e(TAG, "Failed to unregister config monitor", e);
         }
diff --git a/src/com/android/launcher3/util/DefaultDisplay.java b/src/com/android/launcher3/util/DefaultDisplay.java
deleted file mode 100644
index 35788a5..0000000
--- a/src/com/android/launcher3/util/DefaultDisplay.java
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- * Copyright (C) 2019 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 static android.view.Display.DEFAULT_DISPLAY;
-
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-
-import android.content.Context;
-import android.graphics.Point;
-import android.hardware.display.DisplayManager;
-import android.hardware.display.DisplayManager.DisplayListener;
-import android.os.Handler;
-import android.os.Message;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.Display;
-
-import androidx.annotation.VisibleForTesting;
-
-import java.util.ArrayList;
-
-/**
- * Utility class to cache properties of default display to avoid a system RPC on every call.
- */
-public class DefaultDisplay implements DisplayListener {
-
-    public static final MainThreadInitializedObject<DefaultDisplay> INSTANCE =
-            new MainThreadInitializedObject<>(DefaultDisplay::new);
-
-    private static final String TAG = "DefaultDisplay";
-
-    public static final int CHANGE_SIZE = 1 << 0;
-    public static final int CHANGE_ROTATION = 1 << 1;
-    public static final int CHANGE_FRAME_DELAY = 1 << 2;
-
-    public static final int CHANGE_ALL = CHANGE_SIZE | CHANGE_ROTATION | CHANGE_FRAME_DELAY;
-
-    private final Context mDisplayContext;
-    private final int mId;
-    private final ArrayList<DisplayInfoChangeListener> mListeners = new ArrayList<>();
-    private final Handler mChangeHandler;
-    private Info mInfo;
-
-    private DefaultDisplay(Context context) {
-        DisplayManager dm = context.getSystemService(DisplayManager.class);
-        // Use application context to create display context so that it can have its own Resources.
-        mDisplayContext = context.getApplicationContext().createDisplayContext(
-                dm.getDisplay(DEFAULT_DISPLAY));
-        // Note that the Display object must be obtained from DisplayManager which is associated to
-        // the display context, so the Display is isolated from Activity and Application to provide
-        // the actual state of device that excludes the additional adjustment and override.
-        mInfo = new Info(mDisplayContext);
-        mId = mInfo.id;
-        mChangeHandler = new Handler(this::onChange);
-
-        dm.registerDisplayListener(this, UI_HELPER_EXECUTOR.getHandler());
-    }
-
-    @Override
-    public final void onDisplayAdded(int displayId) {  }
-
-    @Override
-    public final void onDisplayRemoved(int displayId) { }
-
-    @Override
-    public final void onDisplayChanged(int displayId) {
-        if (displayId != mId) {
-            return;
-        }
-
-        Info oldInfo = mInfo;
-        Info info = new Info(mDisplayContext);
-
-        int change = 0;
-        if (info.hasDifferentSize(oldInfo)) {
-            change |= CHANGE_SIZE;
-        }
-        if (oldInfo.rotation != info.rotation) {
-            change |= CHANGE_ROTATION;
-        }
-        if (info.singleFrameMs != oldInfo.singleFrameMs) {
-            change |= CHANGE_FRAME_DELAY;
-        }
-
-        if (change != 0) {
-            mInfo = info;
-            mChangeHandler.sendEmptyMessage(change);
-        }
-    }
-
-    public static int getSingleFrameMs(Context context) {
-        return INSTANCE.get(context).getInfo().singleFrameMs;
-    }
-
-    public Info getInfo() {
-        return mInfo;
-    }
-
-    public void addChangeListener(DisplayInfoChangeListener listener) {
-        mListeners.add(listener);
-    }
-
-    public void removeChangeListener(DisplayInfoChangeListener listener) {
-        mListeners.remove(listener);
-    }
-
-    private boolean onChange(Message msg) {
-        for (int i = mListeners.size() - 1; i >= 0; i--) {
-            mListeners.get(i).onDisplayInfoChanged(mInfo, msg.what);
-        }
-        return true;
-    }
-
-    public static class Info {
-
-        public final int id;
-        public final int rotation;
-        public final int singleFrameMs;
-
-        public final Point realSize;
-        public final Point smallestSize;
-        public final Point largestSize;
-
-        public final DisplayMetrics metrics;
-
-        @VisibleForTesting
-        public Info(int id, int rotation, int singleFrameMs, Point realSize, Point smallestSize,
-                Point largestSize, DisplayMetrics metrics) {
-            this.id = id;
-            this.rotation = rotation;
-            this.singleFrameMs = singleFrameMs;
-            this.realSize = realSize;
-            this.smallestSize = smallestSize;
-            this.largestSize = largestSize;
-            this.metrics = metrics;
-        }
-
-        private Info(Context context) {
-            this(context, context.getSystemService(DisplayManager.class)
-                    .getDisplay(DEFAULT_DISPLAY));
-        }
-
-        public Info(Context context, Display display) {
-            id = display.getDisplayId();
-            rotation = display.getRotation();
-
-            float refreshRate = display.getRefreshRate();
-            singleFrameMs = refreshRate > 0 ? (int) (1000 / refreshRate) : 16;
-
-            realSize = new Point();
-            smallestSize = new Point();
-            largestSize = new Point();
-            display.getRealSize(realSize);
-            display.getCurrentSizeRange(smallestSize, largestSize);
-
-            metrics = context.getResources().getDisplayMetrics();
-        }
-
-        private boolean hasDifferentSize(Info info) {
-            if (!realSize.equals(info.realSize)
-                    && !realSize.equals(info.realSize.y, info.realSize.x)) {
-                Log.d(TAG, String.format("Display size changed from %s to %s",
-                        info.realSize, realSize));
-                return true;
-            }
-
-            if (!smallestSize.equals(info.smallestSize) || !largestSize.equals(info.largestSize)) {
-                Log.d(TAG, String.format("Available size changed from [%s, %s] to [%s, %s]",
-                        smallestSize, largestSize, info.smallestSize, info.largestSize));
-                return true;
-            }
-
-            return false;
-        }
-    }
-
-    /**
-     * Interface for listening for display changes
-     */
-    public interface DisplayInfoChangeListener {
-
-        void onDisplayInfoChanged(Info info, int flags);
-    }
-}
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
new file mode 100644
index 0000000..355c949
--- /dev/null
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2019 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 static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
+import android.content.Context;
+import android.graphics.Point;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManager.DisplayListener;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.Display;
+
+import androidx.annotation.VisibleForTesting;
+
+import java.util.ArrayList;
+
+/**
+ * Utility class to cache properties of default display to avoid a system RPC on every call.
+ */
+public class DisplayController implements DisplayListener {
+
+    private static final String TAG = "DisplayController";
+
+    public static final MainThreadInitializedObject<DisplayController> INSTANCE =
+            new MainThreadInitializedObject<>(DisplayController::new);
+
+    private final SparseArray<DisplayHolder> mOtherDisplays = new SparseArray<>(0);
+    // We store the default display separately, to avoid null checks for primary use case.
+    private final DisplayHolder mDefaultDisplay;
+
+    private final ArrayList<DisplayListChangeListener> mListListeners = new ArrayList<>();
+
+    private DisplayController(Context context) {
+        mDefaultDisplay = DisplayHolder.create(context, DEFAULT_DISPLAY);
+
+        DisplayManager dm = context.getSystemService(DisplayManager.class);
+        dm.registerDisplayListener(this, UI_HELPER_EXECUTOR.getHandler());
+    }
+
+    @Override
+    public final void onDisplayAdded(int displayId) {
+        DisplayHolder holder = DisplayHolder.create(mDefaultDisplay.mDisplayContext, displayId);
+        if (holder == null) {
+            // Display is already removed by the time we dot this.
+            return;
+        }
+        synchronized (mOtherDisplays) {
+            mOtherDisplays.put(displayId, holder);
+        }
+        MAIN_EXECUTOR.execute(() -> mListListeners.forEach(l-> l.onDisplayAdded(holder)));
+    }
+
+    @Override
+    public final void onDisplayRemoved(int displayId) {
+        synchronized (mOtherDisplays) {
+            mOtherDisplays.remove(displayId);
+        }
+        MAIN_EXECUTOR.execute(() -> mListListeners.forEach(l-> l.onDisplayRemoved(displayId)));
+    }
+
+    /**
+     * Returns the holder corresponding to the given display
+     */
+    public DisplayHolder getHolder(int displayId) {
+        if (displayId == mDefaultDisplay.mId) {
+            return mDefaultDisplay;
+        } else {
+            synchronized (mOtherDisplays) {
+                return mOtherDisplays.get(displayId);
+            }
+        }
+    }
+
+    /**
+     * Adds a listener for display list changes
+     */
+    public void addListChangeListener(DisplayListChangeListener listener) {
+        mListListeners.add(listener);
+    }
+
+    /**
+     * Removes a previously added display list change listener
+     */
+    public void removeListChangeListener(DisplayListChangeListener listener) {
+        mListListeners.remove(listener);
+    }
+
+    @Override
+    public final void onDisplayChanged(int displayId) {
+        DisplayHolder holder = getHolder(displayId);
+        if (holder != null) {
+            holder.handleOnChange();
+        }
+    }
+
+    public static int getSingleFrameMs(Context context) {
+        return getDefaultDisplay(context).getInfo().singleFrameMs;
+    }
+
+    public static DisplayHolder getDefaultDisplay(Context context) {
+        return INSTANCE.get(context).mDefaultDisplay;
+    }
+
+    /**
+     * A listener to receiving addition or removal of new displays
+     */
+    public interface DisplayListChangeListener {
+
+        /**
+         * Called when a new display is added
+         */
+        void onDisplayAdded(DisplayHolder holder);
+
+        /**
+         * Called when a previously added display is removed
+         */
+        void onDisplayRemoved(int displayId);
+    }
+
+    /**
+     * Interface for listening for display changes
+     */
+    public interface DisplayInfoChangeListener {
+
+        void onDisplayInfoChanged(Info info, int flags);
+    }
+
+    public static class DisplayHolder {
+
+        public static final int CHANGE_SIZE = 1 << 0;
+        public static final int CHANGE_ROTATION = 1 << 1;
+        public static final int CHANGE_FRAME_DELAY = 1 << 2;
+
+        public static final int CHANGE_ALL = CHANGE_SIZE | CHANGE_ROTATION | CHANGE_FRAME_DELAY;
+
+        final Context mDisplayContext;
+        final int mId;
+        private final ArrayList<DisplayInfoChangeListener> mListeners = new ArrayList<>();
+        private DisplayController.Info mInfo;
+
+        private DisplayHolder(Context displayContext) {
+            mDisplayContext = displayContext;
+            // Note that the Display object must be obtained from DisplayManager which is
+            // associated to the display context, so the Display is isolated from Activity and
+            // Application to provide the actual state of device that excludes the additional
+            // adjustment and override.
+            mInfo = new DisplayController.Info(mDisplayContext);
+            mId = mInfo.id;
+        }
+
+        public void addChangeListener(DisplayInfoChangeListener listener) {
+            mListeners.add(listener);
+        }
+
+        public void removeChangeListener(DisplayInfoChangeListener listener) {
+            mListeners.remove(listener);
+        }
+
+        public DisplayController.Info getInfo() {
+            return mInfo;
+        }
+
+        protected void handleOnChange() {
+            Info oldInfo = mInfo;
+            Info info = new Info(mDisplayContext);
+
+            int change = 0;
+            if (info.hasDifferentSize(oldInfo)) {
+                change |= CHANGE_SIZE;
+            }
+            if (oldInfo.rotation != info.rotation) {
+                change |= CHANGE_ROTATION;
+            }
+            if (info.singleFrameMs != oldInfo.singleFrameMs) {
+                change |= CHANGE_FRAME_DELAY;
+            }
+
+            if (change != 0) {
+                mInfo = info;
+                final int flags = change;
+                MAIN_EXECUTOR.execute(() -> notifyChange(flags));
+            }
+        }
+
+        private void notifyChange(int flags) {
+            for (int i = mListeners.size() - 1; i >= 0; i--) {
+                mListeners.get(i).onDisplayInfoChanged(mInfo, flags);
+            }
+        }
+
+        private static DisplayHolder create(Context context, int id) {
+            DisplayManager dm = context.getSystemService(DisplayManager.class);
+            Display display = dm.getDisplay(id);
+            if (display == null) {
+                return null;
+            }
+            // Use application context to create display context so that it can have its own
+            // Resources.
+            Context displayContext = context.getApplicationContext().createDisplayContext(display);
+            return new DisplayHolder(displayContext);
+        }
+    }
+
+    public static class Info {
+
+        public final int id;
+        public final int rotation;
+        public final int singleFrameMs;
+
+        public final Point realSize;
+        public final Point smallestSize;
+        public final Point largestSize;
+
+        public final DisplayMetrics metrics;
+
+        @VisibleForTesting
+        public Info(int id, int rotation, int singleFrameMs, Point realSize, Point smallestSize,
+                Point largestSize, DisplayMetrics metrics) {
+            this.id = id;
+            this.rotation = rotation;
+            this.singleFrameMs = singleFrameMs;
+            this.realSize = realSize;
+            this.smallestSize = smallestSize;
+            this.largestSize = largestSize;
+            this.metrics = metrics;
+        }
+
+        private Info(Context context) {
+            this(context, context.getSystemService(DisplayManager.class)
+                    .getDisplay(DEFAULT_DISPLAY));
+        }
+
+        public Info(Context context, Display display) {
+            id = display.getDisplayId();
+            rotation = display.getRotation();
+
+            float refreshRate = display.getRefreshRate();
+            singleFrameMs = refreshRate > 0 ? (int) (1000 / refreshRate) : 16;
+
+            realSize = new Point();
+            smallestSize = new Point();
+            largestSize = new Point();
+            display.getRealSize(realSize);
+            display.getCurrentSizeRange(smallestSize, largestSize);
+
+            metrics = context.getResources().getDisplayMetrics();
+        }
+
+        private boolean hasDifferentSize(Info info) {
+            if (!realSize.equals(info.realSize)
+                    && !realSize.equals(info.realSize.y, info.realSize.x)) {
+                Log.d(TAG, String.format("Display size changed from %s to %s",
+                        info.realSize, realSize));
+                return true;
+            }
+
+            if (!smallestSize.equals(info.smallestSize) || !largestSize.equals(info.largestSize)) {
+                Log.d(TAG, String.format("Available size changed from [%s, %s] to [%s, %s]",
+                        smallestSize, largestSize, info.smallestSize, info.largestSize));
+                return true;
+            }
+
+            return false;
+        }
+    }
+}
diff --git a/src/com/android/launcher3/util/IOUtils.java b/src/com/android/launcher3/util/IOUtils.java
index fcb96d7..1cec0ec 100644
--- a/src/com/android/launcher3/util/IOUtils.java
+++ b/src/com/android/launcher3/util/IOUtils.java
@@ -16,20 +16,19 @@
 
 package com.android.launcher3.util;
 
-import android.content.Context;
+import android.os.FileUtils;
 import android.util.Log;
 
+import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
 
 import java.io.ByteArrayOutputStream;
 import java.io.Closeable;
 import java.io.File;
 import java.io.FileInputStream;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.util.UUID;
 
 /**
  * Supports various IO utility functions
@@ -52,6 +51,9 @@
     }
 
     public static long copy(InputStream from, OutputStream to) throws IOException {
+        if (Utilities.ATLEAST_Q) {
+            return FileUtils.copy(from, to);
+        }
         byte[] buf = new byte[BUF_SIZE];
         long total = 0;
         int r;
@@ -62,25 +64,6 @@
         return total;
     }
 
-    /**
-     * Utility method to debug binary data
-     */
-    public static String createTempFile(Context context, byte[] data) {
-        if (!FeatureFlags.IS_STUDIO_BUILD) {
-            throw new IllegalStateException("Method only allowed in development mode");
-        }
-
-        String name = UUID.randomUUID().toString();
-        File file = new File(context.getCacheDir(), name);
-        try (FileOutputStream fo = new FileOutputStream(file)) {
-            fo.write(data);
-            fo.flush();
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-        return file.getAbsolutePath();
-    }
-
     public static void closeSilently(Closeable c) {
         if (c != null) {
             try {
diff --git a/src/com/android/launcher3/util/ItemInfoMatcher.java b/src/com/android/launcher3/util/ItemInfoMatcher.java
index 4d5405d..d26bb57 100644
--- a/src/com/android/launcher3/util/ItemInfoMatcher.java
+++ b/src/com/android/launcher3/util/ItemInfoMatcher.java
@@ -27,6 +27,7 @@
 import com.android.launcher3.shortcuts.ShortcutKey;
 
 import java.util.HashSet;
+import java.util.Set;
 
 /**
  * A utility class to check for {@link ItemInfo}
@@ -81,11 +82,10 @@
     }
 
     /**
-     * Returns a new matcher which returns the opposite boolean value of the provided
-     * {@param matcher}.
+     * Returns a new matcher with returns the opposite value of this.
      */
-    static ItemInfoMatcher not(ItemInfoMatcher matcher) {
-        return (info, cn) -> !matcher.matches(info, cn);
+    default ItemInfoMatcher negate() {
+        return (info, cn) -> !matches(info, cn);
     }
 
     static ItemInfoMatcher ofUser(UserHandle user) {
@@ -100,12 +100,15 @@
         return (info, cn) -> packageNames.contains(cn.getPackageName()) && info.user.equals(user);
     }
 
-    static ItemInfoMatcher ofShortcutKeys(HashSet<ShortcutKey> keys) {
+    static ItemInfoMatcher ofShortcutKeys(Set<ShortcutKey> keys) {
         return  (info, cn) -> info.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT &&
                         keys.contains(ShortcutKey.fromItemInfo(info));
     }
 
-    static ItemInfoMatcher ofItemIds(IntSparseArrayMap<Boolean> ids, Boolean matchDefault) {
-        return (info, cn) -> ids.get(info.id, matchDefault);
+    /**
+     * Returns a matcher for items with provided ids
+     */
+    static ItemInfoMatcher ofItemIds(IntSet ids) {
+        return (info, cn) -> ids.contains(info.id);
     }
 }
diff --git a/src/com/android/launcher3/util/MainThreadInitializedObject.java b/src/com/android/launcher3/util/MainThreadInitializedObject.java
index fc9f8f7..f6003dd 100644
--- a/src/com/android/launcher3/util/MainThreadInitializedObject.java
+++ b/src/com/android/launcher3/util/MainThreadInitializedObject.java
@@ -46,7 +46,7 @@
 
         if (mValue == null) {
             if (Looper.myLooper() == Looper.getMainLooper()) {
-                mValue = TraceHelper.whitelistIpcs("main.thread.object",
+                mValue = TraceHelper.allowIpcs("main.thread.object",
                         () -> mProvider.get(context.getApplicationContext()));
             } else {
                 try {
diff --git a/src/com/android/launcher3/util/MultiHashMap.java b/src/com/android/launcher3/util/MultiHashMap.java
deleted file mode 100644
index b7275c1..0000000
--- a/src/com/android/launcher3/util/MultiHashMap.java
+++ /dev/null
@@ -1,52 +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 java.util.ArrayList;
-import java.util.HashMap;
-
-/**
- * A utility map from keys to an ArrayList of values.
- */
-public class MultiHashMap<K, V> extends HashMap<K, ArrayList<V>> {
-
-    public MultiHashMap() { }
-
-    public MultiHashMap(int size) {
-        super(size);
-    }
-
-    public void addToList(K key, V value) {
-        ArrayList<V> list = get(key);
-        if (list == null) {
-            list = new ArrayList<>();
-            list.add(value);
-            put(key, list);
-        } else {
-            list.add(value);
-        }
-    }
-
-    @Override
-    public MultiHashMap<K, V> clone() {
-        MultiHashMap<K, V> map = new MultiHashMap<>(size());
-        for (Entry<K, ArrayList<V>> entry : entrySet()) {
-            map.put(entry.getKey(), new ArrayList<V>(entry.getValue()));
-        }
-        return map;
-    }
-}
diff --git a/src/com/android/launcher3/util/MultiValueAlpha.java b/src/com/android/launcher3/util/MultiValueAlpha.java
index a8642b0..5be9529 100644
--- a/src/com/android/launcher3/util/MultiValueAlpha.java
+++ b/src/com/android/launcher3/util/MultiValueAlpha.java
@@ -19,6 +19,8 @@
 import android.util.FloatProperty;
 import android.view.View;
 
+import com.android.launcher3.anim.AlphaUpdateListener;
+
 import java.util.Arrays;
 
 /**
@@ -44,6 +46,8 @@
     private final AlphaProperty[] mMyProperties;
 
     private int mValidMask;
+    // Whether we should change from INVISIBLE to VISIBLE and vice versa at low alpha values.
+    private boolean mUpdateVisibility;
 
     public MultiValueAlpha(View view, int size) {
         mView = view;
@@ -66,6 +70,11 @@
         return mMyProperties[index];
     }
 
+    /** Sets whether we should update between INVISIBLE and VISIBLE based on alpha. */
+    public void setUpdateVisibility(boolean updateVisibility) {
+        mUpdateVisibility = updateVisibility;
+    }
+
     public class AlphaProperty {
 
         private final int mMyMask;
@@ -99,6 +108,9 @@
             mValue = value;
 
             mView.setAlpha(mOthers * mValue);
+            if (mUpdateVisibility) {
+                AlphaUpdateListener.updateVisibility(mView);
+            }
         }
 
         public float getValue() {
diff --git a/src/com/android/launcher3/util/OnboardingPrefs.java b/src/com/android/launcher3/util/OnboardingPrefs.java
index d4e074c..1b33197 100644
--- a/src/com/android/launcher3/util/OnboardingPrefs.java
+++ b/src/com/android/launcher3/util/OnboardingPrefs.java
@@ -36,7 +36,6 @@
     public static final String SHELF_BOUNCE_SEEN = "launcher.shelf_bounce_seen";
     public static final String HOME_BOUNCE_COUNT = "launcher.home_bounce_count";
     public static final String SHELF_BOUNCE_COUNT = "launcher.shelf_bounce_count";
-    public static final String ALL_APPS_COUNT = "launcher.all_apps_count";
     public static final String HOTSEAT_DISCOVERY_TIP_COUNT = "launcher.hotseat_discovery_tip_count";
     public static final String HOTSEAT_LONGPRESS_TIP_SEEN = "launcher.hotseat_longpress_tip_seen";
 
@@ -45,7 +44,8 @@
      */
     @StringDef(value = {
             HOME_BOUNCE_SEEN,
-            SHELF_BOUNCE_SEEN
+            SHELF_BOUNCE_SEEN,
+            HOTSEAT_LONGPRESS_TIP_SEEN
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface EventBoolKey {}
@@ -56,7 +56,6 @@
     @StringDef(value = {
             HOME_BOUNCE_COUNT,
             SHELF_BOUNCE_COUNT,
-            ALL_APPS_COUNT,
             HOTSEAT_DISCOVERY_TIP_COUNT
     })
     @Retention(RetentionPolicy.SOURCE)
@@ -64,10 +63,9 @@
 
     private static final Map<String, Integer> MAX_COUNTS;
     static {
-        Map<String, Integer> maxCounts = new ArrayMap<>(3);
+        Map<String, Integer> maxCounts = new ArrayMap<>(4);
         maxCounts.put(HOME_BOUNCE_COUNT, 3);
         maxCounts.put(SHELF_BOUNCE_COUNT, 3);
-        maxCounts.put(ALL_APPS_COUNT, 5);
         maxCounts.put(HOTSEAT_DISCOVERY_TIP_COUNT, 5);
         MAX_COUNTS = Collections.unmodifiableMap(maxCounts);
     }
diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index 86f3431..523545a 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -37,7 +37,6 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.PatternMatcher;
-import android.os.Process;
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
@@ -46,7 +45,6 @@
 
 import com.android.launcher3.PendingAddItemInfo;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -96,36 +94,12 @@
      * Returns the application info for the provided package or null
      */
     public ApplicationInfo getApplicationInfo(String packageName, UserHandle user, int flags) {
-        if (Utilities.ATLEAST_OREO) {
-            try {
-                ApplicationInfo info = mLauncherApps.getApplicationInfo(packageName, flags, user);
-                return (info.flags & ApplicationInfo.FLAG_INSTALLED) == 0 || !info.enabled
-                        ? null : info;
-            } catch (PackageManager.NameNotFoundException e) {
-                return null;
-            }
-        } else {
-            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 = mPm.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;
-            }
+        try {
+            ApplicationInfo info = mLauncherApps.getApplicationInfo(packageName, flags, user);
+            return (info.flags & ApplicationInfo.FLAG_INSTALLED) == 0 || !info.enabled
+                    ? null : info;
+        } catch (PackageManager.NameNotFoundException e) {
+            return null;
         }
     }
 
diff --git a/src/com/android/launcher3/util/PersistedItemArray.java b/src/com/android/launcher3/util/PersistedItemArray.java
new file mode 100644
index 0000000..7ff2abb
--- /dev/null
+++ b/src/com/android/launcher3/util/PersistedItemArray.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2020 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;
+import android.os.UserHandle;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.Xml;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
+
+import com.android.launcher3.AutoInstallsLayout;
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.pm.UserCache;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.LongFunction;
+
+/**
+ * Utility class to read/write a list of {@link com.android.launcher3.model.data.ItemInfo} on disk.
+ * This class is not thread safe, the caller should ensure proper threading
+ */
+public class PersistedItemArray<T extends ItemInfo> {
+
+    private static final String TAG = "PersistedItemArray";
+
+    private static final String TAG_ROOT = "items";
+    private static final String TAG_ENTRY = "entry";
+
+    private final String mFileName;
+
+    public PersistedItemArray(String fileName) {
+        mFileName = fileName + ".xml";
+    }
+
+    /**
+     * Writes the provided list of items on the disk
+     */
+    @WorkerThread
+    public void write(Context context, List<T> items) {
+        AtomicFile file = getFile(context);
+        FileOutputStream fos;
+        try {
+            fos = file.startWrite();
+        } catch (IOException e) {
+            Log.e(TAG, "Unable to persist items in " + mFileName, e);
+            return;
+        }
+
+        UserCache userCache = UserCache.INSTANCE.get(context);
+
+        try {
+            XmlSerializer out = Xml.newSerializer();
+            out.setOutput(fos, StandardCharsets.UTF_8.name());
+            out.startDocument(null, true);
+            out.startTag(null, TAG_ROOT);
+            for (T item : items) {
+                Intent intent = item.getIntent();
+                if (intent == null) {
+                    continue;
+                }
+
+                out.startTag(null, TAG_ENTRY);
+                out.attribute(null, Favorites.ITEM_TYPE, Integer.toString(item.itemType));
+                out.attribute(null, Favorites.PROFILE_ID,
+                        Long.toString(userCache.getSerialNumberForUser(item.user)));
+                out.attribute(null, Favorites.INTENT, intent.toUri(0));
+                out.endTag(null, TAG_ENTRY);
+            }
+            out.endTag(null, TAG_ROOT);
+            out.endDocument();
+        } catch (IOException e) {
+            file.failWrite(fos);
+            Log.e(TAG, "Unable to persist items in " + mFileName, e);
+            return;
+        }
+
+        file.finishWrite(fos);
+    }
+
+    /**
+     * Reads the items from the disk
+     */
+    @WorkerThread
+    public List<T> read(Context context, ItemFactory<T> factory) {
+        return read(context, factory, UserCache.INSTANCE.get(context)::getUserForSerialNumber);
+    }
+
+    /**
+     * Reads the items from the disk
+     * @param userFn method to provide user handle for a given user serial
+     */
+    @WorkerThread
+    public List<T> read(Context context, ItemFactory<T> factory, LongFunction<UserHandle> userFn) {
+        List<T> result = new ArrayList<>();
+        try (FileInputStream fis = getFile(context).openRead()) {
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(new InputStreamReader(fis, StandardCharsets.UTF_8));
+
+            AutoInstallsLayout.beginDocument(parser, TAG_ROOT);
+            final int depth = parser.getDepth();
+
+            int type;
+            while (((type = parser.next()) != XmlPullParser.END_TAG
+                    || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
+                if (type != XmlPullParser.START_TAG || !TAG_ENTRY.equals(parser.getName())) {
+                    continue;
+                }
+                try {
+                    int itemType = Integer.parseInt(
+                            parser.getAttributeValue(null, Favorites.ITEM_TYPE));
+                    UserHandle user = userFn.apply(Long.parseLong(
+                            parser.getAttributeValue(null, Favorites.PROFILE_ID)));
+                    Intent intent = Intent.parseUri(
+                            parser.getAttributeValue(null, Favorites.INTENT), 0);
+
+                    if (user != null && intent != null) {
+                        T item = factory.createInfo(itemType, user, intent);
+                        if (item != null) {
+                            result.add(item);
+                        }
+                    }
+                } catch (Exception e) {
+                    // Ignore this entry
+                }
+            }
+        } catch (FileNotFoundException e) {
+            // Ignore
+        } catch (IOException | XmlPullParserException e) {
+            Log.e(TAG, "Unable to read items in " + mFileName, e);
+            return Collections.emptyList();
+        }
+        return result;
+    }
+
+    /**
+     * Returns the underlying file used for persisting data
+     */
+    public AtomicFile getFile(Context context) {
+        return new AtomicFile(context.getFileStreamPath(mFileName));
+    }
+
+    /**
+     * Interface to create an ItemInfo during parsing
+     */
+    public interface ItemFactory<T extends ItemInfo> {
+
+        /**
+         * Returns an item info or null in which case the entry is ignored
+         */
+        @Nullable
+        T createInfo(int itemType, UserHandle user, Intent intent);
+    }
+}
diff --git a/src/com/android/launcher3/util/SecureSettingsObserver.java b/src/com/android/launcher3/util/SecureSettingsObserver.java
index 48aa02b..4b22429 100644
--- a/src/com/android/launcher3/util/SecureSettingsObserver.java
+++ b/src/com/android/launcher3/util/SecureSettingsObserver.java
@@ -29,6 +29,11 @@
 
     /** Hidden field Settings.Secure.NOTIFICATION_BADGING */
     public static final String NOTIFICATION_BADGING = "notification_badging";
+    /** Hidden field Settings.Secure.ONE_HANDED_MODE_ENABLED */
+    public static final String ONE_HANDED_ENABLED = "one_handed_mode_enabled";
+    /** Hidden field Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED */
+    public static final String ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED =
+            "swipe_bottom_to_notification_enabled";
 
     private final ContentResolver mResolver;
     private final String mKeySetting;
@@ -79,4 +84,21 @@
         return new SecureSettingsObserver(
                 context.getContentResolver(), listener, NOTIFICATION_BADGING, 1);
     }
+
+    public static SecureSettingsObserver newOneHandedSettingsObserver(Context context,
+            OnChangeListener listener) {
+        return new SecureSettingsObserver(
+                context.getContentResolver(), listener, ONE_HANDED_ENABLED, 1);
+    }
+
+    /**
+     * Constructs settings observer for {@link #ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED}
+     * preference.
+     */
+    public static SecureSettingsObserver newSwipeToNotificationSettingsObserver(Context context,
+            OnChangeListener listener) {
+        return new SecureSettingsObserver(
+                context.getContentResolver(), listener,
+                ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, 1);
+    }
 }
diff --git a/src/com/android/launcher3/util/SystemUiController.java b/src/com/android/launcher3/util/SystemUiController.java
index 275c024..50166c3 100644
--- a/src/com/android/launcher3/util/SystemUiController.java
+++ b/src/com/android/launcher3/util/SystemUiController.java
@@ -19,8 +19,6 @@
 import android.view.View;
 import android.view.Window;
 
-import com.android.launcher3.Utilities;
-
 import java.util.Arrays;
 
 /**
@@ -33,6 +31,7 @@
     public static final int UI_STATE_SCRIM_VIEW = 1;
     public static final int UI_STATE_WIDGET_BOTTOM_SHEET = 2;
     public static final int UI_STATE_OVERVIEW = 3;
+    public static final int UI_STATE_ALLAPPS = 4;
 
     public static final int FLAG_LIGHT_NAV = 1 << 0;
     public static final int FLAG_DARK_NAV = 1 << 1;
@@ -40,7 +39,7 @@
     public static final int FLAG_DARK_STATUS = 1 << 3;
 
     private final Window mWindow;
-    private final int[] mStates = new int[4];
+    private final int[] mStates = new int[5];
 
     public SystemUiController(Window window) {
         mWindow = window;
@@ -77,12 +76,10 @@
     }
 
     private int getSysUiVisibilityFlags(int stateFlag, int currentVisibility) {
-        if (Utilities.ATLEAST_OREO) {
-            if ((stateFlag & FLAG_LIGHT_NAV) != 0) {
-                currentVisibility |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
-            } else if ((stateFlag & FLAG_DARK_NAV) != 0) {
-                currentVisibility &= ~(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
-            }
+        if ((stateFlag & FLAG_LIGHT_NAV) != 0) {
+            currentVisibility |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
+        } else if ((stateFlag & FLAG_DARK_NAV) != 0) {
+            currentVisibility &= ~(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
         }
 
         if ((stateFlag & FLAG_LIGHT_STATUS) != 0) {
diff --git a/src/com/android/launcher3/util/TraceHelper.java b/src/com/android/launcher3/util/TraceHelper.java
index 168227d..c23df77 100644
--- a/src/com/android/launcher3/util/TraceHelper.java
+++ b/src/com/android/launcher3/util/TraceHelper.java
@@ -78,7 +78,7 @@
      * Temporarily ignore blocking binder calls for the duration of this {@link Supplier}.
      */
     @MainThread
-    public static <T> T whitelistIpcs(String rpcName, Supplier<T> supplier) {
+    public static <T> T allowIpcs(String rpcName, Supplier<T> supplier) {
         Object traceToken = INSTANCE.beginSection(rpcName, FLAG_IGNORE_BINDERS);
         try {
             return supplier.get();
diff --git a/src/com/android/launcher3/views/AccessibilityActionsView.java b/src/com/android/launcher3/views/AccessibilityActionsView.java
new file mode 100644
index 0000000..0eacaa3
--- /dev/null
+++ b/src/com/android/launcher3/views/AccessibilityActionsView.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2020 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.views;
+
+import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.NORMAL;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.R;
+import com.android.launcher3.statemanager.StateManager.StateListener;
+import com.android.launcher3.views.OptionsPopupView.OptionItem;
+
+/**
+ * Placeholder view to expose additional Launcher actions via accessibility actions
+ */
+public class AccessibilityActionsView extends View implements StateListener<LauncherState> {
+
+    public AccessibilityActionsView(Context context) {
+        this(context, null);
+    }
+
+    public AccessibilityActionsView(Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public AccessibilityActionsView(Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        Launcher.getLauncher(context).getStateManager().addStateListener(this);
+        setWillNotDraw(true);
+    }
+
+    @Override
+    public void onStateTransitionComplete(LauncherState finalState) {
+        setImportantForAccessibility(finalState == NORMAL
+                ? IMPORTANT_FOR_ACCESSIBILITY_YES : IMPORTANT_FOR_ACCESSIBILITY_NO);
+    }
+
+    @Override
+    public AccessibilityNodeInfo createAccessibilityNodeInfo() {
+        AccessibilityNodeInfo info = super.createAccessibilityNodeInfo();
+        Launcher l = Launcher.getLauncher(getContext());
+        info.addAction(new AccessibilityAction(
+                R.string.all_apps_button_label, l.getText(R.string.all_apps_button_label)));
+        for (OptionItem item : OptionsPopupView.getOptions(l)) {
+            info.addAction(new AccessibilityAction(item.labelRes, l.getText(item.labelRes)));
+        }
+        return info;
+    }
+
+    @Override
+    public boolean performAccessibilityAction(int action, Bundle arguments) {
+        if (super.performAccessibilityAction(action, arguments)) {
+            return true;
+        }
+        Launcher l = Launcher.getLauncher(getContext());
+        if (action == R.string.all_apps_button_label) {
+            l.getStateManager().goToState(ALL_APPS);
+            return true;
+        }
+        for (OptionItem item : OptionsPopupView.getOptions(l)) {
+            if (item.labelRes == action) {
+                if (item.eventId.getId() > 0) {
+                    l.getStatsLogManager().logger().log(item.eventId);
+                }
+                if (item.clickListener.onLongClick(this)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+}
diff --git a/src/com/android/launcher3/views/ArrowTipView.java b/src/com/android/launcher3/views/ArrowTipView.java
index b4a6b14..1f12a2f 100644
--- a/src/com/android/launcher3/views/ArrowTipView.java
+++ b/src/com/android/launcher3/views/ArrowTipView.java
@@ -87,10 +87,6 @@
     }
 
     @Override
-    public void logActionCommand(int command) {
-    }
-
-    @Override
     protected boolean isOfType(int type) {
         return (type & TYPE_ON_BOARD_POPUP) != 0;
     }
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index b010b4b..2be827b 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -20,7 +20,7 @@
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_UP;
 
-import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
+import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
 
 import android.annotation.TargetApi;
 import android.app.WallpaperInfo;
@@ -602,13 +602,13 @@
      */
     private boolean computeAllowSysuiScrims(@Nullable WallpaperInfo newWallpaperInfo) {
         if (newWallpaperInfo == null) {
-            // New wallpaper is static, not live. Thus, blacklist isn't applicable.
+            // Static wallpapers need scrim unless determined otherwise by wallpaperColors.
             return true;
         }
         ComponentName newWallpaper = newWallpaperInfo.getComponent();
         for (String wallpaperWithoutScrim : mWallpapersWithoutSysuiScrims) {
             if (newWallpaper.equals(ComponentName.unflattenFromString(wallpaperWithoutScrim))) {
-                // New wallpaper is blacklisted from showing a scrim.
+                // New wallpaper does not need a scrim.
                 return false;
             }
         }
diff --git a/src/com/android/launcher3/views/ClipIconView.java b/src/com/android/launcher3/views/ClipIconView.java
index 1a8e11b..fab0bd4 100644
--- a/src/com/android/launcher3/views/ClipIconView.java
+++ b/src/com/android/launcher3/views/ClipIconView.java
@@ -36,6 +36,7 @@
 import android.os.Build;
 import android.util.AttributeSet;
 import android.view.View;
+import android.view.ViewGroup.MarginLayoutParams;
 import android.view.ViewOutlineProvider;
 
 import androidx.annotation.Nullable;
@@ -44,8 +45,6 @@
 import androidx.dynamicanimation.animation.SpringForce;
 
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.InsettableFrameLayout.LayoutParams;
-import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
@@ -94,7 +93,6 @@
                 }
             };
 
-    private final Launcher mLauncher;
     private final int mBlurSizeOutline;
     private final boolean mIsRtl;
 
@@ -128,7 +126,6 @@
 
     public ClipIconView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
-        mLauncher = Launcher.getLauncher(context);
         mBlurSizeOutline = getResources().getDimensionPixelSize(
                 R.dimen.blur_size_medium_outline);
         mIsRtl = Utilities.isRtl(getResources());
@@ -143,10 +140,41 @@
                         .setStiffness(SpringForce.STIFFNESS_LOW));
     }
 
-    void update(RectF rect, float progress, float shapeProgressStart, float cornerRadius,
-            boolean isOpening, float scale, float minSize, LayoutParams parentLp,
-            boolean isVerticalBarLayout) {
-        DeviceProfile dp = mLauncher.getDeviceProfile();
+    /**
+     * Update the icon UI to match the provided parameters during an animation frame
+     */
+    public void update(RectF rect, float progress, float shapeProgressStart,
+            float cornerRadius, boolean isOpening, View container,
+            DeviceProfile dp, boolean isVerticalBarLayout) {
+
+        MarginLayoutParams lp = (MarginLayoutParams) container.getLayoutParams();
+
+        float dX = mIsRtl
+                ? rect.left - (dp.widthPx - lp.getMarginStart() - lp.width)
+                : rect.left - lp.getMarginStart();
+        float dY = rect.top - lp.topMargin;
+        container.setTranslationX(dX);
+        container.setTranslationY(dY);
+
+        float minSize = Math.min(lp.width, lp.height);
+        float scaleX = rect.width() / minSize;
+        float scaleY = rect.height() / minSize;
+        float scale = Math.max(1f, Math.min(scaleX, scaleY));
+
+        update(rect, progress, shapeProgressStart, cornerRadius, isOpening, scale,
+                minSize, lp, isVerticalBarLayout, dp);
+
+        container.setPivotX(0);
+        container.setPivotY(0);
+        container.setScaleX(scale);
+        container.setScaleY(scale);
+
+        container.invalidate();
+    }
+
+    private void update(RectF rect, float progress, float shapeProgressStart, float cornerRadius,
+            boolean isOpening, float scale, float minSize, MarginLayoutParams parentLp,
+            boolean isVerticalBarLayout, DeviceProfile dp) {
         float dX = mIsRtl
                 ? rect.left - (dp.widthPx - parentLp.getMarginStart() - parentLp.width)
                 : rect.left - parentLp.getMarginStart();
@@ -228,8 +256,11 @@
         }
     }
 
-    void setIcon(@Nullable Drawable drawable, int iconOffset, LayoutParams lp, boolean isOpening,
-            boolean isVerticalBarLayout) {
+    /**
+     * Sets the icon for this view as part of initial setup
+     */
+    public void setIcon(@Nullable Drawable drawable, int iconOffset, MarginLayoutParams lp,
+            boolean isOpening, boolean isVerticalBarLayout, DeviceProfile dp) {
         mIsAdaptiveIcon = drawable instanceof AdaptiveIconDrawable;
         if (mIsAdaptiveIcon) {
             boolean isFolderIcon = drawable instanceof FolderAdaptiveIcon;
@@ -264,15 +295,14 @@
                 Utilities.scaleRectAboutCenter(mStartRevealRect, IconShape.getNormalizationScale());
             }
 
-            float aspectRatio = mLauncher.getDeviceProfile().aspectRatio;
             if (isVerticalBarLayout) {
-                lp.width = (int) Math.max(lp.width, lp.height * aspectRatio);
+                lp.width = (int) Math.max(lp.width, lp.height * dp.aspectRatio);
             } else {
-                lp.height = (int) Math.max(lp.height, lp.width * aspectRatio);
+                lp.height = (int) Math.max(lp.height, lp.width * dp.aspectRatio);
             }
 
             int left = mIsRtl
-                    ? mLauncher.getDeviceProfile().widthPx - lp.getMarginStart() - lp.width
+                    ? dp.widthPx - lp.getMarginStart() - lp.width
                     : lp.leftMargin;
             layout(left, lp.topMargin, left + lp.width, lp.topMargin + lp.height);
 
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index 177aff4..52a82f8 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -20,6 +20,7 @@
 import static com.android.launcher3.Utilities.getFullDrawable;
 import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import static com.android.launcher3.views.IconLabelDotView.setIconAndDotVisible;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -47,7 +48,6 @@
 import androidx.annotation.WorkerThread;
 
 import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.InsettableFrameLayout;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
@@ -144,32 +144,8 @@
     public void update(RectF rect, float alpha, float progress, float shapeProgressStart,
             float cornerRadius, boolean isOpening) {
         setAlpha(alpha);
-
-        InsettableFrameLayout.LayoutParams lp =
-                (InsettableFrameLayout.LayoutParams) getLayoutParams();
-
-        DeviceProfile dp = mLauncher.getDeviceProfile();
-        float dX = mIsRtl
-                ? rect.left - (dp.widthPx - lp.getMarginStart() - lp.width)
-                : rect.left - lp.getMarginStart();
-        float dY = rect.top - lp.topMargin;
-        setTranslationX(dX);
-        setTranslationY(dY);
-
-        float minSize = Math.min(lp.width, lp.height);
-        float scaleX = rect.width() / minSize;
-        float scaleY = rect.height() / minSize;
-        float scale = Math.max(1f, Math.min(scaleX, scaleY));
-
-        mClipIconView.update(rect, progress, shapeProgressStart, cornerRadius, isOpening, scale,
-                minSize, lp, mIsVerticalBarLayout);
-
-        setPivotX(0);
-        setPivotY(0);
-        setScaleX(scale);
-        setScaleY(scale);
-
-        invalidate();
+        mClipIconView.update(rect, progress, shapeProgressStart, cornerRadius, isOpening,
+                this, mLauncher.getDeviceProfile(), mIsVerticalBarLayout);
     }
 
     @Override
@@ -220,13 +196,18 @@
         layout(left, lp.topMargin, left + lp.width, lp.topMargin + lp.height);
     }
 
+    private static void getLocationBoundsForView(Launcher launcher, View v, boolean isOpening,
+            RectF outRect) {
+        getLocationBoundsForView(launcher, v, isOpening, outRect, new Rect());
+    }
+
     /**
      * Gets the location bounds of a view and returns the overall rotation.
      * - For DeepShortcutView, we return the bounds of the icon view.
      * - For BubbleTextView, we return the icon bounds.
      */
-    private static void getLocationBoundsForView(Launcher launcher, View v, boolean isOpening,
-            RectF outRect) {
+    public static void getLocationBoundsForView(Launcher launcher, View v, boolean isOpening,
+            RectF outRect, Rect outViewBounds) {
         boolean ignoreTransform = !isOpening;
         if (v instanceof DeepShortcutView) {
             v = ((DeepShortcutView) v).getBubbleText();
@@ -239,17 +220,16 @@
             return;
         }
 
-        Rect iconBounds = new Rect();
         if (v instanceof BubbleTextView) {
-            ((BubbleTextView) v).getIconBounds(iconBounds);
+            ((BubbleTextView) v).getIconBounds(outViewBounds);
         } else if (v instanceof FolderIcon) {
-            ((FolderIcon) v).getPreviewBounds(iconBounds);
+            ((FolderIcon) v).getPreviewBounds(outViewBounds);
         } else {
-            iconBounds.set(0, 0, v.getWidth(), v.getHeight());
+            outViewBounds.set(0, 0, v.getWidth(), v.getHeight());
         }
 
-        float[] points = new float[] {iconBounds.left, iconBounds.top, iconBounds.right,
-                iconBounds.bottom};
+        float[] points = new float[] {outViewBounds.left, outViewBounds.top, outViewBounds.right,
+                outViewBounds.bottom};
         Utilities.getDescendantCoordRelativeToAncestor(v, launcher.getDragLayer(), points,
                 false, ignoreTransform);
         outRect.set(
@@ -276,7 +256,6 @@
         Drawable drawable = null;
         Drawable badge = null;
         boolean supportsAdaptiveIcons = ADAPTIVE_ICON_WINDOW_ANIM.get()
-                && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
                 && !info.isDisabled(); // Use original icon for disabled icons.
         Drawable btvIcon = originalView instanceof BubbleTextView
                 ? ((BubbleTextView) originalView).getIcon() : null;
@@ -336,7 +315,8 @@
         final InsettableFrameLayout.LayoutParams lp =
                 (InsettableFrameLayout.LayoutParams) getLayoutParams();
         mBadge = badge;
-        mClipIconView.setIcon(drawable, iconOffset, lp, mIsOpening, mIsVerticalBarLayout);
+        mClipIconView.setIcon(drawable, iconOffset, lp, mIsOpening, mIsVerticalBarLayout,
+                mLauncher.getDeviceProfile());
         if (drawable instanceof AdaptiveIconDrawable) {
             final int originalHeight = lp.height;
             final int originalWidth = lp.width;
@@ -381,7 +361,7 @@
             if (mIconLoadResult.isIconLoaded) {
                 setIcon(mIconLoadResult.drawable, mIconLoadResult.badge,
                         mIconLoadResult.iconOffset);
-                hideOriginalView(originalView);
+                setIconAndDotVisible(originalView, false);
             } else {
                 mIconLoadResult.onIconLoaded = () -> {
                     if (cancellationSignal.isCanceled()) {
@@ -392,27 +372,17 @@
                             mIconLoadResult.iconOffset);
 
                     setVisibility(VISIBLE);
-                    hideOriginalView(originalView);
+                    setIconAndDotVisible(originalView, false);
                 };
                 mLoadIconSignal = cancellationSignal;
             }
         }
     }
 
-    private void hideOriginalView(View originalView) {
-        if (originalView instanceof IconLabelDotView) {
-            ((IconLabelDotView) originalView).setIconVisible(false);
-            ((IconLabelDotView) originalView).setForceHideDot(true);
-        } else {
-            originalView.setVisibility(INVISIBLE);
-        }
-    }
-
     @WorkerThread
     @SuppressWarnings("WrongThread")
     private static int getOffsetForIconBounds(Launcher l, Drawable drawable, RectF position) {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O
-                || !(drawable instanceof AdaptiveIconDrawable)
+        if (!(drawable instanceof AdaptiveIconDrawable)
                 || (drawable instanceof FolderAdaptiveIcon)) {
             return 0;
         }
@@ -477,7 +447,7 @@
         }
         if (!mIsOpening) {
             // When closing an app, we want the item on the workspace to be invisible immediately
-            hideOriginalView(mOriginalIcon);
+            setIconAndDotVisible(mOriginalIcon, false);
         }
     }
 
@@ -573,12 +543,7 @@
 
             if (hideOriginal) {
                 if (isOpening) {
-                    if (originalView instanceof BubbleTextView) {
-                        ((BubbleTextView) originalView).setIconVisible(true);
-                        ((BubbleTextView) originalView).setForceHideDot(false);
-                    } else {
-                        originalView.setVisibility(VISIBLE);
-                    }
+                    setIconAndDotVisible(originalView, true);
                     view.finish(dragLayer);
                 } else {
                     view.mFadeAnimatorSet = view.createFadeAnimation(originalView, dragLayer);
@@ -615,12 +580,10 @@
         });
 
         if (originalView instanceof IconLabelDotView) {
-            IconLabelDotView view = (IconLabelDotView) originalView;
             fade.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
-                    view.setIconVisible(true);
-                    view.setForceHideDot(false);
+                    setIconAndDotVisible(originalView, true);
                 }
             });
         }
diff --git a/src/com/android/launcher3/views/FloatingSurfaceView.java b/src/com/android/launcher3/views/FloatingSurfaceView.java
new file mode 100644
index 0000000..011f6de
--- /dev/null
+++ b/src/com/android/launcher3/views/FloatingSurfaceView.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2020 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.views;
+
+import static com.android.launcher3.views.FloatingIconView.getLocationBoundsForView;
+import static com.android.launcher3.views.IconLabelDotView.setIconAndDotVisible;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Picture;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.GestureNavContract;
+import com.android.launcher3.Insettable;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.Executors;
+
+/**
+ * Similar to {@link FloatingIconView} but displays a surface with the targetIcon. It then passes
+ * the surfaceHandle to the {@link GestureNavContract}.
+ */
+@TargetApi(Build.VERSION_CODES.R)
+public class FloatingSurfaceView extends AbstractFloatingView implements
+        OnGlobalLayoutListener, Insettable, SurfaceHolder.Callback2 {
+
+    private final RectF mTmpPosition = new RectF();
+
+    private final Launcher mLauncher;
+    private final RectF mIconPosition = new RectF();
+
+    private final Rect mIconBounds = new Rect();
+    private final Picture mPicture = new Picture();
+    private final Runnable mRemoveViewRunnable = this::removeViewFromParent;
+
+    private final SurfaceView mSurfaceView;
+
+
+    private View mIcon;
+    private GestureNavContract mContract;
+
+    public FloatingSurfaceView(Context context) {
+        this(context, null);
+    }
+
+    public FloatingSurfaceView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public FloatingSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        mLauncher = Launcher.getLauncher(context);
+
+        mSurfaceView = new SurfaceView(context);
+        mSurfaceView.setZOrderOnTop(true);
+
+        mSurfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT);
+        mSurfaceView.getHolder().addCallback(this);
+        mIsOpen = true;
+        addView(mSurfaceView);
+    }
+
+    @Override
+    protected void handleClose(boolean animate) {
+        setCurrentIconVisible(true);
+        mLauncher.getViewCache().recycleView(R.layout.floating_surface_view, this);
+        mContract = null;
+        mIcon = null;
+        mIsOpen = false;
+
+        // Remove after some time, to avoid flickering
+        Executors.MAIN_EXECUTOR.getHandler().postDelayed(mRemoveViewRunnable,
+                DisplayController.getDefaultDisplay(mLauncher).getInfo().singleFrameMs);
+    }
+
+    private void removeViewFromParent() {
+        mPicture.beginRecording(1, 1);
+        mPicture.endRecording();
+        mLauncher.getDragLayer().removeView(this);
+    }
+
+    /**
+     * Shows the surfaceView for the provided contract
+     */
+    public static void show(Launcher launcher, GestureNavContract contract) {
+        FloatingSurfaceView view = launcher.getViewCache().getView(R.layout.floating_surface_view,
+                launcher, launcher.getDragLayer());
+        view.mContract = contract;
+        view.mIsOpen = true;
+
+        // Cancel any pending remove
+        Executors.MAIN_EXECUTOR.getHandler().removeCallbacks(view.mRemoveViewRunnable);
+        view.removeViewFromParent();
+        launcher.getDragLayer().addView(view);
+    }
+
+    @Override
+    protected boolean isOfType(int type) {
+        return (type & TYPE_ICON_SURFACE) != 0;
+    }
+
+    @Override
+    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+        close(false);
+        return false;
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        getViewTreeObserver().addOnGlobalLayoutListener(this);
+        updateIconLocation();
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        getViewTreeObserver().removeOnGlobalLayoutListener(this);
+        setCurrentIconVisible(true);
+    }
+
+    @Override
+    public void onGlobalLayout() {
+        updateIconLocation();
+    }
+
+    @Override
+    public void setInsets(Rect insets) { }
+
+    private void updateIconLocation() {
+        if (mContract == null) {
+            return;
+        }
+        View icon = mLauncher.getWorkspace().getFirstMatchForAppClose(
+                mContract.componentName.getPackageName(), mContract.user);
+
+        boolean iconChanged = mIcon != icon;
+        if (iconChanged) {
+            setCurrentIconVisible(true);
+            mIcon = icon;
+            setCurrentIconVisible(false);
+        }
+
+        if (icon != null && icon.isAttachedToWindow()) {
+            getLocationBoundsForView(mLauncher, icon, false, mTmpPosition, mIconBounds);
+
+            if (!mTmpPosition.equals(mIconPosition)) {
+                mIconPosition.set(mTmpPosition);
+                sendIconInfo();
+
+                LayoutParams lp = (LayoutParams) mSurfaceView.getLayoutParams();
+                lp.width = Math.round(mIconPosition.width());
+                lp.height = Math.round(mIconPosition.height());
+                lp.leftMargin = Math.round(mIconPosition.left);
+                lp.topMargin = Math.round(mIconPosition.top);
+            }
+        }
+        if (iconChanged && !mIconBounds.isEmpty()) {
+            // Record the icon display
+            setCurrentIconVisible(true);
+            Canvas c = mPicture.beginRecording(mIconBounds.width(), mIconBounds.height());
+            c.translate(-mIconBounds.left, -mIconBounds.top);
+            mIcon.draw(c);
+            mPicture.endRecording();
+            setCurrentIconVisible(false);
+            drawOnSurface();
+        }
+    }
+
+    private void sendIconInfo() {
+        if (mContract != null && !mIconPosition.isEmpty()) {
+            mContract.sendEndPosition(mIconPosition, mSurfaceView.getSurfaceControl());
+        }
+    }
+
+    @Override
+    public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {
+        drawOnSurface();
+        sendIconInfo();
+    }
+
+    @Override
+    public void surfaceChanged(@NonNull SurfaceHolder surfaceHolder,
+            int format, int width, int height) {
+        drawOnSurface();
+    }
+
+    @Override
+    public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {}
+
+    @Override
+    public void surfaceRedrawNeeded(@NonNull SurfaceHolder surfaceHolder) {
+        drawOnSurface();
+    }
+
+    private void drawOnSurface() {
+        SurfaceHolder surfaceHolder = mSurfaceView.getHolder();
+
+        Canvas c = surfaceHolder.lockHardwareCanvas();
+        if (c != null) {
+            mPicture.draw(c);
+            surfaceHolder.unlockCanvasAndPost(c);
+        }
+    }
+
+    private void setCurrentIconVisible(boolean isVisible) {
+        if (mIcon != null) {
+            setIconAndDotVisible(mIcon, isVisible);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/views/HeroSearchResultView.java b/src/com/android/launcher3/views/HeroSearchResultView.java
new file mode 100644
index 0000000..761ef0d
--- /dev/null
+++ b/src/com/android/launcher3/views/HeroSearchResultView.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2020 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.views;
+
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_ALL_APPS;
+
+import android.content.Context;
+import android.graphics.Point;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.DragSource;
+import com.android.launcher3.DropTarget;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
+import com.android.launcher3.allapps.search.AllAppsSearchBarController;
+import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.dragndrop.DraggableView;
+import com.android.launcher3.graphics.DragPreviewProvider;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
+import com.android.launcher3.touch.ItemLongClickListener;
+
+import java.util.List;
+
+/**
+ * A view representing a high confidence app search result that includes shortcuts
+ */
+public class HeroSearchResultView extends LinearLayout implements DragSource,
+        AllAppsSearchBarController.PayloadResultHandler<List<ItemInfoWithIcon>> {
+
+    public static final int MAX_SHORTCUTS_COUNT = 2;
+    BubbleTextView mBubbleTextView;
+    View mIconView;
+    BubbleTextView[] mDeepShortcutTextViews = new BubbleTextView[2];
+
+    public HeroSearchResultView(Context context) {
+        super(context);
+    }
+
+    public HeroSearchResultView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public HeroSearchResultView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        Launcher launcher = Launcher.getLauncher(getContext());
+        DeviceProfile grid = launcher.getDeviceProfile();
+        mIconView = findViewById(R.id.icon);
+        ViewGroup.LayoutParams iconParams = mIconView.getLayoutParams();
+        iconParams.height = grid.allAppsIconSizePx;
+        iconParams.width = grid.allAppsIconSizePx;
+
+
+        mBubbleTextView = findViewById(R.id.bubble_text);
+        mBubbleTextView.setOnClickListener(launcher.getItemOnClickListener());
+        mBubbleTextView.setOnLongClickListener(new HeroItemDragHandler(getContext(), this));
+        setLayoutParams(
+                new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, grid.allAppsCellHeightPx));
+
+
+        mDeepShortcutTextViews[0] = findViewById(R.id.shortcut_0);
+        mDeepShortcutTextViews[1] = findViewById(R.id.shortcut_1);
+        for (BubbleTextView bubbleTextView : mDeepShortcutTextViews) {
+            bubbleTextView.setLayoutParams(
+                    new LinearLayout.LayoutParams(grid.allAppsIconSizePx,
+                            grid.allAppsIconSizePx));
+            bubbleTextView.setOnClickListener(launcher.getItemOnClickListener());
+        }
+    }
+
+    /**
+     * Apply {@link ItemInfo} for appIcon and shortcut Icons
+     */
+    @Override
+    public void applyAdapterInfo(AdapterItemWithPayload<List<ItemInfoWithIcon>> adapterItem) {
+        mBubbleTextView.applyFromApplicationInfo(adapterItem.appInfo);
+        mIconView.setBackground(mBubbleTextView.getIcon());
+        mIconView.setTag(adapterItem.appInfo);
+        List<ItemInfoWithIcon> shorcutInfos = adapterItem.getPayload();
+        for (int i = 0; i < mDeepShortcutTextViews.length; i++) {
+            mDeepShortcutTextViews[i].setVisibility(shorcutInfos.size() > i ? VISIBLE : GONE);
+            if (i < shorcutInfos.size()) {
+                mDeepShortcutTextViews[i].applyFromItemInfoWithIcon(shorcutInfos.get(i));
+            }
+        }
+    }
+
+    @Override
+    public void onDropCompleted(View target, DropTarget.DragObject d, boolean success) {
+        mBubbleTextView.setVisibility(VISIBLE);
+        mBubbleTextView.setIconVisible(true);
+    }
+
+    private void setWillDrawIcon(boolean willDraw) {
+        mIconView.setVisibility(willDraw ? View.VISIBLE : View.INVISIBLE);
+    }
+
+    /**
+     * Drag and drop handler for popup items in Launcher activity
+     */
+    public static class HeroItemDragHandler implements OnLongClickListener {
+        private final Launcher mLauncher;
+        private final HeroSearchResultView mContainer;
+
+        HeroItemDragHandler(Context context, HeroSearchResultView container) {
+            mLauncher = Launcher.getLauncher(context);
+            mContainer = container;
+        }
+
+        @Override
+        public boolean onLongClick(View v) {
+            if (!ItemLongClickListener.canStartDrag(mLauncher)) return false;
+            mContainer.setWillDrawIcon(false);
+
+            DraggableView draggableView = DraggableView.ofType(DraggableView.DRAGGABLE_ICON);
+            WorkspaceItemInfo itemInfo = new WorkspaceItemInfo((AppInfo) v.getTag());
+            itemInfo.container = CONTAINER_ALL_APPS;
+            DragPreviewProvider previewProvider = new ShortcutDragPreviewProvider(
+                    mContainer.mIconView, new Point());
+            mLauncher.getWorkspace().beginDragShared(mContainer.mBubbleTextView,
+                    draggableView, mContainer, itemInfo, previewProvider, new DragOptions());
+
+            return false;
+        }
+    }
+}
diff --git a/src/com/android/launcher3/views/IconLabelDotView.java b/src/com/android/launcher3/views/IconLabelDotView.java
index 057caaf..e9113cf 100644
--- a/src/com/android/launcher3/views/IconLabelDotView.java
+++ b/src/com/android/launcher3/views/IconLabelDotView.java
@@ -15,10 +15,24 @@
  */
 package com.android.launcher3.views;
 
+import android.view.View;
+
 /**
  * A view that has an icon, label, and notification dot.
  */
 public interface IconLabelDotView {
     void setIconVisible(boolean visible);
     void setForceHideDot(boolean hide);
+
+    /**
+     * Sets the visibility of icon and dot of the view
+     */
+    static void setIconAndDotVisible(View view, boolean visible) {
+        if (view instanceof IconLabelDotView) {
+            ((IconLabelDotView) view).setIconVisible(visible);
+            ((IconLabelDotView) view).setForceHideDot(!visible);
+        } else {
+            view.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+        }
+    }
 }
diff --git a/src/com/android/launcher3/views/ListenerView.java b/src/com/android/launcher3/views/ListenerView.java
index 3ef778b..6e3f0ce 100644
--- a/src/com/android/launcher3/views/ListenerView.java
+++ b/src/com/android/launcher3/views/ListenerView.java
@@ -85,11 +85,6 @@
     }
 
     @Override
-    public void logActionCommand(int command) {
-        // Users do not interact with FloatingIconView, so there is nothing to log here.
-    }
-
-    @Override
     protected boolean isOfType(int type) {
         return (type & TYPE_LISTENER) != 0;
     }
diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
index d558781..3ec20d5 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -85,10 +85,10 @@
         if (item == null) {
             return false;
         }
-        if (item.mEventId.getId() > 0) {
-            mLauncher.getStatsLogManager().logger().log(item.mEventId);
+        if (item.eventId.getId() > 0) {
+            mLauncher.getStatsLogManager().logger().log(item.eventId);
         }
-        if (item.mClickListener.onLongClick(view)) {
+        if (item.clickListener.onLongClick(view)) {
             close(true);
             return true;
         }
@@ -108,11 +108,6 @@
     }
 
     @Override
-    public void logActionCommand(int command) {
-        // TODO:
-    }
-
-    @Override
     protected boolean isOfType(int type) {
         return (type & TYPE_OPTIONS_POPUP) != 0;
     }
@@ -130,8 +125,8 @@
         for (OptionItem item : items) {
             DeepShortcutView view =
                     (DeepShortcutView) popup.inflateAndAdd(R.layout.system_shortcut, popup);
-            view.getIconView().setBackgroundResource(item.mIconRes);
-            view.getBubbleText().setText(item.mLabelRes);
+            view.getIconView().setBackgroundResource(item.iconRes);
+            view.getBubbleText().setText(item.labelRes);
             view.setDividerVisibility(View.INVISIBLE);
             view.setOnClickListener(popup);
             view.setOnLongClickListener(popup);
@@ -152,7 +147,13 @@
             y = launcher.getDragLayer().getHeight() / 2;
         }
         RectF target = new RectF(x - halfSize, y - halfSize, x + halfSize, y + halfSize);
+        show(launcher, target, getOptions(launcher));
+    }
 
+    /**
+     * Returns the list of supported actions
+     */
+    public static ArrayList<OptionItem> getOptions(Launcher launcher) {
         ArrayList<OptionItem> options = new ArrayList<>();
         int resString = Utilities.existsStyleWallpapers(launcher) ?
                 R.string.styles_wallpaper_button_text : R.string.wallpaper_button_text;
@@ -170,10 +171,10 @@
                 LAUNCHER_SETTINGS_BUTTON_TAP_OR_LONGPRESS,
                 OptionsPopupView::startSettings));
 
-        show(launcher, target, options);
+        return options;
     }
 
-    public static boolean onWidgetsClicked(View view) {
+    private static boolean onWidgetsClicked(View view) {
         return openWidgets(Launcher.getLauncher(view.getContext())) != null;
     }
 
@@ -188,7 +189,7 @@
         }
     }
 
-    public static boolean startSettings(View view) {
+    private static boolean startSettings(View view) {
         TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "start: startSettings");
         Launcher launcher = Launcher.getLauncher(view.getContext());
         launcher.startActivity(new Intent(Intent.ACTION_APPLICATION_PREFERENCES)
@@ -201,7 +202,7 @@
      * Event handler for the wallpaper picker button that appears after a long press
      * on the home screen.
      */
-    public static boolean startWallpaperPicker(View v) {
+    private static boolean startWallpaperPicker(View v) {
         Launcher launcher = Launcher.getLauncher(v.getContext());
         if (!Utilities.isWallpaperAllowed(launcher)) {
             Toast.makeText(launcher, R.string.msg_disabled_by_admin, Toast.LENGTH_SHORT).show();
@@ -220,30 +221,30 @@
         if (!TextUtils.isEmpty(pickerPackage)) {
             intent.setPackage(pickerPackage);
         }
-        return launcher.startActivitySafely(v, intent, dummyInfo(intent), null);
+        return launcher.startActivitySafely(v, intent, placeholderInfo(intent));
     }
 
-    static WorkspaceItemInfo dummyInfo(Intent intent) {
-        WorkspaceItemInfo dummyInfo = new WorkspaceItemInfo();
-        dummyInfo.intent = intent;
-        dummyInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
-        dummyInfo.container = LauncherSettings.Favorites.CONTAINER_SETTINGS;
-        return dummyInfo;
+    static WorkspaceItemInfo placeholderInfo(Intent intent) {
+        WorkspaceItemInfo placeholderInfo = new WorkspaceItemInfo();
+        placeholderInfo.intent = intent;
+        placeholderInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
+        placeholderInfo.container = LauncherSettings.Favorites.CONTAINER_SETTINGS;
+        return placeholderInfo;
     }
 
     public static class OptionItem {
 
-        private final int mLabelRes;
-        private final int mIconRes;
-        private final EventEnum mEventId;
-        private final OnLongClickListener mClickListener;
+        public final int labelRes;
+        public final int iconRes;
+        public final EventEnum eventId;
+        public final OnLongClickListener clickListener;
 
         public OptionItem(int labelRes, int iconRes, EventEnum eventId,
                 OnLongClickListener clickListener) {
-            mLabelRes = labelRes;
-            mIconRes = iconRes;
-            mEventId = eventId;
-            mClickListener = clickListener;
+            this.labelRes = labelRes;
+            this.iconRes = iconRes;
+            this.eventId = eventId;
+            this.clickListener = clickListener;
         }
     }
 }
diff --git a/src/com/android/launcher3/views/ScrimView.java b/src/com/android/launcher3/views/ScrimView.java
index 22faf97..77cec80 100644
--- a/src/com/android/launcher3/views/ScrimView.java
+++ b/src/com/android/launcher3/views/ScrimView.java
@@ -15,119 +15,39 @@
  */
 package com.android.launcher3.views;
 
-import static android.content.Context.ACCESSIBILITY_SERVICE;
-import static android.view.MotionEvent.ACTION_DOWN;
-
 import static androidx.core.graphics.ColorUtils.compositeColors;
 
-import static com.android.launcher3.LauncherState.ALL_APPS;
-import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
-import static com.android.launcher3.anim.Interpolators.DEACCEL;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.anim.Interpolators.clampToProgress;
 import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_SCRIM_VIEW;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.Keyframe;
-import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
-import android.animation.RectEvaluator;
 import android.content.Context;
-import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Color;
-import android.graphics.Point;
 import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
 import android.util.AttributeSet;
-import android.util.IntProperty;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
 import android.view.View;
-import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
 
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 import androidx.core.graphics.ColorUtils;
-import androidx.core.view.ViewCompat;
-import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
-import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat;
-import androidx.customview.widget.ExploreByTouchHelper;
 
-import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Insettable;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.statemanager.StateManager;
-import com.android.launcher3.statemanager.StateManager.StateListener;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.uioverrides.WallpaperColorInfo;
 import com.android.launcher3.uioverrides.WallpaperColorInfo.OnChangeListener;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
-import com.android.launcher3.util.MultiValueAlpha;
-import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
 import com.android.launcher3.util.Themes;
-import com.android.launcher3.widget.WidgetsFullSheet;
-
-import java.util.List;
 
 /**
  * Simple scrim which draws a flat color
  */
-public class ScrimView<T extends Launcher> extends View implements Insettable, OnChangeListener,
-        AccessibilityStateChangeListener {
+public class ScrimView<T extends Launcher> extends View implements Insettable, OnChangeListener {
 
-    public static final IntProperty<ScrimView> DRAG_HANDLE_ALPHA =
-            new IntProperty<ScrimView>("dragHandleAlpha") {
-
-                @Override
-                public Integer get(ScrimView scrimView) {
-                    return scrimView.mDragHandleAlpha;
-                }
-
-                @Override
-                public void setValue(ScrimView scrimView, int value) {
-                    scrimView.setDragHandleAlpha(value);
-                }
-            };
-    private static final int WALLPAPERS = R.string.wallpaper_button_text;
-    private static final int WIDGETS = R.string.widget_button_text;
-    private static final int SETTINGS = R.string.settings_button_text;
-    private static final int ALPHA_CHANNEL_COUNT = 1;
-
-    private static final long DRAG_HANDLE_BOUNCE_DURATION_MS = 300;
-    // How much to delay before repeating the bounce.
-    private static final long DRAG_HANDLE_BOUNCE_DELAY_MS = 200;
-    // Repeat this many times (i.e. total number of bounces is 1 + this).
-    private static final int DRAG_HANDLE_BOUNCE_REPEAT_COUNT = 2;
-
-    private final Rect mTempRect = new Rect();
-    private final int[] mTempPos = new int[2];
-
+    private static final float SCRIM_ALPHA = .75f;
     protected final T mLauncher;
     private final WallpaperColorInfo mWallpaperColorInfo;
-    private final AccessibilityManager mAM;
     protected final int mEndScrim;
     protected final boolean mIsScrimDark;
 
-    private final StateListener<LauncherState> mAccessibilityLauncherStateListener =
-            new StateListener<LauncherState>() {
-        @Override
-        public void onStateTransitionComplete(LauncherState finalState) {
-            setImportantForAccessibility(finalState == ALL_APPS
-                    ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
-                    : IMPORTANT_FOR_ACCESSIBILITY_AUTO);
-        }
-    };
-
     protected float mMaxScrimAlpha;
 
     protected float mProgress = 1;
@@ -137,83 +57,36 @@
     protected int mEndFlatColor;
     protected int mEndFlatColorAlpha;
 
-    protected final Point mDragHandleSize;
-    private final int mDragHandleTouchSize;
-    private final int mDragHandlePaddingInVerticalBarLayout;
-    protected float mDragHandleOffset;
-    private final Rect mDragHandleBounds;
-    private final RectF mHitRect = new RectF();
-    private ObjectAnimator mDragHandleAnim;
-
-    private final MultiValueAlpha mMultiValueAlpha;
-
-    private final AccessibilityHelper mAccessibilityHelper;
-    @Nullable
-    protected Drawable mDragHandle;
-
-    private int mDragHandleAlpha = 255;
-
     public ScrimView(Context context, AttributeSet attrs) {
         super(context, attrs);
         mLauncher = Launcher.cast(Launcher.getLauncher(context));
         mWallpaperColorInfo = WallpaperColorInfo.INSTANCE.get(context);
-        mEndScrim = Themes.getAttrColor(context, R.attr.allAppsScrimColor);
+        int endScrim = Themes.getAttrColor(context, R.attr.allAppsScrimColor);
+        if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
+            endScrim = ColorUtils.setAlphaComponent(endScrim, (int) (255  * SCRIM_ALPHA));
+        }
+        mEndScrim = endScrim;
         mIsScrimDark = ColorUtils.calculateLuminance(mEndScrim) < 0.5f;
 
         mMaxScrimAlpha = 0.7f;
-
-        Resources res = context.getResources();
-        mDragHandleSize = new Point(res.getDimensionPixelSize(R.dimen.vertical_drag_handle_width),
-                res.getDimensionPixelSize(R.dimen.vertical_drag_handle_height));
-        mDragHandleBounds = new Rect(0, 0, mDragHandleSize.x, mDragHandleSize.y);
-        mDragHandleTouchSize = res.getDimensionPixelSize(R.dimen.vertical_drag_handle_touch_size);
-        mDragHandlePaddingInVerticalBarLayout = context.getResources()
-                .getDimensionPixelSize(R.dimen.vertical_drag_handle_padding_in_vertical_bar_layout);
-
-        mAccessibilityHelper = createAccessibilityHelper();
-        ViewCompat.setAccessibilityDelegate(this, mAccessibilityHelper);
-
-        mAM = (AccessibilityManager) context.getSystemService(ACCESSIBILITY_SERVICE);
         setFocusable(false);
-        mMultiValueAlpha = new MultiValueAlpha(this, ALPHA_CHANNEL_COUNT);
-    }
-
-    public AlphaProperty getAlphaProperty(int index) {
-        return mMultiValueAlpha.getProperty(index);
-    }
-
-    @NonNull
-    protected AccessibilityHelper createAccessibilityHelper() {
-        return new AccessibilityHelper();
     }
 
     @Override
-    public void setInsets(Rect insets) {
-        updateDragHandleBounds();
-        updateDragHandleVisibility();
-    }
+    public void setInsets(Rect insets) { }
 
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-        updateDragHandleBounds();
-    }
 
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         mWallpaperColorInfo.addOnChangeListener(this);
         onExtractedColorsChanged(mWallpaperColorInfo);
-
-        mAM.addAccessibilityStateChangeListener(this);
-        onAccessibilityStateChanged(mAM.isEnabled());
     }
 
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
         mWallpaperColorInfo.removeOnChangeListener(this);
-        mAM.removeAccessibilityStateChangeListener(this);
     }
 
     @Override
@@ -234,10 +107,8 @@
     public void setProgress(float progress) {
         if (mProgress != progress) {
             mProgress = progress;
-            stopDragHandleEducationAnim();
             updateColors();
             updateSysUiColors();
-            updateDragHandleAlpha();
             invalidate();
         }
     }
@@ -260,286 +131,10 @@
         }
     }
 
-    protected void updateDragHandleAlpha() {
-        if (mDragHandle != null) {
-            mDragHandle.setAlpha(mDragHandleAlpha);
-        }
-    }
-
-    private void setDragHandleAlpha(int alpha) {
-        if (alpha != mDragHandleAlpha) {
-            mDragHandleAlpha = alpha;
-            if (mDragHandle != null) {
-                mDragHandle.setAlpha(mDragHandleAlpha);
-                invalidate();
-            }
-        }
-    }
-
     @Override
     protected void onDraw(Canvas canvas) {
         if (mCurrentFlatColor != 0) {
             canvas.drawColor(mCurrentFlatColor);
         }
-        drawDragHandle(canvas);
-    }
-
-    protected void drawDragHandle(Canvas canvas) {
-        if (mDragHandle != null) {
-            canvas.translate(0, -mDragHandleOffset);
-            mDragHandle.draw(canvas);
-            canvas.translate(0, mDragHandleOffset);
-        }
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent event) {
-        boolean superHandledTouch = super.onTouchEvent(event);
-        if (event.getAction() == ACTION_DOWN) {
-            if (!superHandledTouch && mHitRect.contains(event.getX(), event.getY())) {
-                if (startDragHandleEducationAnim()) {
-                    return true;
-                }
-            }
-            stopDragHandleEducationAnim();
-        }
-        return superHandledTouch;
-    }
-
-    /**
-     * Animates the drag handle to demonstrate how to get to all apps.
-     * @return Whether the animation was started (false if drag handle is invisible).
-     */
-    public boolean startDragHandleEducationAnim() {
-        stopDragHandleEducationAnim();
-
-        if (mDragHandle == null || mDragHandle.getAlpha() != 255) {
-            return false;
-        }
-
-        final Drawable drawable = mDragHandle;
-        mDragHandle = null;
-
-        Rect bounds = new Rect(mDragHandleBounds);
-        bounds.offset(0, -(int) mDragHandleOffset);
-        drawable.setBounds(bounds);
-
-        Rect topBounds = new Rect(bounds);
-        topBounds.offset(0, -bounds.height());
-
-        Rect invalidateRegion = new Rect(bounds);
-        invalidateRegion.top = topBounds.top;
-
-        final float progressToReachTop = 0.6f;
-        Keyframe frameTop = Keyframe.ofObject(progressToReachTop, topBounds);
-        frameTop.setInterpolator(DEACCEL);
-        Keyframe frameBot = Keyframe.ofObject(1, bounds);
-        frameBot.setInterpolator(ACCEL_DEACCEL);
-        PropertyValuesHolder holder = PropertyValuesHolder.ofKeyframe("bounds",
-                Keyframe.ofObject(0, bounds), frameTop, frameBot);
-        holder.setEvaluator(new RectEvaluator());
-
-        mDragHandleAnim = ObjectAnimator.ofPropertyValuesHolder(drawable, holder);
-        long totalBounceDuration = DRAG_HANDLE_BOUNCE_DURATION_MS + DRAG_HANDLE_BOUNCE_DELAY_MS;
-        // The bounce finishes by this progress, the rest of the duration just delays next bounce.
-        float delayStartProgress = 1f - (float) DRAG_HANDLE_BOUNCE_DELAY_MS / totalBounceDuration;
-        mDragHandleAnim.addUpdateListener((v) -> invalidate(invalidateRegion));
-        mDragHandleAnim.setDuration(totalBounceDuration);
-        mDragHandleAnim.setInterpolator(clampToProgress(LINEAR, 0, delayStartProgress));
-        mDragHandleAnim.setRepeatCount(DRAG_HANDLE_BOUNCE_REPEAT_COUNT);
-        getOverlay().add(drawable);
-
-        mDragHandleAnim.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mDragHandleAnim = null;
-                getOverlay().remove(drawable);
-                updateDragHandleVisibility(drawable);
-            }
-        });
-        mDragHandleAnim.start();
-        return true;
-    }
-
-    private void stopDragHandleEducationAnim() {
-        if (mDragHandleAnim != null) {
-            mDragHandleAnim.end();
-        }
-    }
-
-    protected void updateDragHandleBounds() {
-        DeviceProfile grid = mLauncher.getDeviceProfile();
-        final int left;
-        final int width = getMeasuredWidth();
-        final int top = getMeasuredHeight() - mDragHandleSize.y - grid.getInsets().bottom;
-        final int topMargin;
-
-        if (grid.isVerticalBarLayout()) {
-            topMargin = grid.workspacePadding.bottom + mDragHandlePaddingInVerticalBarLayout;
-            if (grid.isSeascape()) {
-                left = width - grid.getInsets().right - mDragHandleSize.x
-                        - mDragHandlePaddingInVerticalBarLayout;
-            } else {
-                left = grid.getInsets().left + mDragHandlePaddingInVerticalBarLayout;
-            }
-        } else {
-            left = Math.round((width - mDragHandleSize.x) / 2f);
-            topMargin = grid.hotseatBarSizePx;
-        }
-        mDragHandleBounds.offsetTo(left, top - topMargin);
-        mHitRect.set(mDragHandleBounds);
-        // Inset outwards to increase touch size.
-        mHitRect.inset((mDragHandleSize.x - mDragHandleTouchSize) / 2f,
-                (mDragHandleSize.y - mDragHandleTouchSize) / 2f);
-
-        if (mDragHandle != null) {
-            mDragHandle.setBounds(mDragHandleBounds);
-        }
-    }
-
-    @Override
-    public void onAccessibilityStateChanged(boolean enabled) {
-        StateManager<LauncherState> stateManager = mLauncher.getStateManager();
-        stateManager.removeStateListener(mAccessibilityLauncherStateListener);
-
-        if (enabled) {
-            stateManager.addStateListener(mAccessibilityLauncherStateListener);
-            mAccessibilityLauncherStateListener.onStateTransitionComplete(stateManager.getState());
-        } else {
-            setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
-        }
-        updateDragHandleVisibility();
-    }
-
-    public void updateDragHandleVisibility() {
-        updateDragHandleVisibility(null);
-    }
-
-    private void updateDragHandleVisibility(@Nullable Drawable recycle) {
-        boolean visible = shouldDragHandleBeVisible();
-        boolean wasVisible = mDragHandle != null;
-        if (visible != wasVisible) {
-            if (visible) {
-                mDragHandle = recycle != null ? recycle :
-                        mLauncher.getDrawable(R.drawable.drag_handle_indicator_shadow);
-                mDragHandle.setBounds(mDragHandleBounds);
-
-                updateDragHandleAlpha();
-            } else {
-                mDragHandle = null;
-            }
-            invalidate();
-        }
-    }
-
-    protected boolean shouldDragHandleBeVisible() {
-        return mLauncher.getDeviceProfile().isVerticalBarLayout() || mAM.isEnabled();
-    }
-
-    @Override
-    public boolean dispatchHoverEvent(MotionEvent event) {
-        return mAccessibilityHelper.dispatchHoverEvent(event) || super.dispatchHoverEvent(event);
-    }
-
-    @Override
-    public boolean dispatchKeyEvent(KeyEvent event) {
-        return mAccessibilityHelper.dispatchKeyEvent(event) || super.dispatchKeyEvent(event);
-    }
-
-    @Override
-    public void onFocusChanged(boolean gainFocus, int direction,
-            Rect previouslyFocusedRect) {
-        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
-        mAccessibilityHelper.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
-    }
-
-    protected class AccessibilityHelper extends ExploreByTouchHelper {
-
-        private static final int DRAG_HANDLE_ID = 1;
-
-        public AccessibilityHelper() {
-            super(ScrimView.this);
-        }
-
-        @Override
-        protected int getVirtualViewAt(float x, float y) {
-            return  mHitRect.contains((int) x, (int) y)
-                    ? DRAG_HANDLE_ID : INVALID_ID;
-        }
-
-        @Override
-        protected void getVisibleVirtualViews(List<Integer> virtualViewIds) {
-            virtualViewIds.add(DRAG_HANDLE_ID);
-        }
-
-        @Override
-        protected void onPopulateNodeForVirtualView(int virtualViewId,
-                AccessibilityNodeInfoCompat node) {
-            node.setContentDescription(getContext().getString(R.string.all_apps_button_label));
-            node.setBoundsInParent(mDragHandleBounds);
-
-            getLocationOnScreen(mTempPos);
-            mTempRect.set(mDragHandleBounds);
-            mTempRect.offset(mTempPos[0], mTempPos[1]);
-            node.setBoundsInScreen(mTempRect);
-
-            node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
-            node.setClickable(true);
-            node.setFocusable(true);
-
-            if (mLauncher.isInState(NORMAL)) {
-                Context context = getContext();
-                if (Utilities.isWallpaperAllowed(context)) {
-                    node.addAction(
-                            new AccessibilityActionCompat(WALLPAPERS, context.getText(WALLPAPERS)));
-                }
-                node.addAction(new AccessibilityActionCompat(WIDGETS, context.getText(WIDGETS)));
-                node.addAction(new AccessibilityActionCompat(SETTINGS, context.getText(SETTINGS)));
-            }
-        }
-
-        @Override
-        protected boolean onPerformActionForVirtualView(
-                int virtualViewId, int action, Bundle arguments) {
-            if (action == AccessibilityNodeInfoCompat.ACTION_CLICK) {
-                mLauncher.getUserEventDispatcher().logActionOnControl(
-                        Action.Touch.TAP, ControlType.ALL_APPS_BUTTON,
-                        mLauncher.getStateManager().getState().containerType);
-                mLauncher.getStateManager().goToState(ALL_APPS);
-                return true;
-            } else if (action == WALLPAPERS) {
-                return OptionsPopupView.startWallpaperPicker(ScrimView.this);
-            } else if (action == WIDGETS) {
-                int originalImportanceForAccessibility = getImportantForAccessibility();
-                setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
-                WidgetsFullSheet widgetsFullSheet = OptionsPopupView.openWidgets(mLauncher);
-                if (widgetsFullSheet == null) {
-                    setImportantForAccessibility(originalImportanceForAccessibility);
-                    return false;
-                }
-                widgetsFullSheet.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
-                    @Override
-                    public void onViewAttachedToWindow(View view) {}
-
-                    @Override
-                    public void onViewDetachedFromWindow(View view) {
-                        setImportantForAccessibility(originalImportanceForAccessibility);
-                        widgetsFullSheet.removeOnAttachStateChangeListener(this);
-                    }
-                });
-                return true;
-            } else if (action == SETTINGS) {
-                return OptionsPopupView.startSettings(ScrimView.this);
-            }
-
-            return false;
-        }
-    }
-
-    /**
-     * @return The top of this scrim view, or {@link Float#MAX_VALUE} if there's no distinct top.
-     */
-    public float getVisualTop() {
-        return Float.MAX_VALUE;
     }
 }
diff --git a/src/com/android/launcher3/views/SearchResultPlayItem.java b/src/com/android/launcher3/views/SearchResultPlayItem.java
new file mode 100644
index 0000000..19a4c5d
--- /dev/null
+++ b/src/com/android/launcher3/views/SearchResultPlayItem.java
@@ -0,0 +1,154 @@
+/*
+ * 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.views;
+
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
+import com.android.launcher3.allapps.search.AllAppsSearchBarController;
+
+import java.io.IOException;
+import java.net.URL;
+
+/**
+ * A View representing a PlayStore item.
+ */
+public class SearchResultPlayItem extends LinearLayout implements
+        AllAppsSearchBarController.PayloadResultHandler<Bundle> {
+    private final DeviceProfile mDeviceProfile;
+    private View mIconView;
+    private TextView mTitleView;
+    private TextView[] mDetailViews = new TextView[3];
+    private Button mPreviewButton;
+    private String mPackageName;
+    private boolean mIsInstantGame;
+
+    public SearchResultPlayItem(Context context) {
+        this(context, null, 0);
+    }
+
+    public SearchResultPlayItem(Context context,
+            @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public SearchResultPlayItem(Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        mDeviceProfile = Launcher.getLauncher(getContext()).getDeviceProfile();
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mIconView = findViewById(R.id.icon);
+        mTitleView = findViewById(R.id.title_view);
+        mPreviewButton = findViewById(R.id.try_button);
+        mPreviewButton.setOnClickListener(view -> launchInstantGame());
+        mDetailViews[0] = findViewById(R.id.detail_0);
+        mDetailViews[1] = findViewById(R.id.detail_1);
+        mDetailViews[2] = findViewById(R.id.detail_2);
+
+        ViewGroup.LayoutParams iconParams = mIconView.getLayoutParams();
+        iconParams.height = mDeviceProfile.allAppsIconSizePx;
+        iconParams.width = mDeviceProfile.allAppsIconSizePx;
+        setOnClickListener(view -> handleSelection());
+
+    }
+
+    @Override
+    public void applyAdapterInfo(AdapterItemWithPayload<Bundle> adapterItemWithPayload) {
+        Bundle bundle = adapterItemWithPayload.getPayload();
+        adapterItemWithPayload.setSelectionHandler(this::handleSelection);
+        if (bundle.getString("package", "").equals(mPackageName)) {
+            return;
+        }
+        mIsInstantGame = bundle.getBoolean("instant_game", false);
+        mPackageName = bundle.getString("package");
+        mPreviewButton.setVisibility(mIsInstantGame ? VISIBLE : GONE);
+        mTitleView.setText(bundle.getString("title"));
+//        TODO: Should use a generic type to get values b/165320033
+        showIfNecessary(mDetailViews[0], bundle.getString("price"));
+        showIfNecessary(mDetailViews[1], bundle.getString("rating"));
+        showIfNecessary(mDetailViews[2], bundle.getString("category"));
+
+        mIconView.setBackgroundResource(R.drawable.ic_deepshortcut_placeholder);
+        UI_HELPER_EXECUTOR.execute(() -> {
+            try {
+//                TODO: Handle caching
+                URL url = new URL(bundle.getString("icon_url"));
+                Bitmap bitmap = BitmapFactory.decodeStream(url.openStream());
+                BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(),
+                        Bitmap.createScaledBitmap(bitmap, mDeviceProfile.allAppsIconSizePx,
+                                mDeviceProfile.allAppsIconSizePx, false));
+                mIconView.post(() -> mIconView.setBackground(bitmapDrawable));
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        });
+    }
+
+    private void showIfNecessary(TextView textView, @Nullable String string) {
+        if (string == null || string.isEmpty()) {
+            textView.setVisibility(GONE);
+        } else {
+            textView.setText(string);
+            textView.setVisibility(VISIBLE);
+        }
+    }
+
+    private void handleSelection() {
+        if (mPackageName == null) return;
+        Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(
+                "https://play.google.com/store/apps/details?id="
+                        + mPackageName));
+        i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        getContext().startActivity(i);
+    }
+
+    private void launchInstantGame() {
+        if (!mIsInstantGame) return;
+        Intent intent = new Intent(Intent.ACTION_VIEW);
+        String referrer = "Pixel_Launcher";
+        String id = mPackageName;
+        String deepLinkUrl = "market://details?id=" + id + "&launch=true&referrer=" + referrer;
+        intent.setPackage("com.android.vending");
+        intent.setData(Uri.parse(deepLinkUrl));
+        intent.putExtra("overlay", true);
+        intent.putExtra("callerId", getContext().getPackageName());
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        getContext().startActivity(intent);
+    }
+}
diff --git a/src/com/android/launcher3/views/SearchSectionHeaderView.java b/src/com/android/launcher3/views/SearchSectionHeaderView.java
new file mode 100644
index 0000000..d439ee3
--- /dev/null
+++ b/src/com/android/launcher3/views/SearchSectionHeaderView.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 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.views;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.allapps.AllAppsGridAdapter;
+import com.android.launcher3.allapps.search.AllAppsSearchBarController;
+
+/**
+ * Header text view that shows a title for a given section in All apps search
+ */
+public class SearchSectionHeaderView extends TextView implements
+        AllAppsSearchBarController.PayloadResultHandler<String> {
+    public SearchSectionHeaderView(Context context) {
+        super(context);
+    }
+
+    public SearchSectionHeaderView(Context context,
+            @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public SearchSectionHeaderView(Context context, @Nullable AttributeSet attrs, int styleAttr) {
+        super(context, attrs, styleAttr);
+    }
+
+    @Override
+    public void applyAdapterInfo(AllAppsGridAdapter.AdapterItemWithPayload<String> adapterItem) {
+        String title = adapterItem.getPayload();
+        if (title == null || !title.isEmpty()) {
+            setText(title);
+            setVisibility(VISIBLE);
+        } else {
+            setVisibility(INVISIBLE);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/views/SearchSettingsRowView.java b/src/com/android/launcher3/views/SearchSettingsRowView.java
new file mode 100644
index 0000000..08c78ff
--- /dev/null
+++ b/src/com/android/launcher3/views/SearchSettingsRowView.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2020 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.views;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.allapps.AllAppsGridAdapter;
+import com.android.launcher3.allapps.search.AllAppsSearchBarController;
+
+import java.util.ArrayList;
+
+/**
+ * A row of tappable TextViews with a breadcrumb for settings search.
+ */
+public class SearchSettingsRowView extends LinearLayout implements
+        View.OnClickListener, AllAppsSearchBarController.PayloadResultHandler<Bundle> {
+
+    private TextView mTitleView;
+    private TextView mDescriptionView;
+    private TextView mBreadcrumbsView;
+    private Intent mIntent;
+
+    public SearchSettingsRowView(@NonNull Context context) {
+        super(context);
+    }
+
+    public SearchSettingsRowView(@NonNull Context context,
+            @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public SearchSettingsRowView(@NonNull Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mTitleView = findViewById(R.id.title);
+        mDescriptionView = findViewById(R.id.description);
+        mBreadcrumbsView = findViewById(R.id.breadcrumbs);
+        setOnClickListener(this);
+    }
+
+    @Override
+    public void applyAdapterInfo(
+            AllAppsGridAdapter.AdapterItemWithPayload<Bundle> adapterItemWithPayload) {
+        Bundle bundle = adapterItemWithPayload.getPayload();
+        mIntent = bundle.getParcelable("intent");
+        showIfAvailable(mTitleView, bundle.getString("title"));
+        showIfAvailable(mDescriptionView, bundle.getString("description"));
+        ArrayList<String> breadcrumbs = bundle.getStringArrayList("breadcrumbs");
+        //TODO: implement RTL friendly breadcrumbs view
+        showIfAvailable(mBreadcrumbsView, breadcrumbs != null
+                ? String.join(" > ", breadcrumbs) : null);
+        adapterItemWithPayload.setSelectionHandler(() -> onClick(this));
+    }
+
+    private void showIfAvailable(TextView view, @Nullable String string) {
+        if (TextUtils.isEmpty(string)) {
+            view.setVisibility(GONE);
+        } else {
+            view.setVisibility(VISIBLE);
+            view.setText(string);
+        }
+    }
+
+    @Override
+    public void onClick(View view) {
+        if (mIntent == null) return;
+        // TODO: create ItemInfo object and then use it to call startActivityForResult for proper
+        //  WW logging
+        Launcher launcher = Launcher.getLauncher(view.getContext());
+        launcher.startActivityForResult(mIntent, 0);
+    }
+}
diff --git a/src/com/android/launcher3/views/Snackbar.java b/src/com/android/launcher3/views/Snackbar.java
index 513ce59..49fcd2e 100644
--- a/src/com/android/launcher3/views/Snackbar.java
+++ b/src/com/android/launcher3/views/Snackbar.java
@@ -167,11 +167,6 @@
     }
 
     @Override
-    public void logActionCommand(int command) {
-        // TODO
-    }
-
-    @Override
     protected boolean isOfType(int type) {
         return (type & TYPE_SNACKBAR) != 0;
     }
diff --git a/src/com/android/launcher3/views/WorkEduView.java b/src/com/android/launcher3/views/WorkEduView.java
index d35a38f..d6737db 100644
--- a/src/com/android/launcher3/views/WorkEduView.java
+++ b/src/com/android/launcher3/views/WorkEduView.java
@@ -38,7 +38,6 @@
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.statemanager.StateManager.StateListener;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
 
 /**
  * On boarding flow for users right after setting up work profile
@@ -89,16 +88,6 @@
     }
 
     @Override
-    public void logActionCommand(int command) {
-        // Since this is on-boarding popup, it is not a user controlled action.
-    }
-
-    @Override
-    public int getLogContainerType() {
-        return LauncherLogProto.ContainerType.TIP;
-    }
-
-    @Override
     protected boolean isOfType(int type) {
         return (type & TYPE_ON_BOARD_POPUP) != 0;
     }
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index 3e5113a..01af96c 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -16,7 +16,6 @@
 package com.android.launcher3.widget;
 
 import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
-import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
 
 import android.content.Context;
 import android.graphics.Point;
@@ -31,20 +30,15 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.popup.PopupDataProvider;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.ItemLongClickListener;
 import com.android.launcher3.uioverrides.WallpaperColorInfo;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.AbstractSlideInView;
 
-import java.util.ArrayList;
-
 /**
  * Base class for various widgets popup
  */
@@ -149,28 +143,6 @@
                 isSheetDark ? SystemUiController.FLAG_DARK_NAV : SystemUiController.FLAG_LIGHT_NAV);
     }
 
-    @Override
-    public void fillInLogContainerData(ItemInfo childInfo, Target child,
-            ArrayList<Target> parents) {
-        Target target = newContainerTarget(ContainerType.WIDGETS);
-        target.cardinality = getElementsRowCount();
-        parents.add(target);
-    }
-
-    @Override
-    public final void logActionCommand(int command) {
-        Target target = newContainerTarget(getLogContainerType());
-        target.cardinality = getElementsRowCount();
-        mLauncher.getUserEventDispatcher().logActionCommand(command, target);
-    }
-
-    @Override
-    public int getLogContainerType() {
-        return ContainerType.WIDGETS;
-    }
-
-    protected abstract int getElementsRowCount();
-
     protected SystemUiController getSystemUiController() {
         return mLauncher.getSystemUiController();
     }
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index 6f2e179..780a1a1 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -80,9 +80,7 @@
         setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
         setBackgroundResource(R.drawable.widget_internal_focus_bg);
 
-        if (Utilities.ATLEAST_OREO) {
-            setExecutor(Executors.THREAD_POOL_EXECUTOR);
-        }
+        setExecutor(Executors.THREAD_POOL_EXECUTOR);
         if (Utilities.ATLEAST_Q && Themes.getAttrBoolean(mLauncher, R.attr.isWorkspaceDarkText)) {
             setOnLightBackground(true);
         }
diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
index 9021d9e..ca47728 100644
--- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
@@ -52,7 +52,6 @@
     private static final float MIN_SATUNATION = 0.7f;
 
     private final Rect mRect = new Rect();
-    private View mDefaultView;
     private OnClickListener mClickListener;
     private final LauncherAppWidgetInfo mInfo;
     private final int mStartState;
@@ -111,12 +110,11 @@
 
     @Override
     protected View getDefaultView() {
-        if (mDefaultView == null) {
-            mDefaultView = mInflater.inflate(R.layout.appwidget_not_ready, this, false);
-            mDefaultView.setOnClickListener(this);
-            applyState();
-        }
-        return mDefaultView;
+        View defaultView = mInflater.inflate(R.layout.appwidget_not_ready, this, false);
+        defaultView.setOnClickListener(this);
+        applyState();
+        invalidate();
+        return defaultView;
     }
 
     @Override
diff --git a/src/com/android/launcher3/widget/WidgetManagerHelper.java b/src/com/android/launcher3/widget/WidgetManagerHelper.java
index 4b6c569..c0c5c48 100644
--- a/src/com/android/launcher3/widget/WidgetManagerHelper.java
+++ b/src/com/android/launcher3/widget/WidgetManagerHelper.java
@@ -23,13 +23,11 @@
 import android.content.Context;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.Process;
 import android.os.UserHandle;
 
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.pm.UserCache;
@@ -84,22 +82,8 @@
             return allWidgetsSteam(mContext).collect(Collectors.toList());
         }
 
-        if (Utilities.ATLEAST_OREO) {
-            return mAppWidgetManager.getInstalledProvidersForPackage(
-                    packageUser.mPackageName, packageUser.mUser);
-        }
-
-        String pkg = packageUser.mPackageName;
-        return Stream.concat(
-                // Only get providers for the given package/user.
-                mAppWidgetManager.getInstalledProvidersForProfile(packageUser.mUser)
-                        .stream()
-                        .filter(w -> w.provider.equals(pkg)),
-                Process.myUserHandle().equals(packageUser.mUser)
-                        && mContext.getPackageName().equals(pkg)
-                        ? CustomWidgetManager.INSTANCE.get(mContext).stream()
-                        : Stream.empty())
-                .collect(Collectors.toList());
+        return mAppWidgetManager.getInstalledProvidersForPackage(
+                packageUser.mPackageName, packageUser.mUser);
     }
 
     /**
diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
index 30be7a6..3585a18 100644
--- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
@@ -180,11 +180,6 @@
     }
 
     @Override
-    protected int getElementsRowCount() {
-        return 1;
-    }
-
-    @Override
     protected Pair<View, String> getAccessibilityTarget() {
         return Pair.create(findViewById(R.id.title),  getContext().getString(
                 mIsOpen ? R.string.widgets_list : R.string.widgets_list_closed));
diff --git a/src/com/android/launcher3/widget/WidgetsFullSheet.java b/src/com/android/launcher3/widget/WidgetsFullSheet.java
index 68a3ec5..4c8c339 100644
--- a/src/com/android/launcher3/widget/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsFullSheet.java
@@ -243,11 +243,6 @@
     }
 
     @Override
-    protected int getElementsRowCount() {
-        return mAdapter.getItemCount();
-    }
-
-    @Override
     public void addHintCloseAnim(
             float distanceToMove, Interpolator interpolator, PendingAnimation target) {
         target.setFloat(mRecyclerView, VIEW_TRANSLATE_Y, -distanceToMove, interpolator);
diff --git a/src/com/android/launcher3/widget/WidgetsListAdapter.java b/src/com/android/launcher3/widget/WidgetsListAdapter.java
index a45521d..5bf9690 100644
--- a/src/com/android/launcher3/widget/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/WidgetsListAdapter.java
@@ -23,9 +23,12 @@
 import android.view.View.OnLongClickListener;
 import android.view.ViewGroup;
 
-import com.android.launcher3.icons.IconCache;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.Adapter;
+
 import com.android.launcher3.R;
 import com.android.launcher3.WidgetPreviewLoader;
+import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.util.LabelComparator;
 
@@ -34,9 +37,6 @@
 import java.util.Comparator;
 import java.util.List;
 
-import androidx.recyclerview.widget.RecyclerView;
-import androidx.recyclerview.widget.RecyclerView.Adapter;
-
 /**
  * List view adapter for the widget tray.
  *
@@ -128,7 +128,7 @@
         int childCount = row.getChildCount();
 
         if (expectedChildCount > childCount) {
-            for (int i = childCount ; i < expectedChildCount; i++) {
+            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);
@@ -144,24 +144,24 @@
                 }
             }
         } else if (expectedChildCount < childCount) {
-            for (int i = expectedChildCount ; i < childCount; i++) {
+            for (int i = expectedChildCount; i < childCount; i++) {
                 row.getChildAt(i).setVisibility(View.GONE);
             }
         }
 
         // Bind the views in the application info section.
-        holder.title.applyFromPackageItemInfo(entry.pkgItem);
+        holder.title.applyFromItemInfoWithIcon(entry.pkgItem);
 
         // Bind the view in the widget horizontal tray region.
-        for (int i=0; i < infoList.size(); i++) {
-            WidgetCell widget = (WidgetCell) row.getChildAt(2*i);
+        for (int i = 0; i < infoList.size(); i++) {
+            WidgetCell widget = (WidgetCell) row.getChildAt(2 * i);
             widget.applyFromCellItem(infoList.get(i), mWidgetPreviewLoader);
             widget.setApplyBitmapDeferred(mApplyBitmapDeferred);
             widget.ensurePreview();
             widget.setVisibility(View.VISIBLE);
 
             if (i > 0) {
-                row.getChildAt(2*i - 1).setVisibility(View.VISIBLE);
+                row.getChildAt(2 * i - 1).setVisibility(View.VISIBLE);
             }
         }
     }
@@ -185,7 +185,7 @@
     @Override
     public void onViewRecycled(WidgetsRowViewHolder holder) {
         int total = holder.cellContainer.getChildCount();
-        for (int i = 0; i < total; i+=2) {
+        for (int i = 0; i < total; i += 2) {
             WidgetCell widget = (WidgetCell) holder.cellContainer.getChildAt(i);
             widget.clear();
         }
diff --git a/src/com/android/launcher3/widget/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
index 82d4110..69de12b 100644
--- a/src/com/android/launcher3/widget/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
@@ -19,14 +19,11 @@
 import android.content.Context;
 import android.graphics.Point;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.view.MotionEvent;
 import android.view.View;
 
 import com.android.launcher3.BaseRecyclerView;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.testing.TestProtocol;
 
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
@@ -158,7 +155,8 @@
                     mScrollbar.isHitInParent(e.getX(), e.getY(), mFastScrollerOffset);
         }
         if (mTouchDownOnScroller) {
-            return mScrollbar.handleTouchEvent(e, mFastScrollerOffset);
+            final boolean result = mScrollbar.handleTouchEvent(e, mFastScrollerOffset);
+            return result;
         }
         return false;
     }
diff --git a/src_plugins/com/android/systemui/plugins/AllAppsSearchPlugin.java b/src_plugins/com/android/systemui/plugins/AllAppsSearchPlugin.java
index c57f07d..be20e2d 100644
--- a/src_plugins/com/android/systemui/plugins/AllAppsSearchPlugin.java
+++ b/src_plugins/com/android/systemui/plugins/AllAppsSearchPlugin.java
@@ -16,35 +16,28 @@
 
 package com.android.systemui.plugins;
 
-import android.app.Activity;
-import android.view.ViewGroup;
-import android.widget.EditText;
+import android.os.Bundle;
 
 import com.android.systemui.plugins.annotations.ProvidesInterface;
 
+import java.util.List;
+import java.util.function.Consumer;
+
 /**
- * Implement this plugin interface to replace the all apps recycler view of the all apps drawer.
+ * Implement this plugin interface to fetch search result data from the plugin side.
  */
 @ProvidesInterface(action = AllAppsSearchPlugin.ACTION, version = AllAppsSearchPlugin.VERSION)
 public interface AllAppsSearchPlugin extends Plugin {
     String ACTION = "com.android.systemui.action.PLUGIN_ALL_APPS_SEARCH_ACTIONS";
-    int VERSION = 3;
-
-    /** Following are the order that these methods should be called. */
-    void setup(ViewGroup parent, Activity activity, float allAppsContainerHeight);
+    int VERSION = 4;
 
     /**
-     * When drag starts, pass window inset related fields and the progress to indicate
-     * whether user is swiping down or swiping up
+     * Send signal when user starts typing.
      */
-    void onDragStart(float progress);
+    void startedTyping();
 
-    /** progress is between [0, 1] 1: down, 0: up */
-    void setProgress(float progress);
-
-    /** Called when container animation stops, so that plugin can perform cleanups */
-    void onAnimationEnd(float progress);
-
-    /** pass over the search box object */
-    void setEditText(EditText editText);
+    /**
+     * Send over the query and get the search results.
+     */
+    void performSearch(String query, Consumer<List<Bundle>> results);
 }
diff --git a/src_plugins/com/android/systemui/plugins/AppLaunchEventsPlugin.java b/src_plugins/com/android/systemui/plugins/AppLaunchEventsPlugin.java
deleted file mode 100644
index 15a0ffa..0000000
--- a/src_plugins/com/android/systemui/plugins/AppLaunchEventsPlugin.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2019 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.systemui.plugins;
-
-import android.content.ComponentName;
-import android.os.UserHandle;
-
-import com.android.systemui.plugins.annotations.ProvidesInterface;
-
-/**
- * Plugin interface which sends app launch events.
- */
-@ProvidesInterface(action = AppLaunchEventsPlugin.ACTION, version = AppLaunchEventsPlugin.VERSION)
-public interface AppLaunchEventsPlugin extends Plugin {
-    String ACTION = "com.android.systemui.action.PLUGIN_APP_EVENTS";
-    int VERSION = 1;
-
-    /**
-     * Receives onStartShortcut event from
-     * {@link com.android.launcher3.appprediction.PredictionAppTracker}.
-     */
-    void onStartShortcut(String packageName, String shortcutId, UserHandle user, String container);
-
-    /**
-     * Receives onStartApp event from
-     * {@link com.android.launcher3.appprediction.PredictionAppTracker}.
-     */
-    void onStartApp(ComponentName componentName, UserHandle user, String container);
-
-    /**
-     * Receives onDismissApp event from
-     * {@link com.android.launcher3.appprediction.PredictionAppTracker}.
-     */
-    void onDismissApp(ComponentName componentName, UserHandle user, String container);
-
-    /**
-     * Receives onReturnedToHome event from
-     * {@link com.android.launcher3.appprediction.PredictionAppTracker}.
-     */
-    void onReturnedToHome();
-}
diff --git a/src_plugins/com/android/systemui/plugins/UserEventPlugin.java b/src_plugins/com/android/systemui/plugins/UserEventPlugin.java
deleted file mode 100644
index 0e3664a..0000000
--- a/src_plugins/com/android/systemui/plugins/UserEventPlugin.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2019 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.systemui.plugins;
-
-import com.android.systemui.plugins.annotations.ProvidesInterface;
-
-/**
- * Implement this plugin interface to access user event log on the device for prototype purpose.
- * NOTE: plugin is for internal prototype only and is not visible in production environment.
- */
-@ProvidesInterface(action = UserEventPlugin.ACTION, version = UserEventPlugin.VERSION)
-public interface UserEventPlugin extends Plugin {
-    String ACTION = "com.android.launcher3.action.PLUGIN_USER_EVENT_LOG";
-    int VERSION = 1;
-
-    /**
-     * Callback to be triggered whenever an user event occurs.
-     */
-    void onUserEvent(Object event);
-}
diff --git a/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java b/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java
index dcb4636..269af7b 100644
--- a/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java
+++ b/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java
@@ -21,7 +21,6 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.LooperExecutor;
 import com.android.launcher3.widget.WidgetListRowEntry;
 
 import java.util.ArrayList;
@@ -34,12 +33,7 @@
 
     public LoaderResults(LauncherAppState app, BgDataModel dataModel,
             AllAppsList allAppsList, Callbacks[] callbacks) {
-        this(app, dataModel, allAppsList, callbacks, MAIN_EXECUTOR);
-    }
-
-    public LoaderResults(LauncherAppState app, BgDataModel dataModel,
-            AllAppsList allAppsList, Callbacks[] callbacks, LooperExecutor executor) {
-        super(app, dataModel, allAppsList, callbacks, executor);
+        super(app, dataModel, allAppsList, callbacks, MAIN_EXECUTOR);
     }
 
     @Override
diff --git a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
index 9d87788..34ebbac 100644
--- a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
+++ b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
@@ -26,7 +26,6 @@
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.model.data.PackageItemInfo;
 import com.android.launcher3.pm.ShortcutConfigActivityInfo;
-import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.widget.WidgetItemComparator;
@@ -36,11 +35,12 @@
 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.Map.Entry;
 import java.util.Set;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
 
 /**
  * Widgets data model that is used by the adapters of the widget views and controllers.
@@ -51,14 +51,13 @@
 
     // True is the widget support is disabled.
     public static final boolean GO_DISABLE_WIDGETS = false;
+    public static final boolean GO_DISABLE_NOTIFICATION_DOTS = false;
 
     private static final String TAG = "WidgetsModel";
     private static final boolean DEBUG = false;
 
     /* Map of widgets and shortcuts that are tracked per package. */
-    private final MultiHashMap<PackageItemInfo, WidgetItem> mWidgetsList = new MultiHashMap<>();
-
-    private AppFilter mAppFilter;
+    private final Map<PackageItemInfo, List<WidgetItem>> mWidgetsList = new HashMap<>();
 
     /**
      * Returns a list of {@link WidgetListRowEntry}. All {@link WidgetItem} in a single row
@@ -73,8 +72,9 @@
         AlphabeticIndexCompat indexer = new AlphabeticIndexCompat(context);
 
         WidgetItemComparator widgetComparator = new WidgetItemComparator();
-        for (Map.Entry<PackageItemInfo, ArrayList<WidgetItem>> entry : mWidgetsList.entrySet()) {
-            WidgetListRowEntry row = new WidgetListRowEntry(entry.getKey(), entry.getValue());
+        for (Map.Entry<PackageItemInfo, List<WidgetItem>> entry : mWidgetsList.entrySet()) {
+            WidgetListRowEntry row = new WidgetListRowEntry(
+                    entry.getKey(), new ArrayList<>(entry.getValue()));
             row.titleSectionName = (row.pkgItem.title == null) ? "" :
                     indexer.computeSectionName(row.pkgItem.title);
             Collections.sort(row.widgets, widgetComparator);
@@ -145,77 +145,42 @@
         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;
-                }
-            }
+            PackageItemInfo packageItem = mWidgetsList.keySet()
+                    .stream()
+                    .filter(item -> item.packageName.equals(packageUser.mPackageName))
+                    .findFirst()
+                    .orElse(null);
             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);
 
-                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();
-                    }
-                }
+                // Add the widgets for other users in the rawList as it only contains widgets for
+                // packageUser
+                List<WidgetItem> otherUserItems = mWidgetsList.remove(packageItem);
+                otherUserItems.removeIf(w -> w.user.equals(packageUser.mUser));
+                rawWidgetsShortcuts.addAll(otherUserItems);
             }
         }
 
-        InvariantDeviceProfile idp = app.getInvariantDeviceProfile();
         UserHandle myUser = Process.myUserHandle();
 
         // add and update.
-        for (WidgetItem item : rawWidgetsShortcuts) {
-            if (item.widgetInfo != null) {
-                if ((item.widgetInfo.getWidgetFeatures() & WIDGET_FEATURE_HIDE_FROM_PICKER) != 0) {
-                    // Widget is hidden from picker
-                    continue;
-                }
-
-                // 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);
-                int minSpanY = Math.min(item.widgetInfo.spanY, item.widgetInfo.minSpanY);
-                if (minSpanX > idp.numColumns || minSpanY > idp.numRows) {
-                    if (DEBUG) {
-                        Log.d(TAG, String.format(
-                                "Widget %s : (%d X %d) can't fit on this device",
-                                item.componentName, minSpanX, minSpanY));
+        mWidgetsList.putAll(rawWidgetsShortcuts.stream()
+                .filter(new WidgetValidityCheck(app))
+                .collect(Collectors.groupingBy(item -> {
+                    String packageName = item.componentName.getPackageName();
+                    PackageItemInfo pInfo = tmpPackageItemInfos.get(packageName);
+                    if (pInfo == null) {
+                        pInfo = new PackageItemInfo(packageName);
+                        pInfo.user = item.user;
+                        tmpPackageItemInfos.put(packageName,  pInfo);
+                    } else if (!myUser.equals(pInfo.user)) {
+                        // Keep updating the user, until we get the primary user.
+                        pInfo.user = item.user;
                     }
-                    continue;
-                }
-            }
-
-            if (mAppFilter == null) {
-                mAppFilter = AppFilter.newInstance(app.getContext());
-            }
-            if (!mAppFilter.shouldShowApp(item.componentName)) {
-                if (DEBUG) {
-                    Log.d(TAG, String.format("%s is filtered and not added to the widget tray.",
-                            item.componentName));
-                }
-                continue;
-            }
-
-            String packageName = item.componentName.getPackageName();
-            PackageItemInfo pInfo = tmpPackageItemInfos.get(packageName);
-            if (pInfo == null) {
-                pInfo = new PackageItemInfo(packageName);
-                pInfo.user = item.user;
-                tmpPackageItemInfos.put(packageName,  pInfo);
-            } else if (!myUser.equals(pInfo.user)) {
-                // Keep updating the user, until we get the primary user.
-                pInfo.user = item.user;
-            }
-            mWidgetsList.addToList(pInfo, item);
-        }
+                    return pInfo;
+                })));
 
         // Update each package entry
         IconCache iconCache = app.getIconCache();
@@ -226,9 +191,9 @@
 
     public void onPackageIconsUpdated(Set<String> packageNames, UserHandle user,
             LauncherAppState app) {
-        for (Entry<PackageItemInfo, ArrayList<WidgetItem>> entry : mWidgetsList.entrySet()) {
+        for (Entry<PackageItemInfo, List<WidgetItem>> entry : mWidgetsList.entrySet()) {
             if (packageNames.contains(entry.getKey().packageName)) {
-                ArrayList<WidgetItem> items = entry.getValue();
+                List<WidgetItem> items = entry.getValue();
                 int count = items.size();
                 for (int i = 0; i < count; i++) {
                     WidgetItem item = items.get(i);
@@ -248,7 +213,7 @@
 
     public WidgetItem getWidgetProviderInfoByProviderName(
             ComponentName providerName) {
-        ArrayList<WidgetItem> widgetsList = mWidgetsList.get(
+        List<WidgetItem> widgetsList = mWidgetsList.get(
                 new PackageItemInfo(providerName.getPackageName()));
         if (widgetsList == null) {
             return null;
@@ -261,4 +226,46 @@
         }
         return null;
     }
+
+    private static class WidgetValidityCheck implements Predicate<WidgetItem> {
+
+        private final InvariantDeviceProfile mIdp;
+        private final AppFilter mAppFilter;
+
+        WidgetValidityCheck(LauncherAppState app) {
+            mIdp = app.getInvariantDeviceProfile();
+            mAppFilter = AppFilter.newInstance(app.getContext());
+        }
+
+        @Override
+        public boolean test(WidgetItem item) {
+            if (item.widgetInfo != null) {
+                if ((item.widgetInfo.getWidgetFeatures() & WIDGET_FEATURE_HIDE_FROM_PICKER) != 0) {
+                    // Widget is hidden from picker
+                    return false;
+                }
+
+                // 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);
+                int minSpanY = Math.min(item.widgetInfo.spanY, item.widgetInfo.minSpanY);
+                if (minSpanX > mIdp.numColumns || minSpanY > mIdp.numRows) {
+                    if (DEBUG) {
+                        Log.d(TAG, String.format(
+                                "Widget %s : (%d X %d) can't fit on this device",
+                                item.componentName, minSpanX, minSpanY));
+                    }
+                    return false;
+                }
+            }
+            if (!mAppFilter.shouldShowApp(item.componentName)) {
+                if (DEBUG) {
+                    Log.d(TAG, String.format("%s is filtered and not added to the widget tray.",
+                            item.componentName));
+                }
+                return false;
+            }
+
+            return true;
+        }
+    }
 }
\ No newline at end of file
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/ColorExtractionAlgorithm.java b/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/ColorExtractionAlgorithm.java
index 5a1f9ca..780a0f0 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/ColorExtractionAlgorithm.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/ColorExtractionAlgorithm.java
@@ -21,16 +21,16 @@
 import android.util.Pair;
 import android.util.Range;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.graphics.ColorUtils;
+
 import com.android.launcher3.Utilities;
 
 import java.util.Arrays;
 import java.util.LinkedList;
 import java.util.List;
 
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.graphics.ColorUtils;
-
 /**
  * Implementation of tonal color extraction
  **/
@@ -69,7 +69,7 @@
         // palettes. The best fit is tweaked to be closer to the source color
         // and replaces the original palette
 
-        // Get the most preeminent, non-blacklisted color.
+        // Get the most preeminent, non-disallowed color.
         Integer bestColor = 0;
         final float[] hsl = new float[3];
         for (int i = 0; i < mainColorsSize; i++) {
@@ -78,7 +78,7 @@
                     Color.blue(colorValue), hsl);
 
             // Stop when we find a color that meets our criteria
-            if (!isBlacklisted(hsl)) {
+            if (!isDisallowed(hsl)) {
                 bestColor = colorValue;
                 break;
             }
@@ -167,12 +167,12 @@
     }
 
     /**
-     * Checks if a given color exists in the blacklist
+     * Checks if a given color exists in the disallowed_colors list.
      * @param hsl float array with 3 components (H 0..360, S 0..1 and L 0..1)
      * @return true if color should be avoided
      */
-    private boolean isBlacklisted(float[] hsl) {
-        for (ColorRange badRange: BLACKLISTED_COLORS) {
+    private boolean isDisallowed(float[] hsl) {
+        for (ColorRange badRange: DISALLOWED_COLORS) {
             if (badRange.containsColor(hsl[0], hsl[1], hsl[2])) {
                 return true;
             }
@@ -592,7 +592,7 @@
     );
 
     @SuppressWarnings("WeakerAccess")
-    static final ColorRange[] BLACKLISTED_COLORS = new ColorRange[] {
+    static final ColorRange[] DISALLOWED_COLORS = new ColorRange[] {
 
             // Red
             new ColorRange(
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/WallpaperManagerCompat.java b/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/WallpaperManagerCompat.java
index 0fd0a35..9dbe47c 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/WallpaperManagerCompat.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/WallpaperManagerCompat.java
@@ -17,8 +17,7 @@
 package com.android.launcher3.uioverrides.dynamicui;
 
 import android.content.Context;
-
-import com.android.launcher3.Utilities;
+import android.os.Build;
 
 import androidx.annotation.Nullable;
 
@@ -32,7 +31,7 @@
             if (sInstance == null) {
                 context = context.getApplicationContext();
 
-                if (Utilities.ATLEAST_OREO_MR1) {
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
                     try {
                         sInstance = new WallpaperManagerCompatVOMR1(context);
                     } catch (Throwable e) {
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java b/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java
index 7a6332c..d102bcc 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -38,10 +38,6 @@
         return new OverviewState(id);
     }
 
-    public static OverviewState newPeekState(int id) {
-        return new OverviewState(id);
-    }
-
     public static OverviewState newSwitchState(int id) {
         return new OverviewState(id);
     }
diff --git a/tests/Android.mk b/tests/Android.mk
index 4d1bfa6..3d9077d 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -23,6 +23,7 @@
 	androidx.annotation_annotation \
 	androidx.test.runner \
 	androidx.test.rules \
+	androidx.preference_preference \
 	androidx.test.uiautomator_uiautomator
 
 ifneq (,$(wildcard frameworks/base))
diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml
index 1c8f095..bc6356f 100644
--- a/tests/AndroidManifest-common.xml
+++ b/tests/AndroidManifest-common.xml
@@ -29,6 +29,7 @@
 
         <receiver
             android:name="com.android.launcher3.testcomponent.AppWidgetNoConfig"
+            android:exported="true"
             android:label="No Config">
             <intent-filter>
                 <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
@@ -39,6 +40,7 @@
 
         <receiver
             android:name="com.android.launcher3.testcomponent.AppWdigetHidden"
+            android:exported="true"
             android:label="Hidden widget">
             <intent-filter>
                 <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
@@ -49,6 +51,7 @@
 
         <receiver
             android:name="com.android.launcher3.testcomponent.AppWidgetWithConfig"
+            android:exported="true"
             android:label="With Config">
             <intent-filter>
                 <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
@@ -58,12 +61,14 @@
         </receiver>
 
         <activity
-            android:name="com.android.launcher3.testcomponent.WidgetConfigActivity">
+            android:name="com.android.launcher3.testcomponent.WidgetConfigActivity"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
             </intent-filter>
         </activity>
-        <activity android:name="com.android.launcher3.testcomponent.CustomShortcutConfigActivity">
+        <activity android:name="com.android.launcher3.testcomponent.CustomShortcutConfigActivity"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.CREATE_SHORTCUT" />
                 <category android:name="android.intent.category.DEFAULT" />
@@ -72,6 +77,7 @@
         <activity
             android:name="com.android.launcher3.testcomponent.RequestPinItemActivity"
             android:icon="@drawable/test_drawable_pin_item"
+            android:exported="true"
             android:label="Test Pin Item">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -92,7 +98,7 @@
         <activity
             android:name="com.android.launcher3.testcomponent.TestLauncherActivity"
             android:clearTaskOnLaunch="true"
-            android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize"
+            android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize|uiMode"
             android:enabled="false"
             android:label="Test launcher"
             android:launchMode="singleTask"
@@ -102,6 +108,7 @@
             android:stateNotNeeded="true"
             android:taskAffinity=""
             android:theme="@android:style/Theme.DeviceDefault.Light"
+            android:exported="true"
             android:windowSoftInputMode="adjustPan">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -114,6 +121,7 @@
         <activity
             android:name="com.android.launcher3.testcomponent.BaseTestingActivity"
             android:label="LauncherTestApp"
+            android:exported="true"
             android:taskAffinity="com.android.launcher3.testcomponent.Affinity1">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -128,6 +136,7 @@
         </activity>
         <activity-alias android:name="Activity2"
                         android:label="TestActivity2"
+                        android:exported="true"
                         android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -136,6 +145,7 @@
         </activity-alias>
         <activity-alias android:name="Activity3"
                         android:label="TestActivity3"
+                        android:exported="true"
                         android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -144,6 +154,7 @@
         </activity-alias>
         <activity-alias android:name="Activity4"
                         android:label="TestActivity4"
+                        android:exported="true"
                         android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -152,6 +163,7 @@
         </activity-alias>
         <activity-alias android:name="Activity5"
                         android:label="TestActivity5"
+                        android:exported="true"
                         android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -160,6 +172,7 @@
         </activity-alias>
         <activity-alias android:name="Activity6"
                         android:label="TestActivity6"
+                        android:exported="true"
                         android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -168,6 +181,7 @@
         </activity-alias>
         <activity-alias android:name="Activity7"
                         android:label="TestActivity7"
+                        android:exported="true"
                         android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -176,6 +190,7 @@
         </activity-alias>
         <activity-alias android:name="Activity8"
                         android:label="TestActivity8"
+                        android:exported="true"
                         android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -184,6 +199,7 @@
         </activity-alias>
         <activity-alias android:name="Activity9"
                         android:label="TestActivity9"
+                        android:exported="true"
                         android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -192,6 +208,7 @@
         </activity-alias>
         <activity-alias android:name="Activity10"
                         android:label="TestActivity10"
+                        android:exported="true"
                         android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -200,6 +217,7 @@
         </activity-alias>
         <activity-alias android:name="Activity11"
                         android:label="TestActivity11"
+                        android:exported="true"
                         android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
diff --git a/tests/dummy_app/AndroidManifest.xml b/tests/dummy_app/AndroidManifest.xml
index f00138c..d5e2320 100644
--- a/tests/dummy_app/AndroidManifest.xml
+++ b/tests/dummy_app/AndroidManifest.xml
@@ -26,6 +26,7 @@
         <activity
             android:name="Activity1"
             android:icon="@mipmap/ic_launcher1"
+            android:exported="true"
             android:label="Aardwolf">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
diff --git a/tests/src/com/android/launcher3/testcomponent/ListViewService.java b/tests/src/com/android/launcher3/testcomponent/ListViewService.java
index 3da20e0..9e3a492 100644
--- a/tests/src/com/android/launcher3/testcomponent/ListViewService.java
+++ b/tests/src/com/android/launcher3/testcomponent/ListViewService.java
@@ -89,7 +89,7 @@
                 public RemoteViewsFactory onGetViewFactory(Intent intent) {
                     return SimpleViewsFactory.this;
                 }
-            }.onBind(new Intent("dummy_intent"));
+            }.onBind(new Intent("stub_intent"));
         }
     }
 }
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 858e183..5e42d9b 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -53,7 +53,6 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.common.WidgetUtils;
-import com.android.launcher3.model.AppLaunchTracker;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.tapl.LauncherInstrumentation;
@@ -116,8 +115,7 @@
         if (TestHelpers.isInLauncherProcess()) {
             StrictMode.VmPolicy.Builder builder =
                     new StrictMode.VmPolicy.Builder()
-// b/154772063
-//                            .detectActivityLeaks()
+                            .detectActivityLeaks()
                             .penaltyLog()
                             .penaltyListener(Runnable::run, violation -> {
                                 if (sStrictmodeDetectedActivityLeak == null) {
@@ -272,8 +270,6 @@
         }
 
         mLauncherPid = 0;
-        // Disable app tracker
-        AppLaunchTracker.INSTANCE.initializeForTesting(new AppLaunchTracker());
 
         mTargetContext = InstrumentationRegistry.getTargetContext();
         mTargetPackage = mTargetContext.getPackageName();
diff --git a/tests/src/com/android/launcher3/ui/ActivityLeakTracker.java b/tests/src/com/android/launcher3/ui/ActivityLeakTracker.java
index 202dcb1..dd216c7 100644
--- a/tests/src/com/android/launcher3/ui/ActivityLeakTracker.java
+++ b/tests/src/com/android/launcher3/ui/ActivityLeakTracker.java
@@ -73,20 +73,12 @@
     }
 
     public boolean noLeakedActivities() {
-        int liveActivities = 0;
-        int destroyedActivities = 0;
-
         for (Activity activity : mActivities.keySet()) {
             if (activity.isDestroyed()) {
-                ++destroyedActivities;
-            } else {
-                ++liveActivities;
+                return false;
             }
         }
 
-        if (liveActivities > 2) return false;
-
-        // It's OK to have 1 leaked activity if no active activities exist.
-        return liveActivities == 0 ? destroyedActivities <= 1 : destroyedActivities == 0;
+        return mActivities.size() <= 2;
     }
 }
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 34e425d..a39f215 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -41,6 +41,7 @@
 import com.android.launcher3.widget.WidgetsRecyclerView;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -101,7 +102,7 @@
         mLauncher.pressHome();
     }
 
-    @Test
+    @Ignore
     public void testOpenHomeSettingsFromWorkspace() {
         mDevice.pressMenu();
         mDevice.waitForIdle();
diff --git a/tests/src/com/android/launcher3/ui/WorkTabTest.java b/tests/src/com/android/launcher3/ui/WorkTabTest.java
index 8d571ff..f5f93c4 100644
--- a/tests/src/com/android/launcher3/ui/WorkTabTest.java
+++ b/tests/src/com/android/launcher3/ui/WorkTabTest.java
@@ -24,6 +24,7 @@
 import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.util.Log;
 import android.widget.TextView;
 
 import androidx.test.filters.LargeTest;
@@ -34,6 +35,7 @@
 import com.android.launcher3.allapps.AllAppsPagedView;
 import com.android.launcher3.allapps.WorkModeSwitch;
 import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.views.WorkEduView;
 
 import org.junit.After;
@@ -132,6 +134,10 @@
                     l.getResources().getString(R.string.work_profile_edu_personal_apps));
             workEduView.findViewById(R.id.proceed).callOnClick();
         });
+
+        executeOnLauncher(launcher -> Log.d(TestProtocol.WORK_PROFILE_REMOVED,
+                "Work profile status: " + launcher.getAppsView().isPersonalTabVisible()));
+
         // verify work edu is seen next
         waitForLauncherCondition("Launcher did not show the next edu screen", l ->
                 ((AllAppsPagedView) l.getAppsView().getContentView()).getCurrentPage() == WORK_PAGE
diff --git a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
index a4aa9f2..fa495f5 100644
--- a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
@@ -192,7 +192,7 @@
                         WidgetManagerHelper.WIDGET_OPTION_RESTORE_COMPLETED));
         executeOnLauncher(l -> l.getAppWidgetHost().startListening());
         verifyWidgetPresent(info);
-        assertNull(mLauncher.getWorkspace().tryGetPendingWidget(DEFAULT_UI_TIMEOUT));
+        assertNull(mLauncher.getWorkspace().tryGetPendingWidget(100));
     }
 
     @Test
diff --git a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
index 0e43d81..822fefc 100644
--- a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
@@ -30,7 +30,6 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace.ItemOperator;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -126,9 +125,6 @@
 
     private void runTest(String activityMethod, boolean isWidget, ItemOperator itemMatcher,
             Intent... commandIntents) throws Throwable {
-        if (!Utilities.ATLEAST_OREO) {
-            return;
-        }
         clearHomescreen();
         mDevice.pressHome();
 
diff --git a/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java b/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java
index e4f520f..77546de 100644
--- a/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java
+++ b/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java
@@ -87,12 +87,6 @@
             return 145935261;
         }
 
-        if (matches("java\\.lang\\.AssertionError\\: http\\:\\/\\/go\\/tapl \\: want to get "
-                + "workspace object; Presence of recents button doesn't match the interaction "
-                + "mode, mode\\=ZERO_BUTTON, hasRecents\\=true", exception)) {
-            return 148422894;
-        }
-
         final String logSinceBoot;
         try {
             final String systemBootTime =
diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java
index ce94a3e..c9c846f 100644
--- a/tests/tapl/com/android/launcher3/tapl/Background.java
+++ b/tests/tapl/com/android/launcher3/tapl/Background.java
@@ -16,8 +16,9 @@
 
 package com.android.launcher3.tapl;
 
+import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
+
 import static com.android.launcher3.tapl.OverviewTask.TASK_START_EVENT;
-import static com.android.launcher3.testing.TestProtocol.BACKGROUND_APP_STATE_ORDINAL;
 import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
 
 import android.graphics.Point;
@@ -85,7 +86,7 @@
                 final LauncherInstrumentation.GestureScope gestureScope =
                         zeroButtonToOverviewGestureStartsInLauncher()
                                 ? LauncherInstrumentation.GestureScope.INSIDE_TO_OUTSIDE
-                                : LauncherInstrumentation.GestureScope.OUTSIDE;
+                                : LauncherInstrumentation.GestureScope.OUTSIDE_WITH_PILFER;
 
                 // b/156044202
                 mLauncher.log("Hierarchy before swiping up to overview:");
@@ -93,7 +94,7 @@
 
                 mLauncher.sendPointer(
                         downTime, downTime, MotionEvent.ACTION_DOWN, start, gestureScope);
-                mLauncher.executeAndWaitForEvent(
+                mLauncher.executeAndWaitForLauncherEvent(
                         () -> mLauncher.movePointer(
                                 downTime,
                                 downTime,
@@ -130,7 +131,7 @@
                 }
 
                 mLauncher.swipeToState(startX, startY, endX, endY, 10, OVERVIEW_STATE_ORDINAL,
-                        LauncherInstrumentation.GestureScope.OUTSIDE);
+                        LauncherInstrumentation.GestureScope.OUTSIDE_WITH_PILFER);
                 break;
             }
 
@@ -150,67 +151,68 @@
     /**
      * Swipes right or double presses the square button to switch to the previous app.
      */
+    @NonNull
     public Background quickSwitchToPreviousApp() {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
              LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                      "want to quick switch to the previous app")) {
             verifyActiveContainer();
-            quickSwitchToPreviousApp(getExpectedStateForQuickSwitch());
+            final boolean launcherWasVisible = mLauncher.isLauncherVisible();
+            boolean transposeInLandscape = false;
+            switch (mLauncher.getNavigationModel()) {
+                case TWO_BUTTON:
+                    transposeInLandscape = true;
+                    // Fall through, zero button and two button modes behave the same.
+                case ZERO_BUTTON: {
+                    final int startX;
+                    final int startY;
+                    final int endX;
+                    final int endY;
+                    if (mLauncher.getDevice().isNaturalOrientation() || !transposeInLandscape) {
+                        // Swipe from the bottom left to the bottom right of the screen.
+                        startX = 0;
+                        startY = getSwipeStartY();
+                        endX = mLauncher.getDevice().getDisplayWidth();
+                        endY = startY;
+                    } else {
+                        // Swipe from the bottom right to the top right of the screen.
+                        startX = getSwipeStartX();
+                        startY = mLauncher.getRealDisplaySize().y - 1;
+                        endX = startX;
+                        endY = 0;
+                    }
+                    final boolean isZeroButton = mLauncher.getNavigationModel()
+                            == LauncherInstrumentation.NavigationModel.ZERO_BUTTON;
+                    LauncherInstrumentation.GestureScope gestureScope =
+                            launcherWasVisible && isZeroButton
+                                    ? LauncherInstrumentation.GestureScope.INSIDE_TO_OUTSIDE
+                                    : LauncherInstrumentation.GestureScope.OUTSIDE_WITH_PILFER;
+                    mLauncher.executeAndWaitForEvent(
+                            () -> mLauncher.linearGesture(
+                                    startX, startY, endX, endY, 20, false, gestureScope),
+                            event -> event.getEventType() == TYPE_WINDOW_STATE_CHANGED,
+                            () -> "Quick switch gesture didn't change window state");
+                    break;
+                }
+
+                case THREE_BUTTON:
+                    // Double press the recents button.
+                    UiObject2 recentsButton = mLauncher.waitForSystemUiObject("recent_apps");
+                    mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
+                    mLauncher.runToState(() -> recentsButton.click(), OVERVIEW_STATE_ORDINAL);
+                    mLauncher.getOverview();
+                    mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
+                    mLauncher.executeAndWaitForEvent(
+                            () -> recentsButton.click(),
+                            event -> event.getEventType() == TYPE_WINDOW_STATE_CHANGED,
+                            () -> "Pressing recents button didn't change window state");
+                    break;
+            }
+            mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, TASK_START_EVENT);
             return new Background(mLauncher);
         }
     }
 
-    protected int getExpectedStateForQuickSwitch() {
-        return BACKGROUND_APP_STATE_ORDINAL;
-    }
-
-    protected void quickSwitchToPreviousApp(int expectedState) {
-        final boolean launcherWasVisible = mLauncher.isLauncherVisible();
-        boolean transposeInLandscape = false;
-        switch (mLauncher.getNavigationModel()) {
-            case TWO_BUTTON:
-                transposeInLandscape = true;
-                // Fall through, zero button and two button modes behave the same.
-            case ZERO_BUTTON: {
-                final int startX;
-                final int startY;
-                final int endX;
-                final int endY;
-                if (mLauncher.getDevice().isNaturalOrientation() || !transposeInLandscape) {
-                    // Swipe from the bottom left to the bottom right of the screen.
-                    startX = 0;
-                    startY = getSwipeStartY();
-                    endX = mLauncher.getDevice().getDisplayWidth();
-                    endY = startY;
-                } else {
-                    // Swipe from the bottom right to the top right of the screen.
-                    startX = getSwipeStartX();
-                    startY = mLauncher.getRealDisplaySize().y - 1;
-                    endX = startX;
-                    endY = 0;
-                }
-                final boolean isZeroButton = mLauncher.getNavigationModel()
-                        == LauncherInstrumentation.NavigationModel.ZERO_BUTTON;
-                mLauncher.swipeToState(startX, startY, endX, endY, 20, expectedState,
-                        launcherWasVisible && isZeroButton
-                                ? LauncherInstrumentation.GestureScope.INSIDE_TO_OUTSIDE
-                                : LauncherInstrumentation.GestureScope.OUTSIDE);
-                break;
-            }
-
-            case THREE_BUTTON:
-                // Double press the recents button.
-                UiObject2 recentsButton = mLauncher.waitForSystemUiObject("recent_apps");
-                mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
-                mLauncher.runToState(() -> recentsButton.click(), OVERVIEW_STATE_ORDINAL);
-                mLauncher.getOverview();
-                mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
-                recentsButton.click();
-                break;
-        }
-        mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, TASK_START_EVENT);
-    }
-
     protected String getSwipeHeightRequestName() {
         return TestProtocol.REQUEST_BACKGROUND_TO_OVERVIEW_SWIPE_HEIGHT;
     }
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index 223ae29..588b6b8 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -27,7 +27,7 @@
 import java.util.List;
 
 /**
- * Common overview pane for both Launcher and fallback recents
+ * Common overview panel for both Launcher and fallback recents
  */
 public class BaseOverview extends LauncherInstrumentation.VisibleContainer {
     private static final int FLINGS_FOR_DISMISS_LIMIT = 40;
@@ -135,4 +135,19 @@
     public boolean hasTasks() {
         return getTasks().size() > 0;
     }
+
+    /**
+     * Gets Overview Actions.
+     *
+     * @return The Overview Actions
+     */
+    @NonNull
+    public OverviewActions getOverviewActions() {
+        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                "want to get overview actions")) {
+            verifyActiveContainer();
+            UiObject2 overviewActions = mLauncher.waitForLauncherObject("action_buttons");
+            return new OverviewActions(overviewActions, mLauncher);
+        }
+    }
 }
\ No newline at end of file
diff --git a/tests/tapl/com/android/launcher3/tapl/Home.java b/tests/tapl/com/android/launcher3/tapl/Home.java
index c06e254..0060844 100644
--- a/tests/tapl/com/android/launcher3/tapl/Home.java
+++ b/tests/tapl/com/android/launcher3/tapl/Home.java
@@ -16,8 +16,6 @@
 
 package com.android.launcher3.tapl;
 
-import static com.android.launcher3.testing.TestProtocol.QUICK_SWITCH_STATE_ORDINAL;
-
 import androidx.annotation.NonNull;
 
 /**
@@ -65,8 +63,4 @@
         return true;
     }
 
-    @Override
-    protected int getExpectedStateForQuickSwitch() {
-        return QUICK_SWITCH_STATE_ORDINAL;
-    }
 }
\ No newline at end of file
diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java
index 13ecfb8..093c024 100644
--- a/tests/tapl/com/android/launcher3/tapl/Launchable.java
+++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java
@@ -56,15 +56,18 @@
     private Background launch(BySelector selector) {
         LauncherInstrumentation.log("Launchable.launch before click " +
                 mObject.getVisibleCenter() + " in " + mLauncher.getVisibleBounds(mObject));
+        final String label = mObject.getText();
 
         mLauncher.executeAndWaitForEvent(
-                () -> mLauncher.clickLauncherObject(mObject),
+                () -> {
+                    mLauncher.clickLauncherObject(mObject);
+                    expectActivityStartEvents();
+                },
                 event -> event.getEventType() == TYPE_WINDOW_STATE_CHANGED,
-                () -> "Launching an app didn't open a new window: " + mObject.getText());
-        expectActivityStartEvents();
+                () -> "Launching an app didn't open a new window: " + label);
 
         mLauncher.assertTrue(
-                "App didn't start: " + selector,
+                "App didn't start: " + label,
                 mLauncher.getDevice().wait(Until.hasObject(selector),
                         LauncherInstrumentation.WAIT_TIME_MS));
         return new Background(mLauncher);
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index f4274a8..80adf05 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -64,6 +64,7 @@
 
 import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.testing.TestProtocol;
+import com.android.systemui.shared.system.ContextUtils;
 import com.android.systemui.shared.system.QuickStepContract;
 
 import org.junit.Assert;
@@ -103,6 +104,7 @@
 
     static final Pattern EVENT_TOUCH_DOWN_TIS = getTouchEventPatternTIS("ACTION_DOWN");
     static final Pattern EVENT_TOUCH_UP_TIS = getTouchEventPatternTIS("ACTION_UP");
+    private final String mLauncherPackage;
 
     // Types for launcher containers that the user is interacting with. "Background" is a
     // pseudo-container corresponding to inactive launcher covered by another app.
@@ -112,9 +114,10 @@
 
     public enum NavigationModel {ZERO_BUTTON, TWO_BUTTON, THREE_BUTTON}
 
-    // Where the gesture happens: outside of Launcher, inside or from inside to outside.
+    // Where the gesture happens: outside of Launcher, inside or from inside to outside and
+    // whether the gesture recognition triggers pilfer.
     public enum GestureScope {
-        OUTSIDE, INSIDE, INSIDE_TO_OUTSIDE
+        OUTSIDE_WITHOUT_PILFER, OUTSIDE_WITH_PILFER, INSIDE, INSIDE_TO_OUTSIDE
     }
 
     ;
@@ -152,7 +155,9 @@
     private static final String WIDGETS_RES_ID = "widgets_list_view";
     private static final String CONTEXT_MENU_RES_ID = "deep_shortcuts_container";
     public static final int WAIT_TIME_MS = 10000;
+    public static final int LONG_WAIT_TIME_MS = 60000;
     private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
+    private static final String ANDROID_PACKAGE = "android";
 
     private static WeakReference<VisibleContainer> sActiveContainer = new WeakReference<>(null);
 
@@ -171,8 +176,7 @@
     private Runnable mOnLauncherCrashed;
 
     private static Pattern getTouchEventPattern(String prefix, String action) {
-        // The pattern includes sanity checks that we don't get a multi-touch events or other
-        // surprises.
+        // The pattern includes checks that we don't get a multi-touch events or other surprises.
         return Pattern.compile(
                 prefix + ": MotionEvent.*?action=" + action + ".*?id\\[0\\]=0"
                         + ".*?toolType\\[0\\]=TOOL_TYPE_FINGER.*?buttonState=0.*?pointerCount=1");
@@ -214,11 +218,11 @@
         // Launcher package. As during inproc tests the tested launcher may not be selected as the
         // current launcher, choosing target package for inproc. For out-of-proc, use the installed
         // launcher package.
-        final String authorityPackage = testPackage.equals(targetPackage) ?
-                getLauncherPackageName() :
-                targetPackage;
+        mLauncherPackage = testPackage.equals(targetPackage)
+                ? getLauncherPackageName()
+                : targetPackage;
 
-        String testProviderAuthority = authorityPackage + ".TestInfo";
+        String testProviderAuthority = mLauncherPackage + ".TestInfo";
         mTestProviderUri = new Uri.Builder()
                 .scheme(ContentResolver.SCHEME_CONTENT)
                 .authority(testProviderAuthority)
@@ -235,11 +239,12 @@
 
         if (pm.getComponentEnabledSetting(cn) != COMPONENT_ENABLED_STATE_ENABLED) {
             if (TestHelpers.isInLauncherProcess()) {
-                getContext().getPackageManager().setComponentEnabledSetting(
-                        cn, COMPONENT_ENABLED_STATE_ENABLED, DONT_KILL_APP);
+                pm.setComponentEnabledSetting(cn, COMPONENT_ENABLED_STATE_ENABLED, DONT_KILL_APP);
             } else {
                 try {
-                    mDevice.executeShellCommand("pm enable " + cn.flattenToString());
+                    final int userId = ContextUtils.getUserId(getContext());
+                    mDevice.executeShellCommand(
+                            "pm enable --user " + userId + " " + cn.flattenToString());
                 } catch (IOException e) {
                     fail(e.toString());
                 }
@@ -448,7 +453,7 @@
             }
         }
 
-        dumpDiagnostics();
+        dumpDiagnostics(message);
 
         log("Hierarchy dump for: " + message);
         dumpViewHierarchy();
@@ -456,10 +461,11 @@
         return message;
     }
 
-    private void dumpDiagnostics() {
-        Log.e("b/156287114", "Input:");
+    private void dumpDiagnostics(String message) {
+        log("Diagnostics for failure: " + message);
+        log("Input:");
         logShellCommand("dumpsys input");
-        Log.e("b/156287114", "TIS:");
+        log("TIS:");
         logShellCommand("dumpsys activity service TouchInteractionService");
     }
 
@@ -467,10 +473,10 @@
         try {
             for (String line : mDevice.executeShellCommand(command).split("\\n")) {
                 SystemClock.sleep(10);
-                Log.d("b/156287114", line);
+                log(line);
             }
         } catch (IOException e) {
-            Log.d("b/156287114", "Failed to execute " + command);
+            log("Failed to execute " + command);
         }
     }
 
@@ -548,13 +554,7 @@
         assertEquals("Unexpected display rotation",
                 mExpectedRotation, mDevice.getDisplayRotation());
 
-        // b/148422894
-        String error = null;
-        for (int i = 0; i != 600; ++i) {
-            error = getNavigationModeMismatchError();
-            if (error == null) break;
-            sleep(100);
-        }
+        final String error = getNavigationModeMismatchError();
         assertTrue(error, error == null);
 
         log("verifyContainerType: " + containerType);
@@ -632,12 +632,20 @@
         fail("Launcher didn't initialize");
     }
 
+    Parcelable executeAndWaitForLauncherEvent(Runnable command,
+            UiAutomation.AccessibilityEventFilter eventFilter, Supplier<String> message) {
+        return executeAndWaitForEvent(
+                command,
+                e -> mLauncherPackage.equals(e.getPackageName()) && eventFilter.accept(e),
+                message);
+    }
+
     Parcelable executeAndWaitForEvent(Runnable command,
             UiAutomation.AccessibilityEventFilter eventFilter, Supplier<String> message) {
         try {
             final AccessibilityEvent event =
                     mInstrumentation.getUiAutomation().executeAndWaitForEvent(
-                            command, eventFilter, WAIT_TIME_MS);
+                            command, eventFilter, LONG_WAIT_TIME_MS);
             assertNotNull("executeAndWaitForEvent returned null (this can't happen)", event);
             final Parcelable parcelableData = event.getParcelableData();
             event.recycle();
@@ -693,7 +701,7 @@
                                 ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME, NORMAL_STATE_ORDINAL,
                                 launcherWasVisible
                                         ? GestureScope.INSIDE_TO_OUTSIDE
-                                        : GestureScope.OUTSIDE);
+                                        : GestureScope.OUTSIDE_WITH_PILFER);
                     }
                 }
             } else {
@@ -925,6 +933,14 @@
         return waitForObjectBySelector(getOverviewObjectSelector(resName));
     }
 
+    @NonNull
+    UiObject2 waitForAndroidObject(String resId) {
+        final UiObject2 object = mDevice.wait(
+                Until.findObject(By.res(ANDROID_PACKAGE, resId)), WAIT_TIME_MS);
+        assertNotNull("Can't find a android object with id: " + resId, object);
+        return object;
+    }
+
     private UiObject2 waitForObjectBySelector(BySelector selector) {
         final UiObject2 object = mDevice.wait(Until.findObject(selector), WAIT_TIME_MS);
         assertNotNull("Can't find a view in Launcher, selector: " + selector, object);
@@ -976,7 +992,7 @@
 
     void runToState(Runnable command, int expectedState) {
         final List<Integer> actualEvents = new ArrayList<>();
-        executeAndWaitForEvent(
+        executeAndWaitForLauncherEvent(
                 command,
                 event -> isSwitchToStateEvent(event, expectedState, actualEvents),
                 () -> "Failed to receive an event for the state change: expected ["
@@ -1091,7 +1107,7 @@
                 return;
         }
 
-        executeAndWaitForEvent(
+        executeAndWaitForLauncherEvent(
                 () -> linearGesture(
                         startX, startY, endX, endY, steps, slowDown, GestureScope.INSIDE),
                 event -> TestProtocol.SCROLL_FINISHED_MESSAGE.equals(event.getClassName()),
@@ -1159,7 +1175,8 @@
         final boolean notLauncher3 = !isLauncher3();
         switch (action) {
             case MotionEvent.ACTION_DOWN:
-                if (gestureScope != GestureScope.OUTSIDE) {
+                if (gestureScope != GestureScope.OUTSIDE_WITH_PILFER
+                        && gestureScope != GestureScope.OUTSIDE_WITHOUT_PILFER) {
                     expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_TOUCH_DOWN);
                 }
                 if (notLauncher3 && getNavigationModel() != NavigationModel.THREE_BUTTON) {
@@ -1167,12 +1184,17 @@
                 }
                 break;
             case MotionEvent.ACTION_UP:
-                if (notLauncher3 && gestureScope != GestureScope.INSIDE) {
+                if (notLauncher3 && gestureScope != GestureScope.INSIDE
+                        && (gestureScope == GestureScope.OUTSIDE_WITH_PILFER
+                        || gestureScope == GestureScope.INSIDE_TO_OUTSIDE)) {
                     expectEvent(TestProtocol.SEQUENCE_PILFER, EVENT_PILFER_POINTERS);
                 }
-                if (gestureScope != GestureScope.OUTSIDE) {
-                    expectEvent(TestProtocol.SEQUENCE_MAIN, gestureScope == GestureScope.INSIDE
-                            ? EVENT_TOUCH_UP : EVENT_TOUCH_CANCEL);
+                if (gestureScope != GestureScope.OUTSIDE_WITH_PILFER
+                        && gestureScope != GestureScope.OUTSIDE_WITHOUT_PILFER) {
+                    expectEvent(TestProtocol.SEQUENCE_MAIN,
+                            gestureScope == GestureScope.INSIDE
+                                    || gestureScope == GestureScope.OUTSIDE_WITHOUT_PILFER
+                                    ? EVENT_TOUCH_UP : EVENT_TOUCH_CANCEL);
                 }
                 if (notLauncher3 && getNavigationModel() != NavigationModel.THREE_BUTTON) {
                     expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_UP_TIS);
@@ -1282,16 +1304,12 @@
         if (getNavigationModel() == NavigationModel.TWO_BUTTON) {
             return true;
         }
-        // Overview actions hide all apps
-        if (overviewActionsEnabled()) {
-            return false;
-        }
-        // ...otherwise there should be all apps
-        return true;
+        // ...otherwise there are overview actions, which hide all apps
+        return false;
     }
 
-    private boolean overviewActionsEnabled() {
-        return getTestInfo(TestProtocol.REQUEST_OVERVIEW_ACTIONS_ENABLED).getBoolean(
+    boolean overviewShareEnabled() {
+        return getTestInfo(TestProtocol.REQUEST_OVERVIEW_SHARE_ENABLED).getBoolean(
                 TestProtocol.TEST_INFO_RESPONSE_FIELD);
     }
 
@@ -1360,7 +1378,7 @@
                 if (mCheckEventsForSuccessfulGestures) {
                     final String message = eventChecker.verify(WAIT_TIME_MS, true);
                     if (message != null) {
-                        dumpDiagnostics();
+                        dumpDiagnostics(message);
                         checkForAnomaly();
                         Assert.fail(formatSystemHealthMessage(
                                 "http://go/tapl : successful gesture produced " + message));
@@ -1392,4 +1410,4 @@
             return null;
         }
     }
-}
+}
\ No newline at end of file
diff --git a/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java b/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java
index 4440b82..ab6465c 100644
--- a/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java
+++ b/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java
@@ -57,6 +57,8 @@
         while (true) {
             rawEvents = mLauncher.getTestInfo(TestProtocol.REQUEST_GET_TEST_EVENTS)
                     .getStringArrayList(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+            if (rawEvents == null) return null;
+
             final int expectedCount = mExpectedEvents.entrySet()
                     .stream().mapToInt(e -> e.getValue().size()).sum();
             if (rawEvents.size() >= expectedCount
@@ -83,6 +85,7 @@
 
     String verify(long waitForExpectedCountMs, boolean successfulGesture) {
         final ListMap<String> actualEvents = finishSync(waitForExpectedCountMs);
+        if (actualEvents == null) return "null event sequences because launcher likely died";
 
         final StringBuilder sb = new StringBuilder();
         boolean hasMismatches = false;
@@ -91,8 +94,7 @@
 
             List<String> actual = new ArrayList<>(actualEvents.getNonNull(sequence));
             final int mismatchPosition = getMismatchPosition(expectedEvents.getValue(), actual);
-            hasMismatches = hasMismatches
-                    || mismatchPosition != -1 && !ignoreMistatch(successfulGesture, sequence);
+            hasMismatches = hasMismatches || mismatchPosition != -1;
             formatSequenceWithMismatch(
                     sb,
                     sequence,
@@ -103,8 +105,7 @@
         // Check for unexpected event sequences in the actual data.
         for (String actualNamedSequence : actualEvents.keySet()) {
             if (!mExpectedEvents.containsKey(actualNamedSequence)) {
-                hasMismatches = hasMismatches
-                        || !ignoreMistatch(successfulGesture, actualNamedSequence);
+                hasMismatches = true;
                 formatSequenceWithMismatch(
                         sb,
                         actualNamedSequence,
@@ -117,13 +118,6 @@
         return hasMismatches ? "mismatching events: " + sb.toString() : null;
     }
 
-    // Workaround for b/154157191
-    private static boolean ignoreMistatch(boolean successfulGesture, String sequence) {
-        // b/156287114
-        return false;
-//        return TestProtocol.SEQUENCE_TIS.equals(sequence) && successfulGesture;
-    }
-
     // If the list of actual events matches the list of expected events, returns -1, otherwise
     // the position of the mismatch.
     private static int getMismatchPosition(List<Pattern> expected, List<String> actual) {
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewActions.java b/tests/tapl/com/android/launcher3/tapl/OverviewActions.java
new file mode 100644
index 0000000..a30a404
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewActions.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2020 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.tapl;
+
+import androidx.annotation.NonNull;
+import androidx.test.uiautomator.UiObject2;
+
+import com.android.launcher3.testing.TestProtocol;
+
+/**
+ * View containing overview actions
+ */
+public class OverviewActions {
+    private final UiObject2 mOverviewActions;
+    private final LauncherInstrumentation mLauncher;
+
+    OverviewActions(UiObject2 overviewActions, LauncherInstrumentation launcherInstrumentation) {
+        this.mOverviewActions = overviewActions;
+        this.mLauncher = launcherInstrumentation;
+    }
+
+    /**
+     * Clicks screenshot button and closes screenshot ui.
+     */
+    @NonNull
+    public Overview clickAndDismissScreenshot() {
+        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+             LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                     "want to click screenshot button and exit screenshot ui")) {
+            UiObject2 screenshot = mLauncher.waitForObjectInContainer(mOverviewActions,
+                    "action_screenshot");
+            mLauncher.clickLauncherObject(screenshot);
+            try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
+                    "clicked screenshot button")) {
+                UiObject2 closeScreenshot = mLauncher.waitForSystemUiObject(
+                        "global_screenshot_dismiss_image");
+                if (mLauncher.getNavigationModel()
+                        != LauncherInstrumentation.NavigationModel.THREE_BUTTON) {
+                    mLauncher.expectEvent(TestProtocol.SEQUENCE_TIS,
+                            LauncherInstrumentation.EVENT_TOUCH_DOWN_TIS);
+                    mLauncher.expectEvent(TestProtocol.SEQUENCE_TIS,
+                            LauncherInstrumentation.EVENT_TOUCH_UP_TIS);
+                }
+                closeScreenshot.click();
+                try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer(
+                        "dismissed screenshot")) {
+                    return new Overview(mLauncher);
+                }
+            }
+        }
+    }
+
+    /**
+     * Click share button, then drags sharesheet down to remove it.
+     *
+     * Share is currently hidden behind flag, test is kept in case share becomes a default feature.
+     * If share is completely removed then remove this test as well.
+     */
+    @NonNull
+    public Overview clickAndDismissShare() {
+        if (mLauncher.overviewShareEnabled()) {
+            try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+                 LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                         "want to click share button and dismiss sharesheet")) {
+                UiObject2 share = mLauncher.waitForObjectInContainer(mOverviewActions,
+                        "action_share");
+                mLauncher.clickLauncherObject(share);
+                try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
+                        "clicked share button")) {
+                    mLauncher.waitForAndroidObject("contentPanel");
+                    mLauncher.getDevice().pressBack();
+                    try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer(
+                            "dismissed sharesheet")) {
+                        return new Overview(mLauncher);
+                    }
+                }
+            }
+        }
+        return new Overview(mLauncher);
+    }
+
+    /**
+     * Click select button
+     *
+     * @return The select mode buttons that are now shown instead of action buttons.
+     */
+    @NonNull
+    public SelectModeButtons clickSelect() {
+        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+             LauncherInstrumentation.Closable c =
+                     mLauncher.addContextLayer("want to click select button")) {
+            UiObject2 select = mLauncher.waitForObjectInContainer(mOverviewActions,
+                    "action_select");
+            mLauncher.clickLauncherObject(select);
+            try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
+                    "clicked select button")) {
+                return getSelectModeButtons();
+            }
+
+        }
+    }
+
+    /**
+     * Gets the Select Mode Buttons.
+     *
+     * @return The Select Mode Buttons.
+     */
+    @NonNull
+    private SelectModeButtons getSelectModeButtons() {
+        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                "want to get select mode buttons")) {
+            UiObject2 selectModeButtons = mLauncher.waitForLauncherObject("select_mode_buttons");
+            return new SelectModeButtons(selectModeButtons, mLauncher);
+        }
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/SelectModeButtons.java b/tests/tapl/com/android/launcher3/tapl/SelectModeButtons.java
new file mode 100644
index 0000000..3507418
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/SelectModeButtons.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2020 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.tapl;
+
+import androidx.annotation.NonNull;
+import androidx.test.uiautomator.UiObject2;
+
+/**
+ * View containing select mode buttons
+ */
+public class SelectModeButtons {
+    private final UiObject2 mSelectModeButtons;
+    private final LauncherInstrumentation mLauncher;
+
+    SelectModeButtons(UiObject2 selectModeButtons,
+            LauncherInstrumentation launcherInstrumentation) {
+        mSelectModeButtons = selectModeButtons;
+        mLauncher = launcherInstrumentation;
+    }
+
+    /**
+     * Click close button.
+     */
+    @NonNull
+    public Overview clickClose() {
+        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+             LauncherInstrumentation.Closable c =
+                     mLauncher.addContextLayer("want to click close button")) {
+            UiObject2 close = mLauncher.waitForObjectInContainer(mSelectModeButtons, "close");
+            mLauncher.clickLauncherObject(close);
+            try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
+                    "clicked close button")) {
+                return new Overview(mLauncher);
+            }
+        }
+    }
+
+    /**
+     * Click feedback button.
+     */
+    @NonNull
+    public Background clickFeedback() {
+        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+             LauncherInstrumentation.Closable c =
+                     mLauncher.addContextLayer("want to click feedback button")) {
+            UiObject2 feedback = mLauncher.waitForObjectInContainer(mSelectModeButtons, "feedback");
+            mLauncher.clickLauncherObject(feedback);
+            try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
+                    "clicked feedback button")) {
+                return new Background(mLauncher);
+            }
+        }
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/Widgets.java b/tests/tapl/com/android/launcher3/tapl/Widgets.java
index 39ac645..49af616 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widgets.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widgets.java
@@ -28,6 +28,7 @@
 import androidx.test.uiautomator.Until;
 
 import com.android.launcher3.tapl.LauncherInstrumentation.GestureScope;
+import com.android.launcher3.testing.TestProtocol;
 
 import java.util.Collection;
 
@@ -90,6 +91,12 @@
         return LauncherInstrumentation.ContainerType.WIDGETS;
     }
 
+    private int getWidgetsScroll() {
+        return mLauncher.getTestInfo(
+                TestProtocol.REQUEST_WIDGETS_SCROLL_Y)
+                .getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+    }
+
     public Widget getWidget(String labelText) {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
              LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
@@ -136,7 +143,13 @@
                 }
 
                 mLauncher.assertTrue("Too many attempts", ++i <= 40);
+                final int scroll = getWidgetsScroll();
                 mLauncher.scrollToLastVisibleRow(widgetsContainer, cells, 0);
+                final int newScroll = getWidgetsScroll();
+                mLauncher.assertTrue(
+                        "Scrolled in a wrong direction in Widgets: from " + scroll + " to "
+                                + newScroll, newScroll >= scroll);
+                mLauncher.assertTrue("Unable to scroll to the widget", newScroll != scroll);
             }
         }
     }