Merge "Add onQuickSwitchToNewTask(int rotation)" into rvc-dev
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index c7a0253..d7191b4 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -138,14 +138,6 @@
         </activity>
 
         <!--
-        Should point to the content provider which can be used to dump Launcher3 compatible
-        worspace configuration to the dump's file descriptor by using launcher_dump.proto
-        -->
-        <meta-data
-            android:name="com.android.launcher3.launcher_dump_provider"
-            android:value="com.android.launcher3.LauncherProvider" />
-
-        <!--
         The settings provider contains Home's data, like the workspace favorites. The permissions
         should be changed to what is defined above. The authorities should also be changed to
         represent the package name.
diff --git a/buglist.txt b/buglist.txt
deleted file mode 100644
index e1e9534..0000000
--- a/buglist.txt
+++ /dev/null
@@ -1,22 +0,0 @@
-150522230
-150260456
-139137636
-150122946
-150260456
-151166786
-139828243
-150876921
-150644839
-146593239
-143361609
-151025334
-150864182
-151050221
-150680980
-138964382
-150788630
-146593239
-149792636
-147305863
-148867106
-139137636
diff --git a/buglist_unique.txt b/buglist_unique.txt
deleted file mode 100644
index 7caec98..0000000
--- a/buglist_unique.txt
+++ /dev/null
@@ -1,19 +0,0 @@
-138964382
-139137636
-139828243
-143361609
-146593239
-147305863
-148867106
-149792636
-150122946
-150260456
-150522230
-150644839
-150680980
-150788630
-150864182
-150876921
-151025334
-151050221
-151166786
diff --git a/buglist_with_title.txt b/buglist_with_title.txt
deleted file mode 100644
index 39a9bb4..0000000
--- a/buglist_with_title.txt
+++ /dev/null
@@ -1,48 +0,0 @@
-133381284  erosky    P1        ASSIGNED  [R] Move split-screen presentation/layout to system-ui. ----
-139750033  sunnygoyal  P2        ASSIGNED  Overview misfired ----
-139828243  xuqiu     P2        ASSIGNED  [Overview Actions] Add Overview actions ----
-142753423  sfufa     P3        ACCEPTED  Prototype predictive hotseat ----
-144052839  tracyzhou  P2        ACCEPTED  Improving Launcher preview in ThemePicker ----
-144854916  winsonc   P2        ASSIGNED  Add winscope logging for systemui/launcher ----
-145253300  awickham  P1        FIXED   [a11y]Talkback doesn't focus on pop up window when long press Smart space. ----
-145297320  zakcohen  P2        FIXED   Overview layout with anchor chips ----
-145595763  sfufa     P4        FIXED   Wrong Tab is highlighted after creating Work profile ----
-145647019  twickham  P1        FIXED   [a11y] Use Talkback page navigation to switch apps in Overview, Talkback couldn't move focus across apps one by one. ----
-148099851  peskal    P1        FIXED   Make reduced scale snapshots toggle on/off base on config_reducedTaskSnapshotScale=0 ----
-148542211  awickham  P2        ASSIGNED  Sandbox for gesture nav tutorial ----
-148867106  vadimt    P2        ASSIGNED  [Flaky test] AddConfigWidgetTest.testConfigCancelled ----
-148896221  hyunyoungs  P3        ASSIGNED  Migrate to soong ----
-148900990  hyunyoungs  P2        FIXED   Hard to know if there's a text field for naming folders without a cursor ----
-149197172  sfufa     P1        FIXED   [Regression] New work profile OOBE doesn't adopt theme color ----
-149198955  sfufa     P2        FIXED   Work tab flash seems superfluous with new OOBE ----
-149199058  sfufa     P1        FIXED   Work profile OOBE not dismissed after locking/unlocking screen ----
-149200572  sfufa     P1        FIXED   [Regression] Work profile toggle is obscured by system navigation bar ----
-149215103  sfufa     P1        FIXED   OOBE still displayed after removing work profile ----
-149422395  vadimt    P2        ASSIGNED  Test should fail with a clear diags if it can't install a required package ----
-149481723  sfufa     P2        FIXED   Launcher OOBE for work profile and COPE devices improvements ----
-149867607  sfufa     P0        FIXED   [Failing test] WorkTabTest.workTabExists ----
-149870691  twickham  P2        ASSIGNED  Cleanup AppWindowAnimationHelper and TransformParams ----
-149927292  sfufa     P0        FIXED   [Flaky test] Apps view did not bind WorkTabTest.toggleWorks ----
-149935239  hyunyoungs  P1        FIXED   Update the suggestFolderName when items are added and deleted from folders ----
-149967272  hyunyoungs  P1        FIXED   Tap on Edit Name interaction drops the first recommendation ----
-149969889  sfufa     P2        FIXED   [Crash] java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.ComponentName ----
-149993849  thiruram  P1        FIXED   Pixel launcher crashes when tapping on empty shortcut folder name ----
-138964382  awickham  P2        ASSIGNED  Move all shared libs to a common location (plugin / iconloaderlib) ----
-139137636  vadimt    P2        ASSIGNED  Create memory tests for Launcher ----
-139828243  xuqiu     P2        ASSIGNED  [Overview Actions] Add Overview actions ----
-143361609  twickham  P2        ASSIGNED  Overview gesture in R ----
-146593239  awickham  P2        FIXED   Gesture navigation fail in Facebook messenger ----
-147305863  sfufa     P2        FIXED   DragNDrop userevent missing target hierarchy ----
-148867106  vadimt    P2        FIXED   [Flaky test] AddConfigWidgetTest.testConfigCancelled ----
-149792636  dupin     P2        ASSIGNED  [Android 11] Blur polish ----
-150122946  sfufa     P4        FIXED   Work toggle styling not correct ----
-150260456  peanutbutter  P1        FIXED   Add Fixed Rotation Transform to Home Settings ----
-150522230  hyunyoungs  P1        ASSIGNED  Pixel launcher keeps crashing ('java.lang.String android.content.ComponentName.getPackageName()) ----
-150644839  twickham  P3        FIXED   In landscape mode, long press on app shortcut UI issue observed. ----
-150680980  jonmiranda  P2        FIXED   Slide up to home screen animation is not smooth ----
-150788630  hyunyoungs  P1        ASSIGNED  Turn off FOLDER_NAME_SUGGEST flag on QQ builds earliest possible ----
-150864182  twickham  P4        FIXED   Crash when dumping while user is locked ----
-150876921  jonmiranda  P2        FIXED   Pages views seem to snap back to previous page ----
-151025334  hyunyoungs  P1        VERIFIED  DeviceConfig doesn't update immediately when Pheonotype pushes ----
-151050221  dupin     P1        FIXED   [NO LAST KMSG] [Short Uptime] [R]DUT will display the "Pixel is starting..." after rebooting and unlocking SIM pin code ----
-151166786  sunnygoyal  P2        FIXED   Swipe handler and activity interface mismatch ----
diff --git a/commitlist.txt b/commitlist.txt
deleted file mode 100644
index e0df402..0000000
--- a/commitlist.txt
+++ /dev/null
@@ -1,777 +0,0 @@
-COMMAND>> git log f3779f129f7326cb7acb57bf6aabd68aca5b6218..3aaf3967348ff55d2b8ac6d50e59ea01d9362af9(B
-commit 3aaf3967348ff55d2b8ac6d50e59ea01d9362af9
-Author: Vinit Nayak <peanutbutter@google.com>
-Date:   Fri Mar 13 13:57:09 2020 -0700
-
-    Remove sensor manager from RecentsView
-    
-    This disables the rotation animation in
-    overview, but should hopefully fix tests.
-    
-    fixes: 150260456
-    Change-Id: I121cad155672c2e325cc0f83ce209be0d3806b1c
-
-commit 9caed38e34671e6b07b95bea1e971dee03b010ce
-Merge: 6d8203ef4 f546e0599
-Author: Hyunyoung Song <hyunyoungs@google.com>
-Date:   Fri Mar 13 16:41:17 2020 +0000
-
-    Merge "Null check every ComponentName call inside FolderNameProvider" into ub-launcher3-master
-
-commit f546e0599e8127068e7d75ff787483453c781275
-Author: Hyunyoung Song <hyunyoungs@google.com>
-Date:   Thu Mar 12 12:53:43 2020 -0700
-
-    Null check every ComponentName call inside FolderNameProvider
-    
-    Bug: 150522230
-    Change-Id: I50007a3a781234797e16d830935a8b8585ac242b
-
-commit 6d8203ef4495f9550999f483f7c9822316b29971
-Merge: 3af717835 3388323bc
-Author: TreeHugger Robot <treehugger-gerrit@google.com>
-Date:   Fri Mar 13 03:00:46 2020 +0000
-
-    Merge "Disable OrientationListener if vertical landscape not supported" into ub-launcher3-master
-
-commit 3388323bc1b598dcee37d9522c14f968a1318f8e
-Author: Vinit Nayak <peanutbutter@google.com>
-Date:   Thu Mar 12 17:18:51 2020 -0700
-
-    Disable OrientationListener if vertical landscape not supported
-    
-    Even if multiple orientations are disabled but the flag
-    is on, we'll be listneing and setting different layouts.
-    Seeing in some tests that that callback was getting fired
-    for some reason.
-    
-    Fixes: 150260456
-    Change-Id: I0a1c9f06cc4830d3dc8410a777d595851f1c35eb
-
-commit 3af717835652536726e42d46d5875adf5ceb3cb7
-Author: thiruram <thiruram@google.com>
-Date:   Thu Mar 12 16:16:24 2020 -0700
-
-    Fixes missing smart folder logging bug. Uses ProtoLite.toString method to log LauncherEvents.
-    
-    Change-Id: I45dbf189e7bd47f8d4d7ba55180e59686bd6ecae
-
-commit b6bc08ad5751e360a0e0407f7fc5cd708b7a28be
-Merge: 6aa63d9f8 984c01cbc
-Author: Tony Wickham <twickham@google.com>
-Date:   Thu Mar 12 21:48:42 2020 +0000
-
-    Merge "Invert playNonAtomicComponent() as onlyPlayAtomicComponent()" into ub-launcher3-master
-
-commit 6aa63d9f8ec9a20c431da2f0cd05be610ed0d152
-Merge: 4e82f5bc3 f0d96f83f
-Author: Vadim Tryshev <vadimt@google.com>
-Date:   Thu Mar 12 21:01:30 2020 +0000
-
-    Merge "Fixing activity leak via accumulation of draw listeners" into ub-launcher3-master
-
-commit f0d96f83f72e81ffc0f14db60c50c022839bb6e7
-Author: vadimt <vadimt@google.com>
-Date:   Tue Mar 10 13:44:58 2020 -0700
-
-    Fixing activity leak via accumulation of draw listeners
-    
-    Bug: 139137636
-    Change-Id: I0a2f0849f886acaac31746ac7c9724c765692e88
-
-commit 4e82f5bc364f66d9a6ac72071db0d18e5a7fd956
-Author: Sunny Goyal <sunnygoyal@google.com>
-Date:   Thu Mar 12 12:29:33 2020 -0700
-
-    Fixing crash when swiping up using 3P Launcher
-    
-    Change-Id: Ia181edc1a00136374b3f0d848beccf0c9acd7b5c
-
-commit f85fcc792f74b3143670cef9e260ffb1b68a1f9f
-Merge: b9ec9319c f5a4deb12
-Author: TreeHugger Robot <treehugger-gerrit@google.com>
-Date:   Thu Mar 12 18:12:59 2020 +0000
-
-    Merge "Hide work apps when work profile is paused" into ub-launcher3-master
-
-commit f5a4deb120731f116aea6b2161560f4e81502260
-Author: Samuel Fufa <sfufa@google.com>
-Date:   Wed Mar 4 16:24:06 2020 -0800
-
-    Hide work apps when work profile is paused
-    
-    - hide overlay icon in landscape mode
-    - don't show edu if user has already seen legacy work profile edu
-    - make sure personal tab is highlighted when work profile is reinstalled
-    - always go home after a work profile is added or removed
-    - add tests for work edu flow
-    
-    Bug: 150122946
-    Test: Manual
-    Change-Id: I8f80ac763acf03ca31a534464f4ddfd84528d329
-
-commit b9ec9319c5534cf6ebd8df2ee1144e1ebb477c33
-Author: Vinit Nayak <peanutbutter@google.com>
-Date:   Wed Mar 4 12:05:28 2020 -0800
-
-    Add fixed_rotation_transform to home settings
-    
-    This sets the feature flag on launcher side
-    and also updates the setting in Settings.Global
-    Launcher DOES NOT listen to the Settings.Global
-    change from adb anymore. This should take
-    preference over setting it from command line.
-    
-    Also fix a related swipe to home animation bug
-    that happened w/ merge conflict.
-    
-    Fixes: 150260456
-    Test: Set and unset, visually verified behavior.
-    Tested w/ autorotate on and off.
-    Checked Settings.Global value correctly updated
-    via "adb shell settings get global
-    fixed_rotation_transform"
-    TODO: Update tests to reflect this new
-    default-on fixed rotation behavior.
-    
-    Change-Id: Id95f006eb1e92a59e24b05567298fd21b1409b13
-
-commit 984c01cbcda8ab31c24c6a27118d9c1934c23795
-Author: Tony Wickham <twickham@google.com>
-Date:   Fri Mar 6 15:56:46 2020 -0800
-
-    Invert playNonAtomicComponent() as onlyPlayAtomicComponent()
-    
-    This avoids the double negative we use in a few places, so should be clearer.
-    Also added some comments to explain what the animComponents are used for.
-    
-    Change-Id: Ibd25bd12efce6553b377bbd9c0651e4f4ac3e498
-
-commit 31ff98e14491edda33a7ccd2be04795bdaad124c
-Merge: 4acdb3bcd 9e19866ed
-Author: TreeHugger Robot <treehugger-gerrit@google.com>
-Date:   Thu Mar 12 00:26:57 2020 +0000
-
-    Merge "Only allow horizontal spring if page will change." into ub-launcher3-master
-
-commit 4acdb3bcd7f69e0cd42b7fae16176cbe15b551aa
-Merge: 6a550f26a 003782f93
-Author: Sunny Goyal <sunnygoyal@google.com>
-Date:   Thu Mar 12 00:20:15 2020 +0000
-
-    Merge "Removing some properties out of AnimationBuilder" into ub-launcher3-master
-
-commit 6a550f26a79564575cee5dfe15338c64ffe00d2b
-Merge: 0abe81991 fa617d89c
-Author: TreeHugger Robot <treehugger-gerrit@google.com>
-Date:   Thu Mar 12 00:15:44 2020 +0000
-
-    Merge "Using FallbackSwipeHandler in 2-button mode" into ub-launcher3-master
-
-commit 003782f93c8b5096ebf6e64fbfa7e3483c11d685
-Author: Sunny Goyal <sunnygoyal@google.com>
-Date:   Fri Mar 6 14:52:17 2020 -0800
-
-    Removing some properties out of AnimationBuilder
-    
-    AnimationBuilder and PendingAnimation have similar logic. This will
-    allow to unify the two classes
-    
-    Change-Id: Id8c1d8a7020d132adbccdc6c80538ed6556cb75e
-
-commit 0abe81991398556485d6605dbeb57b64e2414f92
-Merge: 9c47ddd2a 21167f01d
-Author: TreeHugger Robot <treehugger-gerrit@google.com>
-Date:   Wed Mar 11 23:27:05 2020 +0000
-
-    Merge "[Overview Actions] Hide other tasks for select mode UI." into ub-launcher3-master
-
-commit fa617d89cece2c08c03fa7506700bed93d060cf7
-Author: Sunny Goyal <sunnygoyal@google.com>
-Date:   Wed Mar 11 16:20:25 2020 -0700
-
-    Using FallbackSwipeHandler in 2-button mode
-    
-    Bug: 151166786
-    Change-Id: Ia86af76c1779bafa4690e733a7e6764973c8ae0d
-
-commit 21167f01d4877d5927e4cd473cf6fcf61acc54d8
-Author: Becky Qiu <xuqiu@google.com>
-Date:   Thu Mar 5 11:26:34 2020 -0800
-
-    [Overview Actions] Hide other tasks for select mode UI.
-    
-    Test:local
-    Bug:139828243
-    
-    Change-Id: Idc9c6a0e354b9df7f48f3ce93b560fdc4999fc3a
-
-commit 9e19866ed86de5237ea02810e28fb56f3ab80616
-Author: Jon Miranda <jonmiranda@google.com>
-Date:   Wed Mar 11 14:42:02 2020 -0700
-
-    Only allow horizontal spring if page will change.
-    
-    Bug: 150876921
-    Change-Id: I88db4c28ec3f8213c583e8a0dcd1cce2b1fee322
-
-commit 9c47ddd2a60aacd5663e040cf1f90d65896544a4
-Merge: 8780065fd ddb08885f
-Author: Tony Wickham <twickham@google.com>
-Date:   Wed Mar 11 21:10:04 2020 +0000
-
-    Merge "Try orienting popup the other way if offset pushes it out of bounds" into ub-launcher3-master
-
-commit 8780065fdbe9e0b012f110d523447bcf4022a191
-Merge: 25960bcd8 2768a2468
-Author: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-Date:   Wed Mar 11 20:17:17 2020 +0000
-
-    [automerger skipped] Merge "[DO NOT MERGE] Adds fling gesture suppression to Launcher" into ub-launcher3-qt-qpr1-dev am: 631ed598ee -s ours am: 2768a24688 -s ours
-    
-    am skip reason: subject contains skip directive
-    
-    Change-Id: I36a95c05e2cef2bb615c67c30697c8a1b07b4c6f
-
-commit 25960bcd88b97727c9892dfce0ad57761ef3a992
-Merge: 9c40c83d7 f3b22ffee
-Author: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-Date:   Wed Mar 11 20:17:10 2020 +0000
-
-    [automerger skipped] [DO NOT MERGE] Adds fling gesture suppression to Launcher am: f3b22ffee6 -s ours
-    
-    am skip reason: subject contains skip directive
-    
-    Change-Id: I38695bd05d74b8a526e61c2b55b2d2669e4e31a7
-
-commit 2768a24688171fddd0ed4e8fa720d308736c2ca8
-Merge: f3b22ffee 631ed598e
-Author: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-Date:   Wed Mar 11 20:05:05 2020 +0000
-
-    [automerger skipped] Merge "[DO NOT MERGE] Adds fling gesture suppression to Launcher" into ub-launcher3-qt-qpr1-dev am: 631ed598ee -s ours
-    
-    am skip reason: subject contains skip directive
-    
-    Change-Id: I6a1906e1e7e302ca6886f3ecdeac5d374476020e
-
-commit ddb08885f9d51db577aedc386cc3a90d054132b2
-Author: Tony Wickham <twickham@google.com>
-Date:   Tue Mar 10 18:25:31 2020 -0700
-
-    Try orienting popup the other way if offset pushes it out of bounds
-    
-    orientAboutObject() currently determines whether the popup should align
-    its left side with the icon or the right side. However, after
-    determining this, there is an offset to ensure the popup lines up with
-    the icon as expected, which might push it out of bounds. In that case,
-    we fallback to centering the popup. However, there might be plenty of
-    room on the other side, so we should just align the other direction
-    instead. Updated the logic to do that by first trying to align left
-    (in LTR) or right (in RTL), then trying again with the other alignment
-    if it doesn't fit after all x calculations are made.
-    
-    Bug: 150644839
-    Change-Id: I219dae331bf790e461d91394ffe025d40ec54c9b
-
-commit f3b22ffee691dca3e8a5cc3c0a1fb1d19ce8a5ad
-Author: Govinda Wasserman <gwasserman@google.com>
-Date:   Thu Mar 5 16:50:22 2020 -0500
-
-    [DO NOT MERGE] Adds fling gesture suppression to Launcher
-    
-    Test: Tested locally
-    BUG: 150688842
-    Change-Id: Ifa96bd01363de47cf1d8cdce34d81d525c8c2c04
-    (cherry picked from commit 9b90b1b0345ea57a6152919d318f4ce9cacd7556)
-
-commit 631ed598ee115bdfa1b3249a87c1f266eb93d57d
-Merge: 9a32222ce 9b90b1b03
-Author: Govinda Wasserman <gwasserman@google.com>
-Date:   Wed Mar 11 18:19:26 2020 +0000
-
-    Merge "[DO NOT MERGE] Adds fling gesture suppression to Launcher" into ub-launcher3-qt-qpr1-dev
-
-commit 9b90b1b0345ea57a6152919d318f4ce9cacd7556
-Author: Govinda Wasserman <gwasserman@google.com>
-Date:   Thu Mar 5 16:50:22 2020 -0500
-
-    [DO NOT MERGE] Adds fling gesture suppression to Launcher
-    
-    Test: Tested locally
-    BUG: 150688842
-    Change-Id: Ifa96bd01363de47cf1d8cdce34d81d525c8c2c04
-
-commit 9c40c83d70bbe4689e1bddc012aec6d7a04dc490
-Merge: b41aa64b8 86ace5452
-Author: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-Date:   Tue Mar 10 20:55:30 2020 +0000
-
-    [automerger skipped] Merge "Dismisses system overlays for Home intent." into ub-launcher3-qt-future-dev am: 86ace54523 -s ours
-    
-    am skip reason: Change-Id Ib9c85de2f83f99d1ef53fb17fde5d0b3c514849a with SHA-1 65ced1b1d0 is in history
-    
-    Change-Id: I70904c253ac6ad36820069f64d9338ee067d159b
-
-commit b41aa64b8df2256f554c7ea4fa96c10464d1a7b6
-Merge: d7c844167 5e72945a8
-Author: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-Date:   Tue Mar 10 20:55:18 2020 +0000
-
-    [automerger skipped] Merge "Import translations. DO NOT MERGE" into ub-launcher3-rvc-dev am: 5e72945a85 -s ours
-    
-    am skip reason: subject contains skip directive
-    
-    Change-Id: Ib8b27aa6ad4e4f0ed4bdebf3bb7b7cac654fad25
-
-commit d7c84416765d4883fa2d8bba595c058db042325f
-Merge: 0a9471546 3c1db273b
-Author: Hyunyoung Song <hyunyoungs@google.com>
-Date:   Tue Mar 10 19:15:37 2020 +0000
-
-    Merge "DeviceFlag change is not detected when phenotype updates." into ub-launcher3-master
-
-commit 5e72945a852ba56976835874a7c012726d2e00d6
-Merge: a066cb443 a159b77ef
-Author: TreeHugger Robot <treehugger-gerrit@google.com>
-Date:   Tue Mar 10 17:45:51 2020 +0000
-
-    Merge "Import translations. DO NOT MERGE" into ub-launcher3-rvc-dev
-
-commit 86ace54523dc354fadd65987e5ace43f89586e34
-Merge: 44e729895 b3b8aefe5
-Author: TreeHugger Robot <treehugger-gerrit@google.com>
-Date:   Tue Mar 10 17:42:54 2020 +0000
-
-    Merge "Dismisses system overlays for Home intent." into ub-launcher3-qt-future-dev
-
-commit a159b77ef149652daf56e4f413fd4dbc264b032b
-Author: Bill Yi <byi@google.com>
-Date:   Tue Mar 10 09:56:26 2020 -0700
-
-    Import translations. DO NOT MERGE
-    
-    Auto-generated-cl: translation import
-    Change-Id: I4d6b82f9e793cf2649102e913c81c27f6ccc004a
-
-commit 0a947154691988747aae405018c8ddb36be27a05
-Merge: f2508e783 44e729895
-Author: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-Date:   Tue Mar 10 16:21:57 2020 +0000
-
-    [automerger skipped] Merge "Import translations. DO NOT MERGE" into ub-launcher3-qt-future-dev am: 44e7298953 -s ours
-    am skip reason: subject contains skip directive
-    
-    Change-Id: I1b060550e9d0f839c96b1582712321ef530e353f
-
-commit 44e729895391cd49acc938004c6044fc652ea9db
-Merge: defd3c0e6 83730697f
-Author: TreeHugger Robot <treehugger-gerrit@google.com>
-Date:   Tue Mar 10 16:18:29 2020 +0000
-
-    Merge "Import translations. DO NOT MERGE" into ub-launcher3-qt-future-dev
-
-commit f2508e783f624f423bb892a0060d84cbfc36052f
-Merge: e635a2689 defd3c0e6
-Author: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-Date:   Tue Mar 10 16:16:51 2020 +0000
-
-    [automerger skipped] Import translations. DO NOT MERGE am: 9a32222ce7 -s ours am: defd3c0e6f -s ours
-    am skip reason: subject contains skip directive
-    
-    Change-Id: I5eb69f5179f420f1ab678a025ab7bf28f93f595a
-
-commit defd3c0e6fc964cbbb5801cf045bd6de9689a0c7
-Merge: 94c993a63 9a32222ce
-Author: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-Date:   Tue Mar 10 16:04:16 2020 +0000
-
-    [automerger skipped] Import translations. DO NOT MERGE am: 9a32222ce7 -s ours
-    am skip reason: subject contains skip directive
-    
-    Change-Id: I4fe5c187cc89f91199f3265f43d323556a992841
-
-commit e635a2689d0b75065a7a8d5fae031382137303a6
-Merge: dbcc63ede a066cb443
-Author: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-Date:   Tue Mar 10 16:04:12 2020 +0000
-
-    [automerger skipped] Import translations. DO NOT MERGE am: a066cb4430 -s ours
-    am skip reason: subject contains skip directive
-    
-    Change-Id: I2cd255fd2f265718df096d05f3c8a67c78662c58
-
-commit dbcc63edebe373d5f698e7d404eff83898a5b8b2
-Author: Sunny Goyal <sunnygoyal@google.com>
-Date:   Fri Mar 6 15:35:55 2020 -0800
-
-    Removing some autoboxing during property animation
-    
-    Change-Id: Ibd6f20c565a4d66dc6d606b3f0bbc96fec66fe56
-
-commit add170098c5696948edbd7d7e3f220c801cfc9eb
-Merge: d01ee6d6a 4c9ee6354
-Author: Sunny Goyal <sunnygoyal@google.com>
-Date:   Tue Mar 10 08:43:41 2020 +0000
-
-    Merge "Converting some anonymous classes to lambda calls" into ub-launcher3-master
-
-commit b3b8aefe5751bf55ef32f61148f2a9b6c811db9e
-Author: Andy Wickham <awickham@google.com>
-Date:   Tue Mar 10 01:36:02 2020 +0000
-
-    Dismisses system overlays for Home intent.
-    
-    Test: Used Facebook chatheads (not system bubble).
-    Before the change, Home gesture didn't work. After
-    the change, it does work :)
-    Bug: 146593239
-    
-    Merged-In: Ib9c85de2f83f99d1ef53fb17fde5d0b3c514849a
-    Change-Id: I19d91aaed19ccaec68478e364ce6b80049d49a98
-
-commit a066cb4430989496e80770bbac04c68b2d515f2b
-Author: Bill Yi <byi@google.com>
-Date:   Mon Mar 9 19:41:30 2020 -0700
-
-    Import translations. DO NOT MERGE
-    
-    Auto-generated-cl: translation import
-    Change-Id: I91780ce30d4eb9825d415e2825b9a94e2a4fade8
-
-commit 83730697f8416f8124bb0a6593aba5334f38546e
-Author: Bill Yi <byi@google.com>
-Date:   Mon Mar 9 19:37:07 2020 -0700
-
-    Import translations. DO NOT MERGE
-    
-    Auto-generated-cl: translation import
-    Change-Id: I833e37768c8b22a17cd5e36ac7b01ac024f3bbfc
-
-commit 9a32222ce76b37911e42fdfa500d3e47623d459d
-Author: Bill Yi <byi@google.com>
-Date:   Mon Mar 9 19:32:09 2020 -0700
-
-    Import translations. DO NOT MERGE
-    
-    Auto-generated-cl: translation import
-    Change-Id: I4cb33e7020ee7cb582982fecba72dfd7f2c70469
-
-commit d01ee6d6a84ca0fcc2dbc4757cdc90e36da02692
-Merge: 7dfe1360e 94c993a63
-Author: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-Date:   Tue Mar 10 01:05:38 2020 +0000
-
-    [automerger skipped] Merge "[DO NOT MERGE] Fix some visual jumps when swiping home" into ub-launcher3-qt-future-dev am: 94c993a635 -s ours
-    am skip reason: subject contains skip directive
-    
-    Change-Id: I2b30725cd240cdc7ebfcb50eb2180aaf81ab4267
-
-commit 94c993a635f00796b00c45b47b29496fe891839c
-Merge: e1664fcf1 8caa78790
-Author: Jonathan Miranda <jonmiranda@google.com>
-Date:   Tue Mar 10 00:49:17 2020 +0000
-
-    Merge "[DO NOT MERGE] Fix some visual jumps when swiping home" into ub-launcher3-qt-future-dev
-
-commit 7dfe1360edbe9ea64c0b8c591ffc525ba9a5a581
-Merge: 590914cc5 79a352169
-Author: TreeHugger Robot <treehugger-gerrit@google.com>
-Date:   Tue Mar 10 00:46:11 2020 +0000
-
-    Merge "Show drag handle indictor in 2 zone model" into ub-launcher3-master
-
-commit 79a352169f0197f5d0ea2be32cd8cf2f7dbef1ad
-Author: Tony Wickham <twickham@google.com>
-Date:   Mon Mar 9 16:31:21 2020 -0700
-
-    Show drag handle indictor in 2 zone model
-    
-    Note this is just the tiny arrow we show in accessibility mode, will
-    probably need to get some updated visual treatment going forward.
-    
-    Bug: 143361609
-    Change-Id: I65975727f101984429aadc35a650826e36d9c9aa
-
-commit 590914cc5ec219590f56cea52d975865187e7e0e
-Merge: e9801665a 65ced1b1d
-Author: TreeHugger Robot <treehugger-gerrit@google.com>
-Date:   Mon Mar 9 22:00:20 2020 +0000
-
-    Merge "Dismisses system overlays for Home intent." into ub-launcher3-master
-
-commit e9801665ac81ac0d4059991377a3a782f92dfe66
-Merge: b365cc438 e1664fcf1
-Author: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-Date:   Mon Mar 9 21:42:07 2020 +0000
-
-    [automerger skipped] [DO NOT MERGE] Initiailize APP_SEARCH_IMRPOVEMENT flag outside DEBUG builds. am: e1664fcf14 -s ours
-    am skip reason: subject contains skip directive
-    
-    Change-Id: I06a7e3ef6f5a0d6ed852b0b32238426ca3ba3809
-
-commit 3c1db273bec12d4bb0573e20bc0f6ecc5b7e18b7
-Author: Hyunyoung Song <hyunyoungs@google.com>
-Date:   Sat Mar 7 23:52:17 2020 -0800
-
-    DeviceFlag change is not detected when phenotype updates.
-    
-    Bug: 151025334
-    Test: adb shell device_config put launcher FOLDER_NAME_SUGGEST false
-    
-    Change-Id: I5e478aebfea5847cf7cbe7c0cb6cb0c6f81481cb
-
-commit b365cc43878e0cb4e6d4b998c10cd590adc4cb8d
-Author: Tony Wickham <twickham@google.com>
-Date:   Mon Mar 9 13:20:04 2020 -0700
-
-    Fix crash when dumping before user unlocks
-    
-    Bug: 150864182
-    Bug: 151050221
-    Change-Id: I29ba2ef66b4359a47f866d02306498537c45236e
-
-commit e1664fcf1486d337ea998ded70d092590a0bfbd9
-Author: Alex Mang <alexmang@google.com>
-Date:   Mon Mar 9 12:57:11 2020 -0700
-
-    [DO NOT MERGE] Initiailize APP_SEARCH_IMRPOVEMENT flag outside DEBUG builds.
-    
-    This is addressing a bug where flags are only changed on debug build
-    devices or initially when changes. When nexuslauncher restarts the flag
-    is no longer retrieved.
-    
-    Change-Id: Ieb6f460a271c918ee4e493b34692244f39cb3740
-
-commit 8caa787906b49427afed77e7bd63b72d9bbbe8a7
-Author: Jon Miranda <jonmiranda@google.com>
-Date:   Mon Mar 9 12:50:38 2020 -0700
-
-    [DO NOT MERGE] Fix some visual jumps when swiping home
-    
-    All caused by running the transform progress from 0 to 1 instead of
-    starting at whatever the progress was before ending the gesture, e.g.:
-    - When swiping to home without animating into an icon, the corner radius
-      was set back to the window corner radius.
-    - Before this change, the clip didn't update throughout the animation,
-      making the window slightly bigger than the floating icon view; after
-      this change, the clip jumped to show the insets again before clipping
-      back down during the home animation.
-    
-    Partial backport of ag/Ie48f4b665a5bf3cbef76bdf7f043febe99fb84a0
-    
-    Bug: 150680980
-    Change-Id: Ida65097f0ef7d2e11d48b84ecdd353ef89078015
-
-commit bf48cd480cd131c370760117681917dedd784c51
-Author: Andy Wickham <awickham@google.com>
-Date:   Tue Mar 3 01:15:27 2020 +0000
-
-    Removes iconloaderlib from Launcher3.
-    
-    (It's now in frameworks/libs/systemui)
-    
-    Bug: 138964382
-    Test: builds
-    Change-Id: Ic60adfb2ebdcf1a72b440df26023b861fd6e62d5
-
-commit a9bcd82554534d55e38ca039eb52c1dfacbdb70a
-Merge: e6df7da2a cfaa4889e
-Author: TreeHugger Robot <treehugger-gerrit@google.com>
-Date:   Sat Mar 7 08:38:49 2020 +0000
-
-    Merge "Enabling springs for start dismiss animation" into ub-launcher3-master
-
-commit e6df7da2a252c5d57114346d6bf6d6883b4a9f9a
-Merge: df8232c22 16eca5500
-Author: TreeHugger Robot <treehugger-gerrit@google.com>
-Date:   Sat Mar 7 07:19:13 2020 +0000
-
-    Merge "Enabling event verification for Launcher3" into ub-launcher3-master
-
-commit cfaa4889e65190b40ea988dd03421a01c9e06abc
-Author: Sunny Goyal <sunnygoyal@google.com>
-Date:   Tue Feb 25 14:37:01 2020 -0800
-
-    Enabling springs for start dismiss animation
-    
-    > Adding flag support for PendingAnimation which can be used
-      to define custom behavior for various animations
-    > Using SpringAnimationBuild for spring animation instead of
-      SpringObjectanimator
-    
-    Change-Id: I41ca34b0574981bb3fc7894639a321c12e6feac1
-
-commit df8232c2242eeb2d8efc050a5e7afb88782ed9ca
-Merge: cfea0fb34 f538393e4
-Author: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-Date:   Sat Mar 7 06:50:59 2020 +0000
-
-    [automerger skipped] [DO NOT MERGE] Turn off FOLDER_NAME_SUGGEST feature flag am: f538393e42 -s ours
-    am skip reason: subject contains skip directive
-    
-    Change-Id: I1bbffc4f9582d106ce28ac07e6c6719c9f8f063d
-
-commit f538393e42896e5488ca10fccc4cf9409d7295e2
-Author: Hyunyoung Song <hyunyoungs@google.com>
-Date:   Fri Mar 6 12:53:42 2020 -0800
-
-    [DO NOT MERGE] Turn off FOLDER_NAME_SUGGEST feature flag
-    Bug: 150788630
-    
-    Change-Id: I740d6b6f3ee1a33a95debfafa29b3caea24a03c3
-
-commit 65ced1b1d00bc6a6713b442162020df31d497f54
-Author: Andy Wickham <awickham@google.com>
-Date:   Sat Mar 7 02:14:19 2020 +0000
-
-    Dismisses system overlays for Home intent.
-    
-    Test: Used Facebook chatheads (not system bubble).
-    Before the change, Home gesture didn't work. After
-    the change, it does work :)
-    Fixes: 146593239
-    
-    Change-Id: Ib9c85de2f83f99d1ef53fb17fde5d0b3c514849a
-
-commit cfea0fb348910bb245a03df0e178f0be59905dc7
-Merge: 312340504 e7dd35ef0
-Author: TreeHugger Robot <treehugger-gerrit@google.com>
-Date:   Sat Mar 7 00:20:56 2020 +0000
-
-    Merge "OverviewActions: Renaming overview_actions_container and setActionsView" into ub-launcher3-master
-
-commit 31234050473d511a61ce4dc257c15281c1c886b4
-Merge: f655f5cd1 27409e23d
-Author: Sunny Goyal <sunnygoyal@google.com>
-Date:   Fri Mar 6 23:44:57 2020 +0000
-
-    Merge "Removing SecondaryDisplayLauncher library as it is directly included in Launcher" into ub-launcher3-master
-
-commit f655f5cd16a9579afc11088e5545148261225b9f
-Merge: a579ddc9c 8687bc213
-Author: Winson Chung <winsonc@google.com>
-Date:   Fri Mar 6 23:33:26 2020 +0000
-
-    Merge "Initial changes to support blur" into ub-launcher3-master
-
-commit 4c9ee63540dcd2c039831edb0816a56458e30f8f
-Author: Sunny Goyal <sunnygoyal@google.com>
-Date:   Fri Mar 6 15:16:22 2020 -0800
-
-    Converting some anonymous classes to lambda calls
-    
-    Change-Id: I386046a4a515d84801a8bbd11cfa090ba7adfd71
-
-commit 8687bc2131ce98ea0d058290dd21f03b5a429c82
-Author: Winson Chung <winsonc@google.com>
-Date:   Thu Feb 27 23:34:24 2020 -0800
-
-    Initial changes to support blur
-    
-    - Add a new controller to update the background blur on either the
-      launcher or app surfaces based on state or transition
-    
-    Bug: 149792636
-    
-    Change-Id: I6103cd3d53a00c8025558dd49bb73137e2980014
-
-commit a579ddc9c813f314ab3dfd4e80a9c0cf1c77ec61
-Author: Samuel Fufa <sfufa@google.com>
-Date:   Thu Feb 27 16:59:19 2020 -0800
-
-    Refactor logging to capture Target hierarchy
-    
-    Instead of creating a fixed number of targets, we now pass an ArrayList
-    of targets to. Any class implementing
-    LogContainerProviders#fillInLogContainerData can setup it's own target
-    and add it to the ArrayList, It can also pass the ArrayList to other
-    LogContainerProvider to capture full Target hierarchy.
-    
-    Bug: 147305863
-    Change-Id: I0063c692120fb9e1cff2d8902c5da972d0623418
-
-commit e7dd35ef0dd653764e57665756e33ce75446e520
-Author: Sreyas <sreyasr@google.com>
-Date:   Fri Mar 6 10:50:43 2020 -0800
-
-    OverviewActions: Renaming overview_actions_container and setActionsView
-    
-    Change-Id: Ie444101f246e0d00980b47ce39f6e74dade23f73
-
-commit 9099dfcfb75dff987c157a659de5883fe92b22c4
-Merge: e90adc47e 04b90c0fc
-Author: Vadim Tryshev <vadimt@google.com>
-Date:   Fri Mar 6 18:36:05 2020 +0000
-
-    Merge "Test tweaks for the memory activity recreation test" into ub-launcher3-master
-
-commit e90adc47ef114e129a14382f30d84910ef394348
-Author: Winson Chung <winsonc@google.com>
-Date:   Fri Mar 6 00:01:49 2020 -0800
-
-    Fallback to predefined orientation handler if recents view isn't available
-    
-    Change-Id: Iaed42fb9ef598d65e1cf2d166cc343f888352d47
-
-commit d9da5a45fd1653c14e6fd54b15708a696c43c037
-Merge: f67ab6c64 3abc8511a
-Author: TreeHugger Robot <treehugger-gerrit@google.com>
-Date:   Fri Mar 6 06:53:51 2020 +0000
-
-    Merge "Enabling widget config tests after fixing flakiness" into ub-launcher3-master
-
-commit 16eca5500dab2f253f52920edaed477f5e43f413
-Author: vadimt <vadimt@google.com>
-Date:   Thu Mar 5 19:02:18 2020 -0800
-
-    Enabling event verification for Launcher3
-    
-    Will help investigating otherwise mysterious failures.
-    
-    Change-Id: I805ed136baf6d86921fdb4782304fcdafbd3af5c
-
-commit f67ab6c64d0691f7ff1ede3942179172122dac1e
-Merge: 93648b0a5 bb2bf277c
-Author: TreeHugger Robot <treehugger-gerrit@google.com>
-Date:   Fri Mar 6 02:14:19 2020 +0000
-
-    Merge "Catching everything from dumpHprofData" into ub-launcher3-master
-
-commit bb2bf277c0b22ecb1dc64d9971978d3c958dda44
-Author: vadimt <vadimt@google.com>
-Date:   Thu Mar 5 17:22:18 2020 -0800
-
-    Catching everything from dumpHprofData
-    
-    Change-Id: I79ced1d4bb3e6ea43ce6fa5bd07fe22b577006f9
-
-commit 3abc8511a51afe983b481bdf6631535aa8b94f28
-Author: vadimt <vadimt@google.com>
-Date:   Thu Mar 5 14:01:23 2020 -0800
-
-    Enabling widget config tests after fixing flakiness
-    
-    Bug: 148867106
-    Change-Id: I8bbd9ef9b1ca574f79f3f76869051495b59734ce
-
-commit 27409e23d1ba7ca94031de8ff0603c53b86cfcdc
-Author: Sunny Goyal <sunnygoyal@google.com>
-Date:   Wed Feb 26 16:08:20 2020 -0800
-
-    Removing SecondaryDisplayLauncher library as it is directly included in Launcher
-    
-    Change-Id: I97a1fad07f2f6d34fc31c720fcc1e03d0f56477e
-
-commit 04b90c0fcb316c830f7dc66475ff85c6760402f5
-Author: vadimt <vadimt@google.com>
-Date:   Tue Oct 15 10:47:51 2019 -0700
-
-    Test tweaks for the memory activity recreation test
-    
-    Speeding up switching navigation mode by switching from latch
-    (which is not fired) to polling. I'll figure out later why the latch
-    doesn't work.
-    
-    Bug: 139137636
-    Change-Id: I28a9b2b9a3882919fd2a3280b9804afe1b44a46e
diff --git a/protos/launcher_atom.proto b/protos/launcher_atom.proto
new file mode 100644
index 0000000..a89fe5c
--- /dev/null
+++ b/protos/launcher_atom.proto
@@ -0,0 +1,108 @@
+/*
+ * 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.
+ */
+syntax = "proto2";
+
+option java_package = "com.android.launcher3.logger";
+option java_outer_classname = "LauncherAtom";
+
+//
+// ItemInfos
+message ItemInfo {
+  oneof Item {
+    Application application = 1;
+    Task task= 2;
+    Shortcut shortcut = 3;
+    Widget widget = 4;
+  }
+  // When used for launch event, stores the global predictive rank
+  optional int32 rank = 5;
+
+  // Stores whether the Item belows to non primary user
+  optional bool is_work = 6;
+
+  // Item can be child node to parent container or parent containers (nested)
+  oneof Container {
+    WorkspaceContainer workspace = 7;
+    HotseatContainer hotseat = 8;
+    FolderContainer folder = 9;
+  }
+  // Stores the origin of the Item
+  optional Origin source = 10;
+}
+
+enum Origin {
+  UNKNOWN = 0;
+  DEFAULT_LAYOUT = 1;       // icon automatically placed in workspace, folder, hotseat
+  BACKUP_RESTORE = 2;       // icon layout restored from backup
+  PINITEM = 3;              // from another app (e.g., Chrome's "Add to Home screen")
+  ALLAPPS_ATOZ = 4;         // within launcher surface, all aps a-z
+  WIDGETS = 5;              // within launcher, widgets tray
+  ADD_TO_HOMESCREEN = 6;    // play install + launcher home setting
+  ALLAPPS_PREDICTION = 7;   // from prediction bar in all apps container
+  HOTSEAT_PREDICTION = 8;   // from prediction bar in hotseat container
+}
+
+// Main app icons
+message Application {
+  optional string package_name = 1;
+  optional string component_name = 2;
+}
+
+// Legacy shortcuts and shortcuts handled by ShortcutManager
+message Shortcut {
+  optional string shortcut_name = 1;
+}
+
+// AppWidgets handled by AppWidgetManager
+message Widget {
+  optional int32 span_x = 1;
+  optional int32 span_y = 2;
+  optional int32 app_widget_id = 3;
+  optional string package_name = 4; // only populated during snapshot if from workspace
+  optional string component_name = 5; // only populated during snapshot if from workspace
+}
+
+// Tasks handled by PackageManager
+message Task {
+  optional string package_name = 1;
+  optional string component_name = 2;
+  optional int32 index = 3;
+}
+
+//////////////////////////////////////////////
+// Containers
+
+message WorkspaceContainer {
+  optional int32 page_index = 1; // range [-1, l], 0 is the index of the main homescreen
+  optional int32 grid_x = 2;     // [0, m], m varies based on the display density and resolution
+  optional int32 grid_y = 3;     // [0, n], n varies based on the display density and resolution
+}
+
+message HotseatContainer {
+  optional int32 index = 1;
+}
+
+message FolderContainer {
+  optional int32 page_index = 1;
+  optional int32 grid_x = 2;
+  optional int32 grid_y = 3;
+  oneof Container {
+    WorkspaceContainer workspace = 4;
+    HotseatContainer hotseat = 5;
+  }
+}
+
+
diff --git a/protos/launcher_dump.proto b/protos/launcher_dump.proto
deleted file mode 100644
index dc8fbda..0000000
--- a/protos/launcher_dump.proto
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-syntax = "proto2";
-
-option java_package = "com.android.launcher3.model";
-option java_outer_classname = "LauncherDumpProto";
-
-package model;
-
-message DumpTarget {
-  enum Type {
-    NONE = 0;
-    ITEM = 1;
-    CONTAINER = 2;
-  }
-
-  optional Type type = 1;
-  optional int32 page_id = 2;
-  optional int32 grid_x = 3;
-  optional int32 grid_y = 4;
-
-  // For container types only
-  optional ContainerType container_type = 5;
-
-  // For item types only
-  optional ItemType item_type = 6;
-
-  optional string package_name = 7; // All ItemTypes except UNKNOWN type
-  optional string component = 8;   // All ItemTypes except UNKNOWN type
-  optional string item_id = 9; // For Pinned Shortcuts and appWidgetId
-
-  optional int32 span_x = 10 [default = 1];// Used for ItemType.WIDGET
-  optional int32 span_y = 11 [default = 1];// Used for ItemType.WIDGET
-  optional UserType user_type = 12;
-}
-
-// Used to define what type of item a Target would represent.
-enum ItemType {
-  UNKNOWN_ITEMTYPE = 0;  // Launcher specific items
-  APP_ICON = 1; // Regular app icons
-  WIDGET = 2;   // Elements from AppWidgetManager
-  SHORTCUT = 3; // ShortcutManager
-}
-
-// Used to define what type of container a Target would represent.
-enum ContainerType {
-  UNKNOWN_CONTAINERTYPE = 0;
-  WORKSPACE = 1;
-  HOTSEAT = 2;
-  FOLDER = 3;
-}
-
-// Used to define what type of control a Target would represent.
-enum UserType {
-  DEFAULT = 0;
-  WORK = 1;
-}
-
-// Main message;
-message LauncherImpression {
-  repeated DumpTarget targets = 1;
-}
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index 1d0b045..04506b5 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -73,6 +73,17 @@
             </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>
+
         <service
             android:name="com.android.launcher3.uioverrides.dynamicui.WallpaperManagerCompatVL$ColorExtractionService"
             tools:node="remove" />
diff --git a/quickstep/recents_ui_overrides/res/values/config.xml b/quickstep/recents_ui_overrides/res/values/config.xml
index 527eec6..120e034 100644
--- a/quickstep/recents_ui_overrides/res/values/config.xml
+++ b/quickstep/recents_ui_overrides/res/values/config.xml
@@ -14,8 +14,5 @@
      limitations under the License.
 -->
 <resources>
-    <integer name="app_background_blur_radius">150</integer>
-    <integer name="allapps_background_blur_radius">90</integer>
-    <integer name="overview_background_blur_radius">50</integer>
-    <integer name="folder_background_blur_radius_adjustment">20</integer>
+    <integer name="max_depth_blur_radius">150</integer>
 </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
index 20b1485..363840a 100644
--- a/quickstep/recents_ui_overrides/res/values/dimens.xml
+++ b/quickstep/recents_ui_overrides/res/values/dimens.xml
@@ -31,9 +31,6 @@
     <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>
-    <dimen name="all_apps_tip_bottom_margin">8dp</dimen>
-    <!-- The size of corner radius of the arrow in the arrow toast. -->
-    <dimen name="arrow_toast_corner_radius">2dp</dimen>
 
     <!-- Minimum distance to swipe to trigger accessibility gesture -->
     <dimen name="accessibility_gesture_min_swipe_distance">80dp</dimen>
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index 9afa862..0019ecb 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -91,7 +91,7 @@
         AppWindowAnimationHelper helper =
             new AppWindowAnimationHelper(recentsView.getPagedViewOrientedState(), mLauncher);
         Animator recentsAnimator = getRecentsWindowAnimator(taskView, skipLauncherChanges,
-                appTargets, wallpaperTargets, mLauncher.getBackgroundBlurController(), helper);
+                appTargets, wallpaperTargets, mLauncher.getDepthController(), helper);
         anim.play(recentsAnimator.setDuration(RECENTS_LAUNCH_DURATION));
 
         Animator childStateAnimation = null;
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java
index 0ae7435..079a738 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java
@@ -16,25 +16,12 @@
 
 package com.android.launcher3.appprediction;
 
+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.content.Context;
-import android.graphics.CornerPathEffect;
-import android.graphics.Paint;
-import android.graphics.drawable.ShapeDrawable;
-import android.os.Handler;
 import android.os.UserManager;
-import android.util.AttributeSet;
-import android.util.TypedValue;
-import android.view.Gravity;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import androidx.core.content.ContextCompat;
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Launcher;
@@ -43,106 +30,16 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.FloatingHeaderView;
-import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.dragndrop.DragLayer;
-import com.android.launcher3.graphics.TriangleShape;
+import com.android.launcher3.views.ArrowTipView;
 import com.android.systemui.shared.system.LauncherEventUtil;
 
 /**
- * All apps tip view aligned just above prediction apps, shown to users that enter all apps for the
+ * ArrowTip helper aligned just above prediction apps, shown to users that enter all apps for the
  * first time.
  */
-public class AllAppsTipView extends AbstractFloatingView {
+public class AllAppsTipView {
 
     private static final String ALL_APPS_TIP_SEEN = "launcher.all_apps_tip_seen";
-    private static final long AUTO_CLOSE_TIMEOUT_MILLIS = 10 * 1000;
-    private static final long SHOW_DELAY_MS = 200;
-    private static final long SHOW_DURATION_MS = 300;
-    private static final long HIDE_DURATION_MS = 100;
-
-    private final Launcher mLauncher;
-    private final Handler mHandler = new Handler();
-
-    private AllAppsTipView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    private AllAppsTipView(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-        setOrientation(LinearLayout.VERTICAL);
-
-        mLauncher = Launcher.getLauncher(context);
-
-        init(context);
-    }
-
-    @Override
-    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
-        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-            close(true);
-        }
-        return false;
-    }
-
-    @Override
-    protected void handleClose(boolean animate) {
-        if (mIsOpen) {
-            if (animate) {
-                animate().alpha(0f)
-                        .withLayer()
-                        .setStartDelay(0)
-                        .setDuration(HIDE_DURATION_MS)
-                        .setInterpolator(Interpolators.ACCEL)
-                        .withEndAction(() -> mLauncher.getDragLayer().removeView(this))
-                        .start();
-            } else {
-                animate().cancel();
-                mLauncher.getDragLayer().removeView(this);
-            }
-            mLauncher.getSharedPrefs().edit().putBoolean(ALL_APPS_TIP_SEEN, true).apply();
-            mIsOpen = false;
-        }
-    }
-
-    @Override
-    public void logActionCommand(int command) {
-    }
-
-    @Override
-    protected boolean isOfType(int type) {
-        return (type & TYPE_ON_BOARD_POPUP) != 0;
-    }
-
-    private void init(Context context) {
-        inflate(context, R.layout.arrow_toast, this);
-
-        TextView textView = findViewById(R.id.text);
-        textView.setText(R.string.all_apps_prediction_tip);
-
-        View dismissButton = findViewById(R.id.dismiss);
-        dismissButton.setOnClickListener(view -> {
-            mLauncher.getUserEventDispatcher().logActionTip(
-                    LauncherEventUtil.DISMISS, ALL_APPS_PREDICTION_TIPS);
-            handleClose(true);
-        });
-
-        View arrowView = findViewById(R.id.arrow);
-        ViewGroup.LayoutParams arrowLp = arrowView.getLayoutParams();
-        ShapeDrawable arrowDrawable = new ShapeDrawable(TriangleShape.create(
-                arrowLp.width, arrowLp.height, false));
-        Paint arrowPaint = arrowDrawable.getPaint();
-        TypedValue typedValue = new TypedValue();
-        context.getTheme().resolveAttribute(android.R.attr.colorAccent, typedValue, true);
-        arrowPaint.setColor(ContextCompat.getColor(getContext(), typedValue.resourceId));
-        // The corner path effect won't be reflected in the shadow, but shouldn't be noticeable.
-        arrowPaint.setPathEffect(new CornerPathEffect(
-                context.getResources().getDimension(R.dimen.arrow_toast_corner_radius)));
-        arrowView.setBackground(arrowDrawable);
-
-        mIsOpen = true;
-
-        mHandler.postDelayed(() -> handleClose(true), AUTO_CLOSE_TIMEOUT_MILLIS);
-    }
 
     private static boolean showAllAppsTipIfNecessary(Launcher launcher) {
         FloatingHeaderView floatingHeaderView = launcher.getAppsView().getFloatingHeaderView();
@@ -156,28 +53,15 @@
             return false;
         }
 
-        AllAppsTipView allAppsTipView = new AllAppsTipView(launcher.getAppsView().getContext(),
-            null);
-        launcher.getDragLayer().addView(allAppsTipView);
+        int[] coords = new int[2];
+        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);
+        });
+        arrowTipView.show(launcher.getString(R.string.all_apps_prediction_tip), coords[1]);
 
-        DragLayer.LayoutParams params = (DragLayer.LayoutParams) allAppsTipView.getLayoutParams();
-        params.gravity = Gravity.CENTER_HORIZONTAL;
-
-        int top = floatingHeaderView.findFixedRowByType(PredictionRowView.class).getTop();
-        allAppsTipView.setY(top - launcher.getResources().getDimensionPixelSize(
-                R.dimen.all_apps_tip_bottom_margin));
-
-        allAppsTipView.setAlpha(0);
-        allAppsTipView.animate()
-                .alpha(1f)
-                .withLayer()
-                .setStartDelay(SHOW_DELAY_MS)
-                .setDuration(SHOW_DURATION_MS)
-                .setInterpolator(Interpolators.DEACCEL)
-                .start();
-
-        launcher.getUserEventDispatcher().logActionTip(
-                LauncherEventUtil.VISIBLE, ALL_APPS_PREDICTION_TIPS);
         return true;
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
index a07cd1d..773c6c8 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
@@ -23,23 +23,30 @@
 import android.content.res.Configuration;
 import android.os.Build;
 import android.view.View;
-import android.view.ViewGroup;
 
 import androidx.core.app.NotificationCompat;
 
 import com.android.launcher3.CellLayout;
+import com.android.launcher3.FolderInfo;
+import com.android.launcher3.Hotseat;
+import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.WorkspaceLayoutManager;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.launcher3.util.ActivityTracker;
+import com.android.launcher3.util.GridOccupancy;
+import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.Themes;
+import com.android.launcher3.views.ArrowTipView;
+import com.android.launcher3.views.Snackbar;
 
+import java.util.ArrayDeque;
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -51,49 +58,198 @@
     private static final String NOTIFICATION_CHANNEL_ID = "launcher_onboarding";
     private static final int ONBOARDING_NOTIFICATION_ID = 7641;
 
+    private static final String SETTINGS_ACTION =
+            "android.settings.ACTION_CONTENT_SUGGESTIONS_SETTINGS";
+
     private final Launcher mLauncher;
+    private final Hotseat mHotseat;
+    private final NotificationManager mNotificationManager;
+    private final Notification mNotification;
     private List<WorkspaceItemInfo> mPredictedApps;
     private HotseatEduDialog mActiveDialog;
 
-    private final NotificationManager mNotificationManager;
-    private final Notification mNotification;
+    private ArrayList<ItemInfo> mNewItems = new ArrayList<>();
+    private IntArray mNewScreens = null;
+    private Runnable mOnOnboardingComplete;
 
-    HotseatEduController(Launcher launcher) {
+    HotseatEduController(Launcher launcher, Runnable runnable) {
         mLauncher = launcher;
+        mHotseat = launcher.getHotseat();
+        mOnOnboardingComplete = runnable;
         mNotificationManager = mLauncher.getSystemService(NotificationManager.class);
         createNotificationChannel();
         mNotification = createNotification();
     }
 
-    boolean migrate() {
-        Workspace workspace = mLauncher.getWorkspace();
-        CellLayout firstScreen = workspace.getScreenWithId(WorkspaceLayoutManager.FIRST_SCREEN_ID);
-        int toPage = Workspace.FIRST_SCREEN_ID;
-        int toRow = mLauncher.getDeviceProfile().inv.numRows - 1;
-        if (FeatureFlags.HOTSEAT_MIGRATE_NEW_PAGE.get()) {
-            toPage = workspace.getScreenIdForPageIndex(workspace.getPageCount());
-            toRow = 0;
-        } else if (!firstScreen.makeSpaceForHotseatMigration(true)) {
-            return false;
+    /**
+     * Checks what type of migration should be used and migrates hotseat
+     */
+    void migrate() {
+        if (FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get()) {
+            migrateToFolder();
+        } else {
+            migrateHotseatWhole();
         }
-        ViewGroup hotseatVG = mLauncher.getHotseat().getShortcutsAndWidgets();
-        for (int i = 0; i < hotseatVG.getChildCount(); i++) {
-            View child = hotseatVG.getChildAt(i);
+    }
+
+    /**
+     * This migration places all non folder items in the hotseat into a folder and then moves
+     * all folders in the hotseat to a workspace page that has enough empty spots.
+     *
+     * @return pageId that has accepted the items.
+     */
+    private int migrateToFolder() {
+        ArrayDeque<FolderInfo> folders = new ArrayDeque<>();
+
+        ArrayList<WorkspaceItemInfo> putIntoFolder = new ArrayList<>();
+
+        //separate folders and items that can get in folders
+        for (int i = 0; i < mLauncher.getDeviceProfile().inv.numHotseatIcons; i++) {
+            View view = mHotseat.getChildAt(i, 0);
+            if (view == null) continue;
+            ItemInfo info = (ItemInfo) view.getTag();
+            if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
+                folders.add((FolderInfo) info);
+            } else if (info instanceof WorkspaceItemInfo) {
+                putIntoFolder.add((WorkspaceItemInfo) info);
+            }
+        }
+
+        // create a temp folder and add non folder items to it
+        if (!putIntoFolder.isEmpty()) {
+            ItemInfo firstItem = putIntoFolder.get(0);
+            FolderInfo folderInfo = new FolderInfo();
+            folderInfo.title = "";
+            mLauncher.getModelWriter().addItemToDatabase(folderInfo, firstItem.container,
+                    firstItem.screenId, firstItem.cellX, firstItem.cellY);
+            folderInfo.contents.addAll(putIntoFolder);
+            for (int i = 0; i < folderInfo.contents.size(); i++) {
+                ItemInfo item = folderInfo.contents.get(i);
+                item.rank = i;
+                mLauncher.getModelWriter().moveItemInDatabase(item, folderInfo.id, 0,
+                        item.cellX, item.cellY);
+            }
+            folders.add(folderInfo);
+        }
+        mNewItems.addAll(folders);
+
+        return placeFoldersInWorkspace(folders);
+    }
+
+    private int placeFoldersInWorkspace(ArrayDeque<FolderInfo> folders) {
+        if (folders.isEmpty()) return 0;
+
+        Workspace workspace = mLauncher.getWorkspace();
+        InvariantDeviceProfile idp = mLauncher.getDeviceProfile().inv;
+
+        GridOccupancy[] occupancyList = new GridOccupancy[workspace.getChildCount()];
+        for (int i = 0; i < occupancyList.length; i++) {
+            occupancyList[i] = ((CellLayout) workspace.getChildAt(i)).cloneGridOccupancy();
+        }
+        //scan every screen to find available spots to place folders
+        int occupancyIndex = 0;
+        int[] itemXY = new int[2];
+        while (occupancyIndex < occupancyList.length && !folders.isEmpty()) {
+            GridOccupancy occupancy = occupancyList[occupancyIndex];
+            if (occupancy.findVacantCell(itemXY, 1, 1)) {
+                FolderInfo info = folders.poll();
+                mLauncher.getModelWriter().moveItemInDatabase(info,
+                        LauncherSettings.Favorites.CONTAINER_DESKTOP,
+                        workspace.getScreenIdForPageIndex(occupancyIndex), itemXY[0], itemXY[1]);
+                occupancy.markCells(info, true);
+            } else {
+                occupancyIndex++;
+            }
+        }
+        if (folders.isEmpty()) return workspace.getScreenIdForPageIndex(occupancyIndex);
+        int screenId = LauncherSettings.Settings.call(mLauncher.getContentResolver(),
+                LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
+                .getInt(LauncherSettings.Settings.EXTRA_VALUE);
+        // if all screens are full and we still have folders left, put those on a new page
+        FolderInfo folderInfo;
+        int col = 0;
+        while ((folderInfo = folders.poll()) != null) {
+            mLauncher.getModelWriter().moveItemInDatabase(folderInfo,
+                    LauncherSettings.Favorites.CONTAINER_DESKTOP, screenId, col++,
+                    idp.numRows - 1);
+        }
+        mNewScreens = IntArray.wrap(screenId);
+        return workspace.getPageCount();
+    }
+
+    /**
+     * This migration option attempts to move the entire hotseat up to the first workspace that
+     * has space to host items. If no such page is found, it moves items to a new page.
+     *
+     * @return pageId where items are migrated
+     */
+    private int migrateHotseatWhole() {
+        Workspace workspace = mLauncher.getWorkspace();
+
+        int pageId = -1;
+        int toRow = 0;
+        for (int i = 0; i < workspace.getPageCount(); i++) {
+            CellLayout target = workspace.getScreenWithId(workspace.getScreenIdForPageIndex(i));
+            if (target.makeSpaceForHotseatMigration(true)) {
+                toRow = mLauncher.getDeviceProfile().inv.numRows - 1;
+                pageId = i;
+                break;
+            }
+        }
+        if (pageId == -1) {
+            pageId = LauncherSettings.Settings.call(mLauncher.getContentResolver(),
+                    LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
+                    .getInt(LauncherSettings.Settings.EXTRA_VALUE);
+        }
+        for (int i = 0; i < mLauncher.getDeviceProfile().inv.numHotseatIcons; i++) {
+            View child = mHotseat.getChildAt(i, 0);
+            if (child == null || child.getTag() == null) continue;
             ItemInfo tag = (ItemInfo) child.getTag();
             mLauncher.getModelWriter().moveItemInDatabase(tag,
-                    LauncherSettings.Favorites.CONTAINER_DESKTOP, toPage, tag.screenId, toRow);
+                    LauncherSettings.Favorites.CONTAINER_DESKTOP, pageId, i, toRow);
+            mNewItems.add(tag);
         }
-        return true;
+        return pageId;
     }
 
+
     void removeNotification() {
         mNotificationManager.cancel(ONBOARDING_NOTIFICATION_ID);
     }
 
+    void moveHotseatItems() {
+        mHotseat.removeAllViewsInLayout();
+        if (!mNewItems.isEmpty()) {
+            int lastPage = mNewItems.get(mNewItems.size() - 1).screenId;
+            ArrayList<ItemInfo> animated = new ArrayList<>();
+            ArrayList<ItemInfo> nonAnimated = new ArrayList<>();
+
+            for (ItemInfo info : mNewItems) {
+                if (info.screenId == lastPage) {
+                    animated.add(info);
+                } else {
+                    nonAnimated.add(info);
+                }
+            }
+            mLauncher.bindAppsAdded(mNewScreens, nonAnimated, animated);
+        }
+    }
+
     void finishOnboarding() {
-        mLauncher.getModel().rebindCallbacks();
+        mOnOnboardingComplete.run();
+        destroy();
         mLauncher.getSharedPrefs().edit().putBoolean(KEY_HOTSEAT_EDU_SEEN, true).apply();
-        removeNotification();
+    }
+
+    void showDimissTip() {
+        if (mHotseat.getShortcutsAndWidgets().getChildCount()
+                < mLauncher.getDeviceProfile().inv.numHotseatIcons) {
+            Snackbar.show(mLauncher, R.string.hotseat_tip_gaps_filled, R.string.hotseat_turn_off,
+                    null, () -> mLauncher.startActivity(new Intent(SETTINGS_ACTION)));
+        } else {
+            new ArrowTipView(mLauncher).show(
+                    mLauncher.getString(R.string.hotseat_tip_no_empty_slots), mHotseat.getTop());
+        }
     }
 
     void setPredictedApps(List<WorkspaceItemInfo> predictedApps) {
@@ -102,6 +258,9 @@
                 && mLauncher.getOrientation() == Configuration.ORIENTATION_PORTRAIT) {
             mNotificationManager.notify(ONBOARDING_NOTIFICATION_ID, mNotification);
         }
+        else {
+            removeNotification();
+        }
     }
 
     private void createNotificationChannel() {
@@ -139,6 +298,17 @@
         }
     }
 
+    void showEdu() {
+        // hotseat is already empty and does not require migration. show edu tip
+        if (mHotseat.getShortcutsAndWidgets().getChildCount() == 0) {
+            new ArrowTipView(mLauncher).show(mLauncher.getString(R.string.hotseat_auto_enrolled),
+                    mHotseat.getTop());
+            finishOnboarding();
+        } else {
+            showDialog();
+        }
+    }
+
     void showDialog() {
         if (mPredictedApps == null || mPredictedApps.isEmpty()) {
             return;
@@ -155,7 +325,7 @@
             ActivityTracker.SchedulerCallback<QuickstepLauncher> {
         @Override
         public boolean init(QuickstepLauncher activity, boolean alreadyOnHome) {
-            activity.getHotseatPredictionController().showEduDialog();
+            activity.getHotseatPredictionController().showEdu();
             return true;
         }
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
index 7986c26..ffc1e29 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
@@ -16,7 +16,8 @@
 package com.android.launcher3.hybridhotseat;
 
 import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
-import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.HYBRID_HOTSEAT_CANCELED;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType
+        .HYBRID_HOTSEAT_CANCELED;
 
 import android.animation.PropertyValuesHolder;
 import android.content.Context;
@@ -27,7 +28,6 @@
 import android.view.View;
 import android.widget.Button;
 import android.widget.TextView;
-import android.widget.Toast;
 
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
@@ -53,20 +53,14 @@
     private static final int DEFAULT_CLOSE_DURATION = 200;
     protected static final int FINAL_SCRIM_BG_COLOR = 0x88000000;
 
-    private static final int MIGRATE_SAME_PAGE = 0;
-    private static final int MIGRATE_NEW_PAGE = 1;
-    private static final int MIGRATE_NO_MIGRATE = 2;
-
+    // we use this value to keep track of migration logs as we experiment with different migrations
+    private static final int MIGRATION_EXPERIMENT_IDENTIFIER = 1;
 
     private final Rect mInsets = new Rect();
     private View mHotseatWrapper;
     private CellLayout mSampleHotseat;
-    private TextView mEduHeading;
-    private TextView mEduContent;
     private Button mDismissBtn;
 
-    private int mMigrationMode = MIGRATE_SAME_PAGE;
-
     public void setHotseatEduController(HotseatEduController hotseatEduController) {
         mHotseatEduController = hotseatEduController;
     }
@@ -89,8 +83,6 @@
         super.onFinishInflate();
         mHotseatWrapper = findViewById(R.id.hotseat_wrapper);
         mSampleHotseat = findViewById(R.id.sample_prediction);
-        mEduHeading = findViewById(R.id.hotseat_edu_heading);
-        mEduContent = findViewById(R.id.hotseat_edu_content);
 
         DeviceProfile grid = mLauncher.getDeviceProfile();
         Rect padding = grid.getHotseatLayoutPadding();
@@ -105,25 +97,28 @@
         mDismissBtn = findViewById(R.id.no_thanks);
         mDismissBtn.setOnClickListener(this::onDismiss);
 
+        // update ui to reflect which migration method is going to be used
+        if (FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get()) {
+            ((TextView) findViewById(R.id.hotseat_edu_content)).setText(
+                    R.string.hotseat_edu_message_migrate_alt);
+        }
     }
 
     private void onAccept(View v) {
-        if (mMigrationMode == MIGRATE_NO_MIGRATE || !mHotseatEduController.migrate()) {
-            onDismiss(v);
-            return;
-        }
+        mHotseatEduController.migrate();
         handleClose(true);
+
+        mHotseatEduController.moveHotseatItems();
         mHotseatEduController.finishOnboarding();
-        logUserAction(true);
-        int toastStringRes = mMigrationMode == MIGRATE_SAME_PAGE
-                ? R.string.hotseat_items_migrated : R.string.hotseat_items_migrated_alt;
-        Toast.makeText(mLauncher, toastStringRes, Toast.LENGTH_LONG).show();
+        //TODO: pass actual page index here.
+        // Temporarily we're passing 1 for folder migration and 2 for page migration
+        logUserAction(true, FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get() ? 1 : 2);
     }
 
     private void onDismiss(View v) {
-        Toast.makeText(getContext(), R.string.hotseat_no_migration, Toast.LENGTH_LONG).show();
+        mHotseatEduController.showDimissTip();
         mHotseatEduController.finishOnboarding();
-        logUserAction(false);
+        logUserAction(false, -1);
         handleClose(true);
     }
 
@@ -155,7 +150,7 @@
                 mLauncher.getDeviceProfile().hotseatBarSizePx + insets.bottom;
     }
 
-    private void logUserAction(boolean migrated) {
+    private void logUserAction(boolean migrated, int pageIndex) {
         LauncherLogProto.Action action = new LauncherLogProto.Action();
         LauncherLogProto.Target target = new LauncherLogProto.Target();
         action.type = LauncherLogProto.Action.Type.TOUCH;
@@ -164,8 +159,10 @@
         target.tipType = LauncherLogProto.TipType.HYBRID_HOTSEAT;
         target.controlType = migrated ? LauncherLogProto.ControlType.HYBRID_HOTSEAT_ACCEPTED
                 : HYBRID_HOTSEAT_CANCELED;
+        target.rank = MIGRATION_EXPERIMENT_IDENTIFIER;
         // encoding migration type on pageIndex
-        target.pageIndex = mMigrationMode;
+        target.pageIndex = pageIndex;
+        target.cardinality = HotseatPredictionController.MAX_ITEMS_FOR_MIGRATION;
         LauncherLogProto.LauncherEvent event = newLauncherEvent(action, target);
         UserEventDispatcher.newInstance(getContext()).dispatchUserEvent(event, null);
     }
@@ -218,15 +215,6 @@
         }
     }
 
-    @Override
-    protected void attachToContainer() {
-        super.attachToContainer();
-        if (FeatureFlags.HOTSEAT_MIGRATE_NEW_PAGE.get()) {
-            mEduContent.setText(R.string.hotseat_edu_message_migrate_alt);
-            mMigrationMode = MIGRATE_NEW_PAGE;
-        }
-    }
-
     /**
      * Opens User education dialog with a list of suggested 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
index 2cdcd20..d3bb4f9 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -16,6 +16,9 @@
 package com.android.launcher3.hybridhotseat;
 
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.logging.LoggerUtils.newAction;
+import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
+import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
@@ -28,6 +31,7 @@
 import android.app.prediction.AppTargetId;
 import android.content.ComponentName;
 import android.os.Bundle;
+import android.provider.DeviceConfig;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
@@ -48,6 +52,7 @@
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.allapps.AllAppsStore;
@@ -58,9 +63,11 @@
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.logging.FileLog;
+import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.touch.ItemLongClickListener;
+import com.android.launcher3.uioverrides.DeviceFlag;
 import com.android.launcher3.uioverrides.PredictedAppIcon;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -84,6 +91,9 @@
     private static final String TAG = "PredictiveHotseat";
     private static final boolean DEBUG = false;
 
+    public static final int MAX_ITEMS_FOR_MIGRATION = DeviceConfig.getInt(
+            DeviceFlag.NAMESPACE_LAUNCHER, "max_homepage_items_for_migration", 5);
+
     //TODO: replace this with AppTargetEvent.ACTION_UNPIN (b/144119543)
     private static final int APPTARGET_ACTION_UNPIN = 4;
 
@@ -94,7 +104,6 @@
     private static final String BUNDLE_KEY_WORKSPACE = "workspace_apps";
 
     private static final String PREDICTION_CLIENT = "hotseat";
-
     private DropTarget.DragObject mDragObject;
     private int mHotSeatItemsCount;
     private int mPredictedSpotsCount = 0;
@@ -113,6 +122,7 @@
 
     private HotseatEduController mHotseatEduController;
 
+
     private List<PredictedAppIcon.PredictedIconOutlineDrawing> mOutlineDrawings = new ArrayList<>();
 
     private final View.OnLongClickListener mPredictionLongClickListener = v -> {
@@ -146,12 +156,12 @@
     }
 
     /**
-     * Transitions to NORMAL workspace mode and shows edu dialog
+     * Transitions to NORMAL workspace mode and shows edu
      */
-    public void showEduDialog() {
+    public void showEdu() {
         if (mHotseatEduController == null) return;
         mLauncher.getStateManager().goToState(LauncherState.NORMAL, true,
-                () -> mHotseatEduController.showDialog());
+                () -> mHotseatEduController.showEdu());
     }
 
     @Override
@@ -276,12 +286,10 @@
                         .build());
         mAppPredictor.registerPredictionUpdates(mLauncher.getMainExecutor(),
                 this::setPredictedApps);
-
+        setPauseUIUpdate(false);
+        performBetaCheck();
         if (!isReady()) {
-            if (mHotseatEduController != null) {
-                mHotseatEduController.destroy();
-            }
-            mHotseatEduController = new HotseatEduController(mLauncher);
+            mHotseatEduController = new HotseatEduController(mLauncher, this::createPredictor);
         }
         mAppPredictor.requestPredictionUpdate();
     }
@@ -327,7 +335,7 @@
             mComponentKeyMappers.add(new ComponentKeyMapper(key, mDynamicItemCache));
         }
         predictionLog.append("]");
-        FileLog.d(TAG, predictionLog.toString());
+        if (Utilities.IS_DEBUG_DEVICE) FileLog.d(TAG, predictionLog.toString());
         updateDependencies();
         if (isReady()) {
             fillGapsWithPrediction();
@@ -488,7 +496,6 @@
         }
     }
 
-
     @Override
     public void onDragEnd() {
         if (mDragObject == null) {
@@ -564,7 +571,8 @@
     }
 
     @Override
-    public void reapplyItemInfo(ItemInfoWithIcon info) {}
+    public void reapplyItemInfo(ItemInfoWithIcon info) {
+    }
 
     @Override
     public void onDropCompleted(View target, DropTarget.DragObject d, boolean success) {
@@ -591,6 +599,39 @@
         }
     }
 
+    private void performBetaCheck() {
+        if (isReady()) return;
+        int hotseatItemsCount = mHotseat.getShortcutsAndWidgets().getChildCount();
+
+        // -1 to exclude smart space
+        int workspaceItemCount = mLauncher.getWorkspace().getScreenWithId(
+                Workspace.FIRST_SCREEN_ID).getShortcutsAndWidgets().getChildCount() - 1;
+
+        // opt user into the feature without onboarding tip or migration if they don't have any
+        // open spots in their hotseat and have more than maxItems in their hotseat + workspace
+
+        if (hotseatItemsCount == mHotSeatItemsCount && workspaceItemCount + hotseatItemsCount
+                > MAX_ITEMS_FOR_MIGRATION) {
+            mLauncher.getSharedPrefs().edit().putBoolean(HotseatEduController.KEY_HOTSEAT_EDU_SEEN,
+                    true).apply();
+
+            LauncherLogProto.Action action = newAction(LauncherLogProto.Action.Type.TOUCH);
+            LauncherLogProto.Target target = newContainerTarget(LauncherLogProto.ContainerType.TIP);
+            action.touch = LauncherLogProto.Action.Touch.TAP;
+            target.tipType = LauncherLogProto.TipType.HYBRID_HOTSEAT;
+            target.controlType = LauncherLogProto.ControlType.HYBRID_HOTSEAT_CANCELED;
+
+            // temporarily encode details in log target (go/hotseat_migration)
+            target.rank = 2;
+            target.cardinality = MAX_ITEMS_FOR_MIGRATION;
+            target.pageIndex = (workspaceItemCount * 1000) + hotseatItemsCount;
+            LauncherLogProto.LauncherEvent event = newLauncherEvent(action, target);
+            UserEventDispatcher.newInstance(mLauncher).dispatchUserEvent(event, null);
+
+
+        }
+    }
+
     /**
      * Fill in predicted_rank field based on app prediction.
      * Only applicable when {@link ItemInfo#itemType} is PREDICTED_HOTSEAT
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 30a4e50..9352ace 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -184,8 +184,8 @@
             onStateOrResumeChanged();
         }
 
-        if ((changeBits & ACTIVITY_STATE_STARTED) != 0 && mHotseatPredictionController != null
-                && (getActivityFlags() & ACTIVITY_STATE_USER_ACTIVE) == 0) {
+        if (mHotseatPredictionController != null && ((changeBits & ACTIVITY_STATE_STARTED) != 0
+                || (changeBits & getActivityFlags() & ACTIVITY_STATE_DEFERRED_RESUMED) != 0)) {
             mHotseatPredictionController.setPauseUIUpdate(false);
         }
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 549187f..131b71f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -15,18 +15,22 @@
  */
 package com.android.launcher3.uioverrides;
 
-import static com.android.launcher3.LauncherState.RECENTS_CLEAR_ALL_BUTTON;
+import static com.android.launcher3.LauncherState.OVERVIEW_BUTTONS;
+import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
 import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
 import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
 
 import android.annotation.TargetApi;
 import android.os.Build;
 import android.util.FloatProperty;
+import android.view.View;
+import android.view.animation.Interpolator;
 
 import androidx.annotation.NonNull;
 
-import com.android.launcher3.Launcher;
+import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.PendingAnimation;
@@ -44,7 +48,7 @@
 public final class RecentsViewStateController extends
         BaseRecentsViewStateController<LauncherRecentsView> {
 
-    public RecentsViewStateController(Launcher launcher) {
+    public RecentsViewStateController(BaseQuickstepLauncher launcher) {
         super(launcher);
     }
 
@@ -55,7 +59,7 @@
             mRecentsView.updateEmptyMessage();
             mRecentsView.resetTaskVisuals();
         }
-        setAlphas(PropertySetter.NO_ANIM_PROPERTY_SETTER, state.getVisibleElements(mLauncher));
+        setAlphas(PropertySetter.NO_ANIM_PROPERTY_SETTER, state, LINEAR);
         mRecentsView.setFullscreenProgress(state.getOverviewFullscreenProgress());
     }
 
@@ -73,15 +77,22 @@
                     AnimationSuccessListener.forRunnable(mRecentsView::resetTaskVisuals));
         }
 
-        setAlphas(builder, toState.getVisibleElements(mLauncher));
+        setAlphas(builder, toState,
+                config.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT));
         builder.setFloat(mRecentsView, FULLSCREEN_PROGRESS,
                 toState.getOverviewFullscreenProgress(), LINEAR);
     }
 
-    private void setAlphas(PropertySetter propertySetter, int visibleElements) {
-        boolean hasClearAllButton = (visibleElements & RECENTS_CLEAR_ALL_BUTTON) != 0;
+    private void setAlphas(PropertySetter propertySetter, LauncherState state,
+            Interpolator actionInterpolator) {
+        float buttonAlpha = (state.getVisibleElements(mLauncher) & OVERVIEW_BUTTONS) != 0 ? 1 : 0;
         propertySetter.setFloat(mRecentsView.getClearAllButton(), ClearAllButton.VISIBILITY_ALPHA,
-                hasClearAllButton ? 1f : 0f, LINEAR);
+                buttonAlpha, LINEAR);
+
+        View actionsView = mLauncher.getActionsView();
+        if (actionsView != null) {
+            propertySetter.setViewAlpha(actionsView, buttonAlpha, actionInterpolator);
+        }
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index 5bac964..a87d6d1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -19,7 +19,6 @@
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.quickstep.util.LayoutUtils;
@@ -91,7 +90,7 @@
     @Override
     public int getVisibleElements(Launcher launcher) {
         return super.getVisibleElements(launcher)
-                & ~RECENTS_CLEAR_ALL_BUTTON & ~VERTICAL_SWIPE_INDICATOR;
+                & ~OVERVIEW_BUTTONS & ~VERTICAL_SWIPE_INDICATOR;
     }
 
     @Override
@@ -107,7 +106,7 @@
     }
 
     @Override
-    public int getBackgroundBlurRadius(Context context) {
-        return context.getResources().getInteger(R.integer.app_background_blur_radius);
+    public float getDepth(Context context) {
+        return 1f;
     }
 }
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
index 8087611..1288e7b 100644
--- 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
@@ -25,6 +25,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.states.StateAnimationConfig;
 
 public class OverviewPeekState extends OverviewState {
@@ -37,6 +38,9 @@
         ScaleAndTranslation result = super.getOverviewScaleAndTranslation(launcher);
         result.translationX = NORMAL.getOverviewScaleAndTranslation(launcher).translationX
                 - launcher.getResources().getDimension(R.dimen.overview_peek_distance);
+        if (Utilities.isRtl(launcher.getResources())) {
+            result.translationX = -result.translationX;
+        }
         return result;
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
index 6a34917..bcfb11c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -164,17 +164,15 @@
 
     @Override
     public int getVisibleElements(Launcher launcher) {
-        if (launcher.getDeviceProfile().isVerticalBarLayout()) {
-            return VERTICAL_SWIPE_INDICATOR | RECENTS_CLEAR_ALL_BUTTON;
+        if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(launcher)) {
+            return OVERVIEW_BUTTONS;
+        } else if (launcher.getDeviceProfile().isVerticalBarLayout()) {
+            return VERTICAL_SWIPE_INDICATOR | OVERVIEW_BUTTONS;
         } else {
-            if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(launcher)) {
-                return VERTICAL_SWIPE_INDICATOR | RECENTS_CLEAR_ALL_BUTTON;
-            }
-
             boolean hasAllAppsHeaderExtra = launcher.getAppsView() != null
                     && launcher.getAppsView().getFloatingHeaderView().hasVisibleContent();
-            return HOTSEAT_SEARCH_BOX | VERTICAL_SWIPE_INDICATOR | RECENTS_CLEAR_ALL_BUTTON |
-                    (hasAllAppsHeaderExtra ? ALL_APPS_HEADER_EXTRA : HOTSEAT_ICONS);
+            return HOTSEAT_SEARCH_BOX | VERTICAL_SWIPE_INDICATOR | OVERVIEW_BUTTONS
+                    | (hasAllAppsHeaderExtra ? ALL_APPS_HEADER_EXTRA : HOTSEAT_ICONS);
         }
     }
 
@@ -207,8 +205,8 @@
     }
 
     @Override
-    public int getBackgroundBlurRadius(Context context) {
-        return context.getResources().getInteger(R.integer.overview_background_blur_radius);
+    public float getDepth(Context context) {
+        return 1f;
     }
 
     @Override
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
index 785a480..8af2747 100644
--- 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
@@ -73,7 +73,11 @@
         super(l, false /* allowDragToOverview */);
         mMotionPauseDetector = new MotionPauseDetector(l);
         mMotionPauseMinDisplacement = ViewConfiguration.get(l).getScaledTouchSlop();
-        mMotionPauseMaxDisplacement = getShiftRange() * MAX_DISPLACEMENT_PERCENT;
+        mMotionPauseMaxDisplacement = getMotionPauseMaxDisplacement();
+    }
+
+    protected float getMotionPauseMaxDisplacement() {
+        return getShiftRange() * MAX_DISPLACEMENT_PERCENT;
     }
 
     @Override
@@ -120,8 +124,8 @@
         mPeekAnim.start();
         VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC);
 
-        mLauncher.getDragLayer().getScrim().animateToSysuiMultiplier(isPaused ? 0 : 1,
-                peekDuration, 0);
+        mLauncher.getDragLayer().getScrim().createSysuiMultiplierAnim(isPaused ? 0 : 1)
+                .setDuration(peekDuration).start();
     }
 
     /**
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
index 064133c..71aa2e8 100644
--- 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
@@ -66,6 +66,13 @@
     }
 
     @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);
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index b0125a8..1b3610a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -81,6 +81,9 @@
 
     private boolean canInterceptTouch() {
         if (mCurrentAnimation != null) {
+            mCurrentAnimation.forceFinishIfCloseToEnd();
+        }
+        if (mCurrentAnimation != null) {
             // If we are already animating from a previous state, we can intercept.
             return true;
         }
@@ -126,6 +129,7 @@
 
                 for (int i = 0; i < mRecentsView.getTaskViewCount(); i++) {
                     TaskView view = mRecentsView.getTaskViewAt(i);
+
                     if (mRecentsView.isTaskViewVisible(view) && mActivity.getDragLayer()
                             .isEventOverView(view, ev)) {
                         // Disable swiping up and down if the task overlay is modal.
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
index aaf7619..9dce984 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
@@ -19,7 +19,7 @@
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
-import static com.android.launcher3.uioverrides.BackgroundBlurController.BACKGROUND_BLUR;
+import static com.android.launcher3.statehandlers.DepthController.DEPTH;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
 
@@ -34,7 +34,7 @@
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.launcher3.uioverrides.BackgroundBlurController;
+import com.android.launcher3.statehandlers.DepthController;
 import com.android.quickstep.util.AppWindowAnimationHelper;
 import com.android.quickstep.util.AppWindowAnimationHelper.TransformParams;
 import com.android.quickstep.util.RemoteAnimationProvider;
@@ -105,12 +105,6 @@
             mRecentsView.setRunningTaskIconScaledDown(true);
         }
 
-        BackgroundBlurController blurController = mActivityInterface.getBackgroundBlurController();
-        if (blurController != null) {
-            // Update the surface to be the lowest closing app surface
-            blurController.setSurfaceToLauncher(mRecentsView);
-        }
-
         AnimatorSet anim = new AnimatorSet();
         anim.addListener(new AnimationSuccessListener() {
             @Override
@@ -123,11 +117,17 @@
         });
         if (mActivity == null) {
             Log.e(TAG, "Animation created, before activity");
-            anim.play(ValueAnimator.ofInt(0, 1).setDuration(RECENTS_LAUNCH_DURATION))
-                    .with(createBackgroundBlurAnimator(blurController));
             return anim;
         }
 
+        DepthController depthController = mActivityInterface.getDepthController();
+        if (depthController != null) {
+            anim.play(ObjectAnimator.ofFloat(depthController, DEPTH,
+                    BACKGROUND_APP.getDepth(mActivity),
+                    OVERVIEW.getDepth(mActivity))
+                    .setDuration(RECENTS_LAUNCH_DURATION));
+        }
+
         RemoteAnimationTargets targets = new RemoteAnimationTargets(appTargets,
                 wallpaperTargets, MODE_CLOSING);
 
@@ -135,8 +135,6 @@
         RemoteAnimationTargetCompat runningTaskTarget = targets.findTask(mTargetTaskId);
         if (runningTaskTarget == null) {
             Log.e(TAG, "No closing app");
-            anim.play(ValueAnimator.ofInt(0, 1).setDuration(RECENTS_LAUNCH_DURATION))
-                    .with(createBackgroundBlurAnimator(blurController));
             return anim;
         }
 
@@ -183,8 +181,6 @@
                 transaction.apply();
             });
         }
-        anim.play(valueAnimator)
-                .with(createBackgroundBlurAnimator(blurController));
         return anim;
     }
 
@@ -196,15 +192,4 @@
     long getRecentsLaunchDuration() {
         return RECENTS_LAUNCH_DURATION;
     }
-
-    private Animator createBackgroundBlurAnimator(BackgroundBlurController blurController) {
-        if (blurController == null) {
-            // Dummy animation
-            return ValueAnimator.ofInt(0);
-        }
-        return ObjectAnimator.ofInt(blurController, BACKGROUND_BLUR,
-                BACKGROUND_APP.getBackgroundBlurRadius(mActivity),
-                OVERVIEW.getBackgroundBlurRadius(mActivity))
-                .setDuration(RECENTS_LAUNCH_DURATION);
-    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
index 7786a8f..113cdec 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
@@ -31,6 +31,7 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.os.Build;
+import android.util.Log;
 import android.util.Pair;
 import android.view.MotionEvent;
 import android.view.View;
@@ -48,6 +49,7 @@
 import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.model.PagedViewOrientedState;
 import com.android.launcher3.states.RotationHelper;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.touch.PortraitPagedViewHandler;
 import com.android.launcher3.util.VibratorWrapper;
@@ -198,18 +200,33 @@
     }
 
     protected void startNewTask(int successStateFlag, Consumer<Boolean> resultCallback) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_START_FROM_RECENTS, "startNewTask1");
+        }
         // 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 (TestProtocol.sDebugTracing) {
+                Log.d(TestProtocol.NO_START_FROM_RECENTS, "startNewTask2");
+            }
             int taskId = mRecentsView.getNextPageTaskView().getTask().key.id;
             mFinishingRecentsAnimationForNewTaskId = taskId;
             mRecentsAnimationController.finish(true /* toRecents */, () -> {
+                if (TestProtocol.sDebugTracing) {
+                    Log.d(TestProtocol.NO_START_FROM_RECENTS, "onFinishComplete1");
+                }
                 if (!mCanceled) {
+                    if (TestProtocol.sDebugTracing) {
+                        Log.d(TestProtocol.NO_START_FROM_RECENTS, "onFinishComplete2");
+                    }
                     TaskView nextTask = mRecentsView.getTaskView(taskId);
                     if (nextTask != null) {
+                        if (TestProtocol.sDebugTracing) {
+                            Log.d(TestProtocol.NO_START_FROM_RECENTS, "onFinishComplete3");
+                        }
                         nextTask.launchTask(false /* animate */, true /* freezeTaskList */,
                                 success -> {
                                     resultCallback.accept(success);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
index ea5561b..da73bc0 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -166,6 +166,7 @@
         super.onActivityInit(alreadyOnHome);
         mActivity = mActivityInterface.getCreatedActivity();
         mRecentsView = mActivity.getOverviewPanel();
+        mRecentsView.setOnPageTransitionEndCallback(null);
         linkRecentsViewScroll();
         mRecentsView.setDisallowScrollToClearAll(true);
         mRecentsView.getClearAllButton().setVisibilityAlpha(0);
@@ -434,7 +435,12 @@
 
                 @Override
                 public void onAnimationSuccess(Animator animator) {
-                    finishAnimationTargetSetAnimationComplete();
+                    if (mRecentsView != null) {
+                        mRecentsView.setOnPageTransitionEndCallback(FallbackSwipeHandler.this
+                                ::finishAnimationTargetSetAnimationComplete);
+                    } else {
+                        finishAnimationTargetSetAnimationComplete();
+                    }
                     mFinishAnimation = null;
                 }
             };
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/ImageActionsApi.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/ImageActionsApi.java
new file mode 100644
index 0000000..33fe5a9
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/ImageActionsApi.java
@@ -0,0 +1,94 @@
+/*
+ * 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.content.Intent.EXTRA_STREAM;
+
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.quickstep.util.ImageActionUtils.persistBitmapAndStartActivity;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
+
+import com.android.launcher3.BuildConfig;
+import com.android.quickstep.util.ImageActionUtils;
+
+import java.util.function.Supplier;
+
+/**
+ * Contains image selection functions necessary to complete overview action button functions.
+ */
+public class ImageActionsApi {
+
+    private static final String TAG = BuildConfig.APPLICATION_ID + "ImageActionsApi";
+    private final Context mContext;
+    private final Supplier<Bitmap> mBitmapSupplier;
+    private final SystemUiProxy mSystemUiProxy;
+
+    public ImageActionsApi(Context context, Supplier<Bitmap> bitmapSupplier) {
+        mContext = context;
+        mBitmapSupplier = bitmapSupplier;
+        mSystemUiProxy = SystemUiProxy.INSTANCE.get(context);
+    }
+
+    /**
+     * Share the image this api was constructed with using the provided intent. The implementation
+     * should add an {@link Intent#EXTRA_STREAM} with the URI pointing to the image to the intent.
+     */
+    @UiThread
+    public void shareWithExplicitIntent(@Nullable Rect crop, Intent intent) {
+        if (mBitmapSupplier.get() == null) {
+            Log.e(TAG, "No snapshot available, not starting share.");
+            return;
+        }
+
+        UI_HELPER_EXECUTOR.execute(() -> persistBitmapAndStartActivity(mContext,
+                mBitmapSupplier.get(), crop, intent, (uri, intentForUri) -> {
+                    intentForUri.putExtra(EXTRA_STREAM, uri);
+                    return new Intent[]{intentForUri};
+                }, TAG));
+
+    }
+
+    /**
+     * Share the image this api was constructed with.
+     */
+    @UiThread
+    public void startShareActivity() {
+        ImageActionUtils.startShareActivity(mContext, mBitmapSupplier, null, null, TAG);
+    }
+
+    /**
+     * @param screenshot       to be saved to the media store.
+     * @param screenshotBounds the location of where the bitmap was laid out on the screen in
+     *                         screen coordinates.
+     * @param visibleInsets    that are used to draw the screenshot within the bounds.
+     * @param taskId           of the task that the screenshot was taken of.
+     */
+    public void saveScreenshot(Bitmap screenshot, Rect screenshotBounds,
+            Insets visibleInsets, int taskId) {
+        ImageActionUtils.saveScreenshot(mSystemUiProxy, screenshot, screenshotBounds, visibleInsets,
+                taskId);
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
index d402a75..455ae76 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
@@ -55,9 +55,9 @@
 import com.android.launcher3.allapps.DiscoveryBounce;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.appprediction.PredictionUiStateManager;
+import com.android.launcher3.statehandlers.DepthController;
+import com.android.launcher3.statehandlers.DepthController.ClampedDepthProperty;
 import com.android.launcher3.touch.PagedOrientationHandler;
-import com.android.launcher3.uioverrides.BackgroundBlurController;
-import com.android.launcher3.uioverrides.BackgroundBlurController.ClampedBlurProperty;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.views.FloatingIconView;
 import com.android.quickstep.SysUINavigationMode.Mode;
@@ -330,18 +330,17 @@
                     endState.getVerticalProgress(activity)));
         }
 
-        // Animate the blur
-        BackgroundBlurController blurController = getBackgroundBlurController();
-        int fromBlurRadius = fromState.getBackgroundBlurRadius(activity);
-        int toBlurRadius = endState.getBackgroundBlurRadius(activity);
-        Animator backgroundBlur = ObjectAnimator.ofInt(blurController,
-                new ClampedBlurProperty(toBlurRadius, fromBlurRadius),
-                fromBlurRadius, toBlurRadius);
-        anim.play(backgroundBlur);
+        // Animate the blur and wallpaper zoom
+        DepthController depthController = getDepthController();
+        float fromDepthRatio = fromState.getDepth(activity);
+        float toDepthRatio = endState.getDepth(activity);
+        Animator depthAnimator = ObjectAnimator.ofFloat(depthController,
+                new ClampedDepthProperty(fromDepthRatio, toDepthRatio),
+                fromDepthRatio, toDepthRatio);
+        anim.play(depthAnimator);
 
         playScaleDownAnim(anim, activity, fromState, endState);
 
-
         anim.setDuration(transitionLength * 2);
         anim.setInterpolator(LINEAR);
         AnimatorPlaybackController controller =
@@ -558,11 +557,11 @@
 
     @Nullable
     @Override
-    public BackgroundBlurController getBackgroundBlurController() {
+    public DepthController getDepthController() {
         BaseQuickstepLauncher launcher = getCreatedActivity();
         if (launcher == null) {
             return null;
         }
-        return launcher.getBackgroundBlurController();
+        return launcher.getDepthController();
     }
 }
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
index b813d43..1bd0333 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
@@ -29,6 +29,7 @@
 import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK;
 import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
 import static com.android.quickstep.GestureState.STATE_END_TARGET_ANIMATION_FINISHED;
+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;
@@ -46,6 +47,7 @@
 import android.graphics.RectF;
 import android.os.Build;
 import android.os.SystemClock;
+import android.util.Log;
 import android.view.View;
 import android.view.View.OnApplyWindowInsetsListener;
 import android.view.ViewTreeObserver.OnDrawListener;
@@ -64,6 +66,7 @@
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.logging.UserEventDispatcher;
+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;
@@ -244,7 +247,11 @@
                         | STATE_GESTURE_STARTED,
                 this::setupLauncherUiAfterSwipeUpToRecentsAnimation);
 
-        mGestureState.runOnceAtState(STATE_END_TARGET_ANIMATION_FINISHED, this::onEndTargetSet);
+        mGestureState.runOnceAtState(STATE_END_TARGET_ANIMATION_FINISHED,
+                this::continueComputingRecentsScrollIfNecessary);
+        mGestureState.runOnceAtState(STATE_END_TARGET_ANIMATION_FINISHED
+                        | STATE_RECENTS_SCROLLING_FINISHED,
+                this::onSettledOnEndTarget);
 
         mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED, this::invalidateHandler);
         mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
@@ -283,6 +290,7 @@
         }
 
         mRecentsView = activity.getOverviewPanel();
+        mRecentsView.setOnPageTransitionEndCallback(null);
         linkRecentsViewScroll();
         addLiveTileOverlay();
 
@@ -294,7 +302,6 @@
         }
 
         setupRecentsViewUi();
-        mActivityInterface.getBackgroundBlurController().setSurfaceToLauncher(mRecentsView);
 
         if (mDeviceState.getNavMode() == TWO_BUTTONS) {
             // If the device is in two button mode, swiping up will show overview with predictions
@@ -506,16 +513,22 @@
     }
 
     private void buildAnimationController() {
-        if (mGestureState.getEndTarget() == HOME || mHasLauncherTransitionControllerStarted) {
-            // We don't want a new mLauncherTransitionController if
-            // mGestureState.getEndTarget() == HOME (it has its own animation) or if we're already
-            // animating the current controller.
+        if (!canCreateNewOrUpdateExistingLauncherTransitionController()) {
             return;
         }
         initTransitionEndpoints(mActivity.getDeviceProfile());
         mAnimationFactory.createActivityInterface(mTransitionDragLength);
     }
 
+    /**
+     * 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.
+     * @return Whether we can create the launcher controller or update its progress.
+     */
+    private boolean canCreateNewOrUpdateExistingLauncherTransitionController() {
+        return mGestureState.getEndTarget() != HOME && !mHasLauncherTransitionControllerStarted;
+    }
+
     @Override
     public WindowInsets onApplyWindowInsets(View view, WindowInsets windowInsets) {
         WindowInsets result = view.onApplyWindowInsets(windowInsets);
@@ -559,15 +572,12 @@
             }
         }
 
-        if (mLauncherTransitionController == null || mLauncherTransitionController
-                .getAnimationPlayer().isStarted()) {
-            return;
-        }
         updateLauncherTransitionProgress();
     }
 
     private void updateLauncherTransitionProgress() {
-        if (mGestureState.getEndTarget() == HOME) {
+        if (mLauncherTransitionController == null
+                || !canCreateNewOrUpdateExistingLauncherTransitionController()) {
             return;
         }
         // Normalize the progress to 0 to 1, as the animation controller will clamp it to that
@@ -697,7 +707,7 @@
         }
     }
 
-    private void onEndTargetSet() {
+    private void onSettledOnEndTarget() {
         switch (mGestureState.getEndTarget()) {
             case HOME:
                 mStateCallback.setState(STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT);
@@ -856,13 +866,17 @@
             if (mDeviceState.isFullyGesturalNavMode()) {
                 setShelfState(ShelfAnimState.OVERVIEW, interpolator, duration);
             }
-        } else if (endTarget == NEW_TASK || endTarget == LAST_TASK) {
-            // Let RecentsView handle the scrolling to the task, which we launch in startNewTask()
-            // or resumeLastTask().
-            if (mRecentsView != null) {
-                duration = Math.max(duration, mRecentsView.getScroller().getDuration());
-            }
         }
+
+        // Let RecentsView handle the scrolling to the task, which we launch in startNewTask()
+        // or resumeLastTask().
+        if (mRecentsView != null) {
+            mRecentsView.setOnPageTransitionEndCallback(
+                    () -> mGestureState.setState(STATE_RECENTS_SCROLLING_FINISHED));
+        } else {
+            mGestureState.setState(STATE_RECENTS_SCROLLING_FINISHED);
+        }
+
         animateToProgress(startShift, endShift, duration, interpolator, endTarget, velocityPxPerMs);
     }
 
@@ -946,15 +960,14 @@
             ValueAnimator windowAnim = mCurrentShift.animateToValue(start, end);
             windowAnim.setDuration(duration).setInterpolator(interpolator);
             windowAnim.addUpdateListener(valueAnimator -> {
-                if (mRecentsView != null && mRecentsView.getVisibility() != View.VISIBLE) {
-                    // Views typically don't compute scroll when invisible as an optimization,
-                    // but in our case we need to since the window offset depends on the scroll.
-                    mRecentsView.computeScroll();
-                }
+                computeRecentsScrollIfInvisible();
             });
             windowAnim.addListener(new AnimationSuccessListener() {
                 @Override
                 public void onAnimationSuccess(Animator animator) {
+                    if (TestProtocol.sDebugTracing) {
+                        Log.d(TestProtocol.NO_START_FROM_RECENTS, "onAnimationSuccess");
+                    }
                     if (mRecentsAnimationController == null) {
                         // If the recents animation is interrupted, we still end the running
                         // animation (not canceled) so this is still called. In that case, we can
@@ -1002,6 +1015,21 @@
         mHasLauncherTransitionControllerStarted = true;
     }
 
+    private void computeRecentsScrollIfInvisible() {
+        if (mRecentsView != null && mRecentsView.getVisibility() != View.VISIBLE) {
+            // Views typically don't compute scroll when invisible as an optimization,
+            // but in our case we need to since the window offset depends on the scroll.
+            mRecentsView.computeScroll();
+        }
+    }
+
+    private void continueComputingRecentsScrollIfNecessary() {
+        if (!mGestureState.hasState(STATE_RECENTS_SCROLLING_FINISHED)) {
+            computeRecentsScrollIfInvisible();
+            mRecentsView.post(this::continueComputingRecentsScrollIfNecessary);
+        }
+    }
+
     /**
      * Creates an animation that transforms the current app window into the home app.
      * @param startProgress The progress of {@link #mCurrentShift} to start the window from.
@@ -1163,6 +1191,9 @@
     }
 
     private void switchToScreenshot() {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_START_FROM_RECENTS, "switchToScreenshot");
+        }
         final int runningTaskId = mGestureState.getRunningTaskId();
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             if (mRecentsAnimationController != null) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
index 3ab0f19..42d944f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
@@ -186,7 +186,7 @@
         AppWindowAnimationHelper helper = new AppWindowAnimationHelper(
             mFallbackRecentsView.getPagedViewOrientedState(), this);
         Animator recentsAnimator = getRecentsWindowAnimator(taskView, !activityClosing, appTargets,
-                wallpaperTargets, null /* backgroundBlurController */,
+                wallpaperTargets, null /* depthController */,
                 helper);
         target.play(recentsAnimator.setDuration(RECENTS_LAUNCH_DURATION));
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
index 33d9d9a..fbf29af 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
@@ -16,12 +16,13 @@
 
 package com.android.quickstep;
 
+import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
 import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
 
+import android.content.Context;
+import android.graphics.Insets;
 import android.graphics.Matrix;
-import android.view.View;
-
-import androidx.annotation.Nullable;
+import android.graphics.Rect;
 
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.BaseDraggingActivity;
@@ -29,6 +30,7 @@
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.ResourceBasedOverride;
+import com.android.quickstep.views.OverviewActionsView;
 import com.android.quickstep.views.TaskThumbnailView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.plugins.OverscrollPlugin;
@@ -43,16 +45,6 @@
  */
 public class TaskOverlayFactory implements ResourceBasedOverride {
 
-    /** 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,
-            TaskShortcutFactory.SPLIT_SCREEN,
-            TaskShortcutFactory.PIN,
-            TaskShortcutFactory.INSTALL,
-            TaskShortcutFactory.FREE_FORM,
-            TaskShortcutFactory.WELLBEING
-    };
-
     public static List<SystemShortcut> getEnabledShortcuts(TaskView taskView) {
         final ArrayList<SystemShortcut> shortcuts = new ArrayList<>();
         final BaseDraggingActivity activity = BaseActivity.fromContext(taskView.getContext());
@@ -76,25 +68,68 @@
     }
 
     public TaskOverlay createOverlay(TaskThumbnailView thumbnailView) {
-        return new TaskOverlay();
+        return new TaskOverlay(thumbnailView);
     }
 
+    /** 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,
+            TaskShortcutFactory.SPLIT_SCREEN,
+            TaskShortcutFactory.PIN,
+            TaskShortcutFactory.INSTALL,
+            TaskShortcutFactory.FREE_FORM,
+            TaskShortcutFactory.WELLBEING
+    };
+
+    /**
+     * Overlay on each task handling Overview Action Buttons.
+     */
     public static class TaskOverlay {
 
+        private final Context mApplicationContext;
+        private OverviewActionsView mActionsView;
+        private final TaskThumbnailView mThumbnailView;
+
+
+        protected TaskOverlay(TaskThumbnailView taskThumbnailView) {
+            mApplicationContext = taskThumbnailView.getContext().getApplicationContext();
+            mThumbnailView = taskThumbnailView;
+        }
+
         /**
          * Called when the current task is interactive for the user
          */
-        public void initOverlay(Task task, ThumbnailData thumbnail, Matrix matrix) { }
+        public void initOverlay(Task task, ThumbnailData thumbnail, Matrix matrix) {
+            ImageActionsApi imageApi = new ImageActionsApi(
+                    mApplicationContext, mThumbnailView::getThumbnail);
 
-        @Nullable
-        public View getActionsView() {
-            return null;
+            if (mActionsView == null && ENABLE_OVERVIEW_ACTIONS.get()
+                    && SysUINavigationMode.removeShelfFromOverview(mApplicationContext)) {
+                mActionsView = BaseActivity.fromContext(mThumbnailView.getContext()).findViewById(
+                        R.id.overview_actions_view);
+            }
+            if (mActionsView != null) {
+                mActionsView.setListener(new OverviewActionsView.Listener() {
+                    @Override
+                    public void onShare() {
+                        imageApi.startShareActivity();
+                    }
+
+                    @Override
+                    public void onScreenshot() {
+                        imageApi.saveScreenshot(mThumbnailView.getThumbnail(),
+                                getTaskSnapshotBounds(), getTaskSnapshotInsets(), task.key.id);
+                    }
+                });
+            }
+
         }
 
         /**
          * Called when the overlay is no longer used.
          */
-        public void reset() { }
+        public void reset() {
+        }
 
         /**
          * Whether the overlay is modal, which means only tapping is enabled, but no swiping.
@@ -102,5 +137,28 @@
         public boolean isOverlayModal() {
             return false;
         }
+
+        /**
+         * Gets the task snapshot as it is displayed on the screen.
+         *
+         * @return the bounds of the snapshot in screen coordinates.
+         */
+        public Rect getTaskSnapshotBounds() {
+            int[] location = new int[2];
+            mThumbnailView.getLocationOnScreen(location);
+
+            return new Rect(location[0], location[1], mThumbnailView.getWidth() + location[0],
+                    mThumbnailView.getHeight() + location[1]);
+        }
+
+        /**
+         * Gets the insets that the snapshot is drawn with.
+         *
+         * @return the insets in screen coordinates.
+         */
+        public Insets getTaskSnapshotInsets() {
+            // TODO: return the real insets
+            return Insets.of(0, 0, 0, 0);
+        }
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
index 38b86ce..6a3e1fe 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
@@ -19,7 +19,7 @@
 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.uioverrides.BackgroundBlurController.BACKGROUND_BLUR;
+import static com.android.launcher3.statehandlers.DepthController.DEPTH;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
 
 import android.animation.Animator;
@@ -35,7 +35,7 @@
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.uioverrides.BackgroundBlurController;
+import com.android.launcher3.statehandlers.DepthController;
 import com.android.quickstep.util.AppWindowAnimationHelper;
 import com.android.quickstep.util.MultiValueUpdateListener;
 import com.android.quickstep.views.RecentsView;
@@ -123,7 +123,7 @@
     public static Animator getRecentsWindowAnimator(TaskView v, boolean skipViewChanges,
             RemoteAnimationTargetCompat[] appTargets,
             RemoteAnimationTargetCompat[] wallpaperTargets,
-            BackgroundBlurController backgroundBlurController,
+            DepthController depthController,
             final AppWindowAnimationHelper inOutHelper) {
         SyncRtSurfaceTransactionApplierCompat applier =
                 new SyncRtSurfaceTransactionApplierCompat(v);
@@ -215,9 +215,9 @@
             }
         });
 
-        if (backgroundBlurController != null) {
-            ObjectAnimator backgroundRadiusAnim = ObjectAnimator.ofInt(backgroundBlurController,
-                    BACKGROUND_BLUR, BACKGROUND_APP.getBackgroundBlurRadius(v.getContext()));
+        if (depthController != null) {
+            ObjectAnimator backgroundRadiusAnim = ObjectAnimator.ofFloat(depthController,
+                    DEPTH, BACKGROUND_APP.getDepth(v.getContext()));
             animatorSet.playTogether(appAnimator, backgroundRadiusAnim);
         } else {
             animatorSet.play(appAnimator);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index 25a3078..2d0f978 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -15,11 +15,14 @@
  */
 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.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;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED;
@@ -122,6 +125,7 @@
     private static final String NOTIFY_ACTION_BACK = "com.android.quickstep.action.BACK_GESTURE";
     private static final String HAS_ENABLED_QUICKSTEP_ONCE = "launcher.has_enabled_quickstep_once";
     private static final int MAX_BACK_NOTIFICATION_COUNT = 3;
+
     private int mBackGestureNotificationCounter = -1;
     @Nullable
     private OverscrollPlugin mOverscrollPlugin;
@@ -261,7 +265,7 @@
     private InputConsumer mConsumer = InputConsumer.NO_OP;
     private Choreographer mMainChoreographer;
     private InputConsumer mResetGestureInputConsumer;
-    private GestureState mGestureState = new GestureState();
+    private GestureState mGestureState = DEFAULT_STATE;
 
     private InputMonitorCompat mInputMonitorCompat;
     private InputEventReceiver mInputEventReceiver;
@@ -433,15 +437,14 @@
 
         Object traceToken = TraceHelper.INSTANCE.beginFlagsOverride(
                 TraceHelper.FLAG_ALLOW_BINDER_TRACKING);
-        mDeviceState.setOrientationTransformIfNeeded(event);
 
-        if (event.getAction() == ACTION_DOWN) {
-            GestureState newGestureState = new GestureState(mOverviewComponentObserver,
-                    ActiveGestureLog.INSTANCE.generateAndSetLogId());
-            newGestureState.updateRunningTask(TraceHelper.whitelistIpcs("getRunningTask.0",
-                    () -> mAM.getRunningTask(0)));
+        final int action = event.getAction();
+        if (action == ACTION_DOWN) {
+            mDeviceState.setOrientationTransformIfNeeded(event);
+            GestureState newGestureState;
 
             if (mDeviceState.isInSwipeUpTouchRegion(event)) {
+                newGestureState = createGestureState();
                 mConsumer.onConsumerAboutToBeSwitched();
                 mConsumer = newConsumer(mGestureState, newGestureState, event);
 
@@ -450,6 +453,7 @@
             } else if (mDeviceState.isUserUnlocked()
                     && mDeviceState.isFullyGesturalNavMode()
                     && mDeviceState.canTriggerAssistantAction(event)) {
+                newGestureState = createGestureState();
                 // Do not change mConsumer as if there is an ongoing QuickSwitch gesture, we should
                 // not interrupt it. QuickSwitch assumes that interruption can only happen if the
                 // next gesture is also quick switch.
@@ -459,18 +463,40 @@
                     InputConsumer.NO_OP, mInputMonitorCompat,
                     mOverviewComponentObserver.assistantGestureIsConstrained());
             } else {
+                newGestureState = DEFAULT_STATE;
                 mUncheckedConsumer = InputConsumer.NO_OP;
             }
 
             // Save the current gesture state
             mGestureState = newGestureState;
+        } else {
+            // Other events
+            if (mUncheckedConsumer != InputConsumer.NO_OP) {
+                // Only transform the event if we are handling it in a proper consumer
+                mDeviceState.setOrientationTransformIfNeeded(event);
+            }
         }
 
         ActiveGestureLog.INSTANCE.addLog("onMotionEvent", event.getActionMasked());
         mUncheckedConsumer.onMotionEvent(event);
+
+        if (action == ACTION_UP || action == ACTION_CANCEL) {
+            if (mConsumer != null && !mConsumer.isConsumerDetachedFromGesture()) {
+                onConsumerInactive(mConsumer);
+            }
+        }
+
         TraceHelper.INSTANCE.endFlagsOverride(traceToken);
     }
 
+    private GestureState createGestureState() {
+        GestureState gestureState = new GestureState(mOverviewComponentObserver,
+                ActiveGestureLog.INSTANCE.generateAndSetLogId());
+        gestureState.updateRunningTask(TraceHelper.whitelistIpcs("getRunningTask.0",
+                () -> mAM.getRunningTask(true /* filterOnlyVisibleRecents */)));
+        return gestureState;
+    }
+
     private InputConsumer newConsumer(GestureState previousGestureState,
             GestureState newGestureState, MotionEvent event) {
         boolean canStartSystemGesture = mDeviceState.canStartSystemGesture();
@@ -567,18 +593,10 @@
             return createDeviceLockedInputConsumer(gestureState);
         }
 
-        boolean forceOverviewInputConsumer = false;
-        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",
-                    () -> mAM.getRunningTask(ACTIVITY_TYPE_ASSISTANT /* ignoreActivityType */)));
-            ComponentName homeComponent = mOverviewComponentObserver.getHomeIntent().getComponent();
-            ComponentName runningComponent =
-                    gestureState.getRunningTask().baseIntent.getComponent();
-            forceOverviewInputConsumer =
-                    runningComponent != null && runningComponent.equals(homeComponent);
-        }
+        RunningTaskInfo runningTask = gestureState.getRunningTask();
+        ComponentName homeComponent = mOverviewComponentObserver.getHomeIntent().getComponent();
+        boolean forceOverviewInputConsumer = runningTask != null
+                && runningTask.baseIntent.getComponent().equals(homeComponent);
 
         if (previousGestureState.getFinishingRecentsAnimationTaskId() > 0) {
             // If the finish animation was interrupted, then continue using the other activity input
@@ -661,8 +679,8 @@
      */
     private void onConsumerInactive(InputConsumer caller) {
         if (mConsumer == caller) {
-            mConsumer = mResetGestureInputConsumer;
-            mUncheckedConsumer = mConsumer;
+            mConsumer = mUncheckedConsumer = mResetGestureInputConsumer;
+            mGestureState = new GestureState();
         }
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java
index 2c5d631..7f5ec9b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java
@@ -81,7 +81,8 @@
         if (!insets.equals(mInsets)) {
             super.setInsets(insets);
         }
-        setBackground(insets.top == 0 ? null
+        setBackground(insets.top == 0  || !mAllowSysuiScrims
+                ? null
                 : Themes.getAttrDrawable(getContext(), R.attr.workspaceStatusBarScrim));
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
index 05c206f..71465eb 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
@@ -25,6 +25,11 @@
     }
 
     @Override
+    public boolean isConsumerDetachedFromGesture() {
+        return mDelegate.isConsumerDetachedFromGesture();
+    }
+
+    @Override
     public boolean allowInterceptByParent() {
         return mDelegate.allowInterceptByParent() && mState != STATE_ACTIVE;
     }
@@ -36,7 +41,7 @@
 
     protected void setActive(MotionEvent ev) {
         mState = STATE_ACTIVE;
-        TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "pilferPointers");
+        TestLogging.recordEvent(TestProtocol.SEQUENCE_PILFER, "pilferPointers");
         mInputMonitor.pilferPointers();
 
         // Send cancel event
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
index ba1d38c..7b8d40c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
@@ -204,7 +204,7 @@
 
     private void startRecentsTransition() {
         mThresholdCrossed = true;
-        TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "pilferPointers");
+        TestLogging.recordEvent(TestProtocol.SEQUENCE_PILFER, "pilferPointers");
         mInputMonitorCompat.pilferPointers();
 
         Intent intent = new Intent(Intent.ACTION_MAIN)
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index 893868b..a462949 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -160,6 +160,11 @@
         return TYPE_OTHER_ACTIVITY;
     }
 
+    @Override
+    public boolean isConsumerDetachedFromGesture() {
+        return true;
+    }
+
     private void forceCancelGesture(MotionEvent ev) {
         int action = ev.getAction();
         ev.setAction(ACTION_CANCEL);
@@ -309,7 +314,7 @@
         if (mInteractionHandler == null) {
             return;
         }
-        TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "pilferPointers");
+        TestLogging.recordEvent(TestProtocol.SEQUENCE_PILFER, "pilferPointers");
         mInputMonitorCompat.pilferPointers();
 
         mActivityInterface.closeOverlay();
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
index f8abba5..d3160b3 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
@@ -108,7 +108,7 @@
                 ActiveGestureLog.INSTANCE.addLog("startQuickstep");
             }
             if (mInputMonitor != null) {
-                TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "pilferPointers");
+                TestLogging.recordEvent(TestProtocol.SEQUENCE_PILFER, "pilferPointers");
                 mInputMonitor.pilferPointers();
             }
         }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
index 823b254..ac1c3a8 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
@@ -65,7 +65,7 @@
 
     private void onInterceptTouch() {
         if (mInputMonitor != null) {
-            TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "pilferPointers");
+            TestLogging.recordEvent(TestProtocol.SEQUENCE_PILFER, "pilferPointers");
             mInputMonitor.pilferPointers();
         }
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
index 43c5cb5..9cf45b3 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
@@ -126,6 +126,8 @@
             addScrimAnimationForState(launcher, NORMAL, ALPHA_DURATION_MS);
         }
 
+        mAnimators.play(launcher.getDragLayer().getScrim().createSysuiMultiplierAnim(0f, 1f)
+                .setDuration(ALPHA_DURATION_MS));
         mAnimators.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
@@ -153,6 +155,10 @@
         launcher.<RecentsView>getOverviewPanel().getScroller().forceFinished(true);
     }
 
+    public AnimatorSet getAnimators() {
+        return mAnimators;
+    }
+
     /**
      * Starts the animation.
      */
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
index 3ed7530..e62de18 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -19,7 +19,7 @@
 import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.LauncherState.RECENTS_CLEAR_ALL_BUTTON;
+import static com.android.launcher3.LauncherState.OVERVIEW_BUTTONS;
 import static com.android.launcher3.LauncherState.SPRING_LOADED;
 import static com.android.launcher3.QuickstepAppTransitionManagerImpl.ALL_APPS_PROGRESS_OFF_SCREEN;
 import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
@@ -37,18 +37,18 @@
 import android.view.View;
 import android.widget.FrameLayout;
 
+import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Hotseat;
-import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager.StateListener;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.appprediction.PredictionUiStateManager;
 import com.android.launcher3.appprediction.PredictionUiStateManager.Client;
-import com.android.launcher3.states.RotationHelper;
-import com.android.launcher3.uioverrides.BackgroundBlurController;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.statehandlers.DepthController;
+import com.android.launcher3.states.RotationHelper;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.util.TraceHelper;
 import com.android.launcher3.views.ScrimView;
@@ -63,7 +63,8 @@
  * {@link RecentsView} used in Launcher activity
  */
 @TargetApi(Build.VERSION_CODES.O)
-public class LauncherRecentsView extends RecentsView<Launcher> implements StateListener {
+public class LauncherRecentsView extends RecentsView<BaseQuickstepLauncher>
+        implements StateListener {
 
     private static final Rect sTempRect = new Rect();
 
@@ -322,7 +323,7 @@
         if (enabled) {
             LauncherState state = mActivity.getStateManager().getState();
             boolean hasClearAllButton = (state.getVisibleElements(mActivity)
-                    & RECENTS_CLEAR_ALL_BUTTON) != 0;
+                    & OVERVIEW_BUTTONS) != 0;
             setDisallowScrollToClearAll(!hasClearAllButton);
         }
     }
@@ -409,7 +410,7 @@
     }
 
     @Override
-    protected BackgroundBlurController getBackgroundBlurController() {
-        return mActivity.getBackgroundBlurController();
+    protected DepthController getDepthController() {
+        return mActivity.getDepthController();
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java
new file mode 100644
index 0000000..6a37e2b
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java
@@ -0,0 +1,79 @@
+/*
+ * 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.views;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.R;
+
+/**
+ * View for showing action buttons in Overview
+ */
+public class OverviewActionsView extends FrameLayout {
+
+    private final View mScreenshotButton;
+    private final View mShareButton;
+
+    /**
+     * Listener for taps on the various actions.
+     */
+    public interface Listener {
+        /** User has initiated the share actions. */
+        void onShare();
+
+        /** User has initiated the screenshot action. */
+        void onScreenshot();
+    }
+
+    public OverviewActionsView(Context context) {
+        this(context, null);
+    }
+
+    public OverviewActionsView(Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public OverviewActionsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public OverviewActionsView(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        LayoutInflater.from(context).inflate(R.layout.overview_actions, this, true);
+        mShareButton = findViewById(R.id.action_share);
+        mScreenshotButton = findViewById(R.id.action_screenshot);
+    }
+
+    /**
+     * Set listener for callbacks on action button taps.
+     *
+     * @param listener for callbacks, or {@code null} to clear the listener.
+     */
+    public void setListener(@Nullable OverviewActionsView.Listener listener) {
+        mShareButton.setOnClickListener(
+                listener == null ? null : view -> listener.onShare());
+        mScreenshotButton.setOnClickListener(
+                listener == null ? null : view -> listener.onScreenshot());
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
index 8322d8c..1c95a9e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
@@ -19,6 +19,7 @@
 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.Utilities.EDGE_NAV_BAR;
 import static com.android.launcher3.Utilities.squaredHypot;
@@ -29,7 +30,9 @@
 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.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.launcher3.uioverrides.BackgroundBlurController.BACKGROUND_BLUR;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.TASK_DISMISS_SWIPE_UP;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.TASK_LAUNCH_SWIPE_DOWN;
+import static com.android.launcher3.statehandlers.DepthController.DEPTH;
 import static com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController.SUCCESS_TRANSITION_PROGRESS;
 import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP;
 import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.CLEAR_ALL_BUTTON;
@@ -68,7 +71,6 @@
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
-import android.view.OrientationEventListener;
 import android.view.View;
 import android.view.ViewDebug;
 import android.view.ViewGroup;
@@ -88,7 +90,6 @@
 import com.android.launcher3.PagedView;
 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.PendingAnimation;
 import com.android.launcher3.anim.PendingAnimation.EndState;
@@ -96,11 +97,10 @@
 import com.android.launcher3.anim.SpringProperty;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.graphics.RotationMode;
+import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.states.RotationHelper;
 import com.android.launcher3.touch.PagedOrientationHandler.CurveProperties;
-import com.android.launcher3.uioverrides.BackgroundBlurController;
 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;
@@ -720,6 +720,9 @@
         for (int i = 0; i < taskCount; i++) {
             getTaskViewAt(i).setFullscreenProgress(mFullscreenProgress);
         }
+        if (mActionsView != null) {
+            mActionsView.setVisibility(fullscreenProgress == 0 ? VISIBLE : INVISIBLE);
+        }
     }
 
     private void updateTaskStackListenerState() {
@@ -1165,7 +1168,9 @@
     }
 
     private void addDismissedTaskAnimations(View taskView, long duration, PendingAnimation anim) {
-        anim.add(ObjectAnimator.ofFloat(taskView, ALPHA, 0).setDuration(duration), ACCEL_2);
+        // Use setFloat instead of setViewAlpha as we want to keep the view visible even when it's
+        // alpha is set to 0 so that it can be recycled in the view pool properly
+        anim.setFloat(taskView, VIEW_ALPHA, 0, ACCEL_2);
         FloatProperty<View> secondaryViewTranslate =
             mOrientationHandler.getSecondaryViewTranslate();
         int secondaryTaskDimension = mOrientationHandler.getSecondaryDimension(taskView);
@@ -1180,13 +1185,13 @@
                 verticalFactor * secondaryTaskDimension).setDuration(duration), LINEAR, sp);
     }
 
-    private void removeTask(Task task, int index, EndState endState) {
-        if (task != null) {
-            ActivityManagerWrapper.getInstance().removeTask(task.key.id);
-            ComponentKey componentKey = TaskUtils.getLaunchComponentKeyForTask(task.key);
+    private void removeTask(TaskView taskView, int index, EndState endState) {
+        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, componentKey);
-            mActivity.getStatsLogManager().logTaskDismiss(this, componentKey);
+                    endState.logAction, Direction.UP, index, compKey);
+            mActivity.getStatsLogManager().log(TASK_DISMISS_SWIPE_UP, taskView.buildProto());
         }
     }
 
@@ -1257,9 +1262,7 @@
         }
 
         if (needsCurveUpdates) {
-            ValueAnimator va = ValueAnimator.ofFloat(0, 1);
-            va.addUpdateListener((a) -> updateCurveProperties());
-            anim.add(va);
+            anim.addOnFrameCallback(this::updateCurveProperties);
         }
 
         // Add a tiny bit of translation Z, so that it draws on top of other views
@@ -1279,10 +1282,11 @@
                 }
             }
 
+            @SuppressWarnings("WrongCall")
             private void onEnd(EndState endState) {
                 if (endState.isSuccess) {
                     if (shouldRemoveTask) {
-                        removeTask(taskView.getTask(), draggedIndex, endState);
+                        removeTask(taskView, draggedIndex, endState);
                     }
 
                     int pageToSnapTo = mCurrentPage;
@@ -1290,15 +1294,18 @@
                             pageToSnapTo == (getTaskViewCount() - 1)) {
                         pageToSnapTo -= 1;
                     }
-                    removeView(taskView);
+                    removeViewInLayout(taskView);
 
                     if (getTaskViewCount() == 0) {
-                        removeView(mClearAllButton);
+                        removeViewInLayout(mClearAllButton);
                         hideActionsView();
                         startHome();
                     } else {
                         snapToPageImmediately(pageToSnapTo);
                     }
+                    // Update the layout synchronously so that the position of next view is
+                    // immediately available.
+                    onLayout(false /*  changed */, getLeft(), getTop(), getRight(), getBottom());
                 }
                 resetTaskVisuals();
                 mPendingAnimation = null;
@@ -1548,6 +1555,7 @@
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
+
         updateEmptyStateUi(changed);
 
         // Set the pivot points to match the task preview center
@@ -1702,11 +1710,11 @@
         appWindowAnimationHelper.prepareAnimation(mActivity.getDeviceProfile(), true /* isOpening */);
         AnimatorSet anim = createAdjacentPageAnimForTaskLaunch(tv, appWindowAnimationHelper);
 
-        BackgroundBlurController blurController = getBackgroundBlurController();
-        if (blurController != null) {
-            ObjectAnimator backgroundBlur = ObjectAnimator.ofInt(blurController, BACKGROUND_BLUR,
-                    BACKGROUND_APP.getBackgroundBlurRadius(mActivity));
-            anim.play(backgroundBlur);
+        DepthController depthController = getDepthController();
+        if (depthController != null) {
+            ObjectAnimator depthAnimator = ObjectAnimator.ofFloat(depthController, DEPTH,
+                    BACKGROUND_APP.getDepth(mActivity));
+            anim.play(depthAnimator);
         }
         anim.play(progressAnim);
         anim.setDuration(duration).setInterpolator(interpolator);
@@ -1727,6 +1735,8 @@
                     mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
                             endState.logAction, Direction.DOWN, indexOfChild(tv),
                             TaskUtils.getLaunchComponentKeyForTask(task.key));
+                    mActivity.getStatsLogManager().log(TASK_LAUNCH_SWIPE_DOWN, tv.buildProto()
+                    );
                 }
             } else {
                 onTaskLaunched(false);
@@ -2009,7 +2019,7 @@
     }
 
     @Nullable
-    protected BackgroundBlurController getBackgroundBlurController() {
+    protected DepthController getDepthController() {
         return null;
     }
 
@@ -2029,7 +2039,6 @@
         void onEmptyMessageUpdated(boolean isEmpty);
     }
 
-
     private static class PinnedStackAnimationListener<T extends BaseActivity> extends
             IPinnedStackAnimationListener.Stub {
         private T mActivity;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
index c94b56c..7010f9a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
@@ -29,6 +29,7 @@
 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.TASK_LAUNCH_TAP;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -43,6 +44,7 @@
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.Process;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
 import android.util.Log;
@@ -59,6 +61,7 @@
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.states.RotationHelper;
@@ -68,6 +71,7 @@
 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.ViewPool.Reusable;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.TaskIconCache;
@@ -217,8 +221,7 @@
             mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
                     Touch.TAP, Direction.NONE, getRecentsView().indexOfChild(this),
                     TaskUtils.getLaunchComponentKeyForTask(getTask().key));
-            mActivity.getStatsLogManager().logTaskLaunch(getRecentsView(),
-                    TaskUtils.getLaunchComponentKeyForTask(getTask().key));
+            mActivity.getStatsLogManager().log(TASK_LAUNCH_TAP, buildProto());
         });
         mCornerRadius = TaskCornerRadius.get(context);
         mWindowCornerRadius = QuickStepContract.getWindowCornerRadius(context.getResources());
@@ -229,6 +232,17 @@
         setOutlineProvider(mOutlineProvider);
     }
 
+    /* Builds proto for logging */
+    protected LauncherAtom.ItemInfo buildProto() {
+        ComponentKey componentKey = TaskUtils.getLaunchComponentKeyForTask(getTask().key);
+        LauncherAtom.ItemInfo.Builder itemBuilder = LauncherAtom.ItemInfo.newBuilder();
+        itemBuilder.setIsWork(componentKey.user != Process.myUserHandle());
+        itemBuilder.setTask(LauncherAtom.Task.newBuilder()
+                .setComponentName(componentKey.componentName.flattenToShortString())
+                .setIndex(getRecentsView().indexOfChild(this)));
+        return itemBuilder.build();
+    }
+
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
@@ -247,8 +261,17 @@
 
     /** Updates UI based on whether the task is modal. */
     public void updateUiForModalTask() {
+        boolean isOverlayModal = isTaskOverlayModal();
         if (getRecentsView() != null) {
-            getRecentsView().updateUiForModalTask(this, isTaskOverlayModal());
+            getRecentsView().updateUiForModalTask(this, isOverlayModal);
+        }
+        // Hide footers when overlay is modal.
+        if (isOverlayModal) {
+            for (FooterWrapper footer : mFooters) {
+                if (footer != null) {
+                    footer.animateHide();
+                }
+            }
         }
     }
 
@@ -780,6 +803,22 @@
             animator.setDuration(100);
             animator.start();
         }
+
+        void animateHide() {
+            ValueAnimator animator = ValueAnimator.ofFloat(0.0f, 1.0f);
+            animator.addUpdateListener(anim -> {
+                mFooterVerticalOffset = anim.getAnimatedFraction();
+                updateFooterOffset();
+            });
+            animator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    removeView(mView);
+                }
+            });
+            animator.setDuration(100);
+            animator.start();
+        }
     }
 
     @Override
@@ -787,8 +826,8 @@
         super.onInitializeAccessibilityNodeInfo(info);
 
         info.addAction(
-                new AccessibilityNodeInfo.AccessibilityAction(R.string.accessibility_close_task,
-                        getContext().getText(R.string.accessibility_close_task)));
+                new AccessibilityNodeInfo.AccessibilityAction(R.string.accessibility_close,
+                        getContext().getText(R.string.accessibility_close)));
 
         final Context context = getContext();
         for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this)) {
@@ -812,7 +851,7 @@
 
     @Override
     public boolean performAccessibilityAction(int action, Bundle arguments) {
-        if (action == R.string.accessibility_close_task) {
+        if (action == R.string.accessibility_close) {
             getRecentsView().dismissTask(this, true /*animateTaskView*/,
                     true /*removeTask*/);
             return true;
diff --git a/quickstep/res/drawable/ic_screenshot.xml b/quickstep/res/drawable/ic_screenshot.xml
new file mode 100644
index 0000000..d97eae1
--- /dev/null
+++ b/quickstep/res/drawable/ic_screenshot.xml
@@ -0,0 +1,23 @@
+<!-- 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="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M17,1.01L7,1c-1.1,0 -2,0.9 -2,2v18c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2L19,3c0,-1.1 -0.9,-1.99 -2,-1.99zM17,21L7,21v-1h10v1zM17,18L7,18L7,6h10v12zM17,4L7,4L7,3h10v1zM9.5,8.5L12,8.5L12,7L8,7v4h1.5zM12,17h4v-4h-1.5v2.5L12,15.5z"/>
+</vector>
diff --git a/quickstep/res/drawable/ic_share.xml b/quickstep/res/drawable/ic_share.xml
new file mode 100644
index 0000000..ff4baec
--- /dev/null
+++ b/quickstep/res/drawable/ic_share.xml
@@ -0,0 +1,23 @@
+<!-- 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="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M18,16c-0.79,0 -1.5,0.31 -2.03,0.81L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.53,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.48 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.05,4.12c-0.05,0.22 -0.09,0.45 -0.09,0.69 0,1.66 1.34,3 3,3s3,-1.34 3,-3 -1.34,-3 -3,-3zM18,4c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM6,13c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1zM18,20c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1z"/>
+</vector>
diff --git a/quickstep/res/layout/back_gesture_tutorial_fragment.xml b/quickstep/res/layout/back_gesture_tutorial_fragment.xml
index 294e46e..d8c25bd 100644
--- a/quickstep/res/layout/back_gesture_tutorial_fragment.xml
+++ b/quickstep/res/layout/back_gesture_tutorial_fragment.xml
@@ -16,9 +16,7 @@
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:layerType="software"
     android:background="@color/back_gesture_tutorial_background_color">
-    <!--The layout is rendered on the software layer to avoid b/136158117-->
 
     <ImageView
         android:id="@+id/back_gesture_tutorial_fragment_hand_coaching"
diff --git a/quickstep/res/layout/overview_actions.xml b/quickstep/res/layout/overview_actions.xml
new file mode 100644
index 0000000..ad5efb6
--- /dev/null
+++ b/quickstep/res/layout/overview_actions.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+    <LinearLayout
+        android:id="@+id/action_buttons"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:orientation="horizontal">
+        <Space
+            android:layout_width="0dp"
+            android:layout_height="1dp"
+            android:layout_weight="1" >
+        </Space>
+        <Button
+            android:id="@+id/action_screenshot"
+            style="@style/OverviewActionButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:drawableTop="@drawable/ic_screenshot"
+            android:text="@string/action_screenshot" />
+        <Space
+            android:layout_width="0dp"
+            android:layout_height="1dp"
+            android:layout_weight="1" >
+        </Space>
+
+        <Button
+            android:id="@+id/action_share"
+            style="@style/OverviewActionButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:drawableTop="@drawable/ic_share"
+            android:text="@string/action_share" />
+        <Space
+            android:layout_width="0dp"
+            android:layout_height="1dp"
+            android:layout_weight="1" >
+        </Space>
+    </LinearLayout>
+
+</merge>
diff --git a/quickstep/res/layout/overview_actions_container.xml b/quickstep/res/layout/overview_actions_container.xml
new file mode 100644
index 0000000..328c20b
--- /dev/null
+++ b/quickstep/res/layout/overview_actions_container.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.quickstep.views.OverviewActionsView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:visibility="gone">
+
+</com.android.quickstep.views.OverviewActionsView>
\ No newline at end of file
diff --git a/quickstep/res/values/colors.xml b/quickstep/res/values/colors.xml
new file mode 100644
index 0000000..3583676
--- /dev/null
+++ b/quickstep/res/values/colors.xml
@@ -0,0 +1,18 @@
+<?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="back_arrow_color_dark">#99000000</color>
+</resources>
\ No newline at end of file
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 988c78d..8d42c4a 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -23,7 +23,7 @@
     <dimen name="task_corner_radius_small">2dp</dimen>
 
     <!-- Overrideable in overlay that provides the Overview Actions. -->
-    <dimen name="overview_actions_height">0dp</dimen>
+    <dimen name="overview_actions_height">110dp</dimen>
 
     <dimen name="recents_page_spacing">10dp</dimen>
     <dimen name="recents_clear_all_deadzone_vertical_margin">70dp</dimen>
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index b55b042..548a51f 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -32,9 +32,6 @@
     <!-- Recents: The empty recents string. [CHAR LIMIT=NONE] -->
     <string name="recents_empty_message">No recent items</string>
 
-    <!-- Content description for the recent apps's accessibility option that closes it. [CHAR LIMIT=NONE] -->
-    <string name="accessibility_close_task">Close</string>
-
     <!-- Content description for the recent apps's accessibility option that opens its usage settings. [CHAR LIMIT=NONE] -->
     <string name="accessibility_app_usage_settings">App usage settings</string>
 
@@ -78,16 +75,20 @@
     <string name="hotseat_edu_message_migrate">Easily access your most-used apps right on the Home screen. Suggestions will change based on your routines. Apps on the bottom row will move up to your Home screen. </string>
     <string name="hotseat_edu_message_migrate_alt">Easily access your most-used apps, right on the Home screen. Suggestions will change based on your routines. Apps on the bottom row will move to a new folder.</string>
 
-    <!-- Toast message user sees after opting into fully predicted hybrid hotseat -->
-    <string name="hotseat_items_migrated">Your hotseat items have been moved up to your homescreen</string>
-    <string name="hotseat_items_migrated_alt">Your hotseat items have been moved to a folder</string>
-    <!-- Toast message user sees after opting into fully predicted hybrid hotseat -->
-    <string name="hotseat_no_migration">Drag apps off the bottom row to see app suggestions</string>
     <!-- Button text to opt in for fully predicted hotseat -->
     <string name="hotseat_edu_accept">Get app suggestions</string>
     <!-- Button text to dismiss opt in for fully predicted hotseat -->
     <string name="hotseat_edu_dismiss">No thanks</string>
 
+    <!-- action shown to turn off predictions after onboarding -->
+    <string name="hotseat_turn_off">Settings</string>
+
+    <!-- tip shown if user has no items in hotseat to migrate -->
+    <string name="hotseat_auto_enrolled">Most-used apps appear here, and change based on routines</string>
+    <!-- tip shown if user declines migration and has no open spots for prediction -->
+    <string name="hotseat_tip_no_empty_slots">Drag apps off the bottom row to get app suggestions</string>
+    <!-- tip shown if user declines migration and has some open spots for prediction -->
+    <string name="hotseat_tip_gaps_filled">App suggestions added to empty space</string>
 
     <!-- Title shown during interactive part of Back gesture tutorial for right edge. [CHAR LIMIT=30] -->
     <string name="back_gesture_tutorial_playground_title_swipe_inward_right_edge" translatable="false">Try the back gesture</string>
@@ -108,4 +109,10 @@
     <string name="back_gesture_tutorial_action_button_label" translatable="false">Done</string>
     <!-- Button text shown on a text button on the confirm screen. [CHAR LIMIT=14] -->
     <string name="back_gesture_tutorial_action_text_button_label" translatable="false">Settings</string>
-</resources>
\ No newline at end of file
+
+    <!-- ******* Overview ******* -->
+    <!-- Label for a button that causes the current overview app to be shared. [CHAR_LIMIT=40] -->
+    <string name="action_share">Share</string>
+    <!-- Label for a button that causes a screen shot of the current app to be taken. [CHAR_LIMIT=40] -->
+    <string name="action_screenshot">Screenshot</string>
+</resources>
diff --git a/quickstep/res/values/styles.xml b/quickstep/res/values/styles.xml
index c8d7777..bf107fb 100644
--- a/quickstep/res/values/styles.xml
+++ b/quickstep/res/values/styles.xml
@@ -60,4 +60,13 @@
         parent="TextAppearance.BackGestureTutorial.ButtonLabel">
         <item name="android:textColor">@color/back_gesture_tutorial_primary_color</item>
     </style>
+
+    <style name="OverviewActionButton"
+        parent="@android:style/Widget.DeviceDefault.Button.Borderless">
+        <item name="android:textColor">?attr/workspaceTextColor</item>
+        <item name="android:drawableTint">?attr/workspaceTextColor</item>
+        <item name="android:tint">?attr/workspaceTextColor</item>
+        <item name="android:drawablePadding">4dp</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
 </resources>
\ No newline at end of file
diff --git a/quickstep/res/xml/overview_file_provider_paths.xml b/quickstep/res/xml/overview_file_provider_paths.xml
new file mode 100644
index 0000000..14d7459
--- /dev/null
+++ b/quickstep/res/xml/overview_file_provider_paths.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<paths xmlns:android="http://schemas.android.com/apk/res/android">
+    <cache-path name="shared_images" path="/" />
+    <files-path name="log_files" path="/" />
+</paths>
\ No newline at end of file
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index a7d00c5..135daef 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -34,6 +34,7 @@
 import android.content.IntentSender;
 import android.os.Bundle;
 import android.os.CancellationSignal;
+import android.view.View;
 
 import com.android.launcher3.LauncherState.ScaleAndTranslation;
 import com.android.launcher3.LauncherStateManager.StateHandler;
@@ -43,8 +44,9 @@
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.proxy.ProxyActivityStarter;
 import com.android.launcher3.proxy.StartActivityParams;
+import com.android.launcher3.statehandlers.BackButtonAlphaHandler;
+import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.touch.PagedOrientationHandler;
-import com.android.launcher3.uioverrides.BackButtonAlphaHandler;
 import com.android.launcher3.uioverrides.RecentsViewStateController;
 import com.android.launcher3.util.UiThreadHelper;
 import com.android.quickstep.RecentsModel;
@@ -67,6 +69,7 @@
 public abstract class BaseQuickstepLauncher extends Launcher
         implements NavigationModeChangeListener {
 
+    private DepthController mDepthController = new DepthController(this);
     protected SystemActions mSystemActions;
 
     /**
@@ -78,6 +81,8 @@
 
     private final ShelfPeekAnim mShelfPeekAnim = new ShelfPeekAnim(this);
 
+    private View mActionsView;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -222,16 +227,22 @@
     @Override
     protected void setupViews() {
         super.setupViews();
+        mActionsView = findViewById(R.id.overview_actions_view);
+
 
         if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(this)) {
             // Overview is above all other launcher elements, including qsb, so move it to the top.
             getOverviewPanel().bringToFront();
-            if (getActionsView() != null) {
-                getActionsView().bringToFront();
+            if (mActionsView != null) {
+                mActionsView.bringToFront();
             }
         }
     }
 
+    public View getActionsView() {
+        return mActionsView;
+    }
+
     @Override
     protected void closeOpenViews(boolean animate) {
         super.closeOpenViews(animate);
@@ -244,11 +255,15 @@
         return new StateHandler[] {
                 getAllAppsController(),
                 getWorkspace(),
-                getBackgroundBlurController(),
+                getDepthController(),
                 new RecentsViewStateController(this),
                 new BackButtonAlphaHandler(this)};
     }
 
+    public DepthController getDepthController() {
+        return mDepthController;
+    }
+
     @Override
     protected ScaleAndTranslation getOverviewScaleAndTranslationForNormalState() {
         if (SysUINavigationMode.getMode(this) == Mode.NO_BUTTON) {
@@ -294,6 +309,10 @@
             onLauncherStateOrFocusChanged();
         }
 
+        if ((changeBits & ACTIVITY_STATE_STARTED) != 0) {
+            mDepthController.setActivityStarted(isStarted());
+        }
+
         super.onActivityFlagsChanged(changeBits);
     }
 
diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index b9bd6b1..a30e102 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
@@ -16,6 +16,8 @@
 
 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;
@@ -29,8 +31,9 @@
 import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
 import static com.android.launcher3.anim.Interpolators.EXAGGERATED_EASE;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.config.FeatureFlags.KEYGUARD_ANIMATION;
 import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_TRANSITIONS;
-import static com.android.launcher3.uioverrides.BackgroundBlurController.BACKGROUND_BLUR;
+import static com.android.launcher3.statehandlers.DepthController.DEPTH;
 import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
 import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
 import static com.android.systemui.shared.system.QuickStepContract.getWindowCornerRadius;
@@ -58,6 +61,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.util.Pair;
+import android.util.TypedValue;
 import android.view.View;
 
 import androidx.annotation.NonNull;
@@ -65,17 +69,18 @@
 
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.allapps.AllAppsTransitionController;
-import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.shortcuts.DeepShortcutView;
-import com.android.launcher3.uioverrides.BackgroundBlurController;
+import com.android.launcher3.statehandlers.DepthController;
+import com.android.launcher3.util.DynamicResource;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
 import com.android.launcher3.views.FloatingIconView;
 import com.android.quickstep.RemoteAnimationTargets;
 import com.android.quickstep.util.MultiValueUpdateListener;
 import com.android.quickstep.util.RemoteAnimationProvider;
+import com.android.quickstep.util.StaggeredWorkspaceAnim;
 import com.android.systemui.shared.system.ActivityCompat;
 import com.android.systemui.shared.system.ActivityOptionsCompat;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -156,6 +161,7 @@
     // Strong refs to runners which are cleared when the launcher activity is destroyed
     private WrappedAnimationRunnerImpl mWallpaperOpenRunner;
     private WrappedAnimationRunnerImpl mAppLaunchRunner;
+    private WrappedAnimationRunnerImpl mKeyguardGoingAwayRunner;
 
     private final AnimatorListenerAdapter mForceInvisibleListener = new AnimatorListenerAdapter() {
         @Override
@@ -380,18 +386,35 @@
             alpha.setInterpolator(LINEAR);
             launcherAnimator.play(alpha);
 
-            mDragLayer.setTranslationY(trans[0]);
-            ObjectAnimator transY = ObjectAnimator.ofFloat(mDragLayer, View.TRANSLATION_Y, trans);
-            transY.setInterpolator(AGGRESSIVE_EASE);
-            transY.setDuration(CONTENT_TRANSLATION_DURATION);
-            launcherAnimator.play(transY);
+            Workspace workspace = mLauncher.getWorkspace();
+            View currentPage = ((CellLayout) workspace.getChildAt(workspace.getCurrentPage()))
+                    .getShortcutsAndWidgets();
+            View hotseat = mLauncher.getHotseat();
+            View qsb = mLauncher.findViewById(R.id.search_container_all_apps);
 
-            mDragLayer.getScrim().hideSysUiScrim(true);
+            currentPage.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+            hotseat.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+            qsb.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+
+            launcherAnimator.play(ObjectAnimator.ofFloat(currentPage, View.TRANSLATION_Y, trans));
+            launcherAnimator.play(ObjectAnimator.ofFloat(hotseat, View.TRANSLATION_Y, trans));
+            launcherAnimator.play(ObjectAnimator.ofFloat(qsb, View.TRANSLATION_Y, trans));
+
             // Pause page indicator animations as they lead to layer trashing.
             mLauncher.getWorkspace().getPageIndicator().pauseAnimations();
-            mDragLayer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
 
-            endListener = this::resetContentView;
+            endListener = () -> {
+                currentPage.setTranslationY(0);
+                hotseat.setTranslationY(0);
+                qsb.setTranslationY(0);
+
+                currentPage.setLayerType(View.LAYER_TYPE_NONE, null);
+                hotseat.setLayerType(View.LAYER_TYPE_NONE, null);
+                qsb.setLayerType(View.LAYER_TYPE_NONE, null);
+
+                mDragLayerAlpha.setValue(1f);
+                mLauncher.getWorkspace().getPageIndicator().skipAnimationsToEnd();
+            };
         }
         return new Pair<>(launcherAnimator, endListener);
     }
@@ -589,17 +612,17 @@
         // When launching an app from overview that doesn't map to a task, we still want to just
         // blur the wallpaper instead of the launcher surface as well
         boolean allowBlurringLauncher = mLauncher.getStateManager().getState() != OVERVIEW;
-        BackgroundBlurController blurController = mLauncher.getBackgroundBlurController();
-        ObjectAnimator backgroundRadiusAnim = ObjectAnimator.ofInt(blurController, BACKGROUND_BLUR,
-                BACKGROUND_APP.getBackgroundBlurRadius(mLauncher))
+        DepthController depthController = mLauncher.getDepthController();
+        ObjectAnimator backgroundRadiusAnim = ObjectAnimator.ofFloat(depthController, DEPTH,
+                BACKGROUND_APP.getDepth(mLauncher))
                 .setDuration(APP_LAUNCH_DURATION);
         if (allowBlurringLauncher) {
-            blurController.setSurfaceToApp(RemoteAnimationProvider.findLowestOpaqueLayerTarget(
+            depthController.setSurfaceToApp(RemoteAnimationProvider.findLowestOpaqueLayerTarget(
                     appTargets, MODE_OPENING));
             backgroundRadiusAnim.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
-                    blurController.setSurfaceToLauncher(mLauncher.getDragLayer());
+                    depthController.setSurfaceToApp(null);
                 }
             });
         }
@@ -623,6 +646,17 @@
                             new WrappedLauncherAnimationRunner<>(mWallpaperOpenRunner,
                                     false /* startAtFrontOfQueue */),
                             CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */));
+
+            if (KEYGUARD_ANIMATION.get()) {
+                mKeyguardGoingAwayRunner = createWallpaperOpenRunner(true /* fromUnlock */);
+                definition.addRemoteAnimation(
+                        WindowManagerWrapper.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER,
+                        new RemoteAnimationAdapterCompat(
+                                new WrappedLauncherAnimationRunner<>(mKeyguardGoingAwayRunner,
+                                        true /* startAtFrontOfQueue */),
+                                CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */));
+            }
+
             new ActivityCompat(mLauncher).registerRemoteAnimations(definition);
         }
     }
@@ -639,6 +673,7 @@
             // definition so we don't have to wait for the system gc
             mWallpaperOpenRunner = null;
             mAppLaunchRunner = null;
+            mKeyguardGoingAwayRunner = null;
         }
     }
 
@@ -741,62 +776,6 @@
         return closingAnimator;
     }
 
-    /**
-     * Creates an animator that modifies Launcher as a result from 
-     * {@link #createWallpaperOpenRunner}.
-     */
-    private void createLauncherResumeAnimation(AnimatorSet anim) {
-        if (mLauncher.isInState(LauncherState.ALL_APPS)) {
-            Pair<AnimatorSet, Runnable> contentAnimator =
-                    getLauncherContentAnimator(false /* isAppOpening */,
-                            new float[] {-mContentTransY, 0});
-            contentAnimator.first.setStartDelay(LAUNCHER_RESUME_START_DELAY);
-            anim.play(contentAnimator.first);
-            anim.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    contentAnimator.second.run();
-                }
-            });
-        } else {
-            AnimatorSet workspaceAnimator = new AnimatorSet();
-
-            mDragLayer.setTranslationY(-mWorkspaceTransY);;
-            workspaceAnimator.play(ObjectAnimator.ofFloat(mDragLayer, View.TRANSLATION_Y,
-                    -mWorkspaceTransY, 0));
-
-            mDragLayerAlpha.setValue(0);
-            workspaceAnimator.play(ObjectAnimator.ofFloat(
-                    mDragLayerAlpha, MultiValueAlpha.VALUE, 0, 1f));
-
-            workspaceAnimator.setStartDelay(LAUNCHER_RESUME_START_DELAY);
-            workspaceAnimator.setDuration(333);
-            workspaceAnimator.setInterpolator(Interpolators.DEACCEL_1_7);
-
-            mDragLayer.getScrim().hideSysUiScrim(true);
-
-            // Pause page indicator animations as they lead to layer trashing.
-            mLauncher.getWorkspace().getPageIndicator().pauseAnimations();
-            mDragLayer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
-
-            workspaceAnimator.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    resetContentView();
-                }
-            });
-            anim.play(workspaceAnimator);
-        }
-    }
-
-    private void resetContentView() {
-        mLauncher.getWorkspace().getPageIndicator().skipAnimationsToEnd();
-        mDragLayerAlpha.setValue(1f);
-        mDragLayer.setLayerType(View.LAYER_TYPE_NONE, null);
-        mDragLayer.setTranslationY(0f);
-        mDragLayer.getScrim().hideSysUiScrim(false);
-    }
-
     private boolean hasControlRemoteAppTransitionPermission() {
         return mLauncher.checkSelfPermission(CONTROL_REMOTE_APP_TRANSITION_PERMISSION)
                 == PackageManager.PERMISSION_GRANTED;
@@ -868,11 +847,12 @@
                         || mLauncher.isForceInvisible()) {
                     // Only register the content animation for cancellation when state changes
                     mLauncher.getStateManager().setCurrentAnimation(anim);
-                    if (mFromUnlock) {
+
+                    if (mLauncher.isInState(LauncherState.ALL_APPS)) {
                         Pair<AnimatorSet, Runnable> contentAnimator =
                                 getLauncherContentAnimator(false /* isAppOpening */,
-                                        new float[] {mContentTransY, 0});
-                        contentAnimator.first.setStartDelay(0);
+                                        new float[] {-mContentTransY, 0});
+                        contentAnimator.first.setStartDelay(LAUNCHER_RESUME_START_DELAY);
                         anim.play(contentAnimator.first);
                         anim.addListener(new AnimatorListenerAdapter() {
                             @Override
@@ -881,7 +861,12 @@
                             }
                         });
                     } else {
-                        createLauncherResumeAnimation(anim);
+                        float velocityDpPerS = 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/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java b/quickstep/src/com/android/launcher3/statehandlers/BackButtonAlphaHandler.java
similarity index 95%
rename from quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java
rename to quickstep/src/com/android/launcher3/statehandlers/BackButtonAlphaHandler.java
index e82a504..983702a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/BackButtonAlphaHandler.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.launcher3.uioverrides;
+package com.android.launcher3.statehandlers;
 
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.quickstep.AnimatedFloat.VALUE;
@@ -29,6 +29,9 @@
 import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.SystemUiProxy;
 
+/**
+ * State handler for animating back button alpha
+ */
 public class BackButtonAlphaHandler implements LauncherStateManager.StateHandler {
 
     private final BaseQuickstepLauncher mLauncher;
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
new file mode 100644
index 0000000..24ba89a
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
@@ -0,0 +1,190 @@
+/*
+ * 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.statehandlers;
+
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+
+import android.os.IBinder;
+import android.util.FloatProperty;
+import android.view.View;
+import android.view.ViewTreeObserver;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.states.StateAnimationConfig;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.SurfaceControlCompat;
+import com.android.systemui.shared.system.TransactionCompat;
+import com.android.systemui.shared.system.WallpaperManagerCompat;
+
+/**
+ * Controls blur and wallpaper zoom, for the Launcher surface only.
+ */
+public class DepthController implements LauncherStateManager.StateHandler {
+
+    public static final FloatProperty<DepthController> DEPTH =
+            new FloatProperty<DepthController>("depth") {
+                @Override
+                public void setValue(DepthController depthController, float depth) {
+                    depthController.setDepth(depth);
+                }
+
+                @Override
+                public Float get(DepthController depthController) {
+                    return depthController.mDepth;
+                }
+            };
+
+    /**
+     * A property that updates the background blur within a given range of values (ie. even if the
+     * animator goes beyond 0..1, the interpolated value will still be bounded).
+     */
+    public static class ClampedDepthProperty extends FloatProperty<DepthController> {
+        private final float mMinValue;
+        private final float mMaxValue;
+
+        public ClampedDepthProperty(float minValue, float maxValue) {
+            super("depthClamped");
+            mMinValue = minValue;
+            mMaxValue = maxValue;
+        }
+
+        @Override
+        public void setValue(DepthController depthController, float depth) {
+            depthController.setDepth(Utilities.boundToRange(depth, mMinValue, mMaxValue));
+        }
+
+        @Override
+        public Float get(DepthController depthController) {
+            return depthController.mDepth;
+        }
+    }
+
+    private final ViewTreeObserver.OnDrawListener mOnDrawListener =
+            new ViewTreeObserver.OnDrawListener() {
+                @Override
+                public void onDraw() {
+                    View view = mLauncher.getDragLayer();
+                    setSurface(new SurfaceControlCompat(view));
+                    view.post(() -> view.getViewTreeObserver().removeOnDrawListener(this));
+                }
+            };
+
+    private final Launcher mLauncher;
+    /**
+     * Blur radius when completely zoomed out, in pixels.
+     */
+    private int mMaxBlurRadius;
+    private WallpaperManagerCompat mWallpaperManager;
+    private SurfaceControlCompat mSurface;
+    /**
+     * Ratio from 0 to 1, where 0 is fully zoomed out, and 1 is zoomed in.
+     * @see android.service.wallpaper.WallpaperService.Engine#onZoomChanged(float)
+     */
+    private float mDepth;
+
+    public DepthController(Launcher l) {
+        mLauncher = l;
+    }
+
+    private void ensureDependencies() {
+        if (mWallpaperManager != null) {
+            return;
+        }
+        mMaxBlurRadius = mLauncher.getResources().getInteger(R.integer.max_depth_blur_radius);
+        mWallpaperManager = new WallpaperManagerCompat(mLauncher);
+    }
+
+    /**
+     * Sets if the underlying activity is started or not
+     */
+    public void setActivityStarted(boolean isStarted) {
+        if (isStarted) {
+            mLauncher.getDragLayer().getViewTreeObserver().addOnDrawListener(mOnDrawListener);
+        } else {
+            mLauncher.getDragLayer().getViewTreeObserver().removeOnDrawListener(mOnDrawListener);
+            setSurface(null);
+        }
+    }
+
+    /**
+     * Sets the specified app target surface to apply the blur to.
+     */
+    public void setSurfaceToApp(RemoteAnimationTargetCompat target) {
+        if (target != null) {
+            setSurface(target.leash);
+        } else {
+            setActivityStarted(mLauncher.isStarted());
+        }
+    }
+
+    private void setSurface(SurfaceControlCompat surface) {
+        if (mSurface != surface) {
+            mSurface = surface;
+            if (surface != null) {
+                setDepth(mDepth);
+            } else {
+                // If there is no surface, then reset the ratio
+                setDepth(0f);
+            }
+        }
+    }
+
+    @Override
+    public void setState(LauncherState toState) {
+        if (mSurface == null) {
+            return;
+        }
+
+        float toDepth = toState.getDepth(mLauncher);
+        if (Float.compare(mDepth, toDepth) != 0) {
+            setDepth(toDepth);
+        }
+    }
+
+    @Override
+    public void setStateWithAnimation(LauncherState toState, StateAnimationConfig config,
+            PendingAnimation animation) {
+        if (mSurface == null || config.onlyPlayAtomicComponent()) {
+            return;
+        }
+
+        float toDepth = toState.getDepth(mLauncher);
+        if (Float.compare(mDepth, toDepth) != 0) {
+            animation.setFloat(this, DEPTH, toDepth, LINEAR);
+        }
+    }
+
+    private void setDepth(float depth) {
+        mDepth = depth;
+        if (mSurface == null || !mSurface.isValid()) {
+            return;
+        }
+        ensureDependencies();
+        IBinder windowToken = mLauncher.getRootView().getWindowToken();
+        if (windowToken != null) {
+            mWallpaperManager.setWallpaperZoomOut(windowToken, mDepth);
+        }
+        new TransactionCompat()
+                .setBackgroundBlurRadius(mSurface, (int) (mDepth * mMaxBlurRadius))
+                .apply();
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BackgroundBlurController.java b/quickstep/src/com/android/launcher3/uioverrides/BackgroundBlurController.java
deleted file mode 100644
index 513310e..0000000
--- a/quickstep/src/com/android/launcher3/uioverrides/BackgroundBlurController.java
+++ /dev/null
@@ -1,160 +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;
-
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-
-import android.util.IntProperty;
-import android.view.View;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.states.StateAnimationConfig;
-import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-import com.android.systemui.shared.system.SurfaceControlCompat;
-import com.android.systemui.shared.system.TransactionCompat;
-
-/**
- * Controls the blur, for the Launcher surface only.
- */
-public class BackgroundBlurController implements LauncherStateManager.StateHandler {
-
-    public static final IntProperty<BackgroundBlurController> BACKGROUND_BLUR =
-            new IntProperty<BackgroundBlurController>("backgroundBlur") {
-                @Override
-                public void setValue(BackgroundBlurController blurController, int blurRadius) {
-                    blurController.setBackgroundBlurRadius(blurRadius);
-                }
-
-                @Override
-                public Integer get(BackgroundBlurController blurController) {
-                    return blurController.mBackgroundBlurRadius;
-                }
-            };
-
-    /**
-     * A property that updates the background blur within a given range of values (ie. even if the
-     * animator goes beyond 0..1, the interpolated value will still be bounded).
-     */
-    public static class ClampedBlurProperty extends IntProperty<BackgroundBlurController> {
-        private final int mMinValue;
-        private final int mMaxValue;
-
-        public ClampedBlurProperty(int minValue, int maxValue) {
-            super(("backgroundBlurClamped"));
-            mMinValue = minValue;
-            mMaxValue = maxValue;
-        }
-
-        @Override
-        public void setValue(BackgroundBlurController blurController, int blurRadius) {
-            blurController.setBackgroundBlurRadius(Utilities.boundToRange(blurRadius,
-                    mMinValue, mMaxValue));
-        }
-
-        @Override
-        public Integer get(BackgroundBlurController blurController) {
-            return blurController.mBackgroundBlurRadius;
-        }
-    }
-
-    private final Launcher mLauncher;
-    private SurfaceControlCompat mSurface;
-    private int mBackgroundBlurRadius;
-
-    public BackgroundBlurController(Launcher l) {
-        mLauncher = l;
-    }
-
-    /**
-     * @return the background blur adjustment for folders
-     */
-    public int getFolderBackgroundBlurAdjustment() {
-        return mLauncher.getResources().getInteger(
-                R.integer.folder_background_blur_radius_adjustment);
-    }
-
-    /**
-     * Sets the specified app target surface to apply the blur to.
-     */
-    public void setSurfaceToApp(RemoteAnimationTargetCompat target) {
-        if (target != null) {
-            setSurface(target.leash);
-        }
-    }
-
-    /**
-     * Sets the surface to apply the blur to as the launcher surface.
-     */
-    public void setSurfaceToLauncher(View v) {
-        setSurface(v != null ? new SurfaceControlCompat(v) : null);
-    }
-
-    private void setSurface(SurfaceControlCompat surface) {
-        if (mSurface != surface) {
-            mSurface = surface;
-            if (surface != null) {
-                setBackgroundBlurRadius(mBackgroundBlurRadius);
-            } else {
-                // If there is no surface, then reset the blur radius
-                setBackgroundBlurRadius(0);
-            }
-        }
-    }
-
-    @Override
-    public void setState(LauncherState toState) {
-        if (mSurface == null) {
-            return;
-        }
-
-        int toBackgroundBlurRadius = toState.getBackgroundBlurRadius(mLauncher);
-        if (mBackgroundBlurRadius != toBackgroundBlurRadius) {
-            setBackgroundBlurRadius(toBackgroundBlurRadius);
-        }
-    }
-
-    @Override
-    public void setStateWithAnimation(LauncherState toState, StateAnimationConfig config,
-            PendingAnimation animation) {
-        if (mSurface == null || config.onlyPlayAtomicComponent()) {
-            return;
-        }
-
-        int toBackgroundBlurRadius = toState.getBackgroundBlurRadius(mLauncher);
-        if (mBackgroundBlurRadius != toBackgroundBlurRadius) {
-            animation.setInt(this, BACKGROUND_BLUR, toBackgroundBlurRadius, LINEAR);
-        }
-    }
-
-    private void setBackgroundBlurRadius(int blurRadius) {
-        // TODO: Do nothing if the shadows are not enabled
-        // Always update the background blur as it will be reapplied when a surface is next
-        // available
-        mBackgroundBlurRadius = blurRadius;
-        if (mSurface == null || !mSurface.isValid()) {
-            return;
-        }
-        new TransactionCompat()
-                .setBackgroundBlurRadius(mSurface, blurRadius)
-                .apply();
-    }
-}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index 03454f7..123c988 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -17,7 +17,6 @@
 package com.android.launcher3.uioverrides;
 
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
 import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
@@ -34,11 +33,10 @@
 
 import android.util.FloatProperty;
 import android.view.View;
-import android.view.animation.Interpolator;
 
 import androidx.annotation.NonNull;
 
-import com.android.launcher3.Launcher;
+import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherState.ScaleAndTranslation;
 import com.android.launcher3.LauncherStateManager.StateHandler;
@@ -55,13 +53,11 @@
 public abstract class BaseRecentsViewStateController<T extends View>
         implements StateHandler {
     protected final T mRecentsView;
-    protected final Launcher mLauncher;
-    protected final View mActionsView;
+    protected final BaseQuickstepLauncher mLauncher;
 
-    public BaseRecentsViewStateController(@NonNull Launcher launcher) {
+    public BaseRecentsViewStateController(@NonNull BaseQuickstepLauncher launcher) {
         mLauncher = launcher;
         mRecentsView = launcher.getOverviewPanel();
-        mActionsView = launcher.getActionsView();
     }
 
     @Override
@@ -69,19 +65,12 @@
         ScaleAndTranslation scaleAndTranslation = state
                 .getOverviewScaleAndTranslation(mLauncher);
         SCALE_PROPERTY.set(mRecentsView, scaleAndTranslation.scale);
-        float translationX = scaleAndTranslation.translationX;
-        if (mRecentsView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
-            translationX = -translationX;
-        }
-        mRecentsView.setTranslationX(translationX);
+        mRecentsView.setTranslationX(scaleAndTranslation.translationX);
         mRecentsView.setTranslationY(scaleAndTranslation.translationY);
+
         getContentAlphaProperty().set(mRecentsView, state.overviewUi ? 1f : 0);
         OverviewScrim scrim = mLauncher.getDragLayer().getOverviewScrim();
         SCRIM_PROGRESS.set(scrim, state.getOverviewScrimAlpha(mLauncher));
-        if (mActionsView != null) {
-            mActionsView.setTranslationX(translationX);
-            mActionsView.setAlpha(state.overviewUi ? 1f : 0);
-        }
     }
 
     @Override
@@ -107,29 +96,18 @@
     void setStateWithAnimationInternal(@NonNull final LauncherState toState,
             @NonNull StateAnimationConfig config, @NonNull PendingAnimation setter) {
         ScaleAndTranslation scaleAndTranslation = toState.getOverviewScaleAndTranslation(mLauncher);
-        Interpolator scaleInterpolator = config.getInterpolator(ANIM_OVERVIEW_SCALE, LINEAR);
-        setter.setFloat(mRecentsView, SCALE_PROPERTY, scaleAndTranslation.scale, scaleInterpolator);
-        Interpolator translateXInterpolator = config.getInterpolator(
-                ANIM_OVERVIEW_TRANSLATE_X, LINEAR);
-        Interpolator translateYInterpolator = config.getInterpolator(
-                ANIM_OVERVIEW_TRANSLATE_Y, LINEAR);
-        float translationX = scaleAndTranslation.translationX;
-        if (mRecentsView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
-            translationX = -translationX;
-        }
-        setter.setFloat(mRecentsView, VIEW_TRANSLATE_X, translationX, translateXInterpolator);
+        setter.setFloat(mRecentsView, SCALE_PROPERTY, scaleAndTranslation.scale,
+                config.getInterpolator(ANIM_OVERVIEW_SCALE, LINEAR));
+        setter.setFloat(mRecentsView, VIEW_TRANSLATE_X, scaleAndTranslation.translationX,
+                config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_X, LINEAR));
         setter.setFloat(mRecentsView, VIEW_TRANSLATE_Y, scaleAndTranslation.translationY,
-                translateYInterpolator);
+                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));
-        if (mActionsView != null) {
-            setter.setFloat(mActionsView, VIEW_TRANSLATE_X, translationX, translateXInterpolator);
-            setter.setFloat(mActionsView, VIEW_ALPHA, toState.overviewUi ? 1 : 0,
-                    config.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT));
-        }
     }
 
     /**
diff --git a/quickstep/src/com/android/launcher3/uioverrides/DeviceFlag.java b/quickstep/src/com/android/launcher3/uioverrides/DeviceFlag.java
index 5836ebd..010694b 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/DeviceFlag.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/DeviceFlag.java
@@ -54,6 +54,9 @@
 
     @Override
     public void addChangeListener(Context context, Runnable r) {
+        if (mListeners == null) {
+            initialize(context);
+        }
         mListeners.add(r);
     }
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java b/quickstep/src/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java
index 548223a..c7cce0b 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java
@@ -32,8 +32,11 @@
 
     /** Handle a received surface view request. */
     public static void render(Context context, Bundle bundle) {
-        final String gridName = bundle.getString("name");
+        String gridName = bundle.getString("name");
         bundle.remove("name");
+        if (gridName == null) {
+            gridName = InvariantDeviceProfile.getCurrentGridName(context);
+        }
         final InvariantDeviceProfile idp = new InvariantDeviceProfile(context, gridName);
 
         MAIN_EXECUTOR.execute(() -> {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
index 971d917..93e02a1 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -22,7 +22,6 @@
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.R;
 import com.android.launcher3.allapps.AllAppsContainerView;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.quickstep.SysUINavigationMode;
@@ -88,8 +87,8 @@
     }
 
     @Override
-    public int getBackgroundBlurRadius(Context context) {
-        return context.getResources().getInteger(R.integer.allapps_background_blur_radius);
+    public float getDepth(Context context) {
+        return 1f;
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index be0bdd8..94ef15a 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -32,8 +32,8 @@
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.touch.PagedOrientationHandler;
-import com.android.launcher3.uioverrides.BackgroundBlurController;
 import com.android.quickstep.util.ActivityInitListener;
 import com.android.quickstep.util.ShelfPeekAnim;
 import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -81,7 +81,8 @@
     @Nullable
     T getCreatedActivity();
 
-    default @Nullable BackgroundBlurController getBackgroundBlurController() {
+    @Nullable
+    default DepthController getDepthController() {
         return null;
     }
 
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index 501c6f0..631df4c 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -64,6 +64,8 @@
     private static final String TAG = "GestureState";
 
     private static final ArrayList<String> STATE_NAMES = new ArrayList<>();
+    public static final GestureState DEFAULT_STATE = new GestureState();
+
     private static int FLAG_COUNT = 0;
     private static int getFlagForIndex(String name) {
         if (DEBUG_STATES) {
@@ -103,6 +105,10 @@
     public static final int STATE_RECENTS_ANIMATION_ENDED =
             getFlagForIndex("STATE_RECENTS_ANIMATION_ENDED");
 
+    // Called when RecentsView stops scrolling and settles on a TaskView.
+    public static final int STATE_RECENTS_SCROLLING_FINISHED =
+            getFlagForIndex("STATE_RECENTS_SCROLLING_FINISHED");
+
 
     // Needed to interact with the current activity
     private final Intent mHomeIntent;
diff --git a/quickstep/src/com/android/quickstep/InputConsumer.java b/quickstep/src/com/android/quickstep/InputConsumer.java
index 3e84e7d..8efaeb9 100644
--- a/quickstep/src/com/android/quickstep/InputConsumer.java
+++ b/quickstep/src/com/android/quickstep/InputConsumer.java
@@ -60,6 +60,16 @@
     }
 
     /**
+     * Returns true if the lifecycle of this input consumer is detached from the normal gesture
+     * down/up flow. If so, it is the responsibility of the input consumer to call back to
+     * {@link TouchInteractionService#onConsumerInactive(InputConsumer)} after the consumer is
+     * finished.
+     */
+    default boolean isConsumerDetachedFromGesture() {
+        return false;
+    }
+
+    /**
      * Called by the event queue when the consumer is about to be switched to a new consumer.
      * Consumers should update the state accordingly here before the state is passed to the new
      * consumer.
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
index 0f98b32..783978d 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
@@ -19,11 +19,13 @@
 
 import android.graphics.Rect;
 import android.util.ArraySet;
+import android.util.Log;
 
 import androidx.annotation.BinderThread;
 import androidx.annotation.UiThread;
 
 import com.android.launcher3.Utilities;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.Preconditions;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
@@ -89,6 +91,9 @@
             RemoteAnimationTargetCompat[] appTargets,
             RemoteAnimationTargetCompat[] wallpaperTargets,
             Rect homeContentInsets, Rect minimizedHomeBounds) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_START_FROM_RECENTS, "onAnimationStart");
+        }
         RecentsAnimationTargets targets = new RecentsAnimationTargets(appTargets,
                 wallpaperTargets, homeContentInsets, minimizedHomeBounds);
         mController = new RecentsAnimationController(animationController,
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 1299a53..491c611 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -22,6 +22,7 @@
 import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
 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;
@@ -356,6 +357,7 @@
         return (mSystemUiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) == 0
                 && (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) == 0
                 && (mSystemUiStateFlags & SYSUI_STATE_QUICK_SETTINGS_EXPANDED) == 0
+                && (mSystemUiStateFlags & SYSUI_STATE_BUBBLES_EXPANDED) == 0
                 && ((mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0
                         || (mSystemUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0);
     }
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 6902e37..b04a1ae 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -26,6 +26,7 @@
 
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 
@@ -52,6 +53,9 @@
     @UiThread
     public RecentsAnimationCallbacks startRecentsAnimation(GestureState gestureState,
             Intent intent, RecentsAnimationCallbacks.RecentsAnimationListener listener) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_START_FROM_RECENTS, "startRecentsAnimation");
+        }
         // Notify if recents animation is still running
         if (mController != null) {
             String msg = "New recents animation started before old animation completed";
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
index 3fe91a3..5c2e992 100644
--- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
@@ -25,6 +25,7 @@
 import com.android.launcher3.R;
 import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialStep;
 import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialType;
+import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureResult;
 
 import java.util.Optional;
 
@@ -79,21 +80,33 @@
         mHandCoachingAnimation.stop();
     }
 
-    void onGestureDetected() {
-        hideHandCoachingAnimation();
-
-        if (mTutorialStep == TutorialStep.CONFIRM) {
+    void onGestureAttempted(BackGestureResult result) {
+        if (mTutorialStep == TutorialStep.CONFIRM
+                && (result == BackGestureResult.BACK_COMPLETED_FROM_LEFT
+                    || result == BackGestureResult.BACK_COMPLETED_FROM_RIGHT)) {
             mFragment.closeTutorial();
             return;
         }
 
-        if (mTutorialTypeInfo.get().getTutorialType() == TutorialType.RIGHT_EDGE_BACK_NAVIGATION) {
-            mFragment.changeController(TutorialStep.ENGAGED,
-                    TutorialType.LEFT_EDGE_BACK_NAVIGATION);
+        if (!mTutorialTypeInfo.isPresent()) {
             return;
         }
 
-        mFragment.changeController(TutorialStep.CONFIRM);
+        switch (mTutorialTypeInfo.get().getTutorialType()) {
+            case RIGHT_EDGE_BACK_NAVIGATION:
+                if (result == BackGestureResult.BACK_COMPLETED_FROM_RIGHT) {
+                    hideHandCoachingAnimation();
+                    mFragment.changeController(
+                            TutorialStep.ENGAGED, TutorialType.LEFT_EDGE_BACK_NAVIGATION);
+                }
+                break;
+            case LEFT_EDGE_BACK_NAVIGATION:
+                if (result == BackGestureResult.BACK_COMPLETED_FROM_LEFT) {
+                    hideHandCoachingAnimation();
+                    mFragment.changeController(TutorialStep.CONFIRM);
+                }
+                break;
+        }
     }
 
     abstract Optional<Integer> getTitleStringId();
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java
index 54408ce..593b695 100644
--- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java
@@ -17,21 +17,26 @@
 
 import android.content.ActivityNotFoundException;
 import android.content.Intent;
+import android.graphics.Insets;
 import android.os.Bundle;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.WindowInsets;
 
+import androidx.annotation.NonNull;
 import androidx.fragment.app.Fragment;
 
 import com.android.launcher3.R;
+import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureAttemptCallback;
+import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureResult;
 
 import java.net.URISyntaxException;
 import java.util.Optional;
 
 /** Shows the Back gesture interactive tutorial. */
-public class BackGestureTutorialFragment extends Fragment {
+public class BackGestureTutorialFragment extends Fragment implements BackGestureAttemptCallback {
 
     private static final String LOG_TAG = "TutorialFragment";
     private static final String KEY_TUTORIAL_STEP = "tutorialStep";
@@ -47,6 +52,7 @@
     private Optional<BackGestureTutorialController> mTutorialController = Optional.empty();
     private View mRootView;
     private BackGestureTutorialHandAnimation mHandCoachingAnimation;
+    private EdgeBackGestureHandler mEdgeBackGestureHandler;
 
     public static BackGestureTutorialFragment newInstance(
             TutorialStep tutorialStep, TutorialType tutorialType) {
@@ -64,17 +70,25 @@
         Bundle args = savedInstanceState != null ? savedInstanceState : getArguments();
         mTutorialStep = (TutorialStep) args.getSerializable(KEY_TUTORIAL_STEP);
         mTutorialType = (TutorialType) args.getSerializable(KEY_TUTORIAL_TYPE);
+        mEdgeBackGestureHandler = new EdgeBackGestureHandler(getContext());
+        mEdgeBackGestureHandler.registerBackGestureAttemptCallback(this);
     }
 
     @Override
     public View onCreateView(
-            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+            @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
         super.onCreateView(inflater, container, savedInstanceState);
 
         mRootView = inflater.inflate(R.layout.back_gesture_tutorial_fragment,
                 container, /* attachToRoot= */ false);
         mRootView.findViewById(R.id.back_gesture_tutorial_fragment_close_button)
                 .setOnClickListener(this::onCloseButtonClicked);
+        mRootView.setOnApplyWindowInsetsListener((view, insets) -> {
+            Insets systemInsets = insets.getInsets(WindowInsets.Type.systemBars());
+            mEdgeBackGestureHandler.setInsets(systemInsets.left, systemInsets.right);
+            return insets;
+        });
+        mRootView.setOnTouchListener(mEdgeBackGestureHandler);
         mHandCoachingAnimation = new BackGestureTutorialHandAnimation(getContext(), mRootView);
 
         return mRootView;
@@ -92,6 +106,14 @@
         mHandCoachingAnimation.stop();
     }
 
+    void onAttachedToWindow() {
+        mEdgeBackGestureHandler.setIsEnabled(true);
+    }
+
+    void onDetachedFromWindow() {
+        mEdgeBackGestureHandler.setIsEnabled(false);
+    }
+
     @Override
     public void onSaveInstanceState(Bundle savedInstanceState) {
         savedInstanceState.putSerializable(KEY_TUTORIAL_STEP, mTutorialStep);
@@ -125,10 +147,9 @@
         this.mTutorialType = tutorialType;
     }
 
-    void onBackPressed() {
-        if (mTutorialController.isPresent()) {
-            mTutorialController.get().onGestureDetected();
-        }
+    @Override
+    public void onBackGestureAttempted(BackGestureResult result) {
+        mTutorialController.ifPresent(controller -> controller.onGestureAttempted(result));
     }
 
     void closeTutorial() {
diff --git a/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java b/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java
new file mode 100644
index 0000000..04cd2f4
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java
@@ -0,0 +1,274 @@
+/*
+ * 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.interaction;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManager.DisplayListener;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemProperties;
+import android.view.Display;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnTouchListener;
+import android.view.ViewConfiguration;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+
+import com.android.launcher3.ResourceUtils;
+
+/**
+ * Utility class to handle edge swipes for back gestures.
+ *
+ * Forked from platform/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java.
+ */
+public class EdgeBackGestureHandler implements DisplayListener, OnTouchListener {
+
+    private static final String TAG = "EdgeBackGestureHandler";
+    private static final int MAX_LONG_PRESS_TIMEOUT = SystemProperties.getInt(
+            "gestures.back_timeout", 250);
+
+    private final Context mContext;
+
+    private final Point mDisplaySize = new Point();
+    private final int mDisplayId;
+
+    // The edge width where touch down is allowed
+    private int mEdgeWidth;
+    // The bottom gesture area height
+    private int mBottomGestureHeight;
+    // The slop to distinguish between horizontal and vertical motion
+    private final float mTouchSlop;
+    // Duration after which we consider the event as longpress.
+    private final int mLongPressTimeout;
+
+    private final PointF mDownPoint = new PointF();
+    private boolean mThresholdCrossed = false;
+    private boolean mAllowGesture = false;
+    private boolean mIsEnabled;
+    private int mLeftInset;
+    private int mRightInset;
+
+    private EdgeBackGesturePanel mEdgeBackPanel;
+    private BackGestureAttemptCallback mGestureCallback;
+
+    private final EdgeBackGesturePanel.BackCallback mBackCallback =
+            new EdgeBackGesturePanel.BackCallback() {
+                @Override
+                public void triggerBack() {
+                    if (mGestureCallback != null) {
+                        mGestureCallback.onBackGestureAttempted(mEdgeBackPanel.getIsLeftPanel()
+                                ? BackGestureResult.BACK_COMPLETED_FROM_LEFT
+                                : BackGestureResult.BACK_COMPLETED_FROM_RIGHT);
+                    }
+                }
+
+                @Override
+                public void cancelBack() {
+                    if (mGestureCallback != null) {
+                        mGestureCallback.onBackGestureAttempted(mEdgeBackPanel.getIsLeftPanel()
+                                ? BackGestureResult.BACK_CANCELLED_FROM_LEFT
+                                : BackGestureResult.BACK_CANCELLED_FROM_RIGHT);
+                    }
+                }
+            };
+
+    EdgeBackGestureHandler(Context context) {
+        final Resources res = context.getResources();
+        mContext = context;
+        mDisplayId = context.getDisplay() == null
+                ? Display.DEFAULT_DISPLAY : context.getDisplay().getDisplayId();
+
+        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+        mLongPressTimeout = Math.min(MAX_LONG_PRESS_TIMEOUT,
+                ViewConfiguration.getLongPressTimeout());
+
+        mBottomGestureHeight =
+            ResourceUtils.getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, res);
+        mEdgeWidth = ResourceUtils.getNavbarSize("config_backGestureInset", res);
+    }
+
+    void setIsEnabled(boolean isEnabled) {
+        if (isEnabled == mIsEnabled) {
+            return;
+        }
+        mIsEnabled = isEnabled;
+
+        if (mEdgeBackPanel != null) {
+            mEdgeBackPanel.onDestroy();
+            mEdgeBackPanel = null;
+        }
+
+        if (!mIsEnabled) {
+            mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this);
+        } else {
+            updateDisplaySize();
+            mContext.getSystemService(DisplayManager.class).registerDisplayListener(this,
+                    new Handler(Looper.getMainLooper()));
+
+            // Add a nav bar panel window.
+            mEdgeBackPanel = new EdgeBackGesturePanel(mContext);
+            mEdgeBackPanel.setBackCallback(mBackCallback);
+            mEdgeBackPanel.setLayoutParams(createLayoutParams());
+            updateDisplaySize();
+        }
+    }
+
+    void registerBackGestureAttemptCallback(BackGestureAttemptCallback callback) {
+        mGestureCallback = callback;
+    }
+
+    private WindowManager.LayoutParams createLayoutParams() {
+        Resources resources = mContext.getResources();
+        WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
+                ResourceUtils.getNavbarSize("navigation_edge_panel_width", resources),
+                ResourceUtils.getNavbarSize("navigation_edge_panel_height", resources),
+                LayoutParams.TYPE_APPLICATION_PANEL,
+                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
+                        | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
+                PixelFormat.TRANSLUCENT);
+        layoutParams.setTitle(TAG + mDisplayId);
+        layoutParams.windowAnimations = 0;
+        layoutParams.setFitInsetsTypes(0 /* types */);
+        return layoutParams;
+    }
+
+    @Override
+    public boolean onTouch(View view, MotionEvent motionEvent) {
+        if (mIsEnabled) {
+            onMotionEvent(motionEvent);
+            return true;
+        }
+        return false;
+    }
+
+    private boolean isWithinTouchRegion(int x, int y) {
+        // Disallow if too far from the edge
+        if (x > mEdgeWidth + mLeftInset && x < (mDisplaySize.x - mEdgeWidth - mRightInset)) {
+            return false;
+        }
+
+        // Disallow if we are in the bottom gesture area
+        if (y >= (mDisplaySize.y - mBottomGestureHeight)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    private void cancelGesture(MotionEvent ev) {
+        // Send action cancel to reset all the touch events
+        mAllowGesture = false;
+        MotionEvent cancelEv = MotionEvent.obtain(ev);
+        cancelEv.setAction(MotionEvent.ACTION_CANCEL);
+        mEdgeBackPanel.onMotionEvent(cancelEv);
+        cancelEv.recycle();
+    }
+
+    private void onMotionEvent(MotionEvent ev) {
+        int action = ev.getActionMasked();
+        if (action == MotionEvent.ACTION_DOWN) {
+            boolean isOnLeftEdge = ev.getX() <= mEdgeWidth + mLeftInset;
+            mAllowGesture = isWithinTouchRegion((int) ev.getX(), (int) ev.getY());
+            if (mAllowGesture) {
+                mEdgeBackPanel.setIsLeftPanel(isOnLeftEdge);
+                mEdgeBackPanel.onMotionEvent(ev);
+
+                mDownPoint.set(ev.getX(), ev.getY());
+                mThresholdCrossed = false;
+            }
+        } else if (mAllowGesture) {
+            if (!mThresholdCrossed) {
+                if (action == MotionEvent.ACTION_POINTER_DOWN) {
+                    // We do not support multi touch for back gesture
+                    cancelGesture(ev);
+                    return;
+                } else if (action == MotionEvent.ACTION_MOVE) {
+                    if ((ev.getEventTime() - ev.getDownTime()) > mLongPressTimeout) {
+                        cancelGesture(ev);
+                        return;
+                    }
+                    float dx = Math.abs(ev.getX() - mDownPoint.x);
+                    float dy = Math.abs(ev.getY() - mDownPoint.y);
+                    if (dy > dx && dy > mTouchSlop) {
+                        cancelGesture(ev);
+                        return;
+
+                    } else if (dx > dy && dx > mTouchSlop) {
+                        mThresholdCrossed = true;
+                    }
+                }
+
+            }
+
+            // forward touch
+            mEdgeBackPanel.onMotionEvent(ev);
+        }
+
+        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+            if (!mAllowGesture && mGestureCallback != null) {
+                mGestureCallback.onBackGestureAttempted(BackGestureResult.BACK_NOT_STARTED);
+            }
+        }
+    }
+
+    @Override
+    public void onDisplayAdded(int displayId) { }
+
+    @Override
+    public void onDisplayRemoved(int displayId) { }
+
+    @Override
+    public void onDisplayChanged(int displayId) {
+        if (displayId == mDisplayId) {
+            updateDisplaySize();
+        }
+    }
+
+    private void updateDisplaySize() {
+        mContext.getDisplay().getRealSize(mDisplaySize);
+        if (mEdgeBackPanel != null) {
+            mEdgeBackPanel.setDisplaySize(mDisplaySize);
+        }
+    }
+
+    void setInsets(int leftInset, int rightInset) {
+        mLeftInset = leftInset;
+        mRightInset = rightInset;
+    }
+
+    enum BackGestureResult {
+        UNKNOWN,
+        BACK_COMPLETED_FROM_LEFT,
+        BACK_COMPLETED_FROM_RIGHT,
+        BACK_CANCELLED_FROM_LEFT,
+        BACK_CANCELLED_FROM_RIGHT,
+        BACK_NOT_STARTED,
+    }
+
+    /** Callback to let the UI react to attempted back gestures. */
+    interface BackGestureAttemptCallback {
+        /** Called whenever any touch is completed. */
+        void onBackGestureAttempted(BackGestureResult result);
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java b/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java
new file mode 100644
index 0000000..34eeafc
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java
@@ -0,0 +1,701 @@
+/*
+ * 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.interaction;
+
+import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Point;
+import android.os.SystemClock;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+
+import androidx.core.math.MathUtils;
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.FloatPropertyCompat;
+import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
+
+import com.android.launcher3.R;
+import com.android.launcher3.ResourceUtils;
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.util.VibratorWrapper;
+
+/** Forked from platform/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarEdgePanel.java. */
+public class EdgeBackGesturePanel extends View {
+
+    private static final String LOG_TAG = "EdgeBackGesturePanel";
+
+    private static final long DISAPPEAR_FADE_ANIMATION_DURATION_MS = 80;
+    private static final long DISAPPEAR_ARROW_ANIMATION_DURATION_MS = 100;
+
+    /**
+     * The time required since the first vibration effect to automatically trigger a click
+     */
+    private static final int GESTURE_DURATION_FOR_CLICK_MS = 400;
+
+    /**
+     * The basic translation in dp where the arrow resides
+     */
+    private static final int BASE_TRANSLATION_DP = 32;
+
+    /**
+     * The length of the arrow leg measured from the center to the end
+     */
+    private static final int ARROW_LENGTH_DP = 18;
+
+    /**
+     * The angle measured from the xAxis, where the leg is when the arrow rests
+     */
+    private static final int ARROW_ANGLE_WHEN_EXTENDED_DEGREES = 56;
+
+    /**
+     * The angle that is added per 1000 px speed to the angle of the leg
+     */
+    private static final int ARROW_ANGLE_ADDED_PER_1000_SPEED = 4;
+
+    /**
+     * The maximum angle offset allowed due to speed
+     */
+    private static final int ARROW_MAX_ANGLE_SPEED_OFFSET_DEGREES = 4;
+
+    /**
+     * The thickness of the arrow. Adjusted to match the home handle (approximately)
+     */
+    private static final float ARROW_THICKNESS_DP = 2.5f;
+
+    /**
+     * The amount of rubber banding we do for the vertical translation
+     */
+    private static final int RUBBER_BAND_AMOUNT = 15;
+
+    /**
+     * The interpolator used to rubberband
+     */
+    private static final Interpolator RUBBER_BAND_INTERPOLATOR =
+            new PathInterpolator(1.0f / 5.0f, 1.0f, 1.0f, 1.0f);
+
+    /**
+     * The amount of rubber banding we do for the translation before base translation
+     */
+    private static final int RUBBER_BAND_AMOUNT_APPEAR = 4;
+
+    /**
+     * The interpolator used to rubberband the appearing of the arrow.
+     */
+    private static final Interpolator RUBBER_BAND_INTERPOLATOR_APPEAR =
+            new PathInterpolator(1.0f / RUBBER_BAND_AMOUNT_APPEAR, 1.0f, 1.0f, 1.0f);
+
+    private final WindowManager mWindowManager;
+    private BackCallback mBackCallback;
+
+    /**
+     * The paint the arrow is drawn with
+     */
+    private final Paint mPaint = new Paint();
+
+    private final float mDensity;
+    private final float mBaseTranslation;
+    private final float mArrowLength;
+    private final float mArrowThickness;
+
+    /**
+     * The minimum delta needed in movement for the arrow to change direction / stop triggering back
+     */
+    private final float mMinDeltaForSwitch;
+    // The closest to y = 0 that the arrow will be displayed.
+    private int mMinArrowPosition;
+    // The amount the arrow is shifted to avoid the finger.
+    private int mFingerOffset;
+
+    private final float mSwipeThreshold;
+    private final Path mArrowPath = new Path();
+    private final Point mDisplaySize = new Point();
+
+    private final SpringAnimation mAngleAnimation;
+    private final SpringAnimation mTranslationAnimation;
+    private final SpringAnimation mVerticalTranslationAnimation;
+    private final SpringForce mAngleAppearForce;
+    private final SpringForce mAngleDisappearForce;
+    private final ValueAnimator mArrowDisappearAnimation;
+    private final SpringForce mRegularTranslationSpring;
+    private final SpringForce mTriggerBackSpring;
+
+    private VelocityTracker mVelocityTracker;
+    private int mArrowPaddingEnd;
+    private WindowManager.LayoutParams mLayoutParams;
+
+    /**
+     * True if the panel is currently on the left of the screen
+     */
+    private boolean mIsLeftPanel;
+
+    private float mStartX;
+    private float mStartY;
+    private float mCurrentAngle;
+    /**
+     * The current translation of the arrow
+     */
+    private float mCurrentTranslation;
+    /**
+     * Where the arrow will be in the resting position.
+     */
+    private float mDesiredTranslation;
+
+    private boolean mDragSlopPassed;
+    private boolean mArrowsPointLeft;
+    private float mMaxTranslation;
+    private boolean mTriggerBack;
+    private float mPreviousTouchTranslation;
+    private float mTotalTouchDelta;
+    private float mVerticalTranslation;
+    private float mDesiredVerticalTranslation;
+    private float mDesiredAngle;
+    private float mAngleOffset;
+    private float mDisappearAmount;
+    private long mVibrationTime;
+    private int mScreenSize;
+
+    private final DynamicAnimation.OnAnimationEndListener mSetGoneEndListener =
+            new DynamicAnimation.OnAnimationEndListener() {
+                @Override
+                public void onAnimationEnd(
+                        DynamicAnimation animation, boolean canceled, float value, float velocity) {
+                    animation.removeEndListener(this);
+                    if (!canceled) {
+                        setVisibility(GONE);
+                    }
+                }
+            };
+
+    private static final FloatPropertyCompat<EdgeBackGesturePanel> CURRENT_ANGLE =
+            new FloatPropertyCompat<EdgeBackGesturePanel>("currentAngle") {
+                @Override
+                public void setValue(EdgeBackGesturePanel object, float value) {
+                    object.setCurrentAngle(value);
+                }
+
+                @Override
+                public float getValue(EdgeBackGesturePanel object) {
+                    return object.getCurrentAngle();
+                }
+            };
+
+    private static final FloatPropertyCompat<EdgeBackGesturePanel> CURRENT_TRANSLATION =
+            new FloatPropertyCompat<EdgeBackGesturePanel>("currentTranslation") {
+                @Override
+                public void setValue(EdgeBackGesturePanel object, float value) {
+                    object.setCurrentTranslation(value);
+                }
+
+                @Override
+                public float getValue(EdgeBackGesturePanel object) {
+                    return object.getCurrentTranslation();
+                }
+            };
+
+    private static final FloatPropertyCompat<EdgeBackGesturePanel> CURRENT_VERTICAL_TRANSLATION =
+            new FloatPropertyCompat<EdgeBackGesturePanel>("verticalTranslation") {
+
+                @Override
+                public void setValue(EdgeBackGesturePanel object, float value) {
+                    object.setVerticalTranslation(value);
+                }
+
+                @Override
+                public float getValue(EdgeBackGesturePanel object) {
+                    return object.getVerticalTranslation();
+                }
+            };
+
+    public EdgeBackGesturePanel(Context context) {
+        super(context);
+
+        mWindowManager = context.getSystemService(WindowManager.class);
+
+        mDensity = context.getResources().getDisplayMetrics().density;
+
+        mBaseTranslation = dp(BASE_TRANSLATION_DP);
+        mArrowLength = dp(ARROW_LENGTH_DP);
+        mArrowThickness = dp(ARROW_THICKNESS_DP);
+        mMinDeltaForSwitch = dp(32);
+
+        mPaint.setStrokeWidth(mArrowThickness);
+        mPaint.setStrokeCap(Paint.Cap.ROUND);
+        mPaint.setAntiAlias(true);
+        mPaint.setStyle(Paint.Style.STROKE);
+        mPaint.setStrokeJoin(Paint.Join.ROUND);
+
+        mArrowDisappearAnimation = ValueAnimator.ofFloat(0.0f, 1.0f);
+        mArrowDisappearAnimation.setDuration(DISAPPEAR_ARROW_ANIMATION_DURATION_MS);
+        mArrowDisappearAnimation.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+        mArrowDisappearAnimation.addUpdateListener(animation -> {
+            mDisappearAmount = (float) animation.getAnimatedValue();
+            invalidate();
+        });
+
+        mAngleAnimation =
+                new SpringAnimation(this, CURRENT_ANGLE);
+        mAngleAppearForce = new SpringForce()
+                .setStiffness(500)
+                .setDampingRatio(0.5f);
+        mAngleDisappearForce = new SpringForce()
+                .setStiffness(SpringForce.STIFFNESS_MEDIUM)
+                .setDampingRatio(SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY)
+                .setFinalPosition(90);
+        mAngleAnimation.setSpring(mAngleAppearForce).setMaxValue(90);
+
+        mTranslationAnimation =
+                new SpringAnimation(this, CURRENT_TRANSLATION);
+        mRegularTranslationSpring = new SpringForce()
+                .setStiffness(SpringForce.STIFFNESS_MEDIUM)
+                .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY);
+        mTriggerBackSpring = new SpringForce()
+                .setStiffness(450)
+                .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY);
+        mTranslationAnimation.setSpring(mRegularTranslationSpring);
+        mVerticalTranslationAnimation =
+                new SpringAnimation(this, CURRENT_VERTICAL_TRANSLATION);
+        mVerticalTranslationAnimation.setSpring(
+                new SpringForce()
+                        .setStiffness(SpringForce.STIFFNESS_MEDIUM)
+                        .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
+        mPaint.setColor(context.getColor(R.color.back_arrow_color_dark));
+        loadDimens();
+        updateArrowDirection();
+
+        mSwipeThreshold = ResourceUtils.getDimenByName(
+            "navigation_edge_action_drag_threshold", context.getResources(), 16 /* defaultValue */);
+        setVisibility(GONE);
+    }
+
+    void onDestroy() {
+        mWindowManager.removeView(this);
+    }
+
+    @Override
+    public boolean hasOverlappingRendering() {
+        return false;
+    }
+
+    @SuppressLint("RtlHardcoded")
+    void setIsLeftPanel(boolean isLeftPanel) {
+        mIsLeftPanel = isLeftPanel;
+        mLayoutParams.gravity = mIsLeftPanel
+                ? (Gravity.LEFT | Gravity.TOP)
+                : (Gravity.RIGHT | Gravity.TOP);
+    }
+
+    boolean getIsLeftPanel() {
+        return mIsLeftPanel;
+    }
+
+    void setDisplaySize(Point displaySize) {
+        mDisplaySize.set(displaySize.x, displaySize.y);
+        mScreenSize = Math.min(mDisplaySize.x, mDisplaySize.y);
+    }
+
+    void setBackCallback(BackCallback callback) {
+        mBackCallback = callback;
+    }
+
+    void setLayoutParams(WindowManager.LayoutParams layoutParams) {
+        mLayoutParams = layoutParams;
+        mWindowManager.addView(this, mLayoutParams);
+    }
+
+    private float getCurrentAngle() {
+        return mCurrentAngle;
+    }
+
+    private float getCurrentTranslation() {
+        return mCurrentTranslation;
+    }
+
+    void onMotionEvent(MotionEvent event) {
+        if (mVelocityTracker == null) {
+            mVelocityTracker = VelocityTracker.obtain();
+        }
+        mVelocityTracker.addMovement(event);
+        switch (event.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN:
+                mDragSlopPassed = false;
+                resetOnDown();
+                mStartX = event.getX();
+                mStartY = event.getY();
+                setVisibility(VISIBLE);
+                updatePosition(event.getY());
+                mWindowManager.updateViewLayout(this, mLayoutParams);
+                break;
+            case MotionEvent.ACTION_MOVE:
+                handleMoveEvent(event);
+                break;
+            case MotionEvent.ACTION_UP:
+                if (mTriggerBack) {
+                    triggerBack();
+                } else {
+                    cancelBack();
+                }
+                mVelocityTracker.recycle();
+                mVelocityTracker = null;
+                break;
+            case MotionEvent.ACTION_CANCEL:
+                cancelBack();
+                mVelocityTracker.recycle();
+                mVelocityTracker = null;
+                break;
+        }
+    }
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        updateArrowDirection();
+        loadDimens();
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        float pointerPosition = mCurrentTranslation - mArrowThickness / 2.0f;
+        canvas.save();
+        canvas.translate(
+                mIsLeftPanel ? pointerPosition : getWidth() - pointerPosition,
+                (getHeight() * 0.5f) + mVerticalTranslation);
+
+        // Let's calculate the position of the end based on the angle
+        float x = (polarToCartX(mCurrentAngle) * mArrowLength);
+        float y = (polarToCartY(mCurrentAngle) * mArrowLength);
+        Path arrowPath = calculatePath(x, y);
+
+        canvas.drawPath(arrowPath, mPaint);
+        canvas.restore();
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        mMaxTranslation = getWidth() - mArrowPaddingEnd;
+    }
+
+    private void loadDimens() {
+        Resources res = getResources();
+        mArrowPaddingEnd = ResourceUtils.getDimenByName("navigation_edge_panel_padding", res, 8);
+        mMinArrowPosition = ResourceUtils.getDimenByName("navigation_edge_arrow_min_y", res, 64);
+        mFingerOffset = ResourceUtils.getDimenByName("navigation_edge_finger_offset", res, 48);
+    }
+
+    private void updateArrowDirection() {
+        // Both panels arrow point the same way
+        mArrowsPointLeft = getLayoutDirection() == LAYOUT_DIRECTION_LTR;
+        invalidate();
+    }
+
+    private float getStaticArrowWidth() {
+        return polarToCartX(ARROW_ANGLE_WHEN_EXTENDED_DEGREES) * mArrowLength;
+    }
+
+    private float polarToCartX(float angleInDegrees) {
+        return (float) Math.cos(Math.toRadians(angleInDegrees));
+    }
+
+    private float polarToCartY(float angleInDegrees) {
+        return (float) Math.sin(Math.toRadians(angleInDegrees));
+    }
+
+    private Path calculatePath(float x, float y) {
+        if (!mArrowsPointLeft) {
+            x = -x;
+        }
+        float extent = lerp(1.0f, 0.75f, mDisappearAmount);
+        x = x * extent;
+        y = y * extent;
+        mArrowPath.reset();
+        mArrowPath.moveTo(x, y);
+        mArrowPath.lineTo(0, 0);
+        mArrowPath.lineTo(x, -y);
+        return mArrowPath;
+    }
+
+    private static float lerp(float start, float stop, float amount) {
+        return start + (stop - start) * amount;
+    }
+
+    private void triggerBack() {
+        if (mBackCallback != null) {
+            mBackCallback.triggerBack();
+        }
+
+        if (mVelocityTracker == null) {
+            mVelocityTracker = VelocityTracker.obtain();
+        }
+        mVelocityTracker.computeCurrentVelocity(1000);
+        // Only do the extra translation if we're not already flinging
+        boolean isSlow = Math.abs(mVelocityTracker.getXVelocity()) < 500;
+        if (isSlow
+                || SystemClock.uptimeMillis() - mVibrationTime >= GESTURE_DURATION_FOR_CLICK_MS) {
+            VibratorWrapper.INSTANCE.get(getContext()).vibrate(VibratorWrapper.EFFECT_CLICK);
+        }
+
+        // Let's also snap the angle a bit
+        if (mAngleOffset > -4) {
+            mAngleOffset = Math.max(-8, mAngleOffset - 8);
+            updateAngle(true /* animated */);
+        }
+
+        // Finally, after the translation, animate back and disappear the arrow
+        Runnable translationEnd = () -> {
+            // let's snap it back
+            mAngleOffset = Math.max(0, mAngleOffset + 8);
+            updateAngle(true /* animated */);
+
+            mTranslationAnimation.setSpring(mTriggerBackSpring);
+            // Translate the arrow back a bit to make for a nice transition
+            setDesiredTranslation(mDesiredTranslation - dp(32), true /* animated */);
+            animate().alpha(0f).setDuration(DISAPPEAR_FADE_ANIMATION_DURATION_MS)
+                    .withEndAction(() -> setVisibility(GONE));
+            mArrowDisappearAnimation.start();
+        };
+        if (mTranslationAnimation.isRunning()) {
+            mTranslationAnimation.addEndListener(new DynamicAnimation.OnAnimationEndListener() {
+                @Override
+                public void onAnimationEnd(DynamicAnimation animation, boolean canceled,
+                        float value,
+                        float velocity) {
+                    animation.removeEndListener(this);
+                    if (!canceled) {
+                        translationEnd.run();
+                    }
+                }
+            });
+        } else {
+            translationEnd.run();
+        }
+    }
+
+    private void cancelBack() {
+        if (mBackCallback != null) {
+            mBackCallback.cancelBack();
+        }
+
+        if (mTranslationAnimation.isRunning()) {
+            mTranslationAnimation.addEndListener(mSetGoneEndListener);
+        } else {
+            setVisibility(GONE);
+        }
+    }
+
+    private void resetOnDown() {
+        animate().cancel();
+        mAngleAnimation.cancel();
+        mTranslationAnimation.cancel();
+        mVerticalTranslationAnimation.cancel();
+        mArrowDisappearAnimation.cancel();
+        mAngleOffset = 0;
+        mTranslationAnimation.setSpring(mRegularTranslationSpring);
+        // Reset the arrow to the side
+        setTriggerBack(false /* triggerBack */, false /* animated */);
+        setDesiredTranslation(0, false /* animated */);
+        setCurrentTranslation(0);
+        updateAngle(false /* animate */);
+        mPreviousTouchTranslation = 0;
+        mTotalTouchDelta = 0;
+        mVibrationTime = 0;
+        setDesiredVerticalTransition(0, false /* animated */);
+    }
+
+    private void handleMoveEvent(MotionEvent event) {
+        float x = event.getX();
+        float y = event.getY();
+        float touchTranslation = Math.abs(x - mStartX);
+        float yOffset = y - mStartY;
+        float delta = touchTranslation - mPreviousTouchTranslation;
+        if (Math.abs(delta) > 0) {
+            if (Math.signum(delta) == Math.signum(mTotalTouchDelta)) {
+                mTotalTouchDelta += delta;
+            } else {
+                mTotalTouchDelta = delta;
+            }
+        }
+        mPreviousTouchTranslation = touchTranslation;
+
+        // Apply a haptic on drag slop passed
+        if (!mDragSlopPassed && touchTranslation > mSwipeThreshold) {
+            mDragSlopPassed = true;
+            VibratorWrapper.INSTANCE.get(getContext()).vibrate(VibratorWrapper.EFFECT_CLICK);
+            mVibrationTime = SystemClock.uptimeMillis();
+
+            // Let's show the arrow and animate it in!
+            mDisappearAmount = 0.0f;
+            setAlpha(1f);
+            // And animate it go to back by default!
+            setTriggerBack(true /* triggerBack */, true /* animated */);
+        }
+
+        // Let's make sure we only go to the baseextend and apply rubberbanding afterwards
+        if (touchTranslation > mBaseTranslation) {
+            float diff = touchTranslation - mBaseTranslation;
+            float progress = MathUtils.clamp(diff / (mScreenSize - mBaseTranslation), 0, 1);
+            progress = RUBBER_BAND_INTERPOLATOR.getInterpolation(progress)
+                    * (mMaxTranslation - mBaseTranslation);
+            touchTranslation = mBaseTranslation + progress;
+        } else {
+            float diff = mBaseTranslation - touchTranslation;
+            float progress = MathUtils.clamp(diff / mBaseTranslation, 0, 1);
+            progress = RUBBER_BAND_INTERPOLATOR_APPEAR.getInterpolation(progress)
+                    * (mBaseTranslation / RUBBER_BAND_AMOUNT_APPEAR);
+            touchTranslation = mBaseTranslation - progress;
+        }
+        // By default we just assume the current direction is kept
+        boolean triggerBack = mTriggerBack;
+
+        //  First lets see if we had continuous motion in one direction for a while
+        if (Math.abs(mTotalTouchDelta) > mMinDeltaForSwitch) {
+            triggerBack = mTotalTouchDelta > 0;
+        }
+
+        // Then, let's see if our velocity tells us to change direction
+        mVelocityTracker.computeCurrentVelocity(1000);
+        float xVelocity = mVelocityTracker.getXVelocity();
+        float yVelocity = mVelocityTracker.getYVelocity();
+        float velocity = (float) Math.hypot(xVelocity, yVelocity);
+        mAngleOffset = Math.min(velocity / 1000 * ARROW_ANGLE_ADDED_PER_1000_SPEED,
+                ARROW_MAX_ANGLE_SPEED_OFFSET_DEGREES) * Math.signum(xVelocity);
+        if (mIsLeftPanel && mArrowsPointLeft || !mIsLeftPanel && !mArrowsPointLeft) {
+            mAngleOffset *= -1;
+        }
+
+        // Last if the direction in Y is bigger than X * 2 we also abort
+        if (Math.abs(yOffset) > Math.abs(x - mStartX) * 2) {
+            triggerBack = false;
+        }
+        setTriggerBack(triggerBack, true /* animated */);
+
+        if (!mTriggerBack) {
+            touchTranslation = 0;
+        } else if (mIsLeftPanel && mArrowsPointLeft
+                || (!mIsLeftPanel && !mArrowsPointLeft)) {
+            // If we're on the left we should move less, because the arrow is facing the other
+            // direction
+            touchTranslation -= getStaticArrowWidth();
+        }
+        setDesiredTranslation(touchTranslation, true /* animated */);
+        updateAngle(true /* animated */);
+
+        float maxYOffset = getHeight() / 2.0f - mArrowLength;
+        float progress =
+                MathUtils.clamp(Math.abs(yOffset) / (maxYOffset * RUBBER_BAND_AMOUNT), 0, 1);
+        float verticalTranslation = RUBBER_BAND_INTERPOLATOR.getInterpolation(progress)
+                * maxYOffset * Math.signum(yOffset);
+        setDesiredVerticalTransition(verticalTranslation, true /* animated */);
+    }
+
+    private void updatePosition(float touchY) {
+        float position = touchY - mFingerOffset;
+        position = Math.max(position, mMinArrowPosition);
+        position -= mLayoutParams.height / 2.0f;
+        mLayoutParams.y = MathUtils.clamp((int) position, 0, mDisplaySize.y);
+    }
+
+    private void setDesiredVerticalTransition(float verticalTranslation, boolean animated) {
+        if (mDesiredVerticalTranslation != verticalTranslation) {
+            mDesiredVerticalTranslation = verticalTranslation;
+            if (!animated) {
+                setVerticalTranslation(verticalTranslation);
+            } else {
+                mVerticalTranslationAnimation.animateToFinalPosition(verticalTranslation);
+            }
+            invalidate();
+        }
+    }
+
+    private void setVerticalTranslation(float verticalTranslation) {
+        mVerticalTranslation = verticalTranslation;
+        invalidate();
+    }
+
+    private float getVerticalTranslation() {
+        return mVerticalTranslation;
+    }
+
+    private void setDesiredTranslation(float desiredTranslation, boolean animated) {
+        if (mDesiredTranslation != desiredTranslation) {
+            mDesiredTranslation = desiredTranslation;
+            if (!animated) {
+                setCurrentTranslation(desiredTranslation);
+            } else {
+                mTranslationAnimation.animateToFinalPosition(desiredTranslation);
+            }
+        }
+    }
+
+    private void setCurrentTranslation(float currentTranslation) {
+        mCurrentTranslation = currentTranslation;
+        invalidate();
+    }
+
+    private void setTriggerBack(boolean triggerBack, boolean animated) {
+        if (mTriggerBack != triggerBack) {
+            mTriggerBack = triggerBack;
+            mAngleAnimation.cancel();
+            updateAngle(animated);
+            // Whenever the trigger back state changes the existing translation animation should be
+            // cancelled
+            mTranslationAnimation.cancel();
+        }
+    }
+
+    private void updateAngle(boolean animated) {
+        float newAngle = mTriggerBack ? ARROW_ANGLE_WHEN_EXTENDED_DEGREES + mAngleOffset : 90;
+        if (newAngle != mDesiredAngle) {
+            if (!animated) {
+                setCurrentAngle(newAngle);
+            } else {
+                mAngleAnimation.setSpring(mTriggerBack ? mAngleAppearForce : mAngleDisappearForce);
+                mAngleAnimation.animateToFinalPosition(newAngle);
+            }
+            mDesiredAngle = newAngle;
+        }
+    }
+
+    private void setCurrentAngle(float currentAngle) {
+        mCurrentAngle = currentAngle;
+        invalidate();
+    }
+
+    private float dp(float dp) {
+        return mDensity * dp;
+    }
+
+    /** Callback to let the gesture handler react to the detected back gestures. */
+    interface BackCallback {
+        /** Indicates that a Back gesture was recognized. */
+        void triggerBack();
+
+        /** Indicates that the gesture was cancelled. */
+        void cancelBack();
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
index 8081ad7..4815366 100644
--- a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
@@ -30,12 +30,11 @@
 import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialType;
 
 import java.util.List;
-import java.util.Optional;
 
 /** Shows the gesture interactive sandbox in full screen mode. */
 public class GestureSandboxActivity extends FragmentActivity {
 
-    Optional<BackGestureTutorialFragment> mFragment = Optional.empty();
+    private BackGestureTutorialFragment mFragment;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -43,10 +42,10 @@
         requestWindowFeature(Window.FEATURE_NO_TITLE);
         setContentView(R.layout.back_gesture_tutorial_activity);
 
-        mFragment = Optional.of(BackGestureTutorialFragment.newInstance(TutorialStep.ENGAGED,
-                TutorialType.RIGHT_EDGE_BACK_NAVIGATION));
+        mFragment = BackGestureTutorialFragment.newInstance(
+            TutorialStep.ENGAGED, TutorialType.RIGHT_EDGE_BACK_NAVIGATION);
         getSupportFragmentManager().beginTransaction()
-                .add(R.id.back_gesture_tutorial_fragment_container, mFragment.get())
+                .add(R.id.back_gesture_tutorial_fragment_container, mFragment)
                 .commit();
     }
 
@@ -54,6 +53,13 @@
     public void onAttachedToWindow() {
         super.onAttachedToWindow();
         disableSystemGestures();
+        mFragment.onAttachedToWindow();
+    }
+
+    @Override
+    public void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mFragment.onDetachedFromWindow();
     }
 
     @Override
@@ -64,13 +70,6 @@
         }
     }
 
-    @Override
-    public void onBackPressed() {
-        if (mFragment.isPresent()) {
-            mFragment.get().onBackPressed();
-        }
-    }
-
     private void hideSystemUI() {
         getWindow().getDecorView().setSystemUiVisibility(
                 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index 22fe2e1..58bb980 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -18,36 +18,22 @@
 
 import static android.stats.launcher.nano.Launcher.ALLAPPS;
 import static android.stats.launcher.nano.Launcher.BACKGROUND;
-import static android.stats.launcher.nano.Launcher.DISMISS_TASK;
 import static android.stats.launcher.nano.Launcher.HOME;
-import static android.stats.launcher.nano.Launcher.LAUNCH_APP;
-import static android.stats.launcher.nano.Launcher.LAUNCH_TASK;
 import static android.stats.launcher.nano.Launcher.OVERVIEW;
 
-import static com.android.launcher3.logging.UserEventDispatcher.makeTargetsList;
-
 import android.content.Context;
-import android.content.Intent;
-import android.os.UserHandle;
-import android.stats.launcher.nano.Launcher;
-import android.stats.launcher.nano.LauncherExtension;
-import android.stats.launcher.nano.LauncherTarget;
-import android.util.Log;
-import android.view.View;
 
-import androidx.annotation.Nullable;
-
+import com.android.launcher3.FolderInfo;
 import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.logging.StatsLogUtils;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
-import com.android.launcher3.util.ComponentKey;
-import com.android.systemui.shared.system.SysUiStatsLog;
-
-import com.google.protobuf.nano.MessageNano;
+import com.android.launcher3.model.AllAppsList;
+import com.android.launcher3.model.BaseModelUpdateTask;
+import com.android.launcher3.model.BgDataModel;
+import com.android.launcher3.util.IntSparseArrayMap;
 
 import java.util.ArrayList;
 
@@ -62,186 +48,17 @@
 public class StatsLogCompatManager extends StatsLogManager {
 
     private static final int SUPPORTED_TARGET_DEPTH = 2;
-    private static final String TAG = "StatsLogCompatManager";
+    private static final String TAG = "StatsLog";
     private static final boolean DEBUG = false;
+    private static Context sContext;
 
     public StatsLogCompatManager(Context context) {
+        sContext = context;
     }
 
     @Override
-    public void logAppLaunch(View v, Intent intent, @Nullable UserHandle userHandle) {
-        LauncherExtension ext = new LauncherExtension();
-        ext.srcTarget = new LauncherTarget[SUPPORTED_TARGET_DEPTH];
-        int srcState = mStateProvider.getCurrentState();
-        fillInLauncherExtension(v, ext);
-        if (ext.srcTarget[0] != null) {
-            ext.srcTarget[0].item = LauncherTarget.APP_ICON;
-        }
-        SysUiStatsLog.write(SysUiStatsLog.LAUNCHER_EVENT, LAUNCH_APP, srcState,
-                BACKGROUND /* dstState */, MessageNano.toByteArray(ext), true);
-    }
-
-    @Override
-    public void logTaskLaunch(View v, ComponentKey componentKey) {
-        LauncherExtension ext = new LauncherExtension();
-        ext.srcTarget = new LauncherTarget[SUPPORTED_TARGET_DEPTH];
-        int srcState = OVERVIEW;
-        fillInLauncherExtension(v, ext);
-        SysUiStatsLog.write(SysUiStatsLog.LAUNCHER_EVENT, LAUNCH_TASK, srcState,
-                BACKGROUND /* dstState */, MessageNano.toByteArray(ext), true);
-    }
-
-    @Override
-    public void logTaskDismiss(View v, ComponentKey componentKey) {
-        LauncherExtension ext = new LauncherExtension();
-        ext.srcTarget = new LauncherTarget[SUPPORTED_TARGET_DEPTH];
-        int srcState = OVERVIEW;
-        fillInLauncherExtension(v, ext);
-        SysUiStatsLog.write(SysUiStatsLog.LAUNCHER_EVENT, DISMISS_TASK, srcState,
-                BACKGROUND /* dstState */, MessageNano.toByteArray(ext), true);
-    }
-
-    @Override
-    public void logSwipeOnContainer(boolean isSwipingToLeft, int pageId) {
-        LauncherExtension ext = new LauncherExtension();
-        ext.srcTarget = new LauncherTarget[1];
-        int srcState = mStateProvider.getCurrentState();
-        fillInLauncherExtensionWithPageId(ext, pageId);
-        int launcherAction = isSwipingToLeft ? Launcher.SWIPE_LEFT : Launcher.SWIPE_RIGHT;
-        SysUiStatsLog.write(SysUiStatsLog.LAUNCHER_EVENT, launcherAction, srcState, srcState,
-                MessageNano.toByteArray(ext), true);
-    }
-
-    public static boolean fillInLauncherExtension(View v, LauncherExtension extension) {
-        if (DEBUG) {
-            Log.d(TAG, "fillInLauncherExtension");
-        }
-
-        StatsLogUtils.LogContainerProvider provider = StatsLogUtils.getLaunchProviderRecursive(v);
-        if (v == null || !(v.getTag() instanceof ItemInfo) || provider == null) {
-            if (DEBUG) {
-                Log.d(TAG, "View or provider is null, or view doesn't have an ItemInfo tag.");
-            }
-
-            return false;
-        }
-        Target child = new Target();
-        ArrayList<Target> targets = makeTargetsList(child);
-        targets.add(child);
-        provider.fillInLogContainerData((ItemInfo) v.getTag(), child, targets);
-
-        int maxDepth = Math.min(SUPPORTED_TARGET_DEPTH, targets.size());
-        extension.srcTarget = new LauncherTarget[maxDepth];
-        for (int i = 0; i < maxDepth; i++) {
-            extension.srcTarget[i] = new LauncherTarget();
-            copy(targets.get(i), extension.srcTarget[i]);
-        }
-        return true;
-    }
-
-    public static boolean fillInLauncherExtensionWithPageId(LauncherExtension ext, int pageId) {
-        if (DEBUG) {
-            Log.d(TAG, "fillInLauncherExtensionWithPageId, pageId = " + pageId);
-        }
-
-        Target target = new Target();
-        target.pageIndex = pageId;
-        ext.srcTarget[0] = new LauncherTarget();
-        copy(target, ext.srcTarget[0]);
-        return true;
-    }
-
-    private static void copy(Target src, LauncherTarget dst) {
-        if (DEBUG) {
-            Log.d(TAG, "copy target information from clearcut Target to LauncherTarget.");
-        }
-
-        // Fill in type
-        switch (src.type) {
-            case Target.Type.ITEM:
-                dst.type = LauncherTarget.ITEM_TYPE;
-                break;
-            case Target.Type.CONTROL:
-                dst.type = LauncherTarget.CONTROL_TYPE;
-                break;
-            case Target.Type.CONTAINER:
-                dst.type = LauncherTarget.CONTAINER_TYPE;
-                break;
-            default:
-                dst.type = LauncherTarget.NONE;
-                break;
-        }
-
-        // Fill in item
-        switch (src.itemType) {
-            case ItemType.APP_ICON:
-                dst.item = LauncherTarget.APP_ICON;
-                break;
-            case ItemType.SHORTCUT:
-                dst.item = LauncherTarget.SHORTCUT;
-                break;
-            case ItemType.WIDGET:
-                dst.item = LauncherTarget.WIDGET;
-                break;
-            case ItemType.FOLDER_ICON:
-                dst.item = LauncherTarget.FOLDER_ICON;
-                break;
-            case ItemType.DEEPSHORTCUT:
-                dst.item = LauncherTarget.DEEPSHORTCUT;
-                break;
-            case ItemType.SEARCHBOX:
-                dst.item = LauncherTarget.SEARCHBOX;
-                break;
-            case ItemType.EDITTEXT:
-                dst.item = LauncherTarget.EDITTEXT;
-                break;
-            case ItemType.NOTIFICATION:
-                dst.item = LauncherTarget.NOTIFICATION;
-                break;
-            case ItemType.TASK:
-                dst.item = LauncherTarget.TASK;
-                break;
-            default:
-                dst.item = LauncherTarget.DEFAULT_ITEM;
-                break;
-        }
-
-        // Fill in container
-        switch (src.containerType) {
-            case ContainerType.HOTSEAT:
-                dst.container = LauncherTarget.HOTSEAT;
-                break;
-            case ContainerType.FOLDER:
-                dst.container = LauncherTarget.FOLDER;
-                break;
-            case ContainerType.PREDICTION:
-                dst.container = LauncherTarget.PREDICTION;
-                break;
-            case ContainerType.SEARCHRESULT:
-                dst.container = LauncherTarget.SEARCHRESULT;
-                break;
-            default:
-                dst.container = LauncherTarget.DEFAULT_CONTAINER;
-                break;
-        }
-
-        // Fill in control
-        switch (src.controlType) {
-            case ControlType.UNINSTALL_TARGET:
-                dst.control = LauncherTarget.UNINSTALL;
-                break;
-            case ControlType.REMOVE_TARGET:
-                dst.control = LauncherTarget.REMOVE;
-                break;
-            default:
-                dst.control = LauncherTarget.DEFAULT_CONTROL;
-                break;
-        }
-
-        // Fill in other fields
-        dst.pageId = src.pageIndex;
-        dst.gridX = src.gridX;
-        dst.gridY = src.gridY;
+    public void log(LauncherEvent eventId, LauncherAtom.ItemInfo item) {
+        // Call StatsLog method
     }
 
     @Override
@@ -254,4 +71,36 @@
                     "StatsLogUtil constants doesn't match enums in launcher.proto");
         }
     }
+
+    /**
+     * Logs the workspace layout information on the model thread.
+     */
+    public void logSnapshot() {
+        LauncherAppState.getInstance(sContext).getModel().enqueueModelUpdateTask(
+                new SnapshotWorker());
+    }
+
+    private class SnapshotWorker extends BaseModelUpdateTask {
+        @Override
+        public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+            IntSparseArrayMap<FolderInfo> folders = dataModel.folders.clone();
+            ArrayList<ItemInfo> workspaceItems = (ArrayList) dataModel.workspaceItems.clone();
+            ArrayList<LauncherAppWidgetInfo> appWidgets = (ArrayList) dataModel.appWidgets.clone();
+
+            for (ItemInfo info : workspaceItems) {
+                LauncherAtom.ItemInfo atomInfo = info.buildProto(null, null);
+                // call StatsLog method
+            }
+            for (FolderInfo fInfo : folders) {
+                for (ItemInfo info : fInfo.contents) {
+                    LauncherAtom.ItemInfo atomInfo = info.buildProto(null, fInfo);
+                    // call StatsLog method
+                }
+            }
+            for (ItemInfo info : appWidgets) {
+                LauncherAtom.ItemInfo atomInfo = info.buildProto(null, null);
+                // call StatsLog method
+            }
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/util/ImageActionUtils.java b/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
new file mode 100644
index 0000000..f5fbf28
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
@@ -0,0 +1,169 @@
+/*
+ * 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.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
+
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Insets;
+import android.graphics.Picture;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.util.Log;
+
+import androidx.annotation.UiThread;
+import androidx.annotation.WorkerThread;
+import androidx.core.content.FileProvider;
+
+import com.android.launcher3.BuildConfig;
+import com.android.quickstep.SystemUiProxy;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.function.BiFunction;
+import java.util.function.Supplier;
+
+/**
+ * Utility class containing methods to help manage image actions such as sharing, cropping, and
+ * saving image.
+ */
+public class ImageActionUtils {
+
+    private static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".overview.fileprovider";
+
+    /**
+     * Saves screenshot to location determine by SystemUiProxy
+     */
+    public static void saveScreenshot(SystemUiProxy systemUiProxy, Bitmap screenshot,
+            Rect screenshotBounds,
+            Insets visibleInsets, int taskId) {
+        systemUiProxy.handleImageAsScreenshot(screenshot, screenshotBounds, visibleInsets, taskId);
+    }
+
+    /**
+     * Launch the activity to share image.
+     */
+    @UiThread
+    public static void startShareActivity(Context context, Supplier<Bitmap> bitmapSupplier,
+            Rect crop, Intent intent, String tag) {
+        if (bitmapSupplier.get() == null) {
+            Log.e(tag, "No snapshot available, not starting share.");
+            return;
+        }
+
+        UI_HELPER_EXECUTOR.execute(() -> persistBitmapAndStartActivity(context,
+                bitmapSupplier.get(), crop, intent, ImageActionUtils::getShareIntentForImageUri,
+                tag));
+    }
+
+    /**
+     * Starts activity based on given intent created from image uri.
+     */
+    @WorkerThread
+    public static void persistBitmapAndStartActivity(Context context, Bitmap bitmap, Rect crop,
+            Intent intent, BiFunction<Uri, Intent, Intent[]> uriToIntentMap, String tag) {
+        context.startActivities(
+                uriToIntentMap.apply(getImageUri(bitmap, crop, context, tag), intent));
+    }
+
+    /**
+     * Converts image bitmap to Uri by temporarily saving bitmap to cache, and creating Uri pointing
+     * to that location. Used to be able to share an image with another app.
+     *
+     * @param bitmap  The whole bitmap to be shared.
+     * @param crop    The section of the bitmap to be shared.
+     * @param context The application context, used to interact with file system.
+     * @param tag     Tag used to log errors.
+     * @return Uri that points to the cropped version of desired bitmap to share.
+     */
+    @WorkerThread
+    public static Uri getImageUri(Bitmap bitmap, Rect crop, Context context, String tag) {
+        Bitmap croppedBitmap = cropBitmap(bitmap, crop);
+        int cropHash = crop == null ? 0 : crop.hashCode();
+        String baseName = "image_" + bitmap.hashCode() + "_" + cropHash + ".png";
+        File file = new File(context.getCacheDir(), baseName);
+
+        try (FileOutputStream fos = new FileOutputStream(file)) {
+            croppedBitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
+        } catch (IOException e) {
+            Log.e(tag, "Error saving image", e);
+        }
+
+        return FileProvider.getUriForFile(context, AUTHORITY, file);
+    }
+
+    /**
+     * Crops the bitmap to the provided size and returns a software backed bitmap whenever possible.
+     *
+     * @param bitmap The bitmap to be cropped.
+     * @param crop   The section of the bitmap in the crop.
+     * @return The cropped bitmap.
+     */
+    @WorkerThread
+    public static Bitmap cropBitmap(Bitmap bitmap, Rect crop) {
+        Rect src = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
+        if (crop == null) {
+            crop = new Rect(src);
+        }
+        if (crop.equals(src)) {
+            return bitmap;
+        } else {
+            if (bitmap.getConfig() != Bitmap.Config.HARDWARE) {
+                return Bitmap.createBitmap(bitmap, crop.left, crop.top, crop.width(),
+                        crop.height());
+            }
+
+            // For hardware bitmaps, use the Picture API to directly create a software bitmap
+            Picture picture = new Picture();
+            Canvas canvas = picture.beginRecording(crop.width(), crop.height());
+            canvas.drawBitmap(bitmap, -crop.left, -crop.top, null);
+            picture.endRecording();
+            return Bitmap.createBitmap(picture, crop.width(), crop.height(),
+                    Bitmap.Config.ARGB_8888);
+        }
+    }
+
+    /**
+     * Gets the intent used to share image.
+     */
+    @WorkerThread
+    private static Intent[] getShareIntentForImageUri(Uri uri, Intent intent) {
+        if (intent == null) {
+            intent = new Intent();
+        }
+        ClipData clipdata = new ClipData(new ClipDescription("content",
+                new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}),
+                new ClipData.Item(uri));
+        intent.setAction(Intent.ACTION_SEND)
+                .setComponent(null)
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                .setType("image/png")
+                .setFlags(FLAG_GRANT_READ_URI_PERMISSION)
+                .putExtra(Intent.EXTRA_STREAM, uri)
+                .setClipData(clipdata);
+        return new Intent[]{Intent.createChooser(intent, null).addFlags(FLAG_ACTIVITY_NEW_TASK)};
+    }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java b/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java
index 8e4762d..5904fcd 100644
--- a/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java
+++ b/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java
@@ -88,7 +88,6 @@
      */
     @Test
     public void testPredictionExistsInAllApps() {
-        mDevice.pressHome();
         mLauncher.pressHome().switchToAllApps();
 
         // Dispatch an update
diff --git a/res/drawable-v24/ic_block_shadow.xml b/res/drawable-v24/ic_block_shadow.xml
new file mode 100644
index 0000000..045fe8d
--- /dev/null
+++ b/res/drawable-v24/ic_block_shadow.xml
@@ -0,0 +1,19 @@
+<?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.graphics.ShadowDrawable
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:src="@drawable/ic_block_no_shadow"
+    android:elevation="@dimen/drop_target_shadow_elevation" />
diff --git a/quickstep/recents_ui_overrides/res/drawable/arrow_toast_rounded_background.xml b/res/drawable/arrow_toast_rounded_background.xml
similarity index 100%
rename from quickstep/recents_ui_overrides/res/drawable/arrow_toast_rounded_background.xml
rename to res/drawable/arrow_toast_rounded_background.xml
diff --git a/res/drawable/ic_block.xml b/res/drawable/ic_block_no_shadow.xml
similarity index 100%
rename from res/drawable/ic_block.xml
rename to res/drawable/ic_block_no_shadow.xml
diff --git a/quickstep/recents_ui_overrides/res/layout/arrow_toast.xml b/res/layout/arrow_toast.xml
similarity index 97%
rename from quickstep/recents_ui_overrides/res/layout/arrow_toast.xml
rename to res/layout/arrow_toast.xml
index b0f2b4b..087e45a 100644
--- a/quickstep/recents_ui_overrides/res/layout/arrow_toast.xml
+++ b/res/layout/arrow_toast.xml
@@ -34,6 +34,7 @@
             android:layout_width="0dp"
             android:layout_height="wrap_content"
             android:layout_weight="1"
+            android:gravity="center"
             android:layout_gravity="center_vertical"
             android:textColor="@android:color/white"
             android:textSize="16sp"/>
@@ -49,7 +50,7 @@
             android:src="@drawable/ic_remove_no_shadow"
             android:tint="@android:color/white"
             android:background="?android:attr/selectableItemBackgroundBorderless"
-            android:contentDescription="@string/accessibility_close_task"/>
+            android:contentDescription="@string/accessibility_close"/>
     </LinearLayout>
 
     <View
diff --git a/res/layout/search_container_all_apps.xml b/res/layout/search_container_all_apps.xml
index fd9cb60..e1646ba 100644
--- a/res/layout/search_container_all_apps.xml
+++ b/res/layout/search_container_all_apps.xml
@@ -34,5 +34,4 @@
     android:singleLine="true"
     android:textColor="?android:attr/textColorSecondary"
     android:textColorHint="@drawable/all_apps_search_hint"
-    android:textSize="16sp"
-    android:translationY="24dp" />
\ No newline at end of file
+    android:textSize="16sp" />
\ No newline at end of file
diff --git a/res/layout/work_apps_paused.xml b/res/layout/work_apps_paused.xml
index cf1e835..f64b2d9 100644
--- a/res/layout/work_apps_paused.xml
+++ b/res/layout/work_apps_paused.xml
@@ -20,7 +20,7 @@
     android:gravity="center">
 
     <TextView
-        style="@style/TextHeadline"
+        style="@style/PrimaryMediumText"
         android:textColor="?attr/workProfileOverlayTextColor"
         android:id="@+id/work_apps_paused_title"
         android:layout_width="wrap_content"
diff --git a/res/layout/work_mode_switch.xml b/res/layout/work_mode_switch.xml
new file mode 100644
index 0000000..689dbe7
--- /dev/null
+++ b/res/layout/work_mode_switch.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<com.android.launcher3.allapps.WorkModeSwitch
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    style="@style/PrimaryMediumText"
+    android:id="@+id/work_mode_toggle"
+    android:drawableStart="@drawable/ic_corp"
+    android:drawablePadding="16dp"
+    android:drawableTint="?attr/workProfileOverlayTextColor"
+    android:textColor="?attr/workProfileOverlayTextColor"
+    android:layout_alignParentBottom="true"
+    android:ellipsize="end"
+    android:gravity="start"
+    android:lines="1"
+    android:showText="false"
+    android:textSize="16sp"
+    android:background="?attr/allAppsScrimColor"
+    android:text="@string/work_profile_toggle_label"
+    android:paddingBottom="@dimen/all_apps_work_profile_tab_footer_padding"
+    android:paddingLeft="@dimen/all_apps_work_profile_tab_footer_padding"
+    android:paddingRight="@dimen/all_apps_work_profile_tab_footer_padding"
+    android:paddingTop="@dimen/all_apps_work_profile_tab_footer_padding"
+/>
\ No newline at end of file
diff --git a/res/layout/work_tab_footer.xml b/res/layout/work_tab_footer.xml
deleted file mode 100644
index 264e273..0000000
--- a/res/layout/work_tab_footer.xml
+++ /dev/null
@@ -1,51 +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.
--->
-<com.android.launcher3.views.WorkFooterContainer
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:id="@+id/work_toggle_container"
-    android:focusable="true"
-    android:orientation="horizontal"
-    android:background="?attr/allAppsScrimColor"
-    android:paddingBottom="@dimen/all_apps_work_profile_tab_footer_padding"
-    android:paddingLeft="@dimen/all_apps_work_profile_tab_footer_padding"
-    android:paddingRight="@dimen/all_apps_work_profile_tab_footer_padding"
-    android:paddingTop="@dimen/all_apps_work_profile_tab_footer_padding">
-
-    <TextView
-        style="@style/PrimaryMediumText"
-        android:id="@+id/work_mode_label"
-        android:layout_width="0dp"
-        android:layout_weight="1"
-        android:drawableStart="@drawable/ic_corp"
-        android:drawablePadding="16dp"
-        android:drawableTint="?attr/workProfileOverlayTextColor"
-        android:textColor="?attr/workProfileOverlayTextColor"
-        android:layout_height="wrap_content"
-        android:ellipsize="end"
-        android:gravity="center_vertical"
-        android:lines="1"
-        android:minHeight="24dp"
-        android:paddingEnd="12dp"
-        android:text="@string/work_profile_toggle_label"
-        android:textSize="16sp"/>
-    <com.android.launcher3.allapps.WorkModeSwitch
-        android:id="@+id/work_mode_toggle"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"/>
-
-</com.android.launcher3.views.WorkFooterContainer>
\ No newline at end of file
diff --git a/res/values/config.xml b/res/values/config.xml
index df0f233..1675a98 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -142,6 +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>
 
     <!-- Swipe up to home related -->
     <dimen name="swipe_up_fling_min_visible_change">18dp</dimen>
@@ -170,8 +171,12 @@
 
         <item>@dimen/staggered_damping_ratio</item>
         <item>@dimen/staggered_stiffness</item>
+        <item>@dimen/unlock_staggered_velocity_dp_per_s</item>
 
         <item>@dimen/swipe_up_fling_min_visible_change</item>
         <item>@dimen/swipe_up_y_overshoot</item>
     </array>
+
+    <string-array name="live_wallpapers_remove_sysui_scrims">
+    </string-array>
 </resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 871651d..271511e 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -85,6 +85,10 @@
     <dimen name="all_apps_tabs_side_padding">12dp</dimen>
     <dimen name="all_apps_divider_height">1dp</dimen>
 
+    <dimen name="all_apps_tip_bottom_margin">8dp</dimen>
+    <!-- The size of corner radius of the arrow in the arrow toast. -->
+    <dimen name="arrow_toast_corner_radius">2dp</dimen>
+
     <dimen name="all_apps_work_profile_tab_footer_padding">20dp</dimen>
 
 <!-- Search bar in All Apps -->
diff --git a/res/values/drawables.xml b/res/values/drawables.xml
index 1367174..9c57ec1 100644
--- a/res/values/drawables.xml
+++ b/res/values/drawables.xml
@@ -17,4 +17,5 @@
     <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>
 </resources>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index b1077be..ac04262 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -316,6 +316,9 @@
     <!-- Accessibility action to dismiss a notification in the shortcuts menu for an icon. [CHAR_LIMIT=30] -->
     <string name="action_dismiss_notification">Dismiss</string>
 
+    <!-- Content description for arrow tip close button. [CHAR LIMIT=NONE] -->
+    <string name="accessibility_close">Close</string>
+
     <!-- Accessibility confirmation for notification being dismissed. -->
     <string name="notification_dismissed">Notification dismissed</string>
 
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index 12b5fc1..bed8278 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -180,7 +180,10 @@
         return null;
     }
 
-    protected static <T extends AbstractFloatingView> T getOpenView(
+    /**
+     * Returns a view matching FloatingViewType
+     */
+    public static <T extends AbstractFloatingView> T getOpenView(
             ActivityContext activity, @FloatingViewType int type) {
         BaseDragLayer dragLayer = activity.getDragLayer();
         if (dragLayer == null) return null;
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index 9f3b48f..6fa3c28 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.APP_LAUNCH_TAP;
 import static com.android.launcher3.util.DefaultDisplay.CHANGE_ROTATION;
 
 import android.app.ActivityOptions;
@@ -181,7 +182,7 @@
                         sourceContainer);
             }
             getUserEventDispatcher().logAppLaunch(v, intent, user);
-            getStatsLogManager().logAppLaunch(v, intent, user);
+            getStatsLogManager().log(APP_LAUNCH_TAP, item.buildProto(null, null));
             return true;
         } catch (NullPointerException|ActivityNotFoundException|SecurityException e) {
             Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index 38e1201..9e8441a 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
@@ -25,6 +26,7 @@
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.launcher3.compat.AccessibilityManagerCompat;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.views.RecyclerViewFastScroller;
 
 
@@ -177,6 +179,10 @@
     public void onScrollStateChanged(int state) {
         super.onScrollStateChanged(state);
 
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "onScrollStateChanged: " + state);
+        }
+
         if (state == SCROLL_STATE_IDLE) {
             AccessibilityManagerCompat.sendScrollFinishedEventToTest(getContext());
         }
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index e6f8a85..c8e73ba 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -39,13 +39,13 @@
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewConfiguration;
 import android.view.ViewDebug;
 import android.widget.TextView;
 
 import com.android.launcher3.Launcher.OnResumeCallback;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.dot.DotInfo;
+import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.graphics.IconPalette;
 import com.android.launcher3.graphics.IconShape;
@@ -65,7 +65,7 @@
  * too aggressive.
  */
 public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, OnResumeCallback,
-        IconLabelDotView {
+        IconLabelDotView, DraggableView {
 
     private static final int DISPLAY_WORKSPACE = 0;
     private static final int DISPLAY_ALL_APPS = 1;
@@ -103,13 +103,11 @@
 
     private final ActivityContext mActivity;
     private Drawable mIcon;
-    private final boolean mCenterVertically;
+    private boolean mCenterVertically;
 
     private final int mDisplay;
 
     private final CheckLongPressHelper mLongPressHelper;
-    private final StylusEventHelper mStylusEventHelper;
-    private final float mSlop;
 
     private final boolean mLayoutHorizontal;
     private final int mIconSize;
@@ -136,9 +134,6 @@
     @ViewDebug.ExportedProperty(category = "launcher")
     private boolean mDisableRelayout = false;
 
-    @ViewDebug.ExportedProperty(category = "launcher")
-    private final boolean mIgnorePaddingTouch;
-
     private IconLoadRequest mIconLoadRequest;
 
     public BubbleTextView(Context context) {
@@ -152,7 +147,6 @@
     public BubbleTextView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
         mActivity = ActivityContext.lookupContext(context);
-        mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
 
         TypedArray a = context.obtainStyledAttributes(attrs,
                 R.styleable.BubbleTextView, defStyle, 0);
@@ -165,23 +159,19 @@
             setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.iconTextSizePx);
             setCompoundDrawablePadding(grid.iconDrawablePaddingPx);
             defaultIconSize = grid.iconSizePx;
-            mIgnorePaddingTouch = true;
         } else if (mDisplay == DISPLAY_ALL_APPS) {
             DeviceProfile grid = mActivity.getDeviceProfile();
             setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.allAppsIconTextSizePx);
             setCompoundDrawablePadding(grid.allAppsIconDrawablePaddingPx);
             defaultIconSize = grid.allAppsIconSizePx;
-            mIgnorePaddingTouch = true;
         } else if (mDisplay == DISPLAY_FOLDER) {
             DeviceProfile grid = mActivity.getDeviceProfile();
             setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.folderChildTextSizePx);
             setCompoundDrawablePadding(grid.folderChildDrawablePaddingPx);
             defaultIconSize = grid.folderChildIconSizePx;
-            mIgnorePaddingTouch = true;
         } else {
             // widget_selection or shortcut_popup
             defaultIconSize = mActivity.getDeviceProfile().iconSizePx;
-            mIgnorePaddingTouch = false;
         }
 
         mCenterVertically = a.getBoolean(R.styleable.BubbleTextView_centerVertically, false);
@@ -191,7 +181,6 @@
         a.recycle();
 
         mLongPressHelper = new CheckLongPressHelper(this);
-        mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
 
         mDotParams = new DotRenderer.DrawParams();
 
@@ -332,42 +321,21 @@
     @Override
     public boolean onTouchEvent(MotionEvent event) {
         // ignore events if they happen in padding area
-        if (event.getAction() == MotionEvent.ACTION_DOWN && mIgnorePaddingTouch
+        if (event.getAction() == MotionEvent.ACTION_DOWN
                 && (event.getY() < getPaddingTop()
                 || event.getX() < getPaddingLeft()
                 || event.getY() > getHeight() - getPaddingBottom()
                 || event.getX() > getWidth() - getPaddingRight())) {
             return false;
         }
-
-        // Call the superclass onTouchEvent first, because sometimes it changes the state to
-        // isPressed() on an ACTION_UP
-        boolean result = super.onTouchEvent(event);
-
-        // Check for a stylus button press, if it occurs cancel any long press checks.
-        if (mStylusEventHelper.onMotionEvent(event)) {
-            mLongPressHelper.cancelLongPress();
-            result = true;
+        if (isLongClickable()) {
+            super.onTouchEvent(event);
+            mLongPressHelper.onTouchEvent(event);
+            // Keep receiving the rest of the events
+            return true;
+        } else {
+            return super.onTouchEvent(event);
         }
-
-        switch (event.getAction()) {
-            case MotionEvent.ACTION_DOWN:
-                // If we're in a stylus button press, don't check for long press.
-                if (!mStylusEventHelper.inStylusButtonPressed()) {
-                    mLongPressHelper.postCheckForLongPress();
-                }
-                break;
-            case MotionEvent.ACTION_CANCEL:
-            case MotionEvent.ACTION_UP:
-                mLongPressHelper.cancelLongPress();
-                break;
-            case MotionEvent.ACTION_MOVE:
-                if (!Utilities.pointInView(this, event.getX(), event.getY(), mSlop)) {
-                    mLongPressHelper.cancelLongPress();
-                }
-                break;
-        }
-        return result;
     }
 
     void setStayPressed(boolean stayPressed) {
@@ -530,7 +498,6 @@
     @Override
     public void cancelLongPress() {
         super.cancelLongPress();
-
         mLongPressHelper.cancelLongPress();
     }
 
@@ -701,4 +668,24 @@
     public int getIconSize() {
         return mIconSize;
     }
+
+    @Override
+    public int getViewType() {
+        return DRAGGABLE_ICON;
+    }
+
+    @Override
+    public void getVisualDragBounds(Rect bounds) {
+        DeviceProfile grid = mActivity.getDeviceProfile();
+        BubbleTextView.getIconBounds(this, bounds, grid.iconSizePx);
+    }
+
+    @Override
+    public void prepareDrawDragView() {
+        if (getIcon() instanceof FastBitmapDrawable) {
+            FastBitmapDrawable icon = (FastBitmapDrawable) getIcon();
+            icon.setScale(1f);
+        }
+        setForceHideDot(true);
+    }
 }
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
index 2b0da43..34d7067 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -155,7 +155,7 @@
 
     @Override
     public final void onDragEnter(DragObject d) {
-        if (!d.accessibleDrag && !mTextVisible) {
+        if (!mAccessibleDrag && !mTextVisible) {
             // Show tooltip
             hideTooltip();
 
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index e3eb387..9682d09 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -53,11 +53,10 @@
 
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
-import com.android.launcher3.accessibility.FolderAccessibilityHelper;
-import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.anim.PropertyListBuilder;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.folder.PreviewBackground;
 import com.android.launcher3.graphics.DragPreviewProvider;
 import com.android.launcher3.graphics.RotationMode;
@@ -79,9 +78,6 @@
 import java.util.Stack;
 
 public class CellLayout extends ViewGroup implements Transposable {
-    public static final int WORKSPACE_ACCESSIBILITY_DRAG = 2;
-    public static final int FOLDER_ACCESSIBILITY_DRAG = 1;
-
     private static final String TAG = "CellLayout";
     private static final boolean LOGD = false;
 
@@ -105,6 +101,11 @@
     @Thunk final int[] mTmpPoint = new int[2];
     @Thunk final int[] mTempLocation = new int[2];
 
+    // Used to visualize / debug the Grid of the CellLayout
+    private static final boolean VISUALIZE_GRID = false;
+    private Rect mVisualizeGridRect = new Rect();
+    private Paint mVisualizeGridPaint = new Paint();
+
     private GridOccupancy mOccupied;
     private GridOccupancy mTmpOccupied;
 
@@ -182,7 +183,6 @@
     private static final Paint sPaint = new Paint();
 
     // Related to accessible drag and drop
-    private DragAndDropAccessibilityDelegate mTouchHelper;
     private boolean mUseTouchHelper = false;
     private RotationMode mRotationMode = RotationMode.NORMAL;
 
@@ -292,26 +292,20 @@
         addView(mShortcutsAndWidgets);
     }
 
-    public void enableAccessibleDrag(boolean enable, int dragType) {
-        mUseTouchHelper = enable;
-        if (!enable) {
-            ViewCompat.setAccessibilityDelegate(this, null);
-            setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
-            getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
-            setOnClickListener(null);
-        } else {
-            if (dragType == WORKSPACE_ACCESSIBILITY_DRAG &&
-                    !(mTouchHelper instanceof WorkspaceAccessibilityHelper)) {
-                mTouchHelper = new WorkspaceAccessibilityHelper(this);
-            } else if (dragType == FOLDER_ACCESSIBILITY_DRAG &&
-                    !(mTouchHelper instanceof FolderAccessibilityHelper)) {
-                mTouchHelper = new FolderAccessibilityHelper(this);
-            }
-            ViewCompat.setAccessibilityDelegate(this, mTouchHelper);
-            setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
-            getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
-            setOnClickListener(mTouchHelper);
-        }
+
+    /**
+     * Sets or clears a delegate used for accessible drag and drop
+     */
+    public void setDragAndDropAccessibilityDelegate(DragAndDropAccessibilityDelegate delegate) {
+        setOnClickListener(delegate);
+        setOnHoverListener(delegate);
+        ViewCompat.setAccessibilityDelegate(this, delegate);
+
+        mUseTouchHelper = delegate != null;
+        int accessibilityFlag = mUseTouchHelper
+                ? IMPORTANT_FOR_ACCESSIBILITY_YES : IMPORTANT_FOR_ACCESSIBILITY_NO;
+        setImportantForAccessibility(accessibilityFlag);
+        getShortcutsAndWidgets().setImportantForAccessibility(accessibilityFlag);
 
         // Invalidate the accessibility hierarchy
         if (getParent() != null) {
@@ -339,15 +333,6 @@
     }
 
     @Override
-    public boolean dispatchHoverEvent(MotionEvent event) {
-        // Always attempt to dispatch hover events to accessibility first.
-        if (mUseTouchHelper && mTouchHelper.dispatchHoverEvent(event)) {
-            return true;
-        }
-        return super.dispatchHoverEvent(event);
-    }
-
-    @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         if (mUseTouchHelper ||
                 (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev))) {
@@ -483,6 +468,37 @@
             mFolderLeaveBehind.drawLeaveBehind(canvas);
             canvas.restore();
         }
+
+        if (VISUALIZE_GRID) {
+            visualizeGrid(canvas);
+        }
+    }
+
+    protected void visualizeGrid(Canvas canvas) {
+        mVisualizeGridRect.set(0, 0, mCellWidth, mCellHeight);
+        mVisualizeGridPaint.setStrokeWidth(4);
+
+        for (int i = 0; i < mCountX; i++) {
+            for (int j = 0; j < mCountY; j++) {
+                canvas.save();
+
+                int transX = i * mCellWidth;
+                int transY = j * mCellHeight;
+
+                canvas.translate(getPaddingLeft() + transX, getPaddingTop() + transY);
+
+                mVisualizeGridPaint.setStyle(Paint.Style.FILL);
+                mVisualizeGridPaint.setColor(Color.argb(80, 255, 100, 100));
+
+                canvas.drawRect(mVisualizeGridRect, mVisualizeGridPaint);
+
+                mVisualizeGridPaint.setStyle(Paint.Style.STROKE);
+                mVisualizeGridPaint.setColor(Color.argb(255, 255, 100, 100));
+
+                canvas.drawRect(mVisualizeGridRect, mVisualizeGridPaint);
+                canvas.restore();
+            }
+        }
     }
 
     @Override
@@ -949,8 +965,8 @@
         return false;
     }
 
-    void visualizeDropLocation(View v, DragPreviewProvider outlineProvider, int cellX, int cellY,
-            int spanX, int spanY, boolean resize, DropTarget.DragObject dragObject) {
+    void visualizeDropLocation(DraggableView v, DragPreviewProvider outlineProvider, int cellX, int
+            cellY, int spanX, int spanY, boolean resize, DropTarget.DragObject dragObject) {
         final int oldDragCellX = mDragCell[0];
         final int oldDragCellY = mDragCell[1];
 
@@ -960,9 +976,6 @@
 
         Bitmap dragOutline = outlineProvider.generatedDragOutline;
         if (cellX != oldDragCellX || cellY != oldDragCellY) {
-            Point dragOffset = dragObject.dragView.getDragVisualizeOffset();
-            Rect dragRegion = dragObject.dragView.getDragRegion();
-
             mDragCell[0] = cellX;
             mDragCell[1] = cellY;
 
@@ -971,50 +984,27 @@
             mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
             Rect r = mDragOutlines[mDragOutlineCurrent];
 
+            cellToRect(cellX, cellY, spanX, spanY, r);
+            int left = r.left;
+            int top = r.top;
+
+            int width = dragOutline.getWidth();
+            int height = dragOutline.getHeight();
+
             if (resize) {
-                cellToRect(cellX, cellY, spanX, spanY, r);
-                if (v instanceof LauncherAppWidgetHostView) {
-                    DeviceProfile profile = mActivity.getWallpaperDeviceProfile();
-                    Utilities.shrinkRect(r, profile.appWidgetScale.x, profile.appWidgetScale.y);
-                }
-            } else {
-                // Find the top left corner of the rect the object will occupy
-                final int[] topLeft = mTmpPoint;
-                cellToPoint(cellX, cellY, topLeft);
-
-                int left = topLeft[0];
-                int top = topLeft[1];
-
-                if (v != null && dragOffset == null) {
-                    // When drawing the drag outline, it did not account for margin offsets
-                    // added by the view's parent.
-                    MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
-                    left += lp.leftMargin;
-                    top += lp.topMargin;
-
-                    // Offsets due to the size difference between the View and the dragOutline.
-                    // There is a size difference to account for the outer blur, which may lie
-                    // outside the bounds of the view.
-                    top += ((mCellHeight * spanY) - dragOutline.getHeight()) / 2;
-                    // We center about the x axis
-                    left += ((mCellWidth * spanX) - dragOutline.getWidth()) / 2;
-                } else {
-                    if (dragOffset != null && dragRegion != null) {
-                        // Center the drag region *horizontally* in the cell and apply a drag
-                        // outline offset
-                        left += dragOffset.x + ((mCellWidth * spanX) - dragRegion.width()) / 2;
-                        int cHeight = getShortcutsAndWidgets().getCellContentHeight();
-                        int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f));
-                        top += dragOffset.y + cellPaddingY;
-                    } else {
-                        // Center the drag outline in the cell
-                        left += ((mCellWidth * spanX) - dragOutline.getWidth()) / 2;
-                        top += ((mCellHeight * spanY) - dragOutline.getHeight()) / 2;
-                    }
-                }
-                r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
+                width = r.width();
+                height = r.height();
             }
 
+            if (v != null && v.getViewType() == DraggableView.DRAGGABLE_ICON) {
+                left +=  ((mCellWidth * spanX) - dragOutline.getWidth()) / 2;
+                int cHeight = getShortcutsAndWidgets().getCellContentHeight();
+                int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f));
+                top += cellPaddingY;
+            }
+
+            r.set(left, top, left + width, top + height);
+
             Utilities.scaleRectAboutCenter(r, mChildScale);
             mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
             mDragOutlineAnims[mDragOutlineCurrent].animateIn();
@@ -1900,7 +1890,7 @@
 
     // This method starts or changes the reorder preview animations
     private void beginOrAdjustReorderPreviewAnimations(ItemConfiguration solution,
-            View dragView, int delay, int mode) {
+            View dragView, int mode) {
         int childCount = mShortcutsAndWidgets.getChildCount();
         for (int i = 0; i < childCount; i++) {
             View child = mShortcutsAndWidgets.getChildAt(i);
@@ -1967,6 +1957,8 @@
 
             this.child = child;
             this.mode = mode;
+
+            // TODO issue!
             setInitialAnimationValues(false);
             finalScale = (mChildScale - (CHILD_DIVIDEND / child.getWidth())) * initScale;
             finalDeltaX = initDeltaX;
@@ -2162,6 +2154,8 @@
     */
     private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
             int spanY, View dragView, int[] resultDirection) {
+
+        //TODO(adamcohen) b/151776141 use the items visual center for the direction vector
         int[] targetDestination = new int[2];
 
         findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
@@ -2272,7 +2266,7 @@
                 setItemPlacementDirty(false);
             } else {
                 beginOrAdjustReorderPreviewAnimations(swapSolution, dragView,
-                        REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
+                        ReorderPreviewAnimation.MODE_PREVIEW);
             }
             mShortcutsAndWidgets.requestLayout();
         }
@@ -2326,7 +2320,7 @@
 
         if (mode == MODE_SHOW_REORDER_HINT) {
             if (finalSolution != null) {
-                beginOrAdjustReorderPreviewAnimations(finalSolution, dragView, 0,
+                beginOrAdjustReorderPreviewAnimations(finalSolution, dragView,
                         ReorderPreviewAnimation.MODE_HINT);
                 result[0] = finalSolution.cellX;
                 result[1] = finalSolution.cellY;
@@ -2366,7 +2360,7 @@
                     setItemPlacementDirty(false);
                 } else {
                     beginOrAdjustReorderPreviewAnimations(finalSolution, dragView,
-                            REORDER_ANIMATION_DURATION,  ReorderPreviewAnimation.MODE_PREVIEW);
+                            ReorderPreviewAnimation.MODE_PREVIEW);
                 }
             }
         } else {
@@ -2787,7 +2781,6 @@
      * Finds solution to accept hotseat migration to cell layout. commits solution if commitConfig
      */
     public boolean makeSpaceForHotseatMigration(boolean commitConfig) {
-        if (FeatureFlags.HOTSEAT_MIGRATE_NEW_PAGE.get()) return false;
         int[] cellPoint = new int[2];
         int[] directionVector = new int[]{0, -1};
         cellToPoint(0, mCountY, cellPoint);
@@ -2797,12 +2790,23 @@
             if (commitConfig) {
                 copySolutionToTempState(configuration, null);
                 commitTempPlacement();
+                // undo marking cells occupied since there is actually nothing being placed yet.
+                mOccupied.markCells(0, mCountY - 1, mCountX, 1, false);
             }
             return true;
         }
         return false;
     }
 
+    /**
+     * returns a copy of cell layout's grid occupancy
+     */
+    public GridOccupancy cloneGridOccupancy() {
+        GridOccupancy occupancy = new GridOccupancy(mCountX, mCountY);
+        mOccupied.copyTo(occupancy);
+        return occupancy;
+    }
+
     public boolean isRegionVacant(int x, int y, int spanX, int spanY) {
         return mOccupied.isRegionVacant(x, y, spanX, spanY);
     }
diff --git a/src/com/android/launcher3/CheckLongPressHelper.java b/src/com/android/launcher3/CheckLongPressHelper.java
index 639c173..ef353f9 100644
--- a/src/com/android/launcher3/CheckLongPressHelper.java
+++ b/src/com/android/launcher3/CheckLongPressHelper.java
@@ -16,46 +16,68 @@
 
 package com.android.launcher3;
 
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
 
-import com.android.launcher3.util.Thunk;
-
+/**
+ * Utility class to handle tripper long press on a view with custom timeout and stylus event
+ */
 public class CheckLongPressHelper {
 
     public static final float DEFAULT_LONG_PRESS_TIMEOUT_FACTOR = 0.75f;
 
-    @Thunk View mView;
-    @Thunk View.OnLongClickListener mListener;
-    @Thunk boolean mHasPerformedLongPress;
-    private float mLongPressTimeoutFactor = DEFAULT_LONG_PRESS_TIMEOUT_FACTOR;
-    private CheckForLongPress mPendingCheckForLongPress;
+    private final View mView;
+    private final View.OnLongClickListener mListener;
+    private final float mSlop;
 
-    class CheckForLongPress implements Runnable {
-        public void run() {
-            if ((mView.getParent() != null) && mView.hasWindowFocus()
-                    && !mHasPerformedLongPress) {
-                boolean handled;
-                if (mListener != null) {
-                    handled = mListener.onLongClick(mView);
-                } else {
-                    handled = mView.performLongClick();
-                }
-                if (handled) {
-                    mView.setPressed(false);
-                    mHasPerformedLongPress = true;
-                }
-            }
-        }
-    }
+    private float mLongPressTimeoutFactor = DEFAULT_LONG_PRESS_TIMEOUT_FACTOR;
+
+    private boolean mHasPerformedLongPress;
+
+    private Runnable mPendingCheckForLongPress;
 
     public CheckLongPressHelper(View v) {
-        mView = v;
+        this(v, null);
     }
 
     public CheckLongPressHelper(View v, View.OnLongClickListener listener) {
         mView = v;
         mListener = listener;
+        mSlop = ViewConfiguration.get(mView.getContext()).getScaledTouchSlop();
+    }
+
+    /**
+     * Handles the touch event on a view
+     *
+     * @see View#onTouchEvent(MotionEvent)
+     */
+    public void onTouchEvent(MotionEvent ev) {
+        switch (ev.getAction()) {
+            case MotionEvent.ACTION_DOWN: {
+                // Just in case the previous long press hasn't been cleared, we make sure to
+                // start fresh on touch down.
+                cancelLongPress();
+
+                postCheckForLongPress();
+                if (isStylusButtonPressed(ev)) {
+                    triggerLongPress();
+                }
+                break;
+            }
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP:
+                cancelLongPress();
+                break;
+            case MotionEvent.ACTION_MOVE:
+                if (!Utilities.pointInView(mView, ev.getX(), ev.getY(), mSlop)) {
+                    cancelLongPress();
+                } else if (mPendingCheckForLongPress != null && isStylusButtonPressed(ev)) {
+                    // Only trigger long press if it has not been cancelled before
+                    triggerLongPress();
+                }
+                break;
+        }
     }
 
     /**
@@ -65,25 +87,64 @@
         mLongPressTimeoutFactor = longPressTimeoutFactor;
     }
 
-    public void postCheckForLongPress() {
+    private void postCheckForLongPress() {
         mHasPerformedLongPress = false;
 
         if (mPendingCheckForLongPress == null) {
-            mPendingCheckForLongPress = new CheckForLongPress();
+            mPendingCheckForLongPress = this::triggerLongPress;
         }
         mView.postDelayed(mPendingCheckForLongPress,
                 (long) (ViewConfiguration.getLongPressTimeout() * mLongPressTimeoutFactor));
     }
 
+    /**
+     * Cancels any pending long press
+     */
     public void cancelLongPress() {
         mHasPerformedLongPress = false;
+        clearCallbacks();
+    }
+
+    /**
+     * Returns true if long press has been performed in the current touch gesture
+     */
+    public boolean hasPerformedLongPress() {
+        return mHasPerformedLongPress;
+    }
+
+    private void triggerLongPress() {
+        if ((mView.getParent() != null) && mView.hasWindowFocus() && !mHasPerformedLongPress) {
+            boolean handled;
+            if (mListener != null) {
+                handled = mListener.onLongClick(mView);
+            } else {
+                handled = mView.performLongClick();
+            }
+            if (handled) {
+                mView.setPressed(false);
+                mHasPerformedLongPress = true;
+            }
+            clearCallbacks();
+        }
+    }
+
+    private void clearCallbacks() {
         if (mPendingCheckForLongPress != null) {
             mView.removeCallbacks(mPendingCheckForLongPress);
             mPendingCheckForLongPress = null;
         }
     }
 
-    public boolean hasPerformedLongPress() {
-        return mHasPerformedLongPress;
+
+    /**
+     * Identifies if the provided {@link MotionEvent} is a stylus with the primary stylus button
+     * pressed.
+     *
+     * @param event The event to check.
+     * @return Whether a stylus button press occurred.
+     */
+    private static boolean isStylusButtonPressed(MotionEvent event) {
+        return event.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS
+                && event.isButtonPressed(MotionEvent.BUTTON_SECONDARY);
     }
 }
diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java
index a32fd12..ef02e87 100644
--- a/src/com/android/launcher3/DropTarget.java
+++ b/src/com/android/launcher3/DropTarget.java
@@ -23,6 +23,7 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.folder.FolderNameProvider;
 
 /**
@@ -59,9 +60,6 @@
         /** Where the drag originated */
         public DragSource dragSource = null;
 
-        /** The object is part of an accessible drag operation */
-        public boolean accessibleDrag;
-
         /** Indicates that the drag operation was cancelled */
         public boolean cancelled = false;
 
@@ -72,6 +70,10 @@
 
         public FolderNameProvider folderNameProvider;
 
+        /** The source view (ie. icon, widget etc.) that is being dragged and which the
+         * DragView represents. May be an actual View class or a virtual stand-in */
+        public DraggableView originalView = null;
+
         public DragObject(Context context) {
             if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
                 folderNameProvider = FolderNameProvider.newInstance(context);
diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
index 336e423..b75a5e7 100644
--- a/src/com/android/launcher3/FolderInfo.java
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -79,7 +79,7 @@
      * Add an app or shortcut for a specified rank.
      */
     public void add(WorkspaceItemInfo item, int rank, boolean animate) {
-        rank = Utilities.boundToRange(rank, 0, contents.size() + 1);
+        rank = Utilities.boundToRange(rank, 0, contents.size());
         contents.add(rank, item);
         for (int i = 0; i < mListeners.size(); i++) {
             mListeners.get(i).onAdd(item, rank);
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 2ad84b9..7414a88 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -162,9 +162,7 @@
                     "PreviewContext is passed into this IDP constructor");
         }
 
-        String gridName = Utilities.getPrefs(context).getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false)
-                ? Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null)
-                : null;
+        String gridName = getCurrentGridName(context);
         initGrid(context, gridName);
         mConfigMonitor = new ConfigMonitor(context,
                 APPLY_CONFIG_AT_RUNTIME.get() ? this::onConfigChanged : this::killProcess);
@@ -188,6 +186,12 @@
         initGrid(context, null, new Info(display));
     }
 
+    public static String getCurrentGridName(Context context) {
+        return Utilities.getPrefs(context).getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false)
+                ? Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null)
+                : null;
+    }
+
     /**
      * Retrieve system defined or RRO overriden icon shape.
      */
diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java
index c99465c..8c4e4a0 100644
--- a/src/com/android/launcher3/ItemInfo.java
+++ b/src/com/android/launcher3/ItemInfo.java
@@ -16,6 +16,13 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+import static com.android.launcher3.LauncherSettings.Favorites.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.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
+
 import android.content.ComponentName;
 import android.content.ContentValues;
 import android.content.Intent;
@@ -24,13 +31,17 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.util.ContentWriter;
 
+
+
 /**
  * Represents an item in the launcher.
  */
 public class ItemInfo {
 
+    public static final boolean DEBUG = true;
     public static final int NO_ID = -1;
 
     /**
@@ -190,6 +201,7 @@
         return "id=" + id
                 + " type=" + LauncherSettings.Favorites.itemTypeToString(itemType)
                 + " container=" + LauncherSettings.Favorites.containerToString((int)container)
+                + " targetComponent=" + getTargetComponent()
                 + " screen=" + screenId
                 + " cell(" + cellX + "," + cellY + ")"
                 + " span(" + spanX + "," + spanY + ")"
@@ -221,4 +233,70 @@
         return container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION
                 || container == LauncherSettings.Favorites.CONTAINER_PREDICTION;
     }
+
+    /**
+     * 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(Intent intent, FolderInfo fInfo) {
+
+        LauncherAtom.ItemInfo.Builder itemBuilder = LauncherAtom.ItemInfo.newBuilder();
+        itemBuilder.setIsWork(user != Process.myUserHandle());
+        ComponentName cn = getTargetComponent();
+        switch (itemType) {
+            case ITEM_TYPE_APPLICATION:
+                itemBuilder.setApplication(LauncherAtom.Application.newBuilder()
+                        .setComponentName(cn.flattenToShortString())
+                        .setPackageName(cn.getPackageName()));
+                break;
+            case ITEM_TYPE_DEEP_SHORTCUT:
+            case ITEM_TYPE_SHORTCUT:
+                itemBuilder.setShortcut(LauncherAtom.Shortcut.newBuilder()
+                        .setShortcutName(cn.flattenToShortString()));
+                break;
+            case ITEM_TYPE_APPWIDGET:
+                setItemBuilder(itemBuilder);
+                break;
+            default:
+                break;
+
+        }
+        if (fInfo != null) {
+            LauncherAtom.FolderContainer.Builder folderBuilder =
+                    LauncherAtom.FolderContainer.newBuilder();
+            folderBuilder.setGridX(cellX).setGridY(cellY).setPageIndex(screenId);
+
+            switch (fInfo.container) {
+                case CONTAINER_HOTSEAT:
+                    folderBuilder.setHotseat(LauncherAtom.HotseatContainer.newBuilder()
+                            .setIndex(fInfo.screenId));
+                    break;
+                case CONTAINER_DESKTOP:
+                    folderBuilder.setWorkspace(LauncherAtom.WorkspaceContainer.newBuilder()
+                            .setPageIndex(fInfo.screenId)
+                            .setGridX(fInfo.cellX).setGridY(fInfo.cellY));
+                    break;
+            }
+            itemBuilder.setFolder(folderBuilder);
+        } else {
+            switch (container) {
+                case CONTAINER_HOTSEAT:
+                    itemBuilder.setHotseat(LauncherAtom.HotseatContainer.newBuilder()
+                            .setIndex(screenId));
+                    break;
+                case CONTAINER_DESKTOP:
+                    itemBuilder.setWorkspace(LauncherAtom.WorkspaceContainer.newBuilder()
+                            .setGridX(cellX)
+                            .setGridY(cellY)
+                            .setPageIndex(screenId));
+                    break;
+            }
+        }
+        return itemBuilder.build();
+    }
 }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 1413a5c..5b9f676 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -77,7 +77,6 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.OvershootInterpolator;
 import android.widget.Toast;
@@ -115,6 +114,7 @@
 import com.android.launcher3.model.ModelWriter;
 import com.android.launcher3.notification.NotificationListener;
 import com.android.launcher3.pm.PinRequestHelper;
+import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.popup.PopupContainerWithArrow;
 import com.android.launcher3.popup.PopupDataProvider;
 import com.android.launcher3.popup.SystemShortcut;
@@ -124,7 +124,6 @@
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.AllAppsSwipeController;
 import com.android.launcher3.touch.ItemClickHandler;
-import com.android.launcher3.uioverrides.BackgroundBlurController;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -139,6 +138,7 @@
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.PendingRequestArgs;
+import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.util.ShortcutUtil;
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.util.Themes;
@@ -271,7 +271,6 @@
 
     // UI and state for the overview panel
     private View mOverviewPanel;
-    private View mActionsView;
 
     @Thunk
     boolean mWorkspaceLoading = true;
@@ -326,18 +325,10 @@
     private boolean mDeferOverlayCallbacks;
     private final Runnable mDeferredOverlayCallbacks = this::checkIfOverlayStillDeferred;
 
-    private BackgroundBlurController mBackgroundBlurController =
-            new BackgroundBlurController(this);
+    private long mLastTouchUpTime = -1;
+    private boolean mTouchInProgress;
 
-    private final ViewTreeObserver.OnDrawListener mOnDrawListener =
-            new ViewTreeObserver.OnDrawListener() {
-                @Override
-                public void onDraw() {
-                    getBackgroundBlurController().setSurfaceToLauncher(mDragLayer);
-                    mDragLayer.post(() -> mDragLayer.getViewTreeObserver().removeOnDrawListener(
-                            this));
-                }
-            };
+    private SafeCloseable mUserChangedCallbackCloseable;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -457,6 +448,9 @@
         });
 
         TraceHelper.INSTANCE.endSection(traceToken);
+
+        mUserChangedCallbackCloseable = UserCache.INSTANCE.get(this).addUserChangeListener(
+                () -> getStateManager().goToState(NORMAL));
     }
 
     protected LauncherOverlayManager getDefaultOverlay() {
@@ -758,8 +752,8 @@
                     data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1;
             if (resultCode == RESULT_CANCELED) {
                 completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId, requestArgs);
-                mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
-                        ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
+                mWorkspace.removeExtraEmptyScreenDelayed(
+                        ON_ACTIVITY_RESULT_ANIMATION_DELAY, false, exitSpringLoaded);
             } else if (resultCode == RESULT_OK) {
                 addAppWidgetImpl(
                         appWidgetId, requestArgs, null,
@@ -789,15 +783,9 @@
                         "returned from the widget configuration activity.");
                 result = RESULT_CANCELED;
                 completeTwoStageWidgetDrop(result, appWidgetId, requestArgs);
-                final Runnable onComplete = new Runnable() {
-                    @Override
-                    public void run() {
-                        getStateManager().goToState(NORMAL);
-                    }
-                };
-
-                mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete,
-                        ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
+                mWorkspace.removeExtraEmptyScreenDelayed(
+                        ON_ACTIVITY_RESULT_ANIMATION_DELAY, false,
+                        () -> getStateManager().goToState(NORMAL));
             } else {
                 if (requestArgs.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
                     // When the screen id represents an actual screen (as opposed to a rank)
@@ -816,8 +804,8 @@
                         dropLayout.setDropPending(false);
                     }
                 };
-                mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete,
-                        ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
+                mWorkspace.removeExtraEmptyScreenDelayed(
+                        ON_ACTIVITY_RESULT_ANIMATION_DELAY, false, onComplete);
             }
             return;
         }
@@ -836,12 +824,12 @@
             // Handle custom shortcuts created using ACTION_CREATE_SHORTCUT.
             if (resultCode == RESULT_OK && requestArgs.container != ItemInfo.NO_ID) {
                 completeAdd(requestCode, data, -1, requestArgs);
-                mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
-                        ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
+                mWorkspace.removeExtraEmptyScreenDelayed(
+                        ON_ACTIVITY_RESULT_ANIMATION_DELAY, false, exitSpringLoaded);
 
             } else if (resultCode == RESULT_CANCELED) {
-                mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
-                        ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
+                mWorkspace.removeExtraEmptyScreenDelayed(
+                        ON_ACTIVITY_RESULT_ANIMATION_DELAY, false, exitSpringLoaded);
             }
         }
 
@@ -940,8 +928,6 @@
         final int origDragLayerChildCount = mDragLayer.getChildCount();
         super.onStop();
 
-        mDragLayer.getViewTreeObserver().removeOnDrawListener(mOnDrawListener);
-
         if (mDeferOverlayCallbacks) {
             checkIfOverlayStillDeferred();
         } else {
@@ -954,7 +940,6 @@
 
         NotificationListener.removeNotificationsChangedListener();
         getStateManager().moveToRestState();
-        getBackgroundBlurController().setSurfaceToLauncher(null);
 
         // Workaround for b/78520668, explicitly trim memory once UI is hidden
         onTrimMemory(TRIM_MEMORY_UI_HIDDEN);
@@ -972,6 +957,8 @@
                 }
             });
         }
+
+        TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "Activity.onStop");
     }
 
     @Override
@@ -982,10 +969,10 @@
         if (!mDeferOverlayCallbacks) {
             mOverlayManager.onActivityStarted(this);
         }
-        mDragLayer.getViewTreeObserver().addOnDrawListener(mOnDrawListener);
 
         mAppWidgetHost.setListenIfResumed(true);
         TraceHelper.INSTANCE.endSection(traceToken);
+        TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "Activity.onStart");
     }
 
     private void handleDeferredResume() {
@@ -1115,7 +1102,7 @@
 
         super.onPause();
         mDragController.cancelDrag();
-        mDragController.resetLastGestureUpTime();
+        mLastTouchUpTime = -1;
         mDropTargetBar.animateToVisibility(false);
 
         if (!mDeferOverlayCallbacks) {
@@ -1178,7 +1165,6 @@
         mWorkspace = mDragLayer.findViewById(R.id.workspace);
         mWorkspace.initParentViews(mDragLayer);
         mOverviewPanel = findViewById(R.id.overview_panel);
-        mActionsView = findViewById(R.id.overview_actions_view);
         mHotseat = findViewById(R.id.hotseat);
 
         mLauncherView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
@@ -1205,7 +1191,6 @@
         mScrimView = findViewById(R.id.scrim_view);
 
         // Setup the drag controller (drop targets have to be added in reverse order in priority)
-        mDragController.setMoveTarget(mWorkspace);
         mDropTargetBar.setup(mDragController);
 
         mAllAppsController.setupViews(mAppsView);
@@ -1426,10 +1411,6 @@
         return (T) mOverviewPanel;
     }
 
-    public View getActionsView() {
-        return mActionsView;
-    }
-
     public DropTargetBar getDropTargetBar() {
         return mDropTargetBar;
     }
@@ -1505,11 +1486,7 @@
             target.pageIndex = mWorkspace.getCurrentPage();
             ued.logActionCommand(Action.Command.HOME_INTENT, target,
                     newContainerTarget(ContainerType.WORKSPACE));
-
-            final View v = getWindow().peekDecorView();
-            if (v != null && v.getWindowToken() != null) {
-                UiThreadHelper.hideKeyboardAsync(this, v.getWindowToken());
-            }
+            hideKeyboard();
 
             if (mLauncherCallbacks != null) {
                 mLauncherCallbacks.onHomeIntent(internalStateHandled);
@@ -1520,6 +1497,16 @@
         TraceHelper.INSTANCE.endSection(traceToken);
     }
 
+    /**
+     * Hides the keyboard if visible
+     */
+    public void hideKeyboard() {
+        final View v = getWindow().peekDecorView();
+        if (v != null && v.getWindowToken() != null) {
+            UiThreadHelper.hideKeyboardAsync(this, v.getWindowToken());
+        }
+    }
+
     @Override
     public void onRestoreInstanceState(Bundle state) {
         super.onRestoreInstanceState(state);
@@ -1587,6 +1574,7 @@
 
         mOverlayManager.onActivityDestroyed(this);
         mAppTransitionManager.unregisterRemoteAnimations();
+        mUserChangedCallbackCloseable.close();
     }
 
     public LauncherAccessibilityDelegate getAccessibilityDelegate() {
@@ -1680,7 +1668,7 @@
             };
             completeAddAppWidget(appWidgetId, info, boundWidget,
                     addFlowHandler.getProviderInfo(this));
-            mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, delay, false);
+            mWorkspace.removeExtraEmptyScreenDelayed(delay, false, onComplete);
         }
     }
 
@@ -1838,10 +1826,28 @@
 
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
+        switch (ev.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+                mTouchInProgress = true;
+                break;
+            case MotionEvent.ACTION_UP:
+                mLastTouchUpTime = System.currentTimeMillis();
+                // Follow through
+            case MotionEvent.ACTION_CANCEL:
+                mTouchInProgress = false;
+                break;
+        }
         TestLogging.recordMotionEvent(TestProtocol.SEQUENCE_MAIN, "Touch event", ev);
         return super.dispatchTouchEvent(ev);
     }
 
+    /**
+     * Returns true if a touch interaction is in progress
+     */
+    public boolean isTouchInProgress() {
+        return mTouchInProgress;
+    }
+
     @Override
     public void onBackPressed() {
         if (finishAutoCancelActionMode()) {
@@ -2107,7 +2113,7 @@
         }
 
         // Remove the extra empty screen
-        mWorkspace.removeExtraEmptyScreen(false, false);
+        mWorkspace.removeExtraEmptyScreen(false);
     }
 
     /**
@@ -2465,8 +2471,12 @@
     }
 
     private boolean canRunNewAppsAnimation() {
-        long diff = System.currentTimeMillis() - mDragController.getLastGestureUpTime();
-        return diff > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000);
+        if (mDragController.isDragging()) {
+            return false;
+        } else {
+            return (System.currentTimeMillis() - mLastTouchUpTime)
+                    > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000);
+        }
     }
 
     private ValueAnimator createNewAppBounceAnimation(View v, int i) {
@@ -2707,8 +2717,7 @@
     }
 
     protected StateHandler[] createStateHandlers() {
-        return new StateHandler[] { getAllAppsController(), getWorkspace(),
-                getBackgroundBlurController() };
+        return new StateHandler[] { getAllAppsController(), getWorkspace() };
     }
 
     public TouchController[] createTouchControllers() {
@@ -2745,10 +2754,6 @@
         return Stream.of(APP_INFO, WIDGETS, INSTALL);
     }
 
-    public BackgroundBlurController getBackgroundBlurController() {
-        return mBackgroundBlurController;
-    }
-
     public static Launcher getLauncher(Context context) {
         return fromContext(context);
     }
diff --git a/src/com/android/launcher3/LauncherAppWidgetInfo.java b/src/com/android/launcher3/LauncherAppWidgetInfo.java
index b824301..3a478dd 100644
--- a/src/com/android/launcher3/LauncherAppWidgetInfo.java
+++ b/src/com/android/launcher3/LauncherAppWidgetInfo.java
@@ -21,6 +21,7 @@
 import android.content.Intent;
 import android.os.Process;
 
+import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.model.PackageItemInfo;
 import com.android.launcher3.util.ContentWriter;
 
@@ -162,7 +163,9 @@
 
     @Override
     protected String dumpProperties() {
-        return super.dumpProperties() + " appWidgetId=" + appWidgetId;
+        return super.dumpProperties()
+                + " providerName=" + providerName
+                + " appWidgetId=" + appWidgetId;
     }
 
     public final boolean isWidgetIdAllocated() {
@@ -182,4 +185,13 @@
     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/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index a699c32..8d20bd6 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -85,6 +85,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Locale;
+import java.util.function.Supplier;
 
 public class LauncherProvider extends ContentProvider {
     private static final String TAG = "LauncherProvider";
@@ -145,7 +146,7 @@
      */
     protected synchronized void createDbIfNotExists() {
         if (mOpenHelper == null) {
-            mOpenHelper = new DatabaseHelper(getContext());
+            mOpenHelper = DatabaseHelper.createDatabaseHelper(getContext());
 
             if (RestoreDbTask.isPending(getContext())) {
                 if (!RestoreDbTask.performRestore(getContext(), mOpenHelper,
@@ -159,17 +160,17 @@
         }
     }
 
-    private synchronized boolean updateCurrentOpenHelper() {
-        final InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(getContext());
-        if (TextUtils.equals(idp.dbFile, mOpenHelper.getDatabaseName())) {
+    private synchronized boolean prepForMigration(String dbFile, String targetTableName,
+            Supplier<DatabaseHelper> src, Supplier<DatabaseHelper> dst) {
+        if (TextUtils.equals(dbFile, mOpenHelper.getDatabaseName())) {
             return false;
         }
 
-        DatabaseHelper oldHelper = mOpenHelper;
-        mOpenHelper = new DatabaseHelper(getContext());
-        copyTable(oldHelper.getReadableDatabase(), Favorites.TABLE_NAME,
-                mOpenHelper.getWritableDatabase(), Favorites.TMP_TABLE, getContext());
-        oldHelper.close();
+        final DatabaseHelper helper = src.get();
+        mOpenHelper = dst.get();
+        copyTable(helper.getReadableDatabase(), Favorites.TABLE_NAME,
+                mOpenHelper.getWritableDatabase(), targetTableName, getContext());
+        helper.close();
         return true;
     }
 
@@ -425,7 +426,23 @@
                 if (MULTI_DB_GRID_MIRATION_ALGO.get()) {
                     Bundle result = new Bundle();
                     result.putBoolean(LauncherSettings.Settings.EXTRA_VALUE,
-                            updateCurrentOpenHelper());
+                            prepForMigration(
+                                    InvariantDeviceProfile.INSTANCE.get(getContext()).dbFile,
+                                    Favorites.TMP_TABLE,
+                                    () -> mOpenHelper,
+                                    () -> DatabaseHelper.createDatabaseHelper(getContext())));
+                    return result;
+                }
+            }
+            case LauncherSettings.Settings.METHOD_PREP_FOR_PREVIEW: {
+                if (MULTI_DB_GRID_MIRATION_ALGO.get()) {
+                    Bundle result = new Bundle();
+                    result.putBoolean(LauncherSettings.Settings.EXTRA_VALUE,
+                            prepForMigration(
+                                    arg /* dbFile */,
+                                    Favorites.PREVIEW_TABLE_NAME,
+                                    () -> DatabaseHelper.createDatabaseHelper(getContext(), arg),
+                                    () -> mOpenHelper));
                     return result;
                 }
             }
@@ -596,23 +613,31 @@
         private int mMaxScreenId = -1;
         private boolean mBackupTableExists;
 
-        DatabaseHelper(Context context) {
-            this(context, MULTI_DB_GRID_MIRATION_ALGO.get() ? InvariantDeviceProfile.INSTANCE.get(
-                    context).dbFile : LauncherFiles.LAUNCHER_DB);
+        static DatabaseHelper createDatabaseHelper(Context context) {
+            return createDatabaseHelper(context, null);
+        }
+
+        static DatabaseHelper createDatabaseHelper(Context context, String dbName) {
+            if (dbName == null) {
+                dbName = MULTI_DB_GRID_MIRATION_ALGO.get() ? InvariantDeviceProfile.INSTANCE.get(
+                        context).dbFile : LauncherFiles.LAUNCHER_DB;
+            }
+            DatabaseHelper databaseHelper = new DatabaseHelper(context, dbName);
             // Table creation sometimes fails silently, which leads to a crash loop.
             // This way, we will try to create a table every time after crash, so the device
             // would eventually be able to recover.
-            if (!tableExists(getReadableDatabase(), Favorites.TABLE_NAME)) {
+            if (!tableExists(databaseHelper.getReadableDatabase(), Favorites.TABLE_NAME)) {
                 Log.e(TAG, "Tables are missing after onCreate has been called. Trying to recreate");
                 // This operation is a no-op if the table already exists.
-                addFavoritesTable(getWritableDatabase(), true);
+                databaseHelper.addFavoritesTable(databaseHelper.getWritableDatabase(), true);
             }
             if (!MULTI_DB_GRID_MIRATION_ALGO.get()) {
-                mBackupTableExists = tableExists(getReadableDatabase(),
-                        Favorites.BACKUP_TABLE_NAME);
+                databaseHelper.mBackupTableExists = tableExists(
+                        databaseHelper.getReadableDatabase(), Favorites.BACKUP_TABLE_NAME);
             }
 
-            initIds();
+            databaseHelper.initIds();
+            return databaseHelper;
         }
 
         /**
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index f516446..5262b18 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -326,10 +326,16 @@
 
         public static final String METHOD_UPDATE_CURRENT_OPEN_HELPER = "update_current_open_helper";
 
+        public static final String METHOD_PREP_FOR_PREVIEW = "prep_for_preview";
+
         public static final String EXTRA_VALUE = "value";
 
         public static Bundle call(ContentResolver cr, String method) {
-            return cr.call(CONTENT_URI, method, null, null);
+            return call(cr, method, null);
+        }
+
+        public static Bundle call(ContentResolver cr, String method, String arg) {
+            return cr.call(CONTENT_URI, method, arg, null);
         }
     }
 }
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index df71f16..504666a 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -73,7 +73,7 @@
     public static final int ALL_APPS_HEADER_EXTRA = 1 << 3; // e.g. app predictions
     public static final int ALL_APPS_CONTENT = 1 << 4;
     public static final int VERTICAL_SWIPE_INDICATOR = 1 << 5;
-    public static final int RECENTS_CLEAR_ALL_BUTTON = 1 << 6;
+    public static final int OVERVIEW_BUTTONS = 1 << 6;
 
     /** Mask of all the items that are contained in the apps view. */
     public static final int APPS_VIEW_ITEM_MASK =
@@ -271,11 +271,13 @@
     }
 
     /**
-     * The amount of blur to apply to the background of either the app or Launcher surface in this
-     * state.
+     * The amount of blur and wallpaper zoom to apply to the background of either the app
+     * or Launcher surface in this state. Should be a number between 0 and 1, inclusive.
+     *
+     * 0 means completely zoomed in, without blurs. 1 is zoomed out, with blurs.
      */
-    public int getBackgroundBlurRadius(Context context) {
-        return 0;
+    public float getDepth(Context context) {
+        return 0f;
     }
 
     public String getDescription(Launcher launcher) {
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
index 24d0c41..e071777 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -324,6 +324,7 @@
 
     public AnimatorPlaybackController createAnimationToNewWorkspace(LauncherState state,
             StateAnimationConfig config) {
+        config.userControlled = true;
         mConfig.reset();
         config.copyTo(mConfig);
         mConfig.playbackController = createAnimationToNewWorkspaceInternal(state)
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index e38631d..7d7739e 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -50,6 +50,8 @@
 import static com.android.launcher3.touch.PagedOrientationHandler.VIEW_SCROLL_BY;
 import static com.android.launcher3.touch.PagedOrientationHandler.VIEW_SCROLL_TO;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
@@ -133,6 +135,7 @@
     protected int mActivePointerId = INVALID_POINTER;
 
     protected boolean mIsPageInTransition = false;
+    private Runnable mOnPageTransitionEndCallback;
 
     protected float mSpringOverScroll;
 
@@ -391,6 +394,22 @@
         AccessibilityManagerCompat.sendScrollFinishedEventToTest(getContext());
         AccessibilityManagerCompat.sendCustomAccessibilityEvent(getPageAt(mCurrentPage),
                 AccessibilityEvent.TYPE_VIEW_FOCUSED, null);
+        if (mOnPageTransitionEndCallback != null) {
+            mOnPageTransitionEndCallback.run();
+            mOnPageTransitionEndCallback = null;
+        }
+    }
+
+    /**
+     * Sets a callback to run once when the scrolling finishes. If there is currently
+     * no page in transition, then the callback is called immediately.
+     */
+    public void setOnPageTransitionEndCallback(@Nullable Runnable callback) {
+        if (mIsPageInTransition || callback == null) {
+            mOnPageTransitionEndCallback = callback;
+        } else {
+            callback.run();
+        }
     }
 
     protected int getUnboundedScroll() {
diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java
index 2430d5e..983c289 100644
--- a/src/com/android/launcher3/SecondaryDropTarget.java
+++ b/src/com/android/launcher3/SecondaryDropTarget.java
@@ -108,7 +108,7 @@
             updateText(R.string.uninstall_drop_target_label);
         } else if (action == DISMISS_PREDICTION) {
             mHoverColor = Themes.getColorAccent(getContext());
-            setDrawable(R.drawable.ic_block);
+            setDrawable(R.drawable.ic_block_shadow);
             updateText(R.string.dismiss_prediction_label);
         } else if (action == RECONFIGURE) {
             mHoverColor = Themes.getColorAccent(getContext());
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index 1bd8263..c07dd9d 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -145,38 +145,46 @@
             final View child = getChildAt(i);
             if (child.getVisibility() != GONE) {
                 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
-
-                if (child instanceof LauncherAppWidgetHostView) {
-                    LauncherAppWidgetHostView lahv = (LauncherAppWidgetHostView) child;
-
-                    // Scale and center the widget to fit within its cells.
-                    DeviceProfile profile = mActivity.getDeviceProfile();
-                    float scaleX = profile.appWidgetScale.x;
-                    float scaleY = profile.appWidgetScale.y;
-
-                    lahv.setScaleToFit(Math.min(scaleX, scaleY));
-                    lahv.setTranslationForCentering(-(lp.width - (lp.width * scaleX)) / 2.0f,
-                            -(lp.height - (lp.height * scaleY)) / 2.0f);
-                }
-
-                int childLeft = lp.x;
-                int childTop = lp.y;
-                child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);
-
-                if (lp.dropped) {
-                    lp.dropped = false;
-
-                    final int[] cellXY = mTmpCellXY;
-                    getLocationOnScreen(cellXY);
-                    mWallpaperManager.sendWallpaperCommand(getWindowToken(),
-                            WallpaperManager.COMMAND_DROP,
-                            cellXY[0] + childLeft + lp.width / 2,
-                            cellXY[1] + childTop + lp.height / 2, 0, null);
-                }
+                layoutChild(child);
             }
         }
     }
 
+    /**
+     * Core logic to layout a child for this ViewGroup.
+     */
+    public void layoutChild(View child) {
+        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
+        if (child instanceof LauncherAppWidgetHostView) {
+            LauncherAppWidgetHostView lahv = (LauncherAppWidgetHostView) child;
+
+            // Scale and center the widget to fit within its cells.
+            DeviceProfile profile = mActivity.getDeviceProfile();
+            float scaleX = profile.appWidgetScale.x;
+            float scaleY = profile.appWidgetScale.y;
+
+            lahv.setScaleToFit(Math.min(scaleX, scaleY));
+            lahv.setTranslationForCentering(-(lp.width - (lp.width * scaleX)) / 2.0f,
+                    -(lp.height - (lp.height * scaleY)) / 2.0f);
+        }
+
+        int childLeft = lp.x;
+        int childTop = lp.y;
+        child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);
+
+        if (lp.dropped) {
+            lp.dropped = false;
+
+            final int[] cellXY = mTmpCellXY;
+            getLocationOnScreen(cellXY);
+            mWallpaperManager.sendWallpaperCommand(getWindowToken(),
+                    WallpaperManager.COMMAND_DROP,
+                    cellXY[0] + childLeft + lp.width / 2,
+                    cellXY[1] + childTop + lp.height / 2, 0, null);
+        }
+    }
+
+
     @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         if (ev.getAction() == ACTION_DOWN && getAlpha() == 0) {
diff --git a/src/com/android/launcher3/SimpleOnStylusPressListener.java b/src/com/android/launcher3/SimpleOnStylusPressListener.java
deleted file mode 100644
index 6b97dce..0000000
--- a/src/com/android/launcher3/SimpleOnStylusPressListener.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.android.launcher3;
-
-import android.view.MotionEvent;
-import android.view.View;
-
-import com.android.launcher3.StylusEventHelper.StylusButtonListener;
-
-/**
- * Simple listener that performs a long click on the view after a stylus button press.
- */
-public class SimpleOnStylusPressListener implements StylusButtonListener {
-    private View mView;
-
-    public SimpleOnStylusPressListener(View view) {
-        mView = view;
-    }
-
-    public boolean onPressed(MotionEvent event) {
-        return mView.isLongClickable() && mView.performLongClick();
-    }
-
-    public boolean onReleased(MotionEvent event) {
-        return false;
-    }
-}
\ No newline at end of file
diff --git a/src/com/android/launcher3/StylusEventHelper.java b/src/com/android/launcher3/StylusEventHelper.java
deleted file mode 100644
index d5fc0fa..0000000
--- a/src/com/android/launcher3/StylusEventHelper.java
+++ /dev/null
@@ -1,109 +0,0 @@
-package com.android.launcher3;
-
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewConfiguration;
-
-/**
- * Helper for identifying when a stylus touches a view while the primary stylus button is pressed.
- * This can occur in {@value MotionEvent#ACTION_DOWN} or {@value MotionEvent#ACTION_MOVE}.
- */
-public class StylusEventHelper {
-
-    /**
-     * Implement this interface to receive callbacks for a stylus button press and release.
-     */
-    public interface StylusButtonListener {
-        /**
-         * Called when the stylus button is pressed.
-         *
-         * @param event The MotionEvent that the button press occurred for.
-         * @return Whether the event was handled.
-         */
-        public boolean onPressed(MotionEvent event);
-
-        /**
-         * Called when the stylus button is released after a button press. This is also called if
-         * the event is canceled or the stylus is lifted off the screen.
-         *
-         * @param event The MotionEvent the button release occurred for.
-         * @return Whether the event was handled.
-         */
-        public boolean onReleased(MotionEvent event);
-    }
-
-    private boolean mIsButtonPressed;
-    private View mView;
-    private StylusButtonListener mListener;
-    private final float mSlop;
-
-    /**
-     * Constructs a helper for listening to stylus button presses and releases. Ensure that {
-     * {@link #onMotionEvent(MotionEvent)} and {@link #onGenericMotionEvent(MotionEvent)} are called on
-     * the helper to correctly identify stylus events.
-     *
-     * @param listener The listener to call for stylus events.
-     * @param view Optional view associated with the touch events.
-     */
-    public StylusEventHelper(StylusButtonListener listener, View view) {
-        mListener = listener;
-        mView = view;
-        if (mView != null) {
-            mSlop = ViewConfiguration.get(mView.getContext()).getScaledTouchSlop();
-        } else {
-            mSlop = ViewConfiguration.getTouchSlop();
-        }
-    }
-
-    public boolean onMotionEvent(MotionEvent event) {
-        final boolean stylusButtonPressed = isStylusButtonPressed(event);
-        switch (event.getAction()) {
-            case MotionEvent.ACTION_DOWN:
-                mIsButtonPressed = stylusButtonPressed;
-                if (mIsButtonPressed) {
-                    return mListener.onPressed(event);
-                }
-                break;
-            case MotionEvent.ACTION_MOVE:
-                if (!Utilities.pointInView(mView, event.getX(), event.getY(), mSlop)) {
-                    return false;
-                }
-                if (!mIsButtonPressed && stylusButtonPressed) {
-                    mIsButtonPressed = true;
-                    return mListener.onPressed(event);
-                } else if (mIsButtonPressed && !stylusButtonPressed) {
-                    mIsButtonPressed = false;
-                    return mListener.onReleased(event);
-                }
-                break;
-            case MotionEvent.ACTION_UP:
-            case MotionEvent.ACTION_CANCEL:
-                if (mIsButtonPressed) {
-                    mIsButtonPressed = false;
-                    return mListener.onReleased(event);
-                }
-                break;
-        }
-        return false;
-    }
-
-    /**
-     * Whether a stylus button press is occurring.
-     */
-    public boolean inStylusButtonPressed() {
-        return mIsButtonPressed;
-    }
-
-    /**
-     * Identifies if the provided {@link MotionEvent} is a stylus with the primary stylus button
-     * pressed.
-     *
-     * @param event The event to check.
-     * @return Whether a stylus button press occurred.
-     */
-    private static boolean isStylusButtonPressed(MotionEvent event) {
-        return event.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS
-                && ((event.getButtonState() & MotionEvent.BUTTON_SECONDARY)
-                        == MotionEvent.BUTTON_SECONDARY);
-    }
-}
\ No newline at end of file
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index ead6018..8bc0242 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -29,7 +29,6 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.LayoutTransition;
-import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.annotation.SuppressLint;
@@ -43,7 +42,6 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
-import android.os.IBinder;
 import android.os.Message;
 import android.os.Parcelable;
 import android.os.UserHandle;
@@ -70,6 +68,7 @@
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.dragndrop.SpringLoadedDragController;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
@@ -81,7 +80,6 @@
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.pageindicators.WorkspacePageIndicator;
 import com.android.launcher3.popup.PopupContainerWithArrow;
-import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.touch.WorkspaceTouchListener;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
@@ -125,9 +123,6 @@
 
     private static final boolean ENFORCE_DRAG_EVENT_ORDER = false;
 
-    private static final int SNAP_OFF_EMPTY_SCREEN_DURATION = 400;
-    private static final int FADE_EMPTY_SCREEN_DURATION = 150;
-
     private static final int ADJACENT_SCREEN_DROP_DURATION = 300;
 
     private static final int DEFAULT_PAGE = 0;
@@ -140,7 +135,6 @@
     @Thunk final IntSparseArrayMap<CellLayout> mWorkspaceScreens = new IntSparseArrayMap<>();
     @Thunk final IntArray mScreenOrder = new IntArray();
 
-    @Thunk Runnable mRemoveEmptyScreenRunnable;
     @Thunk boolean mDeferRemoveExtraEmptyScreen = false;
 
     /**
@@ -192,10 +186,7 @@
     final WallpaperOffsetInterpolator mWallpaperOffset;
     private boolean mUnlockWallpaperFromDefaultPageOnLayout;
 
-    // Variables relating to the creation of user folders by hovering shortcuts over shortcuts
-    private static final int FOLDER_CREATION_TIMEOUT = 0;
     public static final int REORDER_TIMEOUT = 650;
-    private final Alarm mFolderCreationAlarm = new Alarm();
     private final Alarm mReorderAlarm = new Alarm();
     private PreviewBackground mFolderCreateBg;
     private FolderIcon mDragOverFolderIcon = null;
@@ -431,7 +422,7 @@
         }
 
         if (!mDeferRemoveExtraEmptyScreen) {
-            removeExtraEmptyScreen(true, mDragSourceInternal != null);
+            removeExtraEmptyScreen(mDragSourceInternal != null);
         }
 
         updateChildrenLayersEnabled();
@@ -456,8 +447,16 @@
     private void setupLayoutTransition() {
         // We want to show layout transitions when pages are deleted, to close the gap.
         mLayoutTransition = new LayoutTransition();
+
         mLayoutTransition.enableTransitionType(LayoutTransition.DISAPPEARING);
         mLayoutTransition.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
+        // Change the interpolators such that the fade animation plays before the move animation.
+        // This prevents empty adjacent pages to overlay during animation
+        mLayoutTransition.setInterpolator(LayoutTransition.DISAPPEARING,
+                Interpolators.clampToProgress(Interpolators.ACCEL_DEACCEL, 0, 0.5f));
+        mLayoutTransition.setInterpolator(LayoutTransition.CHANGE_DISAPPEARING,
+                Interpolators.clampToProgress(Interpolators.ACCEL_DEACCEL, 0.5f, 1));
+
         mLayoutTransition.disableTransitionType(LayoutTransition.APPEARING);
         mLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
         setLayoutTransition(mLayoutTransition);
@@ -567,11 +566,6 @@
         addView(newScreen, insertIndex);
         mStateTransitionAnimation.applyChildState(
                 mLauncher.getStateManager().getState(), newScreen, insertIndex);
-
-        if (mLauncher.getAccessibilityDelegate().isInAccessibleDrag()) {
-            newScreen.enableAccessibleDrag(true, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG);
-        }
-
         return newScreen;
     }
 
@@ -579,9 +573,6 @@
         boolean lastChildOnScreen = false;
         boolean childOnFinalScreen = false;
 
-        // Cancel any pending removal of empty screen
-        mRemoveEmptyScreenRunnable = null;
-
         if (mDragSourceInternal != null) {
             if (mDragSourceInternal.getChildCount() == 1) {
                 lastChildOnScreen = true;
@@ -632,43 +623,34 @@
         }
     }
 
-    public void removeExtraEmptyScreen(final boolean animate, boolean stripEmptyScreens) {
-        removeExtraEmptyScreenDelayed(animate, null, 0, stripEmptyScreens);
+    public void removeExtraEmptyScreen(boolean stripEmptyScreens) {
+        removeExtraEmptyScreenDelayed(0, stripEmptyScreens, null);
     }
 
-    public void removeExtraEmptyScreenDelayed(final boolean animate, final Runnable onComplete,
-            final int delay, final boolean stripEmptyScreens) {
+    public void removeExtraEmptyScreenDelayed(
+            int delay, boolean stripEmptyScreens, Runnable onComplete) {
         if (mLauncher.isWorkspaceLoading()) {
             // Don't strip empty screens if the workspace is still loading
             return;
         }
 
         if (delay > 0) {
-            postDelayed(new Runnable() {
-                @Override
-                public void run() {
-                    removeExtraEmptyScreenDelayed(animate, onComplete, 0, stripEmptyScreens);
-                }
-            }, delay);
+            postDelayed(
+                    () -> removeExtraEmptyScreenDelayed(0, stripEmptyScreens, onComplete), delay);
             return;
         }
 
         convertFinalScreenToEmptyScreenIfNecessary();
         if (hasExtraEmptyScreen()) {
-            int emptyIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
-            if (getNextPage() == emptyIndex) {
-                snapToPage(getNextPage() - 1, SNAP_OFF_EMPTY_SCREEN_DURATION);
-                fadeAndRemoveEmptyScreen(SNAP_OFF_EMPTY_SCREEN_DURATION, FADE_EMPTY_SCREEN_DURATION,
-                        onComplete, stripEmptyScreens);
-            } else {
-                snapToPage(getNextPage(), 0);
-                fadeAndRemoveEmptyScreen(0, FADE_EMPTY_SCREEN_DURATION,
-                        onComplete, stripEmptyScreens);
-            }
-            return;
-        } else if (stripEmptyScreens) {
-            // If we're not going to strip the empty screens after removing
-            // the extra empty screen, do it right away.
+            removeView(mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID));
+            mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID);
+            mScreenOrder.removeValue(EXTRA_EMPTY_SCREEN_ID);
+
+            // Update the page indicator to reflect the removed page.
+            showPageIndicatorAtCurrentScroll();
+        }
+
+        if (stripEmptyScreens) {
             stripEmptyScreens();
         }
 
@@ -677,44 +659,6 @@
         }
     }
 
-    private void fadeAndRemoveEmptyScreen(int delay, int duration, final Runnable onComplete,
-            final boolean stripEmptyScreens) {
-        // XXX: Do we need to update LM workspace screens below?
-        final CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID);
-
-        mRemoveEmptyScreenRunnable = new Runnable() {
-            @Override
-            public void run() {
-                if (hasExtraEmptyScreen()) {
-                    mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID);
-                    mScreenOrder.removeValue(EXTRA_EMPTY_SCREEN_ID);
-                    removeView(cl);
-                    if (stripEmptyScreens) {
-                        stripEmptyScreens();
-                    }
-                    // Update the page indicator to reflect the removed page.
-                    showPageIndicatorAtCurrentScroll();
-                }
-            }
-        };
-
-        ObjectAnimator oa = ObjectAnimator.ofFloat(cl, ALPHA, 0f);
-        oa.setDuration(duration);
-        oa.setStartDelay(delay);
-        oa.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                if (mRemoveEmptyScreenRunnable != null) {
-                    mRemoveEmptyScreenRunnable.run();
-                }
-                if (onComplete != null) {
-                    onComplete.run();
-                }
-            }
-        });
-        oa.start();
-    }
-
     public boolean hasExtraEmptyScreen() {
         return mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID) && getChildCount() > 1;
     }
@@ -801,8 +745,6 @@
             }
         }
 
-        boolean isInAccessibleDrag = mLauncher.getAccessibilityDelegate().isInAccessibleDrag();
-
         // We enforce at least one page to add new items to. In the case that we remove the last
         // such screen, we convert the last screen to the empty screen
         int minScreens = 1;
@@ -818,15 +760,9 @@
                 if (indexOfChild(cl) < currentPage) {
                     pageShift++;
                 }
-
-                if (isInAccessibleDrag) {
-                    cl.enableAccessibleDrag(false, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG);
-                }
-
                 removeView(cl);
             } else {
                 // if this is the last screen, convert it to the empty screen
-                mRemoveEmptyScreenRunnable = null;
                 mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, cl);
                 mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID);
             }
@@ -1055,7 +991,6 @@
             if (!mOverlayShown) {
                 mLauncher.getUserEventDispatcher().logActionOnContainer(Action.Touch.SWIPE,
                         Action.Direction.LEFT, ContainerType.WORKSPACE, 0);
-                mLauncher.getStatsLogManager().logSwipeOnContainer(true, 0);
             }
             mOverlayShown = true;
             // Not announcing the overlay page for accessibility since it announces itself.
@@ -1065,7 +1000,6 @@
                 if (!ued.isPreviousHomeGesture()) {
                     mLauncher.getUserEventDispatcher().logActionOnContainer(Action.Touch.SWIPE,
                         Action.Direction.RIGHT, ContainerType.WORKSPACE, -1);
-                    mLauncher.getStatsLogManager().logSwipeOnContainer(false, -1);
                 }
             } else if (Float.compare(mOverlayTranslation, 0f) != 0) {
                 // When arriving to 0 overscroll from non-zero overscroll, announce page for
@@ -1237,10 +1171,8 @@
 
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        IBinder windowToken = getWindowToken();
-        mWallpaperOffset.setWindowToken(windowToken);
+        mWallpaperOffset.setWindowToken(getWindowToken());
         computeScroll();
-        mDragController.setWindowToken(windowToken);
     }
 
     protected void onDetachedFromWindow() {
@@ -1444,14 +1376,14 @@
         child.setVisibility(INVISIBLE);
 
         if (options.isAccessibleDrag) {
-            mDragController.addDragListener(new AccessibleDragListenerAdapter(
-                    this, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG) {
-                @Override
-                protected void enableAccessibleDrag(boolean enable) {
-                    super.enableAccessibleDrag(enable);
-                    setEnableForLayout(mLauncher.getHotseat(),enable);
-                }
-            });
+            mDragController.addDragListener(
+                    new AccessibleDragListenerAdapter(this, WorkspaceAccessibilityHelper::new) {
+                        @Override
+                        protected void enableAccessibleDrag(boolean enable) {
+                            super.enableAccessibleDrag(enable);
+                            setEnableForLayout(mLauncher.getHotseat(), enable);
+                        }
+                    });
         }
 
         beginDragShared(child, this, options);
@@ -1465,12 +1397,17 @@
                     + "View: " + child + "  tag: " + child.getTag();
             throw new IllegalStateException(msg);
         }
-        beginDragShared(child, source, (ItemInfo) dragObject,
+        beginDragShared(child, null, source, (ItemInfo) dragObject,
                 new DragPreviewProvider(child), options);
     }
 
-    public DragView beginDragShared(View child, DragSource source, ItemInfo dragObject,
-            DragPreviewProvider previewProvider, DragOptions dragOptions) {
+    /**
+     * Core functionality for beginning a drag operation for an item that will be dropped within
+     * the workspace
+     */
+    public DragView beginDragShared(View child, DraggableView draggableView, DragSource source,
+            ItemInfo dragObject, DragPreviewProvider previewProvider, DragOptions dragOptions) {
+
         float iconScale = 1f;
         if (child instanceof BubbleTextView) {
             Drawable icon = ((BubbleTextView) child).getIcon();
@@ -1479,41 +1416,36 @@
             }
         }
 
+        // Clear the pressed state if necessary
         child.clearFocus();
         child.setPressed(false);
+        if (child instanceof BubbleTextView) {
+            BubbleTextView icon = (BubbleTextView) child;
+            icon.clearPressedBackground();
+        }
+
         mOutlineProvider = previewProvider;
 
         // The drag bitmap follows the touch point around on the screen
         final Bitmap b = previewProvider.createDragBitmap();
         int halfPadding = previewProvider.previewPadding / 2;
-
         float scale = previewProvider.getScaleAndPosition(b, mTempXY);
         int dragLayerX = mTempXY[0];
         int dragLayerY = mTempXY[1];
 
-        DeviceProfile grid = mLauncher.getDeviceProfile();
         Point dragVisualizeOffset = null;
-        Rect dragRect = null;
-        if (child instanceof BubbleTextView) {
-            dragRect = new Rect();
-            BubbleTextView.getIconBounds(child, dragRect, grid.iconSizePx);
+        Rect dragRect = new Rect();
+
+        if (draggableView == null && child instanceof DraggableView) {
+            draggableView = (DraggableView) child;
+        }
+
+        if (draggableView != null) {
+            draggableView.getVisualDragBounds(dragRect);
             dragLayerY += dragRect.top;
-            // Note: The dragRect is used to calculate drag layer offsets, but the
-            // dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
-            dragVisualizeOffset = new Point(- halfPadding, halfPadding);
-        } else if (child instanceof FolderIcon) {
-            int previewSize = grid.folderIconSizePx;
-            dragVisualizeOffset = new Point(- halfPadding, halfPadding - child.getPaddingTop());
-            dragRect = new Rect(0, child.getPaddingTop(), child.getWidth(), previewSize);
-        } else if (previewProvider instanceof ShortcutDragPreviewProvider) {
             dragVisualizeOffset = new Point(- halfPadding, halfPadding);
         }
 
-        // Clear the pressed state if necessary
-        if (child instanceof BubbleTextView) {
-            BubbleTextView icon = (BubbleTextView) child;
-            icon.clearPressedBackground();
-        }
 
         if (child.getParent() instanceof ShortcutAndWidgetContainer) {
             mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
@@ -1524,13 +1456,13 @@
                     .showForIcon((BubbleTextView) child);
             if (popupContainer != null) {
                 dragOptions.preDragCondition = popupContainer.createPreDragCondition();
-
                 mLauncher.getUserEventDispatcher().resetElapsedContainerMillis("dragging started");
             }
         }
 
-        DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source,
-                dragObject, dragVisualizeOffset, dragRect, scale * iconScale, scale, dragOptions);
+        DragView dv = mDragController.startDrag(b, draggableView, dragLayerX, dragLayerY, source,
+                dragObject, dragVisualizeOffset, dragRect, scale * iconScale,
+                scale, dragOptions);
         dv.setIntrinsicIconScaleFactor(dragOptions.intrinsicIconScaleFactor);
         return dv;
     }
@@ -1883,12 +1815,10 @@
                         final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell;
                         AppWidgetProviderInfo pInfo = hostView.getAppWidgetInfo();
                         if (pInfo != null && pInfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE
-                                && !d.accessibleDrag) {
-                            onCompleteRunnable = new Runnable() {
-                                public void run() {
-                                    if (!isPageInTransition()) {
-                                        AppWidgetResizeFrame.showForWidget(hostView, cellLayout);
-                                    }
+                                && !options.isAccessibleDrag) {
+                            onCompleteRunnable = () -> {
+                                if (!isPageInTransition()) {
+                                    AppWidgetResizeFrame.showForWidget(hostView, cellLayout);
                                 }
                             };
                         }
@@ -2088,8 +2018,6 @@
         if (mFolderCreateBg != null) {
             mFolderCreateBg.animateToRest();
         }
-        mFolderCreationAlarm.setOnAlarmListener(null);
-        mFolderCreationAlarm.cancelAlarm();
     }
 
     private void cleanupAddToFolder() {
@@ -2196,14 +2124,14 @@
             float targetCellDistance = mDragTargetLayout.getDistanceFromCell(
                     mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell);
 
-            manageFolderFeedback(mDragTargetLayout, mTargetCell, targetCellDistance, d);
+            manageFolderFeedback(targetCellDistance, d);
 
             boolean nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied((int)
                     mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX,
                     item.spanY, child, mTargetCell);
 
             if (!nearestDropOccupied) {
-                mDragTargetLayout.visualizeDropLocation(child, mOutlineProvider,
+                mDragTargetLayout.visualizeDropLocation(d.originalView, mOutlineProvider,
                         mTargetCell[0], mTargetCell[1], item.spanX, item.spanY, false, d);
             } else if ((mDragMode == DRAG_MODE_NONE || mDragMode == DRAG_MODE_REORDER)
                     && !mReorderAlarm.alarmPending() && (mLastReorderX != reorderX ||
@@ -2294,10 +2222,10 @@
         return null;
     }
 
-    private void manageFolderFeedback(CellLayout targetLayout,
-            int[] targetCell, float distance, DragObject dragObject) {
+    private void manageFolderFeedback(float distance, DragObject dragObject) {
         if (distance > mMaxDistanceForFolderCreation) {
-            if (mDragMode != DRAG_MODE_NONE) {
+            if ((mDragMode == DRAG_MODE_ADD_TO_FOLDER
+                    || mDragMode == DRAG_MODE_CREATE_FOLDER)) {
                 setDragMode(DRAG_MODE_NONE);
             }
             return;
@@ -2306,18 +2234,18 @@
         final View dragOverView = mDragTargetLayout.getChildAt(mTargetCell[0], mTargetCell[1]);
         ItemInfo info = dragObject.dragInfo;
         boolean userFolderPending = willCreateUserFolder(info, dragOverView, false);
-        if (mDragMode == DRAG_MODE_NONE && userFolderPending &&
-                !mFolderCreationAlarm.alarmPending()) {
+        if (mDragMode == DRAG_MODE_NONE && userFolderPending) {
 
-            FolderCreationAlarmListener listener = new
-                    FolderCreationAlarmListener(targetLayout, targetCell[0], targetCell[1]);
+            mFolderCreateBg = new PreviewBackground();
+            mFolderCreateBg.setup(mLauncher, mLauncher, null,
+                    dragOverView.getMeasuredWidth(), dragOverView.getPaddingTop());
 
-            if (!dragObject.accessibleDrag) {
-                mFolderCreationAlarm.setOnAlarmListener(listener);
-                mFolderCreationAlarm.setAlarm(FOLDER_CREATION_TIMEOUT);
-            } else {
-                listener.onAlarm(mFolderCreationAlarm);
-            }
+            // The full preview background should appear behind the icon
+            mFolderCreateBg.isClipping = false;
+
+            mFolderCreateBg.animateToAccept(mDragTargetLayout, mTargetCell[0], mTargetCell[1]);
+            mDragTargetLayout.clearDragOutlines();
+            setDragMode(DRAG_MODE_CREATE_FOLDER);
 
             if (dragObject.stateAnnouncer != null) {
                 dragObject.stateAnnouncer.announce(WorkspaceAccessibilityHelper
@@ -2330,8 +2258,8 @@
         if (willAddToFolder && mDragMode == DRAG_MODE_NONE) {
             mDragOverFolderIcon = ((FolderIcon) dragOverView);
             mDragOverFolderIcon.onDragEnter(info);
-            if (targetLayout != null) {
-                targetLayout.clearDragOutlines();
+            if (mDragTargetLayout != null) {
+                mDragTargetLayout.clearDragOutlines();
             }
             setDragMode(DRAG_MODE_ADD_TO_FOLDER);
 
@@ -2350,33 +2278,6 @@
         }
     }
 
-    class FolderCreationAlarmListener implements OnAlarmListener {
-        final CellLayout layout;
-        final int cellX;
-        final int cellY;
-
-        final PreviewBackground bg = new PreviewBackground();
-
-        public FolderCreationAlarmListener(CellLayout layout, int cellX, int cellY) {
-            this.layout = layout;
-            this.cellX = cellX;
-            this.cellY = cellY;
-
-            BubbleTextView cell = (BubbleTextView) layout.getChildAt(cellX, cellY);
-            bg.setup(mLauncher, mLauncher, null, cell.getMeasuredWidth(), cell.getPaddingTop());
-
-            // The full preview background should appear behind the icon
-            bg.isClipping = false;
-        }
-
-        public void onAlarm(Alarm alarm) {
-            mFolderCreateBg = bg;
-            mFolderCreateBg.animateToAccept(layout, cellX, cellY);
-            layout.clearDragOutlines();
-            setDragMode(DRAG_MODE_CREATE_FOLDER);
-        }
-    }
-
     class ReorderAlarmListener implements OnAlarmListener {
         final float[] dragViewCenter;
         final int minSpanX, minSpanY, spanX, spanY;
@@ -2413,7 +2314,7 @@
             }
 
             boolean resize = resultSpan[0] != spanX || resultSpan[1] != spanY;
-            mDragTargetLayout.visualizeDropLocation(child, mOutlineProvider,
+            mDragTargetLayout.visualizeDropLocation(dragObject.originalView, mOutlineProvider,
                 mTargetCell[0], mTargetCell[1], resultSpan[0], resultSpan[1], resize, dragObject);
         }
     }
diff --git a/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java b/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java
index f8df5d7..0d7df2b 100644
--- a/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java
+++ b/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java
@@ -16,7 +16,9 @@
 
 package com.android.launcher3.accessibility;
 
+import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewGroup.OnHierarchyChangeListener;
 
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DropTarget.DragObject;
@@ -24,36 +26,55 @@
 import com.android.launcher3.dragndrop.DragController.DragListener;
 import com.android.launcher3.dragndrop.DragOptions;
 
+import java.util.function.Function;
+
 /**
  * Utility listener to enable/disable accessibility drag flags for a ViewGroup
  * containing CellLayouts
  */
-public class AccessibleDragListenerAdapter implements DragListener {
+public class AccessibleDragListenerAdapter implements DragListener, OnHierarchyChangeListener {
 
     private final ViewGroup mViewGroup;
-    private final int mDragType;
+    private final Function<CellLayout, DragAndDropAccessibilityDelegate> mDelegateFactory;
 
     /**
-     * @param parent
-     * @param dragType either {@link CellLayout#WORKSPACE_ACCESSIBILITY_DRAG} or
-     *                 {@link CellLayout#FOLDER_ACCESSIBILITY_DRAG}
+     * @param parent the viewgroup containing all the children
+     * @param delegateFactory function to create no delegates
      */
-    public AccessibleDragListenerAdapter(ViewGroup parent, int dragType) {
+    public AccessibleDragListenerAdapter(ViewGroup parent,
+            Function<CellLayout, DragAndDropAccessibilityDelegate> delegateFactory) {
         mViewGroup = parent;
-        mDragType = dragType;
+        mDelegateFactory = delegateFactory;
     }
 
     @Override
     public void onDragStart(DragObject dragObject, DragOptions options) {
+        mViewGroup.setOnHierarchyChangeListener(this);
         enableAccessibleDrag(true);
     }
 
     @Override
     public void onDragEnd() {
+        mViewGroup.setOnHierarchyChangeListener(null);
         enableAccessibleDrag(false);
         Launcher.getLauncher(mViewGroup.getContext()).getDragController().removeDragListener(this);
     }
 
+
+    @Override
+    public void onChildViewAdded(View parent, View child) {
+        if (parent == mViewGroup) {
+            setEnableForLayout((CellLayout) child, true);
+        }
+    }
+
+    @Override
+    public void onChildViewRemoved(View parent, View child) {
+        if (parent == mViewGroup) {
+            setEnableForLayout((CellLayout) child, false);
+        }
+    }
+
     protected void enableAccessibleDrag(boolean enable) {
         for (int i = 0; i < mViewGroup.getChildCount(); i++) {
             setEnableForLayout((CellLayout) mViewGroup.getChildAt(i), enable);
@@ -61,6 +82,6 @@
     }
 
     protected final void setEnableForLayout(CellLayout layout, boolean enable) {
-        layout.enableAccessibleDrag(enable, mDragType);
+        layout.setDragAndDropAccessibilityDelegate(enable ? mDelegateFactory.apply(layout) : null);
     }
 }
diff --git a/src/com/android/launcher3/accessibility/DragAndDropAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/DragAndDropAccessibilityDelegate.java
index 117296d..ddb547f 100644
--- a/src/com/android/launcher3/accessibility/DragAndDropAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/DragAndDropAccessibilityDelegate.java
@@ -19,24 +19,26 @@
 import android.content.Context;
 import android.graphics.Rect;
 import android.os.Bundle;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnClickListener;
+import android.view.View.OnHoverListener;
 import android.view.accessibility.AccessibilityEvent;
 
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
+import androidx.customview.widget.ExploreByTouchHelper;
+
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 
 import java.util.List;
 
-import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
-import androidx.customview.widget.ExploreByTouchHelper;
-
 /**
  * Helper class to make drag-and-drop in a {@link CellLayout} accessible.
  */
 public abstract class DragAndDropAccessibilityDelegate extends ExploreByTouchHelper
-        implements OnClickListener {
+        implements OnClickListener, OnHoverListener {
     protected static final int INVALID_POSITION = -1;
 
     private static final int[] sTempArray = new int[2];
@@ -123,6 +125,11 @@
         node.setFocusable(true);
     }
 
+    @Override
+    public boolean onHover(View view, MotionEvent motionEvent) {
+        return dispatchHoverEvent(motionEvent);
+    }
+
     protected abstract String getLocationDescriptionForIconDrop(int id);
 
     protected abstract String getConfirmationForIconDrop(int id);
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index 0b439ec..24c846c 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -7,6 +7,7 @@
 import android.app.AlertDialog;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.DialogInterface;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.Handler;
@@ -145,11 +146,21 @@
             }
         }
 
+        if (!fromKeyboard && !itemSupportsLongClick(host, item)) {
+            info.setLongClickable(false);
+            info.removeAction(AccessibilityAction.ACTION_LONG_CLICK);
+        }
+
         if ((item instanceof AppInfo) || (item instanceof PendingAddItemInfo)) {
             info.addAction(mActions.get(ADD_TO_WORKSPACE));
         }
     }
 
+    private boolean itemSupportsLongClick(View host, ItemInfo info) {
+        return new CustomActionsPopup(mLauncher, host).canShow()
+                || ShortcutUtil.supportsShortcuts(info);
+    }
+
     private boolean itemSupportsAccessibleDrag(ItemInfo item) {
         if (item instanceof WorkspaceItemInfo) {
             // Support the action unless the item is in a context menu.
@@ -170,18 +181,18 @@
 
     public boolean performAction(final View host, final ItemInfo item, int action) {
         if (action == ACTION_LONG_CLICK) {
-            if (ShortcutUtil.isDeepShortcut(item)) {
-                CustomActionsPopup popup = new CustomActionsPopup(mLauncher, host);
-                if (popup.canShow()) {
-                    popup.show();
-                    return true;
-                }
-            } else if (host instanceof BubbleTextView) {
+            if (ShortcutUtil.supportsShortcuts(item)) {
                 // Long press should be consumed for workspace items, and it should invoke the
                 // Shortcuts / Notifications / Actions pop-up menu, and not start a drag as the
                 // standard long press path does.
                 PopupContainerWithArrow.showForIcon((BubbleTextView) host);
                 return true;
+            } else {
+                CustomActionsPopup popup = new CustomActionsPopup(mLauncher, host);
+                if (popup.canShow()) {
+                    popup.show();
+                    return true;
+                }
             }
         }
 
@@ -400,11 +411,11 @@
 
         Rect pos = new Rect();
         mLauncher.getDragLayer().getDescendantRectRelativeToSelf(item, pos);
-        mLauncher.getDragController().prepareAccessibleDrag(pos.centerX(), pos.centerY());
         mLauncher.getDragController().addDragListener(this);
 
         DragOptions options = new DragOptions();
         options.isAccessibleDrag = true;
+        options.simulatedDndStartPoint = new Point(pos.centerX(), pos.centerY());
         ItemLongClickListener.beginDrag(item, mLauncher, info, options);
     }
 
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index e085ff0..8d1a102 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -38,6 +38,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.StringRes;
 import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.recyclerview.widget.DefaultItemAnimator;
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 
@@ -62,7 +63,6 @@
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.RecyclerViewFastScroller;
 import com.android.launcher3.views.SpringRelativeLayout;
-import com.android.launcher3.views.WorkFooterContainer;
 
 import java.util.ArrayList;
 
@@ -91,7 +91,7 @@
     private AllAppsPagedView mViewPager;
 
     private FloatingHeaderView mHeader;
-    private WorkFooterContainer mWorkFooterContainer;
+    private WorkModeSwitch mWorkModeSwitch;
 
 
     private SpannableStringBuilder mSearchQueryBuilder = null;
@@ -156,8 +156,8 @@
         return mMultiValueAlpha.getProperty(index);
     }
 
-    public WorkFooterContainer getWorkFooterContainer() {
-        return mWorkFooterContainer;
+    public WorkModeSwitch getWorkModeSwitch() {
+        return mWorkModeSwitch;
     }
 
 
@@ -195,7 +195,7 @@
     }
 
     private void resetWorkProfile() {
-        mWorkFooterContainer.refresh();
+        mWorkModeSwitch.refresh();
         mAH[AdapterHolder.WORK].setupOverlay();
         mAH[AdapterHolder.WORK].applyPadding();
     }
@@ -406,13 +406,18 @@
             setupWorkToggle();
             mAH[AdapterHolder.MAIN].setup(mViewPager.getChildAt(0), mPersonalMatcher);
             mAH[AdapterHolder.WORK].setup(mViewPager.getChildAt(1), mWorkMatcher);
+            mViewPager.getPageIndicator().setActiveMarker(AdapterHolder.MAIN);
+            findViewById(R.id.tab_personal)
+                    .setOnClickListener((View view) -> mViewPager.snapToPage(AdapterHolder.MAIN));
+            findViewById(R.id.tab_work)
+                    .setOnClickListener((View view) -> mViewPager.snapToPage(AdapterHolder.WORK));
             onTabChanged(mViewPager.getNextPage());
         } else {
             mAH[AdapterHolder.MAIN].setup(findViewById(R.id.apps_list_view), null);
             mAH[AdapterHolder.WORK].recyclerView = null;
-            if (mWorkFooterContainer != null) {
-                ((ViewGroup) mWorkFooterContainer.getParent()).removeView(mWorkFooterContainer);
-                mWorkFooterContainer = null;
+            if (mWorkModeSwitch != null) {
+                ((ViewGroup) mWorkModeSwitch.getParent()).removeView(mWorkModeSwitch);
+                mWorkModeSwitch = null;
             }
         }
         setupHeader();
@@ -422,14 +427,11 @@
     }
 
     private void setupWorkToggle() {
-        mWorkFooterContainer = (WorkFooterContainer) mLauncher.getLayoutInflater().inflate(
-                R.layout.work_tab_footer, findViewById(R.id.work_toggle_container));
-        mWorkFooterContainer.setLayoutParams(
-                new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
-                        ViewGroup.LayoutParams.WRAP_CONTENT));
-        this.addView(mWorkFooterContainer);
-        mWorkFooterContainer.setInsets(mInsets);
-        mWorkFooterContainer.post(() -> mAH[AdapterHolder.WORK].applyPadding());
+        mWorkModeSwitch = (WorkModeSwitch) mLauncher.getLayoutInflater().inflate(
+                R.layout.work_mode_switch, this, false);
+        this.addView(mWorkModeSwitch);
+        mWorkModeSwitch.setInsets(mInsets);
+        mWorkModeSwitch.post(() -> mAH[AdapterHolder.WORK].applyPadding());
     }
 
     private void replaceRVContainer(boolean showTabs) {
@@ -459,18 +461,12 @@
 
     public void onTabChanged(int pos) {
         mHeader.setMainActive(pos == 0);
-        reset(true /* animate */);
-        mViewPager.getPageIndicator().updateTabTextColor(pos);
         if (mAH[pos].recyclerView != null) {
             mAH[pos].recyclerView.bindFastScrollbar();
-
-            findViewById(R.id.tab_personal)
-                    .setOnClickListener((View view) -> mViewPager.snapToPage(AdapterHolder.MAIN));
-            findViewById(R.id.tab_work)
-                    .setOnClickListener((View view) -> mViewPager.snapToPage(AdapterHolder.WORK));
         }
-        if (mWorkFooterContainer != null) {
-            mWorkFooterContainer.setWorkTabVisible(pos == AdapterHolder.WORK);
+        reset(true /* animate */);
+        if (mWorkModeSwitch != null) {
+            mWorkModeSwitch.setWorkTabVisible(pos == AdapterHolder.WORK);
         }
     }
 
@@ -614,6 +610,7 @@
         final Rect padding = new Rect();
         AllAppsRecyclerView recyclerView;
         boolean verticalFadingEdge;
+        private View mOverlay;
 
         boolean mWorkDisabled;
 
@@ -648,13 +645,22 @@
             if (!mIsWork || recyclerView == null) return;
             boolean workDisabled = UserCache.INSTANCE.get(mLauncher).isAnyProfileQuietModeEnabled();
             if (mWorkDisabled == workDisabled) return;
+            recyclerView.setContentDescription(
+                    workDisabled ? mLauncher.getString(R.string.work_apps_paused_title) : null);
+            View overlayView = getOverlayView();
+            recyclerView.setItemAnimator(new DefaultItemAnimator());
             if (workDisabled) {
+                overlayView.setAlpha(0);
                 appsList.updateItemFilter((info, cn) -> false);
-                recyclerView.addAutoSizedOverlay(
-                        mLauncher.getLayoutInflater().inflate(R.layout.work_apps_paused, null));
+                recyclerView.addAutoSizedOverlay(overlayView);
+                overlayView.animate().alpha(1).withEndAction(
+                        () -> recyclerView.setItemAnimator(null)).start();
             } else if (mInfoMatcher != null) {
                 appsList.updateItemFilter(mInfoMatcher);
-                recyclerView.clearAutoSizedOverlays();
+                overlayView.animate().alpha(0).withEndAction(() -> {
+                    recyclerView.setItemAnimator(null);
+                    recyclerView.clearAutoSizedOverlays();
+                }).start();
             }
             mWorkDisabled = workDisabled;
         }
@@ -662,8 +668,7 @@
         void applyPadding() {
             if (recyclerView != null) {
                 int bottomOffset =
-                        mWorkFooterContainer != null && mIsWork ? mWorkFooterContainer.getHeight()
-                                : 0;
+                        mWorkModeSwitch != null && mIsWork ? mWorkModeSwitch.getHeight() : 0;
                 recyclerView.setPadding(padding.left, padding.top, padding.right,
                         padding.bottom + bottomOffset);
             }
@@ -674,5 +679,12 @@
             mAH[AdapterHolder.MAIN].recyclerView.setVerticalFadingEdgeEnabled(!mUsingTabs
                     && verticalFadingEdge);
         }
+
+        private View getOverlayView() {
+            if (mOverlay == null) {
+                mOverlay = mLauncher.getLayoutInflater().inflate(R.layout.work_apps_paused, null);
+            }
+            return mOverlay;
+        }
     }
 }
diff --git a/src/com/android/launcher3/allapps/AllAppsStore.java b/src/com/android/launcher3/allapps/AllAppsStore.java
index c4b2f68..a6ef10a 100644
--- a/src/com/android/launcher3/allapps/AllAppsStore.java
+++ b/src/com/android/launcher3/allapps/AllAppsStore.java
@@ -18,6 +18,7 @@
 import static com.android.launcher3.AppInfo.COMPONENT_KEY_COMPARATOR;
 import static com.android.launcher3.AppInfo.EMPTY_ARRAY;
 
+import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -55,6 +56,8 @@
     private int mDeferUpdatesFlags = 0;
     private boolean mUpdatePending = false;
 
+    private boolean mListenerUpdateInProgress = false;
+
     public AppInfo[] getApps() {
         return mApps;
     }
@@ -99,10 +102,12 @@
             mUpdatePending = true;
             return;
         }
+        mListenerUpdateInProgress = true;
         int count = mUpdateListeners.size();
         for (int i = 0; i < count; i++) {
             mUpdateListeners.get(i).onAppsUpdated();
         }
+        mListenerUpdateInProgress = false;
     }
 
     public void addUpdateListener(OnUpdateListener listener) {
@@ -110,6 +115,9 @@
     }
 
     public void removeUpdateListener(OnUpdateListener listener) {
+        if (mListenerUpdateInProgress) {
+            Log.e("AllAppsStore", "Trying to remove listener during update", new Exception());
+        }
         mUpdateListeners.remove(listener);
     }
 
diff --git a/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java b/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
index 3e40392..2515c24 100644
--- a/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
+++ b/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
@@ -135,6 +135,7 @@
     @Override
     public void setActiveMarker(int activePage) {
         updateTabTextColor(activePage);
+        updateIndicatorPosition(activePage);
         if (mContainerView != null && mLastActivePage != activePage) {
             mContainerView.onTabChanged(activePage);
         }
diff --git a/src/com/android/launcher3/allapps/WorkModeSwitch.java b/src/com/android/launcher3/allapps/WorkModeSwitch.java
index aadb297..05db18e 100644
--- a/src/com/android/launcher3/allapps/WorkModeSwitch.java
+++ b/src/com/android/launcher3/allapps/WorkModeSwitch.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -15,21 +15,36 @@
  */
 package com.android.launcher3.allapps;
 
+import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
+
 import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.Rect;
 import android.os.AsyncTask;
 import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.AttributeSet;
-import android.view.MotionEvent;
 import android.widget.Switch;
 
+import com.android.launcher3.Insettable;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.views.ArrowTipView;
 
 import java.lang.ref.WeakReference;
 
-public class WorkModeSwitch extends Switch {
+/**
+ * Work profile toggle switch shown at the bottom of AllApps work tab
+ */
+public class WorkModeSwitch extends Switch implements Insettable {
+
+    private static final int WORK_TIP_THRESHOLD = 2;
+    public static final String KEY_WORK_TIP_COUNTER = "worked_tip_counter";
+
+    private Rect mInsets = new Rect();
 
     public WorkModeSwitch(Context context) {
         super(context);
@@ -45,33 +60,64 @@
 
     @Override
     public void setChecked(boolean checked) {
-        // No-op, do not change the checked state until broadcast is received.
+
     }
 
     @Override
     public void toggle() {
+        Launcher launcher = Launcher.getLauncher(getContext());
+        // don't show tip if user uses toggle
+        launcher.getSharedPrefs().edit().putInt(KEY_WORK_TIP_COUNTER, -1).apply();
         trySetQuietModeEnabledToAllProfilesAsync(isChecked());
     }
 
     private void setCheckedInternal(boolean checked) {
         super.setChecked(checked);
+        setCompoundDrawablesWithIntrinsicBounds(
+                checked ? R.drawable.ic_corp : R.drawable.ic_corp_off, 0, 0, 0);
     }
 
     public void refresh() {
+        if (!shouldShowWorkSwitch()) return;
         UserCache userManager = UserCache.INSTANCE.get(getContext());
         setCheckedInternal(!userManager.isAnyProfileQuietModeEnabled());
         setEnabled(true);
     }
 
     @Override
-    public boolean onTouchEvent(MotionEvent ev) {
-        return ev.getActionMasked() == MotionEvent.ACTION_MOVE || super.onTouchEvent(ev);
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        super.onLayout(changed, l, t, r, b);
+        this.setVisibility(shouldShowWorkSwitch() ? VISIBLE : GONE);
     }
 
     private void trySetQuietModeEnabledToAllProfilesAsync(boolean enabled) {
         new SetQuietModeEnabledAsyncTask(enabled, new WeakReference<>(this)).execute();
     }
 
+    @Override
+    public void setInsets(Rect insets) {
+        int bottomInset = insets.bottom - mInsets.bottom;
+        mInsets.set(insets);
+        setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(),
+                getPaddingBottom() + bottomInset);
+    }
+
+    /**
+     * Animates in/out work profile toggle panel based on the tab user is on
+     */
+    public void setWorkTabVisible(boolean workTabVisible) {
+        if (!shouldShowWorkSwitch()) return;
+        clearAnimation();
+        if (workTabVisible) {
+            setVisibility(VISIBLE);
+            setAlpha(0);
+            animate().alpha(1).start();
+            showTipifNeeded();
+        } else {
+            animate().alpha(0).withEndAction(() -> this.setVisibility(GONE)).start();
+        }
+    }
+
     private static final class SetQuietModeEnabledAsyncTask
             extends AsyncTask<Void, Void, Boolean> {
 
@@ -122,4 +168,24 @@
             }
         }
     }
+
+    private boolean shouldShowWorkSwitch() {
+        Launcher launcher = Launcher.getLauncher(getContext());
+        return Utilities.ATLEAST_P && (hasShortcutsPermission(launcher)
+                || launcher.checkSelfPermission("android.permission.MODIFY_QUIET_MODE")
+                == PackageManager.PERMISSION_GRANTED);
+    }
+
+    /**
+     * Shows a work tip on the Nth work tab open
+     */
+    public void showTipifNeeded() {
+        Launcher launcher = Launcher.getLauncher(getContext());
+        int tipCounter = launcher.getSharedPrefs().getInt(KEY_WORK_TIP_COUNTER, WORK_TIP_THRESHOLD);
+        if (tipCounter < 0) return;
+        if (tipCounter == 0) {
+            new ArrowTipView(launcher).show(launcher.getString(R.string.work_switch_tip), getTop());
+        }
+        launcher.getSharedPrefs().edit().putInt(KEY_WORK_TIP_COUNTER, tipCounter - 1).apply();
+    }
 }
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
index d497c3a..9e3a862 100644
--- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
+++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
@@ -62,9 +62,8 @@
     private AlphabeticalAppsList mApps;
     private AllAppsContainerView mAppsView;
 
-    // This value was used to position the QSB. We store it here for translationY animations.
-    private final float mFixedTranslationY;
-    private final float mMarginTopAdjusting;
+    // The amount of pixels to shift down and overlap with the rest of the content.
+    private final int mContentOverlap;
 
     public AppsSearchContainerLayout(Context context) {
         this(context, null);
@@ -82,11 +81,10 @@
 
         mSearchQueryBuilder = new SpannableStringBuilder();
         Selection.setSelection(mSearchQueryBuilder, 0);
-
-        mFixedTranslationY = getTranslationY();
-        mMarginTopAdjusting = mFixedTranslationY - getPaddingTop();
-
         setHint(prefixTextWithIcon(getContext(), R.drawable.ic_allapps_search, getHint()));
+
+        mContentOverlap =
+                getResources().getDimensionPixelSize(R.dimen.all_apps_search_bar_field_height) / 2;
     }
 
     @Override
@@ -128,6 +126,8 @@
         int expectedLeft = parent.getPaddingLeft() + (availableWidth - myWidth) / 2;
         int shift = expectedLeft - left;
         setTranslationX(shift);
+
+        offsetTopAndBottom(mContentOverlap);
     }
 
     @Override
@@ -196,7 +196,7 @@
     @Override
     public void setInsets(Rect insets) {
         MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
-        mlp.topMargin = Math.round(Math.max(-mFixedTranslationY, insets.top - mMarginTopAdjusting));
+        mlp.topMargin = insets.top;
         requestLayout();
     }
 
@@ -205,9 +205,7 @@
         if (mLauncher.getDeviceProfile().isVerticalBarLayout()) {
             return 0;
         } else {
-            int topMargin = Math.round(Math.max(
-                    -mFixedTranslationY, insets.top - mMarginTopAdjusting));
-           return insets.bottom + topMargin + mFixedTranslationY;
+            return insets.bottom + insets.top;
         }
     }
 
diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
index 958c863..f12789a 100644
--- a/src/com/android/launcher3/anim/AnimatorPlaybackController.java
+++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
@@ -77,6 +77,9 @@
                 }
             };
 
+    // Progress factor after which an animation is considered almost completed.
+    private static final float ANIMATION_COMPLETE_THRESHOLD = 0.95f;
+
     private final ValueAnimator mAnimationPlayer;
     private final long mDuration;
 
@@ -210,6 +213,16 @@
     }
 
     /**
+     * Tries to finish the running animation if it is close to completion.
+     */
+    public void forceFinishIfCloseToEnd() {
+        if (mAnimationPlayer.isRunning()
+                && mAnimationPlayer.getAnimatedFraction() > ANIMATION_COMPLETE_THRESHOLD) {
+            mAnimationPlayer.end();
+        }
+    }
+
+    /**
      * Pauses the currently playing animation.
      */
     public void pause() {
diff --git a/src/com/android/launcher3/anim/PendingAnimation.java b/src/com/android/launcher3/anim/PendingAnimation.java
index 9a25c47..a95a5e1 100644
--- a/src/com/android/launcher3/anim/PendingAnimation.java
+++ b/src/com/android/launcher3/anim/PendingAnimation.java
@@ -62,10 +62,6 @@
     /**
      * Utility method to sent an interpolator on an animation and add it to the list
      */
-    public void add(Animator anim, TimeInterpolator interpolator) {
-        add(anim, interpolator, SpringProperty.DEFAULT);
-    }
-
     public void add(Animator anim, TimeInterpolator interpolator, SpringProperty springProperty) {
         anim.setInterpolator(interpolator);
         add(anim, springProperty);
diff --git a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
index 1d32d1d..737c97b 100644
--- a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
+++ b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
@@ -75,6 +75,9 @@
     }
 
     public static void sendScrollFinishedEventToTest(Context context) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "sendScrollFinishedEventToTest");
+        }
         final AccessibilityManager accessibilityManager = getAccessibilityManagerForTest(context);
         if (accessibilityManager == null) return;
 
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index dfd36e7..92f5112 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -83,6 +83,9 @@
     public static final BooleanFlag UNSTABLE_SPRINGS = getDebugFlag(
             "UNSTABLE_SPRINGS", false, "Enable unstable springs for quickstep animations");
 
+    public static final BooleanFlag KEYGUARD_ANIMATION = getDebugFlag(
+            "KEYGUARD_ANIMATION", false, "Enable animation for keyguard going away on wallpaper");
+
     public static final BooleanFlag ADAPTIVE_ICON_WINDOW_ANIM = getDebugFlag(
             "ADAPTIVE_ICON_WINDOW_ANIM", true, "Use adaptive icons for window animations.");
 
@@ -117,12 +120,11 @@
             "ASSISTANT_GIVES_LAUNCHER_FOCUS", false,
             "Allow Launcher to handle nav bar gestures while Assistant is running over it");
 
-    public static final BooleanFlag ENABLE_HYBRID_HOTSEAT = getDebugFlag(
+    public static final BooleanFlag ENABLE_HYBRID_HOTSEAT = new DeviceFlag(
             "ENABLE_HYBRID_HOTSEAT", false, "Fill gaps in hotseat with predicted apps");
 
-    public static final BooleanFlag HOTSEAT_MIGRATE_NEW_PAGE = getDebugFlag(
-            "HOTSEAT_MIGRATE_NEW_PAGE", false,
-            "Migrates hotseat to a new workspace page instead of same page");
+    public static final BooleanFlag HOTSEAT_MIGRATE_TO_FOLDER = new DeviceFlag(
+            "HOTSEAT_MIGRATE_TO_FOLDER", false, "Should move hotseat items into a folder");
 
     public static final BooleanFlag ENABLE_DEEP_SHORTCUT_ICON_CACHE = getDebugFlag(
             "ENABLE_DEEP_SHORTCUT_ICON_CACHE", true, "R/W deep shortcut in IconCache");
@@ -134,7 +136,7 @@
             "ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER", true, "Show launcher preview in grid picker");
 
     public static final BooleanFlag USE_SURFACE_VIEW_FOR_GRID_PREVIEW = getDebugFlag(
-            "USE_SURFACE_VIEW_FOR_GRID_PREVIEW", false, "Use surface view for grid preview");
+            "USE_SURFACE_VIEW_FOR_GRID_PREVIEW", true, "Use surface view for grid preview");
 
     public static final BooleanFlag ENABLE_OVERVIEW_ACTIONS = getDebugFlag(
             "ENABLE_OVERVIEW_ACTIONS", true, "Show app actions instead of the shelf in Overview."
@@ -152,7 +154,7 @@
             "Replace Smartspace with a version rendered by System UI.");
 
     public static final BooleanFlag ENABLE_LSQ_VELOCITY_PROVIDER = getDebugFlag(
-            "ENABLE_LSQ_VELOCITY_PROVIDER", false,
+            "ENABLE_LSQ_VELOCITY_PROVIDER", true,
             "Use Least Square algorithm for motion pause detection.");
 
     public static final BooleanFlag ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS =
@@ -161,7 +163,7 @@
             "Always use hardware optimization for folder animations.");
 
     public static final BooleanFlag ENABLE_FIXED_ROTATION_TRANSFORM = getDebugFlag(
-            FLAG_ENABLE_FIXED_ROTATION_TRANSFORM, false,
+            FLAG_ENABLE_FIXED_ROTATION_TRANSFORM, true,
             "Launch/close apps without rotation animation. Fix Launcher to portrait");
 
     public static void initialize(Context context) {
diff --git a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
index 75693c6..9b91a1d 100644
--- a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
+++ b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
@@ -26,7 +26,6 @@
 import android.graphics.Rect;
 import android.os.Handler;
 import android.os.Looper;
-import android.os.SystemClock;
 import android.util.Log;
 import android.view.DragEvent;
 import android.view.View;
@@ -63,7 +62,6 @@
 
     protected Launcher mLauncher;
     private DragController mDragController;
-    private long mDragStartTime;
 
     public BaseItemDragListener(Rect previewRect, int previewBitmapWidth, int previewViewWidth) {
         mPreviewRect = previewRect;
@@ -102,7 +100,7 @@
                 return false;
             }
         }
-        return mDragController.onDragEvent(mDragStartTime, event);
+        return mDragController.onDragEvent(event);
     }
 
     protected boolean onDragStart(DragEvent event) {
@@ -118,7 +116,7 @@
 
         Point downPos = new Point((int) event.getX(), (int) event.getY());
         DragOptions options = new DragOptions();
-        options.systemDndStartPoint = downPos;
+        options.simulatedDndStartPoint = downPos;
         options.preDragCondition = preDragCondition;
 
         // We use drag event position as the screenPos for the preview image. Since mPreviewRect
@@ -128,7 +126,6 @@
         // to source window.
         createDragHelper().startDrag(new Rect(mPreviewRect),
                 mPreviewBitmapWidth, mPreviewViewWidth, downPos, this, options);
-        mDragStartTime = SystemClock.uptimeMillis();
         return true;
     }
 
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index de7bc6d..db61f59 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -20,6 +20,7 @@
 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.Utilities.ATLEAST_Q;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
 import android.animation.ValueAnimator;
 import android.content.ComponentName;
@@ -27,7 +28,6 @@
 import android.graphics.Bitmap;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.os.IBinder;
 import android.view.DragEvent;
 import android.view.HapticFeedbackConstants;
 import android.view.KeyEvent;
@@ -43,9 +43,7 @@
 import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.accessibility.DragViewStateAnnouncer;
 import com.android.launcher3.util.ItemInfoMatcher;
-import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.TouchController;
-import com.android.launcher3.util.UiThreadHelper;
 
 import java.util.ArrayList;
 
@@ -61,11 +59,11 @@
      */
     private static final int DEEP_PRESS_DISTANCE_FACTOR = 3;
 
-    @Thunk Launcher mLauncher;
-    private FlingToDeleteHelper mFlingToDeleteHelper;
+    private final Launcher mLauncher;
+    private final FlingToDeleteHelper mFlingToDeleteHelper;
 
     // temporaries to avoid gc thrash
-    private Rect mRectTemp = new Rect();
+    private final Rect mRectTemp = new Rect();
     private final int[] mCoordinatesTemp = new int[2];
 
     /**
@@ -77,33 +75,24 @@
     /** Options controlling the drag behavior. */
     private DragOptions mOptions;
 
-    /** X coordinate of the down event. */
-    private int mMotionDownX;
+    /** Coordinate for motion down event */
+    private final Point mMotionDown = new Point();
+    /** Coordinate for last touch event **/
+    private final Point mLastTouch = new Point();
 
-    /** Y coordinate of the down event. */
-    private int mMotionDownY;
+    private final Point mTmpPoint = new Point();
 
     private DropTarget.DragObject mDragObject;
 
     /** Who can receive drop events */
-    private ArrayList<DropTarget> mDropTargets = new ArrayList<>();
-    private ArrayList<DragListener> mListeners = new ArrayList<>();
-
-    /** The window token used as the parent for the DragView. */
-    private IBinder mWindowToken;
-
-    private View mMoveTarget;
+    private final ArrayList<DropTarget> mDropTargets = new ArrayList<>();
+    private final ArrayList<DragListener> mListeners = new ArrayList<>();
 
     private DropTarget mLastDropTarget;
 
-    private final int[] mLastTouch = new int[2];
-    private long mLastTouchUpTime = -1;
     private int mLastTouchClassification;
     private int mDistanceSinceScroll = 0;
 
-    private int mTmpPoint[] = new int[2];
-    private Rect mDragLayerRect = new Rect();
-
     private boolean mIsInPreDrag;
 
     /**
@@ -140,6 +129,8 @@
      *
      * @param b The bitmap to display as the drag image.  It will be re-scaled to the
      *          enlarged size.
+     * @param originalView The source view (ie. icon, widget etc.) that is being dragged
+     *          and which the DragView represents
      * @param dragLayerX The x position in the DragLayer of the left-top of the bitmap.
      * @param dragLayerY The y position in the DragLayer of the left-top of the bitmap.
      * @param source An object representing where the drag originated
@@ -147,25 +138,24 @@
      * @param dragRegion Coordinates within the bitmap b for the position of item being dragged.
      *          Makes dragging feel more precise, e.g. you can clip out a transparent border
      */
-    public DragView startDrag(Bitmap b, int dragLayerX, int dragLayerY,
+    public DragView startDrag(Bitmap b, DraggableView originalView, int dragLayerX, int dragLayerY,
             DragSource source, ItemInfo dragInfo, Point dragOffset, Rect dragRegion,
             float initialDragViewScale, float dragViewScaleOnDrop, DragOptions options) {
         if (PROFILE_DRAWING_DURING_DRAG) {
             android.os.Debug.startMethodTracing("Launcher");
         }
 
-        // Hide soft keyboard, if visible
-        UiThreadHelper.hideKeyboardAsync(mLauncher, mWindowToken);
+        mLauncher.hideKeyboard();
         AbstractFloatingView.closeOpenViews(mLauncher, false, TYPE_DISCOVERY_BOUNCE);
 
         mOptions = options;
-        if (mOptions.systemDndStartPoint != null) {
-            mLastTouch[0] = mMotionDownX = mOptions.systemDndStartPoint.x;
-            mLastTouch[1] = mMotionDownY = mOptions.systemDndStartPoint.y;
+        if (mOptions.simulatedDndStartPoint != null) {
+            mLastTouch.x = mMotionDown.x = mOptions.simulatedDndStartPoint.x;
+            mLastTouch.y = mMotionDown.y = mOptions.simulatedDndStartPoint.y;
         }
 
-        final int registrationX = mMotionDownX - dragLayerX;
-        final int registrationY = mMotionDownY - dragLayerY;
+        final int registrationX = mMotionDown.x - dragLayerX;
+        final int registrationY = mMotionDown.y - dragLayerY;
 
         final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left;
         final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top;
@@ -173,6 +163,7 @@
         mLastDropTarget = null;
 
         mDragObject = new DropTarget.DragObject(mLauncher.getApplicationContext());
+        mDragObject.originalView = originalView;
 
         mIsInPreDrag = mOptions.preDragCondition != null
                 && !mOptions.preDragCondition.shouldStartDrag(0);
@@ -184,17 +175,13 @@
                 registrationY, initialDragViewScale, dragViewScaleOnDrop, scaleDps);
         dragView.setItemInfo(dragInfo);
         mDragObject.dragComplete = false;
-        if (mOptions.isAccessibleDrag) {
-            // For an accessible drag, we assume the view is being dragged from the center.
-            mDragObject.xOffset = b.getWidth() / 2;
-            mDragObject.yOffset = b.getHeight() / 2;
-            mDragObject.accessibleDrag = true;
-        } else {
-            mDragObject.xOffset = mMotionDownX - (dragLayerX + dragRegionLeft);
-            mDragObject.yOffset = mMotionDownY - (dragLayerY + dragRegionTop);
-            mDragObject.stateAnnouncer = DragViewStateAnnouncer.createFor(dragView);
 
-            mDragDriver = DragDriver.create(mLauncher, this, mDragObject, mOptions);
+        mDragObject.xOffset = mMotionDown.x - (dragLayerX + dragRegionLeft);
+        mDragObject.yOffset = mMotionDown.y - (dragLayerY + dragRegionTop);
+
+        mDragDriver = DragDriver.create(this, mOptions, mFlingToDeleteHelper::recordMotionEvent);
+        if (!mOptions.isAccessibleDrag) {
+            mDragObject.stateAnnouncer = DragViewStateAnnouncer.createFor(dragView);
         }
 
         mDragObject.dragSource = source;
@@ -210,7 +197,7 @@
         }
 
         mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
-        dragView.show(mLastTouch[0], mLastTouch[1]);
+        dragView.show(mLastTouch.x, mLastTouch.y);
         mDistanceSinceScroll = 0;
 
         if (!mIsInPreDrag) {
@@ -219,8 +206,13 @@
             mOptions.preDragCondition.onPreDragStart(mDragObject);
         }
 
-        handleMoveEvent(mLastTouch[0], mLastTouch[1]);
+        handleMoveEvent(mLastTouch.x, mLastTouch.y);
         mLauncher.getUserEventDispatcher().resetActionDurationMillis();
+
+        if (!mLauncher.isTouchInProgress() && options.simulatedDndStartPoint == null) {
+            // If it is an internal drag and the touch is already complete, cancel immediately
+            MAIN_EXECUTOR.submit(this::cancelDrag);
+        }
         return dragView;
     }
 
@@ -336,7 +328,7 @@
                 }
             }
         };
-        mDragObject.dragView.animateTo(mMotionDownX, mMotionDownY, onCompleteRunnable, duration);
+        mDragObject.dragView.animateTo(mMotionDown.x, mMotionDown.y, onCompleteRunnable, duration);
     }
 
     private void callOnDragEnd() {
@@ -365,30 +357,17 @@
     /**
      * Clamps the position to the drag layer bounds.
      */
-    private int[] getClampedDragLayerPos(float x, float y) {
-        mLauncher.getDragLayer().getLocalVisibleRect(mDragLayerRect);
-        mTmpPoint[0] = (int) Math.max(mDragLayerRect.left, Math.min(x, mDragLayerRect.right - 1));
-        mTmpPoint[1] = (int) Math.max(mDragLayerRect.top, Math.min(y, mDragLayerRect.bottom - 1));
+    private Point getClampedDragLayerPos(float x, float y) {
+        mLauncher.getDragLayer().getLocalVisibleRect(mRectTemp);
+        mTmpPoint.x = (int) Math.max(mRectTemp.left, Math.min(x, mRectTemp.right - 1));
+        mTmpPoint.y = (int) Math.max(mRectTemp.top, Math.min(y, mRectTemp.bottom - 1));
         return mTmpPoint;
     }
 
-    public long getLastGestureUpTime() {
-        if (mDragDriver != null) {
-            return System.currentTimeMillis();
-        } else {
-            return mLastTouchUpTime;
-        }
-    }
-
-    public void resetLastGestureUpTime() {
-        mLastTouchUpTime = -1;
-    }
-
     @Override
     public void onDriverDragMove(float x, float y) {
-        final int[] dragLayerPos = getClampedDragLayerPos(x, y);
-
-        handleMoveEvent(dragLayerPos[0], dragLayerPos[1]);
+        Point dragLayerPos = getClampedDragLayerPos(x, y);
+        handleMoveEvent(dragLayerPos.x, dragLayerPos.y);
     }
 
     @Override
@@ -422,66 +401,40 @@
     /**
      * Call this from a drag source view.
      */
+    @Override
     public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
         if (mOptions != null && mOptions.isAccessibleDrag) {
             return false;
         }
 
-        // Update the velocity tracker
-        mFlingToDeleteHelper.recordMotionEvent(ev);
+        Point dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
+        mLastTouch.set(dragLayerPos.x,  dragLayerPos.y);
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            // Remember location of down touch
+            mMotionDown.set(dragLayerPos.x,  dragLayerPos.y);
+        }
 
-        final int action = ev.getAction();
-        final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
-        final int dragLayerX = dragLayerPos[0];
-        final int dragLayerY = dragLayerPos[1];
-        mLastTouch[0] = dragLayerX;
-        mLastTouch[1] = dragLayerY;
         if (ATLEAST_Q) {
             mLastTouchClassification = ev.getClassification();
         }
-
-        switch (action) {
-            case MotionEvent.ACTION_DOWN:
-                // Remember location of down touch
-                mMotionDownX = dragLayerX;
-                mMotionDownY = dragLayerY;
-                break;
-            case MotionEvent.ACTION_UP:
-                mLastTouchUpTime = System.currentTimeMillis();
-                break;
-        }
-
         return mDragDriver != null && mDragDriver.onInterceptTouchEvent(ev);
     }
 
     /**
      * Call this from a drag source view.
      */
-    public boolean onDragEvent(long dragStartTime, DragEvent event) {
-        mFlingToDeleteHelper.recordDragEvent(dragStartTime, event);
+    @Override
+    public boolean onControllerTouchEvent(MotionEvent ev) {
+        return mDragDriver != null && mDragDriver.onTouchEvent(ev);
+    }
+
+    /**
+     * Call this from a drag source view.
+     */
+    public boolean onDragEvent(DragEvent event) {
         return mDragDriver != null && mDragDriver.onDragEvent(event);
     }
 
-    /**
-     * Call this from a drag view.
-     */
-    public void onDragViewAnimationEnd() {
-        if (mDragDriver != null) {
-            mDragDriver.onDragViewAnimationEnd();
-        }
-    }
-
-    /**
-     * Sets the view that should handle move events.
-     */
-    public void setMoveTarget(View view) {
-        mMoveTarget = view;
-    }
-
-    public boolean dispatchUnhandledMove(View focused, int direction) {
-        return mMoveTarget != null && mMoveTarget.dispatchUnhandledMove(focused, direction);
-    }
-
     private void handleMoveEvent(int x, int y) {
         mDragObject.dragView.move(x, y);
 
@@ -493,9 +446,8 @@
         checkTouchMove(dropTarget);
 
         // Check if we are hovering over the scroll areas
-        mDistanceSinceScroll += Math.hypot(mLastTouch[0] - x, mLastTouch[1] - y);
-        mLastTouch[0] = x;
-        mLastTouch[1] = y;
+        mDistanceSinceScroll += Math.hypot(mLastTouch.x - x, mLastTouch.y - y);
+        mLastTouch.set(x, y);
 
         int distanceDragged = mDistanceSinceScroll;
         if (ATLEAST_Q && mLastTouchClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS) {
@@ -513,7 +465,7 @@
 
     public void forceTouchMove() {
         int[] dummyCoordinates = mCoordinatesTemp;
-        DropTarget dropTarget = findDropTarget(mLastTouch[0], mLastTouch[1], dummyCoordinates);
+        DropTarget dropTarget = findDropTarget(mLastTouch.x, mLastTouch.y, dummyCoordinates);
         mDragObject.x = dummyCoordinates[0];
         mDragObject.y = dummyCoordinates[1];
         checkTouchMove(dropTarget);
@@ -537,44 +489,6 @@
     }
 
     /**
-     * Call this from a drag source view.
-     */
-    public boolean onControllerTouchEvent(MotionEvent ev) {
-        if (mDragDriver == null || mOptions == null || mOptions.isAccessibleDrag) {
-            return false;
-        }
-
-        // Update the velocity tracker
-        mFlingToDeleteHelper.recordMotionEvent(ev);
-
-        final int action = ev.getAction();
-        final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
-        final int dragLayerX = dragLayerPos[0];
-        final int dragLayerY = dragLayerPos[1];
-
-        switch (action) {
-            case MotionEvent.ACTION_DOWN:
-                // Remember where the motion event started
-                mMotionDownX = dragLayerX;
-                mMotionDownY = dragLayerY;
-                break;
-        }
-
-        return mDragDriver.onTouchEvent(ev);
-    }
-
-    /**
-     * Since accessible drag and drop won't cause the same sequence of touch events, we manually
-     * inject the appropriate state which would have been otherwise initiated via touch events.
-     */
-    public void prepareAccessibleDrag(int x, int y) {
-        mMotionDownX = x;
-        mMotionDownY = y;
-        mLastTouch[0] = x;
-        mLastTouch[1] = y;
-    }
-
-    /**
      * As above, since accessible drag and drop won't cause the same sequence of touch events,
      * we manually ensure appropriate drag and drop events get emulated for accessible drag.
      */
@@ -664,10 +578,6 @@
         return mLauncher.getWorkspace();
     }
 
-    public void setWindowToken(IBinder token) {
-        mWindowToken = token;
-    }
-
     /**
      * Sets the drag listener which will be notified when a drag starts or ends.
      */
diff --git a/src/com/android/launcher3/dragndrop/DragDriver.java b/src/com/android/launcher3/dragndrop/DragDriver.java
index 87461d5..d4ce308 100644
--- a/src/com/android/launcher3/dragndrop/DragDriver.java
+++ b/src/com/android/launcher3/dragndrop/DragDriver.java
@@ -16,19 +16,19 @@
 
 package com.android.launcher3.dragndrop;
 
-import android.content.Context;
-import android.util.Log;
+import android.os.SystemClock;
 import android.view.DragEvent;
 import android.view.MotionEvent;
 
-import com.android.launcher3.DropTarget.DragObject;
-import com.android.launcher3.testing.TestProtocol;
+import java.util.function.Consumer;
 
 /**
  * Base class for driving a drag/drop operation.
  */
 public abstract class DragDriver {
+
     protected final EventListener mEventListener;
+    protected final Consumer<MotionEvent> mSecondaryEventConsumer;
 
     public interface EventListener {
         void onDriverDragMove(float x, float y);
@@ -37,131 +37,175 @@
         void onDriverDragCancel();
     }
 
-    public DragDriver(EventListener eventListener) {
+    public DragDriver(EventListener eventListener, Consumer<MotionEvent> sec) {
         mEventListener = eventListener;
+        mSecondaryEventConsumer = sec;
     }
 
     /**
-     * Handles ending of the DragView animation.
+     * Called to handle system touch event
      */
-    public void onDragViewAnimationEnd() { }
-
     public boolean onTouchEvent(MotionEvent ev) {
-        final int action = ev.getAction();
-
-        switch (action) {
-            case MotionEvent.ACTION_MOVE:
-                mEventListener.onDriverDragMove(ev.getX(), ev.getY());
-                break;
-            case MotionEvent.ACTION_UP:
-                mEventListener.onDriverDragMove(ev.getX(), ev.getY());
-                mEventListener.onDriverDragEnd(ev.getX(), ev.getY());
-                break;
-            case MotionEvent.ACTION_CANCEL:
-                mEventListener.onDriverDragCancel();
-                break;
-        }
-
-        return true;
+        return false;
     }
 
-    public abstract boolean onDragEvent (DragEvent event);
-
-
+    /**
+     * Called to handle system touch intercept event
+     */
     public boolean onInterceptTouchEvent(MotionEvent ev) {
-        final int action = ev.getAction();
-
-        switch (action) {
-            case MotionEvent.ACTION_UP:
-                mEventListener.onDriverDragEnd(ev.getX(), ev.getY());
-                break;
-            case MotionEvent.ACTION_CANCEL:
-                mEventListener.onDriverDragCancel();
-                break;
-        }
-
-        return true;
+        return false;
     }
 
-    public static DragDriver create(Context context, DragController dragController,
-            DragObject dragObject, DragOptions options) {
-        if (options.systemDndStartPoint != null) {
-            return new SystemDragDriver(dragController, context, dragObject);
+    /**
+     * Called to handle system drag event
+     */
+    public boolean onDragEvent(DragEvent event) {
+        return false;
+    }
+
+    /**
+     * Created a driver for handing the actual events
+     */
+    public static DragDriver create(DragController dragController, DragOptions options,
+            Consumer<MotionEvent> sec) {
+        if (options.simulatedDndStartPoint != null) {
+            if  (options.isAccessibleDrag) {
+                return null;
+            }
+            return new SystemDragDriver(dragController, sec);
         } else {
-            return new InternalDragDriver(dragController);
+            return new InternalDragDriver(dragController, sec);
+        }
+    }
+
+    /**
+     * Class for driving a system (i.e. framework) drag/drop operation.
+     */
+    static class SystemDragDriver extends DragDriver {
+
+        private final long mDragStartTime;
+        float mLastX = 0;
+        float mLastY = 0;
+
+        SystemDragDriver(DragController dragController, Consumer<MotionEvent> sec) {
+            super(dragController, sec);
+            mDragStartTime = SystemClock.uptimeMillis();
+        }
+
+        @Override
+        public boolean onInterceptTouchEvent(MotionEvent ev) {
+            return false;
+        }
+
+        /**
+         * It creates a temporary {@link MotionEvent} object for secondary consumer
+         */
+        private void simulateSecondaryMotionEvent(DragEvent event) {
+            final int motionAction;
+            switch (event.getAction()) {
+                case DragEvent.ACTION_DRAG_STARTED:
+                    motionAction = MotionEvent.ACTION_DOWN;
+                    break;
+                case DragEvent.ACTION_DRAG_LOCATION:
+                    motionAction = MotionEvent.ACTION_MOVE;
+                    break;
+                case DragEvent.ACTION_DRAG_ENDED:
+                    motionAction = MotionEvent.ACTION_UP;
+                    break;
+                default:
+                    return;
+            }
+            MotionEvent emulatedEvent = MotionEvent.obtain(mDragStartTime,
+                    SystemClock.uptimeMillis(), motionAction, event.getX(), event.getY(), 0);
+            mSecondaryEventConsumer.accept(emulatedEvent);
+            emulatedEvent.recycle();
+        }
+
+        @Override
+        public boolean onDragEvent(DragEvent event) {
+            simulateSecondaryMotionEvent(event);
+            final int action = event.getAction();
+
+            switch (action) {
+                case DragEvent.ACTION_DRAG_STARTED:
+                    mLastX = event.getX();
+                    mLastY = event.getY();
+                    return true;
+
+                case DragEvent.ACTION_DRAG_ENTERED:
+                    return true;
+
+                case DragEvent.ACTION_DRAG_LOCATION:
+                    mLastX = event.getX();
+                    mLastY = event.getY();
+                    mEventListener.onDriverDragMove(event.getX(), event.getY());
+                    return true;
+
+                case DragEvent.ACTION_DROP:
+                    mLastX = event.getX();
+                    mLastY = event.getY();
+                    mEventListener.onDriverDragMove(event.getX(), event.getY());
+                    mEventListener.onDriverDragEnd(mLastX, mLastY);
+                    return true;
+                case DragEvent.ACTION_DRAG_EXITED:
+                    mEventListener.onDriverDragExitWindow();
+                    return true;
+
+                case DragEvent.ACTION_DRAG_ENDED:
+                    mEventListener.onDriverDragCancel();
+                    return true;
+
+                default:
+                    return false;
+            }
+        }
+    }
+
+    /**
+     * Class for driving an internal (i.e. not using framework) drag/drop operation.
+     */
+    static class InternalDragDriver extends DragDriver {
+        InternalDragDriver(DragController dragController, Consumer<MotionEvent> sec) {
+            super(dragController, sec);
+        }
+
+        @Override
+        public boolean onTouchEvent(MotionEvent ev) {
+            mSecondaryEventConsumer.accept(ev);
+            final int action = ev.getAction();
+
+            switch (action) {
+                case MotionEvent.ACTION_MOVE:
+                    mEventListener.onDriverDragMove(ev.getX(), ev.getY());
+                    break;
+                case MotionEvent.ACTION_UP:
+                    mEventListener.onDriverDragMove(ev.getX(), ev.getY());
+                    mEventListener.onDriverDragEnd(ev.getX(), ev.getY());
+                    break;
+                case MotionEvent.ACTION_CANCEL:
+                    mEventListener.onDriverDragCancel();
+                    break;
+            }
+
+            return true;
+        }
+
+
+        public boolean onInterceptTouchEvent(MotionEvent ev) {
+            mSecondaryEventConsumer.accept(ev);
+            final int action = ev.getAction();
+
+            switch (action) {
+                case MotionEvent.ACTION_UP:
+                    mEventListener.onDriverDragEnd(ev.getX(), ev.getY());
+                    break;
+                case MotionEvent.ACTION_CANCEL:
+                    mEventListener.onDriverDragCancel();
+                    break;
+            }
+            return true;
         }
     }
 }
 
-/**
- * Class for driving a system (i.e. framework) drag/drop operation.
- */
-class SystemDragDriver extends DragDriver {
 
-    float mLastX = 0;
-    float mLastY = 0;
-
-    SystemDragDriver(DragController dragController, Context context, DragObject dragObject) {
-        super(dragController);
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent ev) {
-        return false;
-    }
-
-    @Override
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
-        return false;
-    }
-
-    @Override
-    public boolean onDragEvent (DragEvent event) {
-        final int action = event.getAction();
-
-        switch (action) {
-            case DragEvent.ACTION_DRAG_STARTED:
-                mLastX = event.getX();
-                mLastY = event.getY();
-                return true;
-
-            case DragEvent.ACTION_DRAG_ENTERED:
-                return true;
-
-            case DragEvent.ACTION_DRAG_LOCATION:
-                mLastX = event.getX();
-                mLastY = event.getY();
-                mEventListener.onDriverDragMove(event.getX(), event.getY());
-                return true;
-
-            case DragEvent.ACTION_DROP:
-                mLastX = event.getX();
-                mLastY = event.getY();
-                mEventListener.onDriverDragMove(event.getX(), event.getY());
-                mEventListener.onDriverDragEnd(mLastX, mLastY);
-                return true;
-            case DragEvent.ACTION_DRAG_EXITED:
-                mEventListener.onDriverDragExitWindow();
-                return true;
-
-            case DragEvent.ACTION_DRAG_ENDED:
-                mEventListener.onDriverDragCancel();
-                return true;
-
-            default:
-                return false;
-        }
-    }
-}
-
-/**
- * Class for driving an internal (i.e. not using framework) drag/drop operation.
- */
-class InternalDragDriver extends DragDriver {
-    InternalDragDriver(DragController dragController) {
-        super(dragController);
-    }
-
-    @Override
-    public boolean onDragEvent (DragEvent event) { return false; }
-}
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 369bf28..9ece3d3 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -21,6 +21,7 @@
 import static android.view.View.MeasureSpec.getMode;
 import static android.view.View.MeasureSpec.getSize;
 
+import static com.android.launcher3.anim.Interpolators.DEACCEL_1_5;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
 
 import android.animation.Animator;
@@ -41,7 +42,6 @@
 import android.view.accessibility.AccessibilityManager;
 import android.view.animation.Interpolator;
 import android.widget.FrameLayout;
-import android.widget.TextView;
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.CellLayout;
@@ -50,9 +50,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutAndWidgetContainer;
 import com.android.launcher3.Workspace;
-import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.folder.Folder;
-import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.graphics.OverviewScrim;
 import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.graphics.WorkspaceAndHotseatScrim;
@@ -76,11 +74,11 @@
     public static final int ANIMATION_END_DISAPPEAR = 0;
     public static final int ANIMATION_END_REMAIN_VISIBLE = 2;
 
-    @Thunk DragController mDragController;
+    private DragController mDragController;
 
     // Variables relating to animation of views after drop
     private ValueAnimator mDropAnim = null;
-    private final TimeInterpolator mCubicEaseOutInterpolator = Interpolators.DEACCEL_1_5;
+
     @Thunk DragView mDropView = null;
     @Thunk int mAnchorViewInitialScrollX = 0;
     @Thunk View mAnchorView = null;
@@ -95,6 +93,9 @@
     private final WorkspaceAndHotseatScrim mWorkspaceScrim;
     private final OverviewScrim mOverviewScrim;
 
+    // View that should handle move events
+    private View mMoveTarget;
+
     /**
      * Used to create a new DragLayer from XML.
      *
@@ -116,6 +117,7 @@
     public void setup(DragController dragController, Workspace workspace) {
         mDragController = dragController;
         mWorkspaceScrim.setWorkspace(workspace);
+        mMoveTarget = workspace;
         recreateControllers();
     }
 
@@ -223,7 +225,7 @@
     @Override
     public boolean dispatchUnhandledMove(View focused, int direction) {
         return super.dispatchUnhandledMove(focused, direction)
-                || mDragController.dispatchUnhandledMove(focused, direction);
+                || mMoveTarget.dispatchUnhandledMove(focused, direction);
     }
 
     @Override
@@ -254,59 +256,47 @@
 
     public void animateViewIntoPosition(DragView dragView, final View child, int duration,
             View anchorView) {
+
         ShortcutAndWidgetContainer parentChildren = (ShortcutAndWidgetContainer) child.getParent();
         CellLayout.LayoutParams lp =  (CellLayout.LayoutParams) child.getLayoutParams();
         parentChildren.measureChild(child);
+        parentChildren.layoutChild(child);
 
-        Rect r = new Rect();
-        getViewRectRelativeToSelf(dragView, r);
+        Rect dragViewBounds = new Rect();
+        getViewRectRelativeToSelf(dragView, dragViewBounds);
+        final int fromX = dragViewBounds.left;
+        final int fromY = dragViewBounds.top;
 
         float coord[] = new float[2];
         float childScale = child.getScaleX();
+
         coord[0] = lp.x + (child.getMeasuredWidth() * (1 - childScale) / 2);
         coord[1] = lp.y + (child.getMeasuredHeight() * (1 - childScale) / 2);
 
         // Since the child hasn't necessarily been laid out, we force the lp to be updated with
         // the correct coordinates (above) and use these to determine the final location
         float scale = getDescendantCoordRelativeToSelf((View) child.getParent(), coord);
+
         // We need to account for the scale of the child itself, as the above only accounts for
         // for the scale in parents.
         scale *= childScale;
         int toX = Math.round(coord[0]);
         int toY = Math.round(coord[1]);
         float toScale = scale;
-        if (child instanceof TextView) {
-            TextView tv = (TextView) child;
-            // Account for the source scale of the icon (ie. from AllApps to Workspace, in which
-            // the workspace may have smaller icon bounds).
-            toScale = scale / dragView.getIntrinsicIconScaleFactor();
 
-            // The child may be scaled (always about the center of the view) so to account for it,
-            // we have to offset the position by the scaled size.  Once we do that, we can center
-            // the drag view about the scaled child view.
-            // padding will remain constant (does not scale with size)
-            toY += tv.getPaddingTop();
-            toY -= dragView.getMeasuredHeight() * (1 - toScale) / 2;
-            if (dragView.getDragVisualizeOffset() != null) {
-                toY -=  Math.round(toScale * dragView.getDragVisualizeOffset().y);
-            }
+        if (child instanceof DraggableView) {
+            DraggableView d = (DraggableView) child;
+            d.getVisualDragBounds(dragViewBounds);
 
-            toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
-        } else if (child instanceof FolderIcon) {
-            // Account for holographic blur padding on the drag view
-            toY += Math.round(scale * (child.getPaddingTop() - dragView.getDragRegionTop()));
-            toY -= scale * dragView.getBlurSizeOutline() / 2;
-            toY -= (1 - scale) * dragView.getMeasuredHeight() / 2;
-            // Center in the x coordinate about the target's drawable
-            toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
-        } else {
-            toY -= (Math.round(scale * (dragView.getHeight() - child.getMeasuredHeight()))) / 2;
-            toX -= (Math.round(scale * (dragView.getMeasuredWidth()
-                    - child.getMeasuredWidth()))) / 2;
+            // This accounts for the offset of the DragView created by scaling it about its
+            // center as it animates into place.
+            float scaleShiftX = dragView.getMeasuredWidth() * (1 - scale) / 2;
+            float scaleShiftY = dragView.getMeasuredHeight() * (1 - scale) / 2;
+
+            toX += scale * (dragViewBounds.left - dragView.getBlurSizeOutline() / 2) - scaleShiftX;
+            toY += scale * (dragViewBounds.top - dragView.getBlurSizeOutline() / 2) - scaleShiftY;
         }
 
-        final int fromX = r.left;
-        final int fromY = r.top;
         child.setVisibility(INVISIBLE);
         Runnable onCompleteRunnable = () -> child.setVisibility(VISIBLE);
         animateViewIntoPosition(dragView, fromX, fromY, toX, toY, 1, 1, 1, toScale, toScale,
@@ -361,7 +351,7 @@
         if (duration < 0) {
             duration = res.getInteger(R.integer.config_dropAnimMaxDuration);
             if (dist < maxDist) {
-                duration *= mCubicEaseOutInterpolator.getInterpolation(dist / maxDist);
+                duration *= DEACCEL_1_5.getInterpolation(dist / maxDist);
             }
             duration = Math.max(duration, res.getInteger(R.integer.config_dropAnimMinDuration));
         }
@@ -369,7 +359,7 @@
         // Fall back to cubic ease out interpolator for the animation if none is specified
         TimeInterpolator interpolator = null;
         if (alphaInterpolator == null || motionInterpolator == null) {
-            interpolator = mCubicEaseOutInterpolator;
+            interpolator = DEACCEL_1_5;
         }
 
         // Animate the view
@@ -559,7 +549,7 @@
     @Override
     public void setInsets(Rect insets) {
         super.setInsets(insets);
-        mWorkspaceScrim.onInsetsChanged(insets);
+        mWorkspaceScrim.onInsetsChanged(insets, mAllowSysuiScrims);
         mOverviewScrim.onInsetsChanged(insets);
     }
 
diff --git a/src/com/android/launcher3/dragndrop/DragOptions.java b/src/com/android/launcher3/dragndrop/DragOptions.java
index 2d19f36..959602b 100644
--- a/src/com/android/launcher3/dragndrop/DragOptions.java
+++ b/src/com/android/launcher3/dragndrop/DragOptions.java
@@ -28,8 +28,11 @@
     /** Whether or not an accessible drag operation is in progress. */
     public boolean isAccessibleDrag = false;
 
-    /** Specifies the start location for the system DnD, null when using internal DnD */
-    public Point systemDndStartPoint = null;
+    /**
+     * Specifies the start location for a simulated DnD (like system drag or accessibility drag),
+     * null when using internal DnD
+     */
+    public Point simulatedDndStartPoint = null;
 
     /** Determines when a pre-drag should transition to a drag. By default, this is immediate. */
     public PreDragCondition preDragCondition = null;
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 145885a..7c76d34 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -19,8 +19,6 @@
 import static com.android.launcher3.Utilities.getBadge;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
 import android.animation.FloatArrayEvaluator;
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
@@ -146,15 +144,6 @@
             }
         });
 
-        mAnim.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                if (!mAnimationCancelled) {
-                    mDragController.onDragViewAnimationEnd();
-                }
-            }
-        });
-
         mBitmap = bitmap;
         setDragRegion(new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()));
 
diff --git a/src/com/android/launcher3/dragndrop/DraggableView.java b/src/com/android/launcher3/dragndrop/DraggableView.java
new file mode 100644
index 0000000..df99902
--- /dev/null
+++ b/src/com/android/launcher3/dragndrop/DraggableView.java
@@ -0,0 +1,57 @@
+/*
+ * 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.dragndrop;
+
+import android.graphics.Rect;
+
+/**
+ * Interface defining methods required for drawing and previewing DragViews, drag previews, and
+ * related animations
+ */
+public interface DraggableView {
+    int DRAGGABLE_ICON = 0;
+    int DRAGGABLE_WIDGET = 1;
+
+    /**
+     * Static ctr for a simple instance which just returns the view type.
+     */
+    static DraggableView ofType(int type) {
+        return () -> type;
+    }
+
+    /**
+     * Certain handling of DragViews depend only on whether this is an Icon Type item or a Widget
+     * Type item.
+     *
+     * @return DRAGGABLE_ICON or DRAGGABLE_WIDGET as appropriate
+     */
+    int getViewType();
+
+    /**
+     * Before rendering as a DragView bitmap, some views need a preparation step.
+     */
+    default void prepareDrawDragView() { }
+
+    /**
+     * If an actual View subclass, this method returns the rectangle (within the View's coordinates)
+     * of the visual region that should get dragged. This is used to extract exactly that element
+     * as well as to offset that element as appropriate for various animations
+     *
+     * @param bounds Visual bounds in the views coordinates will be written here.
+     */
+    default void getVisualDragBounds(Rect bounds) { }
+}
diff --git a/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java b/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java
index 06b5c40..7788f93 100644
--- a/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java
+++ b/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java
@@ -17,8 +17,6 @@
 package com.android.launcher3.dragndrop;
 
 import android.graphics.PointF;
-import android.os.SystemClock;
-import android.view.DragEvent;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.ViewConfiguration;
@@ -55,31 +53,6 @@
         mVelocityTracker.addMovement(ev);
     }
 
-    /**
-     * Same as {@link #recordMotionEvent}. It creates a temporary {@link MotionEvent} object
-     * using {@param event} for tracking velocity.
-     */
-    public void recordDragEvent(long dragStartTime, DragEvent event) {
-        final int motionAction;
-        switch (event.getAction()) {
-            case DragEvent.ACTION_DRAG_STARTED:
-                motionAction = MotionEvent.ACTION_DOWN;
-                break;
-            case DragEvent.ACTION_DRAG_LOCATION:
-                motionAction = MotionEvent.ACTION_MOVE;
-                break;
-            case DragEvent.ACTION_DRAG_ENDED:
-                motionAction = MotionEvent.ACTION_UP;
-                break;
-            default:
-                return;
-        }
-        MotionEvent emulatedEvent = MotionEvent.obtain(dragStartTime, SystemClock.uptimeMillis(),
-                motionAction, event.getX(), event.getY(), 0);
-        recordMotionEvent(emulatedEvent);
-        emulatedEvent.recycle();
-    }
-
     public void releaseVelocityTracker() {
         if (mVelocityTracker != null) {
             mVelocityTracker.recycle();
diff --git a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
index 0bb3fba..ea1fbdb 100644
--- a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
+++ b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
@@ -20,10 +20,10 @@
 
 import android.annotation.TargetApi;
 import android.graphics.Bitmap;
-import android.graphics.Canvas;
 import android.graphics.Matrix;
 import android.graphics.Path;
 import android.graphics.Point;
+import android.graphics.Rect;
 import android.graphics.drawable.AdaptiveIconDrawable;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
@@ -33,7 +33,6 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.folder.PreviewBackground;
 import com.android.launcher3.graphics.ShiftedBitmapDrawable;
@@ -50,6 +49,7 @@
     private final Drawable mBadge;
     private final Path mMask;
     private final ConstantState mConstantState;
+    private static final Rect sTmpRect = new Rect();
 
     private FolderAdaptiveIcon(Drawable bg, Drawable fg, Drawable badge, Path mask) {
         super(bg, fg);
@@ -72,23 +72,14 @@
     public static @Nullable FolderAdaptiveIcon createFolderAdaptiveIcon(
             Launcher launcher, int folderId, Point dragViewSize) {
         Preconditions.assertNonUiThread();
-        int margin = launcher.getResources()
-                .getDimensionPixelSize(R.dimen.blur_size_medium_outline);
-
-        // Allocate various bitmaps on the background thread, because why not!
-        int width = dragViewSize.x - margin;
-        int height = dragViewSize.y - margin;
-        if (width <= 0 || height <= 0) {
-            return null;
-        }
-        final Bitmap badge = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
 
         // Create the actual drawable on the UI thread to avoid race conditions with
         // FolderIcon draw pass
         try {
             return MAIN_EXECUTOR.submit(() -> {
                 FolderIcon icon = launcher.findFolderIcon(folderId);
-                return icon == null ? null : createDrawableOnUiThread(icon, badge, dragViewSize);
+                return icon == null ? null : createDrawableOnUiThread(icon, dragViewSize);
+
             }).get();
         } catch (Exception e) {
             Log.e(TAG, "Unable to create folder icon", e);
@@ -96,49 +87,50 @@
         }
     }
 
-    /**
-     * Initializes various bitmaps on the UI thread and returns the final drawable.
-     */
     private static FolderAdaptiveIcon createDrawableOnUiThread(FolderIcon icon,
-            Bitmap badgeBitmap, Point dragViewSize) {
+                                                               Point dragViewSize) {
         Preconditions.assertUIThread();
-        float margin = icon.getResources().getDimension(R.dimen.blur_size_medium_outline) / 2;
 
-        Canvas c = new Canvas();
+        icon.getPreviewBounds(sTmpRect);
+
         PreviewBackground bg = icon.getFolderBackground();
 
-        // Initialize badge
-        c.setBitmap(badgeBitmap);
-        bg.drawShadow(c);
-        bg.drawBackgroundStroke(c);
-        icon.drawDot(c);
+        // assume square
+        assert (dragViewSize.x == dragViewSize.y);
+        final int previewSize = sTmpRect.width();
 
-        // Initialize preview
-        final float sizeScaleFactor = 1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction();
-        final int previewWidth = (int) (dragViewSize.x * sizeScaleFactor);
-        final int previewHeight = (int) (dragViewSize.y * sizeScaleFactor);
+        final int margin = (dragViewSize.x - previewSize) / 2;
+        final float previewShiftX = -sTmpRect.left + margin;
+        final float previewShiftY = -sTmpRect.top + margin;
 
-        final float shiftFactor = AdaptiveIconDrawable.getExtraInsetFraction() / sizeScaleFactor;
-        final float previewShiftX = shiftFactor * previewWidth;
-        final float previewShiftY = shiftFactor * previewHeight;
-
-        Bitmap previewBitmap = BitmapRenderer.createHardwareBitmap(previewWidth, previewHeight,
+        // Initialize badge, which consists of the outline stroke, shadow and dot; these
+        // must be rendered above the foreground
+        Bitmap badgeBmp = BitmapRenderer.createHardwareBitmap(dragViewSize.x, dragViewSize.y,
                 (canvas) -> {
-                    int count = canvas.save();
+                    canvas.save();
                     canvas.translate(previewShiftX, previewShiftY);
-                    icon.getPreviewItemManager().draw(canvas);
-                    canvas.restoreToCount(count);
+                    bg.drawShadow(canvas);
+                    bg.drawBackgroundStroke(canvas);
+                    icon.drawDot(canvas);
+                    canvas.restore();
                 });
 
         // Initialize mask
         Path mask = new Path();
         Matrix m = new Matrix();
-        m.setTranslate(margin, margin);
+        m.setTranslate(previewShiftX, previewShiftY);
         bg.getClipPath().transform(m, mask);
 
-        ShiftedBitmapDrawable badge = new ShiftedBitmapDrawable(badgeBitmap, margin, margin);
-        ShiftedBitmapDrawable foreground = new ShiftedBitmapDrawable(previewBitmap,
-                margin - previewShiftX, margin - previewShiftY);
+        Bitmap previewBitmap = BitmapRenderer.createHardwareBitmap(dragViewSize.x, dragViewSize.y,
+                (canvas) -> {
+                    canvas.save();
+                    canvas.translate(previewShiftX, previewShiftY);
+                    icon.getPreviewItemManager().draw(canvas);
+                    canvas.restore();
+                });
+
+        ShiftedBitmapDrawable badge = new ShiftedBitmapDrawable(badgeBmp, 0, 0);
+        ShiftedBitmapDrawable foreground = new ShiftedBitmapDrawable(previewBitmap, 0, 0);
 
         return new FolderAdaptiveIcon(new ColorDrawable(bg.getBgColor()), foreground, badge, mask);
     }
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 2be8ff4..365e76f 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -81,10 +81,12 @@
 import com.android.launcher3.PagedView;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutAndWidgetContainer;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.Workspace.ItemOperator;
 import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
+import com.android.launcher3.accessibility.FolderAccessibilityHelper;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragController.DragListener;
@@ -271,16 +273,15 @@
             mDragController.addDragListener(this);
             if (options.isAccessibleDrag) {
                 mDragController.addDragListener(new AccessibleDragListenerAdapter(
-                        mContent, CellLayout.FOLDER_ACCESSIBILITY_DRAG) {
-
-                    @Override
-                    protected void enableAccessibleDrag(boolean enable) {
-                        super.enableAccessibleDrag(enable);
-                        mFooter.setImportantForAccessibility(enable
-                                ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
-                                : IMPORTANT_FOR_ACCESSIBILITY_AUTO);
-                    }
-                });
+                        mContent, FolderAccessibilityHelper::new) {
+                            @Override
+                            protected void enableAccessibleDrag(boolean enable) {
+                                super.enableAccessibleDrag(enable);
+                                mFooter.setImportantForAccessibility(enable
+                                        ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+                                        : IMPORTANT_FOR_ACCESSIBILITY_AUTO);
+                            }
+                        });
             }
 
             mLauncher.getWorkspace().beginDragShared(v, this, options);
@@ -324,13 +325,11 @@
     public void startEditingFolderName() {
         post(() -> {
             if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
-                if (isEmpty(mFolderName.getText())) {
-                    ofNullable(mInfo)
-                            .map(info -> info.suggestedFolderNames)
-                            .map(folderNames -> (FolderNameInfo[]) folderNames
-                                    .getParcelableArrayExtra(FolderInfo.EXTRA_FOLDER_SUGGESTIONS))
-                            .ifPresent(nameInfos -> showLabelSuggestion(nameInfos, false));
-                }
+                ofNullable(mInfo)
+                        .map(info -> info.suggestedFolderNames)
+                        .map(folderNames -> (FolderNameInfo[]) folderNames
+                                .getParcelableArrayExtra(FolderInfo.EXTRA_FOLDER_SUGGESTIONS))
+                        .ifPresent(nameInfos -> showLabelSuggestion(nameInfos, false));
             }
             mFolderName.setHint("");
             mIsEditingName = true;
@@ -485,19 +484,24 @@
                 nameInfos[1].getLabel());
 
         if (shouldOpen) {
-            CharSequence firstLabel = nameInfos[0] == null ? "" : nameInfos[0].getLabel();
-            if (!isEmpty(firstLabel)) {
-                mFolderName.setHint("");
-                mFolderName.setText(firstLabel);
+            // update the primary suggestion if the folder name is empty.
+            if (isEmpty(mFolderName.getText())) {
+                CharSequence firstLabel = nameInfos[0] == null ? "" : nameInfos[0].getLabel();
+                if (!isEmpty(firstLabel)) {
+                    mFolderName.setHint("");
+                    mFolderName.setText(firstLabel);
+                }
             }
             if (animate) {
                 animateOpen(mInfo.contents, 0, true);
             }
             mFolderName.showKeyboard();
             mFolderName.displayCompletions(
-                    asList(nameInfos).subList(1, nameInfos.length).stream()
+                    asList(nameInfos).subList(0, nameInfos.length).stream()
                             .filter(Objects::nonNull)
                             .map(s -> s.getLabel().toString())
+                            .filter(s -> !s.isEmpty())
+                            .filter(s -> !s.equalsIgnoreCase(mFolderName.getText().toString()))
                             .collect(Collectors.toList()));
         }
     }
@@ -516,9 +520,6 @@
     }
 
     private void startAnimation(final AnimatorSet a) {
-        if (mCurrentAnimator != null && mCurrentAnimator.isRunning()) {
-            mCurrentAnimator.cancel();
-        }
         final Workspace workspace = mLauncher.getWorkspace();
         final CellLayout currentCellLayout =
                 (CellLayout) workspace.getChildAt(workspace.getCurrentPage());
@@ -635,6 +636,9 @@
         // dropping. One resulting issue is that replaceFolderWithFinalItem() can be called twice.
         mDeleteFolderOnDropCompleted = false;
 
+        if (mCurrentAnimator != null && mCurrentAnimator.isRunning()) {
+            mCurrentAnimator.cancel();
+        }
         AnimatorSet anim = new FolderAnimationManager(this, true /* isOpening */).getAnimator();
         anim.addListener(new AnimatorListenerAdapter() {
             @Override
@@ -738,6 +742,9 @@
     }
 
     private void animateClosed() {
+        if (mCurrentAnimator != null && mCurrentAnimator.isRunning()) {
+            mCurrentAnimator.cancel();
+        }
         AnimatorSet a = new FolderAnimationManager(this, false /* isOpening */).getAnimator();
         a.addListener(new AnimatorListenerAdapter() {
             @Override
@@ -956,6 +963,7 @@
             View icon = (mCurrentDragView != null && mCurrentDragView.getTag() == info)
                     ? mCurrentDragView : mContent.createNewView(info);
             ArrayList<View> views = getIconsInReadingOrder();
+            info.rank = Utilities.boundToRange(info.rank, 0, views.size());
             views.add(info.rank, icon);
             mContent.arrangeChildren(views);
             mItemsInvalidated = true;
diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java
index f72e674..b83609e 100644
--- a/src/com/android/launcher3/folder/FolderAnimationManager.java
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -21,7 +21,6 @@
 import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
 import static com.android.launcher3.graphics.IconShape.getShape;
 import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
-import static com.android.launcher3.uioverrides.BackgroundBlurController.BACKGROUND_BLUR;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -47,7 +46,6 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.PropertyResetListener;
 import com.android.launcher3.dragndrop.DragLayer;
-import com.android.launcher3.uioverrides.BackgroundBlurController;
 import com.android.launcher3.util.Themes;
 
 import java.util.List;
@@ -222,14 +220,6 @@
         Animator z = getAnimator(mFolder, View.TRANSLATION_Z, -mFolder.getElevation(), 0);
         play(a, z, mIsOpening ? midDuration : 0, midDuration);
 
-        BackgroundBlurController blurController = mLauncher.getBackgroundBlurController();
-        int stateBackgroundBlur = mLauncher.getStateManager().getState()
-                .getBackgroundBlurRadius(mLauncher);
-        int folderBackgroundBlurAdjustment = blurController.getFolderBackgroundBlurAdjustment();
-        play(a, ObjectAnimator.ofInt(blurController, BACKGROUND_BLUR, mIsOpening
-                ? stateBackgroundBlur + folderBackgroundBlurAdjustment
-                : stateBackgroundBlur));
-
         // Store clip variables
         CellLayout cellLayout = mContent.getCurrentCellLayout();
         boolean folderClipChildren = mFolder.getClipChildren();
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index ab1ff10..8251d68 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -31,7 +31,6 @@
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewConfiguration;
 import android.view.ViewDebug;
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
@@ -52,8 +51,6 @@
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.OnAlarmListener;
 import com.android.launcher3.R;
-import com.android.launcher3.SimpleOnStylusPressListener;
-import com.android.launcher3.StylusEventHelper;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.WorkspaceItemInfo;
@@ -63,6 +60,7 @@
 import com.android.launcher3.dragndrop.BaseItemDragListener;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.icons.DotRenderer;
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.util.Executors;
@@ -78,14 +76,14 @@
 /**
  * An icon that can appear on in the workspace representing an {@link Folder}.
  */
-public class FolderIcon extends FrameLayout implements FolderListener, IconLabelDotView {
+public class FolderIcon extends FrameLayout implements FolderListener, IconLabelDotView,
+        DraggableView {
 
     @Thunk ActivityContext mActivity;
     @Thunk Folder mFolder;
     private FolderInfo mInfo;
 
     private CheckLongPressHelper mLongPressHelper;
-    private StylusEventHelper mStylusEventHelper;
 
     static final int DROP_IN_ANIMATION_DURATION = 400;
 
@@ -108,8 +106,6 @@
 
     boolean mAnimating = false;
 
-    private float mSlop;
-
     private Alarm mOpenAlarm = new Alarm();
 
     private boolean mForceHideDot;
@@ -147,9 +143,7 @@
 
     private void init() {
         mLongPressHelper = new CheckLongPressHelper(this);
-        mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
         mPreviewLayoutRule = new ClippedFolderIconLayoutRule();
-        mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
         mPreviewItemManager = new PreviewItemManager(this);
         mDotParams = new DotRenderer.DrawParams();
     }
@@ -230,6 +224,16 @@
         mBackground.getBounds(outBounds);
     }
 
+    @Override
+    public int getViewType() {
+        return DRAGGABLE_ICON;
+    }
+
+    @Override
+    public void getVisualDragBounds(Rect bounds) {
+        getPreviewBounds(bounds);
+    }
+
     public float getBackgroundStrokeWidth() {
         return mBackground.getStrokeWidth();
     }
@@ -525,6 +529,10 @@
         invalidate();
     }
 
+    public boolean getIconVisible() {
+        return mBackgroundIsVisible;
+    }
+
     public PreviewBackground getFolderBackground() {
         return mBackground;
     }
@@ -647,29 +655,10 @@
     public boolean onTouchEvent(MotionEvent event) {
         // Call the superclass onTouchEvent first, because sometimes it changes the state to
         // isPressed() on an ACTION_UP
-        boolean result = super.onTouchEvent(event);
-
-        // Check for a stylus button press, if it occurs cancel any long press checks.
-        if (mStylusEventHelper.onMotionEvent(event)) {
-            mLongPressHelper.cancelLongPress();
-            return true;
-        }
-
-        switch (event.getAction()) {
-            case MotionEvent.ACTION_DOWN:
-                mLongPressHelper.postCheckForLongPress();
-                break;
-            case MotionEvent.ACTION_CANCEL:
-            case MotionEvent.ACTION_UP:
-                mLongPressHelper.cancelLongPress();
-                break;
-            case MotionEvent.ACTION_MOVE:
-                if (!Utilities.pointInView(this, event.getX(), event.getY(), mSlop)) {
-                    mLongPressHelper.cancelLongPress();
-                }
-                break;
-        }
-        return result;
+        super.onTouchEvent(event);
+        mLongPressHelper.onTouchEvent(event);
+        // Keep receiving the rest of the events
+        return true;
     }
 
     @Override
diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java
index 94d30f6..ed9dfbb 100644
--- a/src/com/android/launcher3/graphics/DragPreviewProvider.java
+++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java
@@ -30,14 +30,12 @@
 import android.view.View;
 
 import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.icons.BitmapRenderer;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
-import com.android.launcher3.widget.PendingAppWidgetHostView;
 
 import java.nio.ByteBuffer;
 
@@ -53,7 +51,7 @@
     // The padding added to the drag view during the preview generation.
     public final int previewPadding;
 
-    protected final int blurSizeOutline;
+    public final int blurSizeOutline;
 
     private OutlineGeneratorCallback mOutlineGeneratorCallback;
     public Bitmap generatedDragOutline;
@@ -66,56 +64,25 @@
         mView = view;
         blurSizeOutline =
                 context.getResources().getDimensionPixelSize(R.dimen.blur_size_medium_outline);
-
-        if (mView instanceof BubbleTextView) {
-            Drawable d = ((BubbleTextView) mView).getIcon();
-            Rect bounds = getDrawableBounds(d);
-            previewPadding = blurSizeOutline - bounds.left - bounds.top;
-        } else {
-            previewPadding = blurSizeOutline;
-        }
+        previewPadding = blurSizeOutline;
     }
 
     /**
      * Draws the {@link #mView} into the given {@param destCanvas}.
      */
     protected void drawDragView(Canvas destCanvas, float scale) {
-        destCanvas.save();
+        int saveCount = destCanvas.save();
         destCanvas.scale(scale, scale);
 
-        if (mView instanceof BubbleTextView) {
-            Drawable d = ((BubbleTextView) mView).getIcon();
-            Rect bounds = getDrawableBounds(d);
-            destCanvas.translate(blurSizeOutline / 2 - bounds.left,
-                    blurSizeOutline / 2 - bounds.top);
-            if (d instanceof FastBitmapDrawable) {
-                ((FastBitmapDrawable) d).setScale(1);
-            }
-            d.draw(destCanvas);
-        } else {
-            final Rect clipRect = mTempRect;
-            mView.getDrawingRect(clipRect);
-
-            boolean textVisible = false;
-            if (mView instanceof FolderIcon) {
-                // For FolderIcons the text can bleed into the icon area, and so we need to
-                // hide the text completely (which can't be achieved by clipping).
-                if (((FolderIcon) mView).getTextVisible()) {
-                    ((FolderIcon) mView).setTextVisible(false);
-                    textVisible = true;
-                }
-            }
-            destCanvas.translate(-mView.getScrollX() + blurSizeOutline / 2,
-                    -mView.getScrollY() + blurSizeOutline / 2);
-            destCanvas.clipRect(clipRect);
+        if (mView instanceof DraggableView) {
+            DraggableView dv = (DraggableView) mView;
+            dv.prepareDrawDragView();
+            dv.getVisualDragBounds(mTempRect);
+            destCanvas.translate(blurSizeOutline / 2 - mTempRect.left,
+                    blurSizeOutline / 2 - mTempRect.top);
             mView.draw(destCanvas);
-
-            // Restore text visibility of FolderIcon if necessary
-            if (textVisible) {
-                ((FolderIcon) mView).setTextVisible(true);
-            }
         }
-        destCanvas.restore();
+        destCanvas.restoreToCount(saveCount);
     }
 
     /**
@@ -123,28 +90,15 @@
      * Responsibility for the bitmap is transferred to the caller.
      */
     public Bitmap createDragBitmap() {
-        int width = mView.getWidth();
-        int height = mView.getHeight();
-
-        if (mView instanceof BubbleTextView) {
-            Drawable d = ((BubbleTextView) mView).getIcon();
-            Rect bounds = getDrawableBounds(d);
-            width = bounds.width();
-            height = bounds.height();
-        } else if (mView instanceof LauncherAppWidgetHostView) {
-            float scale = ((LauncherAppWidgetHostView) mView).getScaleToFit();
-            width = (int) (mView.getWidth() * scale);
-            height = (int) (mView.getHeight() * scale);
-
-            if (mView instanceof PendingAppWidgetHostView) {
-                // Use hardware renderer as the icon for the pending app widget may be a hw bitmap
-                return BitmapRenderer.createHardwareBitmap(width + blurSizeOutline,
-                        height + blurSizeOutline, (c) -> drawDragView(c, scale));
-            } else {
-                // Use software renderer for widgets as we know that they already work
-                return BitmapRenderer.createSoftwareBitmap(width + blurSizeOutline,
-                        height + blurSizeOutline, (c) -> drawDragView(c, scale));
-            }
+        int width = 0;
+        int height = 0;
+        if (mView instanceof DraggableView) {
+            ((DraggableView) mView).getVisualDragBounds(mTempRect);
+            width = mTempRect.width();
+            height = mTempRect.height();
+        } else {
+            width = mView.getWidth();
+            height = mView.getHeight();
         }
 
         return BitmapRenderer.createHardwareBitmap(width + blurSizeOutline,
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 5bc6610..7d4eb0e 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -21,7 +21,6 @@
 
 import static com.android.launcher3.config.FeatureFlags.ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER;
 import static com.android.launcher3.config.FeatureFlags.MULTI_DB_GRID_MIRATION_ALGO;
-import static com.android.launcher3.model.GridSizeMigrationTask.needsToMigrate;
 import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
 import static com.android.launcher3.model.ModelUtils.sortWorkspaceItemsSpatially;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -397,7 +396,10 @@
 
         private void populate() {
             if (ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER.get()) {
-                boolean needsToMigrate = needsToMigrate(mContext, mIdp);
+                boolean needsToMigrate =
+                        MULTI_DB_GRID_MIRATION_ALGO.get()
+                                ? GridSizeMigrationTaskV2.needsToMigrate(mContext, mIdp)
+                                : GridSizeMigrationTask.needsToMigrate(mContext, mIdp);
                 boolean success = false;
                 if (needsToMigrate) {
                     success = MULTI_DB_GRID_MIRATION_ALGO.get()
diff --git a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java b/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
index 83349bc..7b7ab5e 100644
--- a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
+++ b/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
@@ -19,6 +19,7 @@
 import static android.content.Intent.ACTION_SCREEN_OFF;
 import static android.content.Intent.ACTION_USER_PRESENT;
 
+import static com.android.launcher3.config.FeatureFlags.KEYGUARD_ANIMATION;
 import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
 
 import android.animation.ObjectAnimator;
@@ -39,12 +40,14 @@
 import android.util.DisplayMetrics;
 import android.util.FloatProperty;
 import android.view.View;
+import android.view.WindowInsets;
 
 import androidx.core.graphics.ColorUtils;
 
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.R;
 import com.android.launcher3.ResourceUtils;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.uioverrides.WallpaperColorInfo;
 import com.android.launcher3.util.Themes;
@@ -81,6 +84,10 @@
                 }
             };
 
+    /**
+     * Receiver used to get a signal that the user unlocked their device.
+     * @see KEYGUARD_ANIMATION For proper signal.
+     */
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -164,8 +171,10 @@
                 mSysUiAnimMultiplier = 0;
                 reapplySysUiAlphaNoInvalidate();
 
-                animateToSysuiMultiplier(1, 600,
-                        mLauncher.getWindow().getTransitionBackgroundFadeDuration());
+                ObjectAnimator oa = createSysuiMultiplierAnim(1);
+                oa.setDuration(600);
+                oa.setStartDelay(mLauncher.getWindow().getTransitionBackgroundFadeDuration());
+                oa.start();
                 mAnimateScrimOnNextDraw = false;
             }
 
@@ -178,26 +187,42 @@
         }
     }
 
-    public void animateToSysuiMultiplier(float toMultiplier, long duration,
-            long startDelay) {
-        ObjectAnimator anim = ObjectAnimator.ofFloat(this, SYSUI_ANIM_MULTIPLIER, toMultiplier);
+    /**
+     * @return an ObjectAnimator that controls the fade in/out of the sys ui scrim.
+     */
+    public ObjectAnimator createSysuiMultiplierAnim(float... values) {
+        ObjectAnimator anim = ObjectAnimator.ofFloat(this, SYSUI_ANIM_MULTIPLIER, values);
         anim.setAutoCancel(true);
-        anim.setDuration(duration);
-        anim.setStartDelay(startDelay);
-        anim.start();
+        return anim;
     }
 
-    public void onInsetsChanged(Rect insets) {
-        mDrawTopScrim = mTopScrim != null && insets.top > 0;
-        mDrawBottomScrim = mBottomMask != null &&
-                !mLauncher.getDeviceProfile().isVerticalBarLayout();
+    /**
+     * Determines whether to draw the top and/or bottom scrim based on new insets.
+     */
+    public void onInsetsChanged(Rect insets, boolean allowSysuiScrims) {
+        mDrawTopScrim = allowSysuiScrims
+                && mTopScrim != null
+                && insets.top > 0;
+        mDrawBottomScrim = allowSysuiScrims
+                && mBottomMask != null
+                && !mLauncher.getDeviceProfile().isVerticalBarLayout()
+                && hasBottomNavButtons();
+    }
+
+    private boolean hasBottomNavButtons() {
+        if (Utilities.ATLEAST_Q && mLauncher.getRootView() != null
+                && mLauncher.getRootView().getRootWindowInsets() != null) {
+            WindowInsets windowInsets = mLauncher.getRootView().getRootWindowInsets();
+            return windowInsets.getTappableElementInsets().bottom > 0;
+        }
+        return true;
     }
 
     @Override
     public void onViewAttachedToWindow(View view) {
         super.onViewAttachedToWindow(view);
 
-        if (mTopScrim != null) {
+        if (!KEYGUARD_ANIMATION.get() && mTopScrim != null) {
             IntentFilter filter = new IntentFilter(ACTION_SCREEN_OFF);
             filter.addAction(ACTION_USER_PRESENT); // When the device wakes up + keyguard is gone
             mRoot.getContext().registerReceiver(mReceiver, filter);
@@ -207,7 +232,7 @@
     @Override
     public void onViewDetachedFromWindow(View view) {
         super.onViewDetachedFromWindow(view);
-        if (mTopScrim != null) {
+        if (!KEYGUARD_ANIMATION.get() && mTopScrim != null) {
             mRoot.getContext().unregisterReceiver(mReceiver);
         }
     }
@@ -229,14 +254,6 @@
         }
     }
 
-    public void hideSysUiScrim(boolean hideSysUiScrim) {
-        mHideSysUiScrim = hideSysUiScrim || (mTopScrim == null);
-        if (!hideSysUiScrim) {
-            mAnimateScrimOnNextDraw = true;
-        }
-        invalidate();
-    }
-
     private void setSysUiProgress(float progress) {
         if (progress != mSysUiProgress) {
             mSysUiProgress = progress;
diff --git a/src/com/android/launcher3/logging/DumpTargetWrapper.java b/src/com/android/launcher3/logging/DumpTargetWrapper.java
deleted file mode 100644
index 067bdfd..0000000
--- a/src/com/android/launcher3/logging/DumpTargetWrapper.java
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.logging;
-
-import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
-
-import android.content.ComponentName;
-import android.os.Process;
-import android.text.TextUtils;
-
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.LauncherAppWidgetInfo;
-import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.model.nano.LauncherDumpProto;
-import com.android.launcher3.model.nano.LauncherDumpProto.ContainerType;
-import com.android.launcher3.model.nano.LauncherDumpProto.DumpTarget;
-import com.android.launcher3.model.nano.LauncherDumpProto.ItemType;
-import com.android.launcher3.model.nano.LauncherDumpProto.UserType;
-import com.android.launcher3.util.ShortcutUtil;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * This class can be used when proto definition doesn't support nesting.
- */
-public class DumpTargetWrapper {
-    DumpTarget node;
-    ArrayList<DumpTargetWrapper> children;
-
-    public DumpTargetWrapper() {
-        children = new ArrayList<>();
-    }
-
-    public DumpTargetWrapper(int containerType, int id) {
-        this();
-        node = newContainerTarget(containerType, id);
-    }
-
-    public DumpTargetWrapper(ItemInfo info) {
-        this();
-        node = newItemTarget(info);
-    }
-
-    public DumpTarget getDumpTarget() {
-        return node;
-    }
-
-    public void add(DumpTargetWrapper child) {
-        children.add(child);
-    }
-
-    public List<DumpTarget> getFlattenedList() {
-        ArrayList<DumpTarget> list = new ArrayList<>();
-        list.add(node);
-        if (!children.isEmpty()) {
-            for(DumpTargetWrapper t: children) {
-                list.addAll(t.getFlattenedList());
-            }
-            list.add(node); // add a delimiter empty object
-        }
-        return list;
-    }
-    public DumpTarget newItemTarget(ItemInfo info) {
-        DumpTarget dt = new DumpTarget();
-        dt.type = DumpTarget.Type.ITEM;
-        if (info == null) {
-            return dt;
-        }
-        switch (info.itemType) {
-            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
-                dt.itemType = ItemType.APP_ICON;
-                break;
-            case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
-                dt.itemType = ItemType.WIDGET;
-                break;
-            case ITEM_TYPE_DEEP_SHORTCUT:
-            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
-                dt.itemType = ItemType.SHORTCUT;
-                break;
-            default:
-                dt.itemType = ItemType.UNKNOWN_ITEMTYPE;
-                break;
-        }
-        return dt;
-    }
-
-    public DumpTarget newContainerTarget(int type, int id) {
-        DumpTarget dt = new DumpTarget();
-        dt.type = DumpTarget.Type.CONTAINER;
-        dt.containerType = type;
-        dt.pageId = id;
-        return dt;
-    }
-
-    public static String getDumpTargetStr(DumpTarget t) {
-        if (t == null){
-            return "";
-        }
-        switch (t.type) {
-            case LauncherDumpProto.DumpTarget.Type.ITEM:
-                return getItemStr(t);
-            case LauncherDumpProto.DumpTarget.Type.CONTAINER:
-                String str = LoggerUtils.getFieldName(t.containerType, ContainerType.class);
-                if (t.containerType == ContainerType.WORKSPACE) {
-                    str += " id=" + t.pageId;
-                } else if (t.containerType == ContainerType.FOLDER) {
-                    str += " grid(" + t.gridX + "," + t.gridY+ ")";
-                }
-                return str;
-            default:
-                return "UNKNOWN TARGET TYPE";
-        }
-    }
-
-    private static String getItemStr(DumpTarget t) {
-        if (t == null) {
-            return "";
-        }
-        String typeStr = LoggerUtils.getFieldName(t.itemType, ItemType.class);
-        if (!TextUtils.isEmpty(t.packageName)) {
-            typeStr += ", package=" + t.packageName;
-        }
-        if (!TextUtils.isEmpty(t.component)) {
-            typeStr += ", component=" + t.component;
-        }
-        return typeStr + ", grid(" + t.gridX + "," + t.gridY + "), span(" + t.spanX + "," + t.spanY
-                + "), pageIdx=" + t.pageId + " user=" + t.userType;
-    }
-
-    public DumpTarget writeToDumpTarget(ItemInfo info) {
-        if (info == null) {
-            return node;
-        }
-        if (ShortcutUtil.isDeepShortcut(info)) {
-            node.component = ((WorkspaceItemInfo) info).getDeepShortcutId();
-        } else {
-            ComponentName cmp = info.getTargetComponent();
-            node.component = cmp == null ? "" : cmp.flattenToString();
-        }
-        node.packageName = info.getTargetComponent() == null? "":
-                info.getTargetComponent().getPackageName();
-        if (info instanceof LauncherAppWidgetInfo) {
-            node.component = ((LauncherAppWidgetInfo) info).providerName.flattenToString();
-            node.packageName = ((LauncherAppWidgetInfo) info).providerName.getPackageName();
-        }
-
-        node.gridX = info.cellX;
-        node.gridY = info.cellY;
-        node.spanX = info.spanX;
-        node.spanY = info.spanY;
-        node.userType = (info.user.equals(Process.myUserHandle()))? UserType.DEFAULT : UserType.WORK;
-        return node;
-    }
-}
diff --git a/src/com/android/launcher3/logging/LauncherUiEvent.java b/src/com/android/launcher3/logging/LauncherUiEvent.java
new file mode 100644
index 0000000..4507ff7
--- /dev/null
+++ b/src/com/android/launcher3/logging/LauncherUiEvent.java
@@ -0,0 +1,30 @@
+/*
+ * 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.logging;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Retention(SOURCE)
+@Target(FIELD)
+public @interface LauncherUiEvent {
+    /** An explanation, suitable for Android analysts, of the UI event that this log represents. */
+    String doc();
+}
+
diff --git a/src/com/android/launcher3/logging/LoggerUtils.java b/src/com/android/launcher3/logging/LoggerUtils.java
index a9d10d7..1b70fde 100644
--- a/src/com/android/launcher3/logging/LoggerUtils.java
+++ b/src/com/android/launcher3/logging/LoggerUtils.java
@@ -15,8 +15,6 @@
  */
 package com.android.launcher3.logging;
 
-import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType.NAVBAR;
-
 import android.util.ArrayMap;
 import android.util.SparseArray;
 import android.view.View;
@@ -27,12 +25,9 @@
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.userevent.nano.LauncherLogExtensions.TargetExtension;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.LauncherEvent;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
-import com.android.launcher3.userevent.nano.LauncherLogProto.TipType;
 import com.android.launcher3.util.InstantAppResolver;
 
 import java.lang.reflect.Field;
@@ -70,93 +65,6 @@
         return result != null ? result : UNKNOWN;
     }
 
-    public static String getActionStr(Action action) {
-        String str = "";
-        switch (action.type) {
-            case Action.Type.TOUCH:
-                str += getFieldName(action.touch, Action.Touch.class);
-                if (action.touch == Action.Touch.SWIPE || action.touch == Action.Touch.FLING) {
-                    str += " direction=" + getFieldName(action.dir, Action.Direction.class);
-                }
-                break;
-            case Action.Type.COMMAND:
-                str += getFieldName(action.command, Action.Command.class);
-                break;
-            default: return getFieldName(action.type, Action.Type.class);
-        }
-        if (action.touch == Action.Touch.SWIPE || action.touch == Action.Touch.FLING ||
-                (action.command == Action.Command.BACK && action.dir != Action.Direction.NONE)) {
-            str += " direction=" + getFieldName(action.dir, Action.Direction.class);
-        }
-        return str;
-    }
-
-    public static String getTargetStr(Target t) {
-        if (t == null) {
-            return "";
-        }
-        String str = "";
-        switch (t.type) {
-            case Target.Type.ITEM:
-                str = getItemStr(t);
-                break;
-            case Target.Type.CONTROL:
-                str = getFieldName(t.controlType, ControlType.class);
-                break;
-            case Target.Type.CONTAINER:
-                str = getFieldName(t.containerType, ContainerType.class);
-                if (t.containerType == ContainerType.WORKSPACE ||
-                        t.containerType == ContainerType.HOTSEAT ||
-                        t.containerType == NAVBAR) {
-                    str += " id=" + t.pageIndex;
-                } else if (t.containerType == ContainerType.FOLDER) {
-                    str += " grid(" + t.gridX + "," + t.gridY + ")";
-                }
-                break;
-            default:
-                str += "UNKNOWN TARGET TYPE";
-        }
-
-        if (t.spanX != 1 || t.spanY != 1) {
-            str += " span(" + t.spanX + "," + t.spanY + ")";
-        }
-
-        if (t.tipType != TipType.DEFAULT_NONE) {
-            str += " " + getFieldName(t.tipType, TipType.class);
-        }
-
-        return str;
-    }
-
-    private static String getItemStr(Target t) {
-        String typeStr = getFieldName(t.itemType, ItemType.class);
-        if (t.packageNameHash != 0) {
-            typeStr += ", packageHash=" + t.packageNameHash;
-        }
-        if (t.componentHash != 0) {
-            typeStr += ", componentHash=" + t.componentHash;
-        }
-        if (t.intentHash != 0) {
-            typeStr += ", intentHash=" + t.intentHash;
-        }
-        if (t.itemType == ItemType.FOLDER_ICON) {
-            typeStr += ", grid(" + t.gridX + "," + t.gridY + ")";
-        } else if ((t.packageNameHash != 0 || t.componentHash != 0 || t.intentHash != 0)
-                && t.itemType != ItemType.TASK) {
-            typeStr +=
-                    ", isWorkApp=" + t.isWorkApp + ", predictiveRank=" + t.predictedRank + ", grid("
-                            + t.gridX + "," + t.gridY + "), span(" + t.spanX + "," + t.spanY
-                            + "), pageIdx=" + t.pageIndex;
-        }
-        if (t.searchQueryLength != 0) {
-            typeStr += ", searchQueryLength=" + t.searchQueryLength;
-        }
-        if (t.itemType == ItemType.TASK) {
-            typeStr += ", pageIdx=" + t.pageIndex;
-        }
-        return typeStr;
-    }
-
     public static Target newItemTarget(int itemType) {
         Target t = newTarget(Target.Type.ITEM);
         t.itemType = itemType;
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 9dfd7ab..2829951 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -16,23 +16,44 @@
 package com.android.launcher3.logging;
 
 import android.content.Context;
-import android.content.Intent;
-import android.os.UserHandle;
-import android.view.View;
-
-import androidx.annotation.Nullable;
 
 import com.android.launcher3.R;
+import com.android.launcher3.logger.LauncherAtom;
+import com.android.launcher3.logger.LauncherAtom.ItemInfo;
 import com.android.launcher3.logging.StatsLogUtils.LogStateProvider;
-import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.ResourceBasedOverride;
 
 /**
- * Handles the user event logging in Q.
+ * Handles the user event logging in R+.
  */
 public class StatsLogManager implements ResourceBasedOverride {
 
+    interface EventEnum {
+        int getId();
+    }
+
+    public enum LauncherEvent implements EventEnum {
+        @LauncherUiEvent(doc = "App launched from workspace, hotseat or folder in launcher")
+        APP_LAUNCH_TAP(1),
+        @LauncherUiEvent(doc = "Task launched from overview using TAP")
+        TASK_LAUNCH_TAP(2),
+        @LauncherUiEvent(doc = "Task launched from overview using SWIPE DOWN")
+        TASK_LAUNCH_SWIPE_DOWN(2),
+        @LauncherUiEvent(doc = "TASK dismissed from overview using SWIPE UP")
+        TASK_DISMISS_SWIPE_UP(3);
+        // ADD MORE
+
+        private final int mId;
+        LauncherEvent(int id) {
+            mId = id;
+        }
+        public int getId() {
+            return mId;
+        }
+    }
+
     protected LogStateProvider mStateProvider;
+
     public static StatsLogManager newInstance(Context context, LogStateProvider stateProvider) {
         StatsLogManager mgr = Overrides.getObject(StatsLogManager.class,
                 context.getApplicationContext(), R.string.stats_log_manager_class);
@@ -42,11 +63,14 @@
     }
 
     /**
-     * Logs app launches
+     * Logs an event and accompanying {@link ItemInfo}
      */
-    public void logAppLaunch(View v, Intent intent, @Nullable UserHandle userHandle) { }
-    public void logTaskLaunch(View v, ComponentKey key) { }
-    public void logTaskDismiss(View v, ComponentKey key) { }
-    public void logSwipeOnContainer(boolean isSwipingToLeft, int pageId) { }
+    public void log(LauncherEvent eventId, LauncherAtom.ItemInfo itemInfo) { }
+
+    /**
+     * Logs snapshot, or impression of the current workspace.
+     */
+    public void logSnapshot() { }
+
     public void verify() {}     // TODO: should move into robo tests
 }
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 32fce0b..206688a 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -36,10 +36,6 @@
 import com.android.launcher3.Workspace;
 import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.logging.DumpTargetWrapper;
-import com.android.launcher3.model.nano.LauncherDumpProto;
-import com.android.launcher3.model.nano.LauncherDumpProto.ContainerType;
-import com.android.launcher3.model.nano.LauncherDumpProto.DumpTarget;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.shortcuts.ShortcutRequest;
 import com.android.launcher3.util.ComponentKey;
@@ -50,11 +46,7 @@
 import com.android.launcher3.util.ViewOnDrawExecutor;
 import com.android.launcher3.widget.WidgetListRowEntry;
 
-import com.google.protobuf.nano.MessageNano;
-
 import java.io.FileDescriptor;
-import java.io.FileOutputStream;
-import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -150,10 +142,6 @@
 
     public synchronized void dump(String prefix, FileDescriptor fd, PrintWriter writer,
             String[] args) {
-        if (Arrays.asList(args).contains("--proto")) {
-            dumpProto(prefix, fd, writer, args);
-            return;
-        }
         writer.println(prefix + "Data Model:");
         writer.println(prefix + " ---- workspace items ");
         for (int i = 0; i < workspaceItems.size(); i++) {
@@ -181,89 +169,6 @@
         }
     }
 
-    private synchronized void dumpProto(String prefix, FileDescriptor fd, PrintWriter writer,
-            String[] args) {
-
-        // Add top parent nodes. (L1)
-        DumpTargetWrapper hotseat = new DumpTargetWrapper(ContainerType.HOTSEAT, 0);
-        IntSparseArrayMap<DumpTargetWrapper> workspaces = new IntSparseArrayMap<>();
-        IntArray workspaceScreens = collectWorkspaceScreens();
-        for (int i = 0; i < workspaceScreens.size(); i++) {
-            workspaces.put(workspaceScreens.get(i),
-                    new DumpTargetWrapper(ContainerType.WORKSPACE, i));
-        }
-        DumpTargetWrapper dtw;
-        // Add non leaf / non top nodes (L2)
-        for (int i = 0; i < folders.size(); i++) {
-            FolderInfo fInfo = folders.valueAt(i);
-            dtw = new DumpTargetWrapper(ContainerType.FOLDER, folders.size());
-            dtw.writeToDumpTarget(fInfo);
-            for(WorkspaceItemInfo sInfo: fInfo.contents) {
-                DumpTargetWrapper child = new DumpTargetWrapper(sInfo);
-                child.writeToDumpTarget(sInfo);
-                dtw.add(child);
-            }
-            if (fInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
-                hotseat.add(dtw);
-            } else if (fInfo.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
-                workspaces.get(fInfo.screenId).add(dtw);
-            }
-        }
-        // Add leaf nodes (L3): *Info
-        for (int i = 0; i < workspaceItems.size(); i++) {
-            ItemInfo info = workspaceItems.get(i);
-            if (info instanceof FolderInfo) {
-                continue;
-            }
-            dtw = new DumpTargetWrapper(info);
-            dtw.writeToDumpTarget(info);
-            if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
-                hotseat.add(dtw);
-            } else if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
-                workspaces.get(info.screenId).add(dtw);
-            }
-        }
-        for (int i = 0; i < appWidgets.size(); i++) {
-            ItemInfo info = appWidgets.get(i);
-            dtw = new DumpTargetWrapper(info);
-            dtw.writeToDumpTarget(info);
-            if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
-                hotseat.add(dtw);
-            } else if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
-                workspaces.get(info.screenId).add(dtw);
-            }
-        }
-
-
-        // Traverse target wrapper
-        ArrayList<DumpTarget> targetList = new ArrayList<>();
-        targetList.addAll(hotseat.getFlattenedList());
-        for (int i = 0; i < workspaces.size(); i++) {
-            targetList.addAll(workspaces.valueAt(i).getFlattenedList());
-        }
-
-        if (Arrays.asList(args).contains("--debug")) {
-            for (int i = 0; i < targetList.size(); i++) {
-                writer.println(prefix + DumpTargetWrapper.getDumpTargetStr(targetList.get(i)));
-            }
-            return;
-        } else {
-            LauncherDumpProto.LauncherImpression proto = new LauncherDumpProto.LauncherImpression();
-            proto.targets = new DumpTarget[targetList.size()];
-            for (int i = 0; i < targetList.size(); i++) {
-                proto.targets[i] = targetList.get(i);
-            }
-            FileOutputStream fos = new FileOutputStream(fd);
-            try {
-
-                fos.write(MessageNano.toByteArray(proto));
-                Log.d(TAG, MessageNano.toByteArray(proto).length + "Bytes");
-            } catch (IOException e) {
-                Log.e(TAG, "Exception writing dumpsys --proto", e);
-            }
-        }
-    }
-
     public synchronized void removeItem(Context context, ItemInfo... items) {
         removeItem(context, Arrays.asList(items));
     }
@@ -424,7 +329,8 @@
         // Now add the new shortcuts to the map.
         for (ShortcutInfo shortcut : shortcuts) {
             boolean shouldShowInContainer = shortcut.isEnabled()
-                    && (shortcut.isDeclaredInManifest() || shortcut.isDynamic());
+                    && (shortcut.isDeclaredInManifest() || shortcut.isDynamic())
+                    && shortcut.getActivity() != null;
             if (shouldShowInContainer) {
                 ComponentKey targetComponent
                         = new ComponentKey(shortcut.getActivity(), shortcut.getUserHandle());
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
index 0bdccfa..79ae4c5 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
@@ -123,8 +123,16 @@
     }
 
     /**
-     * Run the migration algorithm if needed. For preview, we provide the intended idp because it
-     * has not been changed. If idp is null, we read it from the context, for actual grid migration.
+     * When migrating the grid for preview, we copy the table
+     * {@link LauncherSettings.Favorites.TABLE_NAME} into
+     * {@link LauncherSettings.Favorites.PREVIEW_TABLE_NAME}, run grid size migration from the
+     * former to the later, then use the later table for preview.
+     *
+     * Similarly when doing the actual grid migration, the former grid option's table
+     * {@link LauncherSettings.Favorites.TABLE_NAME} is copied into the new grid option's
+     * {@link LauncherSettings.Favorites.TMP_TABLE}, we then run the grid size migration algorithm
+     * to migrate the later to the former, and load the workspace from the default
+     * {@link LauncherSettings.Favorites.TABLE_NAME}.
      *
      * @return false if the migration failed.
      */
@@ -151,7 +159,14 @@
         HashSet<String> validPackages = getValidPackages(context);
         int srcHotseatCount = prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, idp.numHotseatIcons);
 
-        if (!LauncherSettings.Settings.call(
+        if (migrateForPreview) {
+            if (!LauncherSettings.Settings.call(
+                    context.getContentResolver(),
+                    LauncherSettings.Settings.METHOD_PREP_FOR_PREVIEW, idp.dbFile).getBoolean(
+                    LauncherSettings.Settings.EXTRA_VALUE)) {
+                return false;
+            }
+        } else if (!LauncherSettings.Settings.call(
                 context.getContentResolver(),
                 LauncherSettings.Settings.METHOD_UPDATE_CURRENT_OPEN_HELPER).getBoolean(
                 LauncherSettings.Settings.EXTRA_VALUE)) {
@@ -164,9 +179,13 @@
                 LauncherSettings.Settings.METHOD_NEW_TRANSACTION).getBinder(
                 LauncherSettings.Settings.EXTRA_VALUE)) {
 
-            DbReader srcReader = new DbReader(t.getDb(), LauncherSettings.Favorites.TMP_TABLE,
+            DbReader srcReader = new DbReader(t.getDb(),
+                    migrateForPreview ? LauncherSettings.Favorites.TABLE_NAME
+                            : LauncherSettings.Favorites.TMP_TABLE,
                     context, validPackages, srcHotseatCount);
-            DbReader destReader = new DbReader(t.getDb(), LauncherSettings.Favorites.TABLE_NAME,
+            DbReader destReader = new DbReader(t.getDb(),
+                    migrateForPreview ? LauncherSettings.Favorites.PREVIEW_TABLE_NAME
+                            : LauncherSettings.Favorites.TABLE_NAME,
                     context, validPackages, idp.numHotseatIcons);
 
             Point targetSize = new Point(idp.numColumns, idp.numRows);
@@ -174,7 +193,9 @@
                     srcReader, destReader, idp.numHotseatIcons, targetSize);
             task.migrate();
 
-            dropTable(t.getDb(), LauncherSettings.Favorites.TMP_TABLE);
+            if (!migrateForPreview) {
+                dropTable(t.getDb(), LauncherSettings.Favorites.TMP_TABLE);
+            }
 
             t.commit();
             return true;
@@ -186,11 +207,13 @@
             Log.v(TAG, "Workspace migration completed in "
                     + (System.currentTimeMillis() - migrationStartTime));
 
-            // Save current configuration, so that the migration does not run again.
-            prefs.edit()
-                    .putString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, gridSizeString)
-                    .putInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, idp.numHotseatIcons)
-                    .apply();
+            if (!migrateForPreview) {
+                // Save current configuration, so that the migration does not run again.
+                prefs.edit()
+                        .putString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, gridSizeString)
+                        .putInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, idp.numHotseatIcons)
+                        .apply();
+            }
         }
     }
 
@@ -202,7 +225,7 @@
 
         // Migrate hotseat
         HotseatPlacementSolution hotseatSolution = new HotseatPlacementSolution(mDb, mSrcReader,
-                mContext, mDestHotseatSize, mHotseatItems, mHotseatDiff);
+                mDestReader, mContext, mDestHotseatSize, mHotseatItems, mHotseatDiff);
         hotseatSolution.find();
 
         // Sort the items by the reading order.
@@ -215,7 +238,7 @@
             }
             List<DbEntry> entries = mDestReader.loadWorkspaceEntries(screenId);
             GridPlacementSolution workspaceSolution = new GridPlacementSolution(mDb, mSrcReader,
-                    mContext, entries, screenId, mTrgX, mTrgY, mWorkspaceDiff);
+                    mDestReader, mContext, entries, screenId, mTrgX, mTrgY, mWorkspaceDiff);
             workspaceSolution.find();
             if (mWorkspaceDiff.isEmpty()) {
                 break;
@@ -225,7 +248,8 @@
         int screenId = mDestReader.mLastScreenId + 1;
         while (!mWorkspaceDiff.isEmpty()) {
             GridPlacementSolution workspaceSolution = new GridPlacementSolution(mDb, mSrcReader,
-                    mContext, new ArrayList<>(), screenId, mTrgX, mTrgY, mWorkspaceDiff);
+                    mDestReader, mContext, new ArrayList<>(), screenId, mTrgX, mTrgY,
+                    mWorkspaceDiff);
             workspaceSolution.find();
             screenId++;
         }
@@ -246,7 +270,8 @@
     }
 
     private static void insertEntryInDb(SQLiteDatabase db, Context context,
-            ArrayList<DbEntry> entriesFromSrcDb, DbEntry entry) {
+            ArrayList<DbEntry> entriesFromSrcDb, DbEntry entry, String srcTableName,
+            String destTableName) {
         int id = -1;
         switch (entry.itemType) {
             case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
@@ -283,8 +308,8 @@
                 return;
         }
 
-        Cursor c = db.query(LauncherSettings.Favorites.TMP_TABLE, null,
-                LauncherSettings.Favorites._ID + " = '" + id + "'", null, null, null, null);
+        Cursor c = db.query(srcTableName, null, LauncherSettings.Favorites._ID + " = '" + id + "'",
+                null, null, null, null);
 
         while (c.moveToNext()) {
             ContentValues values = new ContentValues();
@@ -294,14 +319,14 @@
                     LauncherSettings.Settings.call(context.getContentResolver(),
                             LauncherSettings.Settings.METHOD_NEW_ITEM_ID).getInt(
                             LauncherSettings.Settings.EXTRA_VALUE));
-            db.insert(LauncherSettings.Favorites.TABLE_NAME, null, values);
+            db.insert(destTableName, null, values);
         }
         c.close();
     }
 
-    private static void removeEntryFromDb(SQLiteDatabase db, IntArray entryId) {
-        db.delete(LauncherSettings.Favorites.TABLE_NAME, Utilities.createDbSelectionQuery(
-                LauncherSettings.Favorites._ID, entryId), null);
+    private static void removeEntryFromDb(SQLiteDatabase db, String tableName, IntArray entryId) {
+        db.delete(tableName,
+                Utilities.createDbSelectionQuery(LauncherSettings.Favorites._ID, entryId), null);
     }
 
     private static HashSet<String> getValidPackages(Context context) {
@@ -325,6 +350,7 @@
 
         private final SQLiteDatabase mDb;
         private final DbReader mSrcReader;
+        private final DbReader mDestReader;
         private final Context mContext;
         private final GridOccupancy mOccupied;
         private final int mScreenId;
@@ -335,11 +361,12 @@
         private int mNextStartX;
         private int mNextStartY;
 
-        GridPlacementSolution(SQLiteDatabase db, DbReader srcReader, Context context,
-                List<DbEntry> placedWorkspaceItems, int screenId, int trgX,
+        GridPlacementSolution(SQLiteDatabase db, DbReader srcReader, DbReader destReader,
+                Context context, List<DbEntry> placedWorkspaceItems, int screenId, int trgX,
                 int trgY, List<DbEntry> itemsToPlace) {
             mDb = db;
             mSrcReader = srcReader;
+            mDestReader = destReader;
             mContext = context;
             mOccupied = new GridOccupancy(trgX, trgY);
             mScreenId = screenId;
@@ -362,7 +389,8 @@
                     continue;
                 }
                 if (findPlacement(entry)) {
-                    insertEntryInDb(mDb, mContext, mSrcReader.mWorkspaceEntries, entry);
+                    insertEntryInDb(mDb, mContext, mSrcReader.mWorkspaceEntries, entry,
+                            mSrcReader.mTableName, mDestReader.mTableName);
                     iterator.remove();
                 }
             }
@@ -397,14 +425,17 @@
 
         private final SQLiteDatabase mDb;
         private final DbReader mSrcReader;
+        private final DbReader mDestReader;
         private final Context mContext;
         private final HotseatOccupancy mOccupied;
         private final List<DbEntry> mItemsToPlace;
 
-        HotseatPlacementSolution(SQLiteDatabase db, DbReader srcReader, Context context,
-                int hotseatSize, List<DbEntry> placedHotseatItems, List<DbEntry> itemsToPlace) {
+        HotseatPlacementSolution(SQLiteDatabase db, DbReader srcReader, DbReader destReader,
+                Context context, int hotseatSize, List<DbEntry> placedHotseatItems,
+                List<DbEntry> itemsToPlace) {
             mDb = db;
             mSrcReader = srcReader;
+            mDestReader = destReader;
             mContext = context;
             mOccupied = new HotseatOccupancy(hotseatSize);
             for (DbEntry entry : placedHotseatItems) {
@@ -422,7 +453,8 @@
                     // to something other than -1.
                     entry.cellX = i;
                     entry.cellY = 0;
-                    insertEntryInDb(mDb, mContext, mSrcReader.mHotseatEntries, entry);
+                    insertEntryInDb(mDb, mContext, mSrcReader.mHotseatEntries, entry,
+                            mSrcReader.mTableName, mDestReader.mTableName);
                     mOccupied.markCells(entry, true);
                 }
             }
@@ -519,7 +551,7 @@
                 }
                 mHotseatEntries.add(entry);
             }
-            removeEntryFromDb(mDb, entriesToRemove);
+            removeEntryFromDb(mDb, mTableName, entriesToRemove);
             c.close();
             return mHotseatEntries;
         }
@@ -639,7 +671,7 @@
                 }
                 mWorkspaceEntries.add(entry);
             }
-            removeEntryFromDb(mDb, entriesToRemove);
+            removeEntryFromDb(mDb, mTableName, entriesToRemove);
             c.close();
             return mWorkspaceEntries;
         }
@@ -657,7 +689,7 @@
                     total++;
                     entry.mFolderItems.add(intent);
                 } catch (Exception e) {
-                    removeEntryFromDb(mDb, IntArray.wrap(c.getInt(0)));
+                    removeEntryFromDb(mDb, mTableName, IntArray.wrap(c.getInt(0)));
                 }
             }
             c.close();
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 2311dcc..695d2a6 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -35,6 +35,8 @@
 import android.util.Log;
 import android.util.LongSparseArray;
 
+import androidx.annotation.VisibleForTesting;
+
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.ItemInfo;
@@ -150,8 +152,10 @@
         }
     }
 
+    @VisibleForTesting
     public WorkspaceItemInfo loadSimpleWorkspaceItem() {
         final WorkspaceItemInfo info = new WorkspaceItemInfo();
+        info.intent = new Intent();
         // Non-app shortcuts are only supported for current user.
         info.user = user;
         info.itemType = itemType;
diff --git a/src/com/android/launcher3/pm/UserCache.java b/src/com/android/launcher3/pm/UserCache.java
index 678b647..f723256 100644
--- a/src/com/android/launcher3/pm/UserCache.java
+++ b/src/com/android/launcher3/pm/UserCache.java
@@ -95,7 +95,7 @@
 
     private void removeUserChangeListener(Runnable command) {
         synchronized (this) {
-            mUserChangeListeners.add(command);
+            mUserChangeListeners.remove(command);
             if (mUserChangeListeners.isEmpty()) {
                 // Disable cache and stop listening
                 mContext.unregisterReceiver(mUserChangeReceiver);
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 5af5ebb..9bac259 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -59,6 +59,7 @@
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.notification.NotificationInfo;
 import com.android.launcher3.notification.NotificationItemView;
 import com.android.launcher3.notification.NotificationKeyData;
@@ -662,7 +663,8 @@
             iconShift.x = mIconLastTouchPos.x - sv.getIconCenter().x;
             iconShift.y = mIconLastTouchPos.y - mLauncher.getDeviceProfile().iconSizePx;
 
-            DragView dv = mLauncher.getWorkspace().beginDragShared(sv.getIconView(),
+            DraggableView draggableView = DraggableView.ofType(DraggableView.DRAGGABLE_ICON);
+            DragView dv = mLauncher.getWorkspace().beginDragShared(sv.getIconView(), draggableView,
                     mContainer, sv.getFinalInfo(),
                     new ShortcutDragPreviewProvider(sv.getIconView(), iconShift),
                     new DragOptions());
diff --git a/src/com/android/launcher3/provider/LauncherDbUtils.java b/src/com/android/launcher3/provider/LauncherDbUtils.java
index dacea84..7e05a5a 100644
--- a/src/com/android/launcher3/provider/LauncherDbUtils.java
+++ b/src/com/android/launcher3/provider/LauncherDbUtils.java
@@ -129,6 +129,7 @@
             toDb.execSQL("ATTACH DATABASE '" + fromDb.getPath() + "' AS from_db");
             toDb.execSQL(
                     "INSERT INTO " + toTable + " SELECT * FROM from_db." + fromTable);
+            toDb.execSQL("DETACH DATABASE 'from_db'");
         } else {
             toDb.execSQL("INSERT INTO " + toTable + " SELECT * FROM " + fromTable);
         }
diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
index 8c8aa9b..d28fcf6 100644
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -15,10 +15,12 @@
  */
 package com.android.launcher3.states;
 
+import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE;
 
 import static com.android.launcher3.config.FeatureFlags.FLAG_ENABLE_FIXED_ROTATION_TRANSFORM;
@@ -37,7 +39,6 @@
 import android.view.WindowManager;
 
 import com.android.launcher3.Launcher;
-import com.android.launcher3.PagedView;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
@@ -134,7 +135,7 @@
      */
     private void updateForcedRotation(boolean setValueFromPrefs) {
         boolean isForcedRotation = mFeatureFlagsPrefs
-                .getBoolean(FLAG_ENABLE_FIXED_ROTATION_TRANSFORM, false)
+                .getBoolean(FLAG_ENABLE_FIXED_ROTATION_TRANSFORM, true)
                 && !getAllowRotationDefaultValue();
         if (mForcedRotation == isForcedRotation) {
             return;
@@ -142,9 +143,12 @@
         if (setValueFromPrefs) {
             mForcedRotation = isForcedRotation;
         }
-        UI_HELPER_EXECUTOR.execute(
-                () -> Settings.Global.putInt(mContentResolver, FIXED_ROTATION_TRANSFORM_SETTING_NAME,
-                        mForcedRotation ? 1 : 0));
+        UI_HELPER_EXECUTOR.execute(() -> {
+            if (mLauncher.checkSelfPermission(WRITE_SECURE_SETTINGS) == PERMISSION_GRANTED) {
+                Settings.Global.putInt(mContentResolver, FIXED_ROTATION_TRANSFORM_SETTING_NAME,
+                            mForcedRotation ? 1 : 0);
+            }
+        });
         for (ForcedRotationChangedListener listener : mForcedRotationChangedListeners) {
             listener.onForcedRotationChanged(mForcedRotation);
         }
diff --git a/src/com/android/launcher3/states/SpringLoadedState.java b/src/com/android/launcher3/states/SpringLoadedState.java
index 97c67c5..2ba624c 100644
--- a/src/com/android/launcher3/states/SpringLoadedState.java
+++ b/src/com/android/launcher3/states/SpringLoadedState.java
@@ -17,6 +17,7 @@
 
 import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
 
+import android.content.Context;
 import android.graphics.Rect;
 
 import com.android.launcher3.DeviceProfile;
@@ -77,6 +78,11 @@
     }
 
     @Override
+    public float getDepth(Context context) {
+        return 0.5f;
+    }
+
+    @Override
     public ScaleAndTranslation getHotseatScaleAndTranslation(Launcher launcher) {
         return new ScaleAndTranslation(1, 0, 0);
     }
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 97ce65e..9f20df6 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -35,6 +35,7 @@
     public static final String TAPL_EVENTS_TAG = "TaplEvents";
     public static final String SEQUENCE_MAIN = "Main";
     public static final String SEQUENCE_TIS = "TIS";
+    public static final String SEQUENCE_PILFER = "Pilfer";
 
     public static String stateOrdinalToString(int ordinal) {
         switch (ordinal) {
@@ -95,4 +96,6 @@
 
     public static final String NO_BACKGROUND_TO_OVERVIEW_TAG = "b/138251824";
     public static final String APP_NOT_DISABLED = "b/139891609";
+    public static final String NO_SCROLL_END_WIDGETS = "b/152354290";
+    public static final String NO_START_FROM_RECENTS = "b/152658211";
 }
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index 1d14a8f..cbc5257 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -535,7 +535,7 @@
             // case the user started interacting with it before the animation finished.
             mLauncher.getStateManager().goToState(targetState, false /* animated */);
         }
-        mLauncher.getDragLayer().getScrim().animateToSysuiMultiplier(1, 0, 0);
+        mLauncher.getDragLayer().getScrim().createSysuiMultiplierAnim(1f).setDuration(0).start();
     }
 
     private void logReachedState(int logAction, LauncherState targetState) {
diff --git a/src/com/android/launcher3/touch/ItemLongClickListener.java b/src/com/android/launcher3/touch/ItemLongClickListener.java
index ba1bfa5..8537bdf 100644
--- a/src/com/android/launcher3/touch/ItemLongClickListener.java
+++ b/src/com/android/launcher3/touch/ItemLongClickListener.java
@@ -33,6 +33,8 @@
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.folder.Folder;
+import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.TestProtocol;
 
 /**
  * Class to handle long-clicks on workspace items and start drag as a result.
@@ -46,6 +48,7 @@
             ItemLongClickListener::onAllAppsItemLongClick;
 
     private static boolean onWorkspaceItemLongClick(View v) {
+        TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onWorkspaceItemLongClick");
         Launcher launcher = Launcher.getLauncher(v.getContext());
         if (!canStartDrag(launcher)) return false;
         if (!launcher.isInState(NORMAL) && !launcher.isInState(OVERVIEW)) return false;
@@ -75,6 +78,8 @@
     }
 
     private static boolean onAllAppsItemLongClick(View v) {
+        TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onAllAppsItemLongClick");
+        v.cancelLongPress();
         Launcher launcher = Launcher.getLauncher(v.getContext());
         if (!canStartDrag(launcher)) return false;
         // When we have exited all apps or are in transition, disregard long clicks
diff --git a/src/com/android/launcher3/touch/WorkspaceTouchListener.java b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
index 310d598..da631bd 100644
--- a/src/com/android/launcher3/touch/WorkspaceTouchListener.java
+++ b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
@@ -38,6 +38,8 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.views.OptionsPopupView;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -165,6 +167,7 @@
 
     @Override
     public void onLongPress(MotionEvent event) {
+        TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "Workspace.longPress");
         if (mLongPressState == STATE_REQUESTED) {
             if (canHandleLongPress()) {
                 mLongPressState = STATE_PENDING_PARENT_INFORM;
diff --git a/src/com/android/launcher3/util/OverScroller.java b/src/com/android/launcher3/util/OverScroller.java
index 3c398b8..34efb12 100644
--- a/src/com/android/launcher3/util/OverScroller.java
+++ b/src/com/android/launcher3/util/OverScroller.java
@@ -165,6 +165,9 @@
     /**
      * Returns how long the scroll event will take, in milliseconds.
      *
+     * Note that if mScroller.mState == SPRING, this duration is ignored, so can only
+     * serve as an estimate for how long the spring-controlled scroll will take.
+     *
      * @return The duration of the scroll in milliseconds.
      */
     public final int getDuration() {
diff --git a/src/com/android/launcher3/util/VibratorWrapper.java b/src/com/android/launcher3/util/VibratorWrapper.java
index 04741a1..b0defd4 100644
--- a/src/com/android/launcher3/util/VibratorWrapper.java
+++ b/src/com/android/launcher3/util/VibratorWrapper.java
@@ -39,7 +39,7 @@
     public static final MainThreadInitializedObject<VibratorWrapper> INSTANCE =
             new MainThreadInitializedObject<>(VibratorWrapper::new);
 
-    private static final VibrationEffect EFFECT_CLICK =
+    public static final VibrationEffect EFFECT_CLICK =
             createPredefined(VibrationEffect.EFFECT_CLICK);
 
     /**
diff --git a/src/com/android/launcher3/views/ArrowTipView.java b/src/com/android/launcher3/views/ArrowTipView.java
new file mode 100644
index 0000000..60470dc
--- /dev/null
+++ b/src/com/android/launcher3/views/ArrowTipView.java
@@ -0,0 +1,154 @@
+/*
+ * 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.views;
+
+import android.content.Context;
+import android.graphics.CornerPathEffect;
+import android.graphics.Paint;
+import android.graphics.drawable.ShapeDrawable;
+import android.os.Handler;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.core.content.ContextCompat;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.graphics.TriangleShape;
+
+/**
+ * A base class for arrow tip view in launcher
+ */
+public class ArrowTipView extends AbstractFloatingView {
+
+    private static final long AUTO_CLOSE_TIMEOUT_MILLIS = 10 * 1000;
+    private static final long SHOW_DELAY_MS = 200;
+    private static final long SHOW_DURATION_MS = 300;
+    private static final long HIDE_DURATION_MS = 100;
+
+    protected final Launcher mLauncher;
+    private final Handler mHandler = new Handler();
+    private Runnable mOnClosed;
+
+    public ArrowTipView(Context context) {
+        super(context, null, 0);
+        mLauncher = Launcher.getLauncher(context);
+        init(context);
+    }
+
+    @Override
+    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            close(true);
+        }
+        return false;
+    }
+
+    @Override
+    protected void handleClose(boolean animate) {
+        if (mIsOpen) {
+            if (animate) {
+                animate().alpha(0f)
+                        .withLayer()
+                        .setStartDelay(0)
+                        .setDuration(HIDE_DURATION_MS)
+                        .setInterpolator(Interpolators.ACCEL)
+                        .withEndAction(() -> mLauncher.getDragLayer().removeView(this))
+                        .start();
+            } else {
+                animate().cancel();
+                mLauncher.getDragLayer().removeView(this);
+            }
+            if (mOnClosed != null) mOnClosed.run();
+            mIsOpen = false;
+        }
+    }
+
+    @Override
+    public void logActionCommand(int command) {
+    }
+
+    @Override
+    protected boolean isOfType(int type) {
+        return (type & TYPE_ON_BOARD_POPUP) != 0;
+    }
+
+    private void init(Context context) {
+        inflate(context, R.layout.arrow_toast, this);
+        setOrientation(LinearLayout.VERTICAL);
+        View dismissButton = findViewById(R.id.dismiss);
+        dismissButton.setOnClickListener(view -> {
+            handleClose(true);
+        });
+
+        View arrowView = findViewById(R.id.arrow);
+        ViewGroup.LayoutParams arrowLp = arrowView.getLayoutParams();
+        ShapeDrawable arrowDrawable = new ShapeDrawable(TriangleShape.create(
+                arrowLp.width, arrowLp.height, false));
+        Paint arrowPaint = arrowDrawable.getPaint();
+        TypedValue typedValue = new TypedValue();
+        context.getTheme().resolveAttribute(android.R.attr.colorAccent, typedValue, true);
+        arrowPaint.setColor(ContextCompat.getColor(getContext(), typedValue.resourceId));
+        // The corner path effect won't be reflected in the shadow, but shouldn't be noticeable.
+        arrowPaint.setPathEffect(new CornerPathEffect(
+                context.getResources().getDimension(R.dimen.arrow_toast_corner_radius)));
+        arrowView.setBackground(arrowDrawable);
+
+        mIsOpen = true;
+
+        mHandler.postDelayed(() -> handleClose(true), AUTO_CLOSE_TIMEOUT_MILLIS);
+    }
+
+    /**
+     * Show Tip with specified string and Y location
+     */
+    public ArrowTipView show(String text, int top) {
+        ((TextView) findViewById(R.id.text)).setText(text);
+        mLauncher.getDragLayer().addView(this);
+
+        DragLayer.LayoutParams params = (DragLayer.LayoutParams) getLayoutParams();
+        params.gravity = Gravity.CENTER_HORIZONTAL;
+        params.leftMargin = mLauncher.getDeviceProfile().workspacePadding.left;
+        params.rightMargin = mLauncher.getDeviceProfile().workspacePadding.right;
+        post(() -> setY(top - getHeight()));
+        setAlpha(0);
+        animate()
+                .alpha(1f)
+                .withLayer()
+                .setStartDelay(SHOW_DELAY_MS)
+                .setDuration(SHOW_DURATION_MS)
+                .setInterpolator(Interpolators.DEACCEL)
+                .start();
+        return this;
+    }
+
+    /**
+     * Register a callback fired when toast is hidden
+     */
+    public ArrowTipView setOnClosedCallback(Runnable runnable) {
+        mOnClosed = runnable;
+        return this;
+    }
+}
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index 25748ae..2fc3eaf 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -23,12 +23,17 @@
 import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
 
 import android.annotation.TargetApi;
+import android.app.WallpaperInfo;
+import android.app.WallpaperManager;
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.os.Build;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.util.Property;
 import android.view.MotionEvent;
 import android.view.View;
@@ -38,11 +43,16 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.FrameLayout;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.InsettableFrameLayout;
+import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
+import com.android.launcher3.util.SimpleBroadcastReceiver;
 import com.android.launcher3.util.TouchController;
 
 import java.io.PrintWriter;
@@ -98,6 +108,10 @@
 
     protected final T mActivity;
     private final MultiValueAlpha mMultiValueAlpha;
+    private final WallpaperManager mWallpaperManager;
+    private final SimpleBroadcastReceiver mWallpaperChangeReceiver =
+            new SimpleBroadcastReceiver(this::onWallpaperChanged);
+    private final String[] mWallpapersWithoutSysuiScrims;
 
     // All the touch controllers for the view
     protected TouchController[] mControllers;
@@ -108,10 +122,15 @@
 
     private TouchCompleteListener mTouchCompleteListener;
 
+    protected boolean mAllowSysuiScrims = true;
+
     public BaseDragLayer(Context context, AttributeSet attrs, int alphaChannelCount) {
         super(context, attrs);
         mActivity = (T) ActivityContext.lookupContext(context);
         mMultiValueAlpha = new MultiValueAlpha(this, alphaChannelCount);
+        mWallpaperManager = context.getSystemService(WallpaperManager.class);
+        mWallpapersWithoutSysuiScrims = getResources().getStringArray(
+                R.array.live_wallpapers_remove_sysui_scrims);
     }
 
     /**
@@ -256,6 +275,9 @@
 
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
+        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+            Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "BaseDragLayer: " + ev);
+        }
         switch (ev.getAction()) {
             case ACTION_DOWN: {
                 mTouchDispatchState |= TOUCH_DISPATCHING_VIEW;
@@ -517,4 +539,46 @@
         }
         return super.dispatchApplyWindowInsets(insets);
     }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mWallpaperChangeReceiver.register(mActivity, Intent.ACTION_WALLPAPER_CHANGED);
+        onWallpaperChanged(null);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mActivity.unregisterReceiver(mWallpaperChangeReceiver);
+    }
+
+    private void onWallpaperChanged(Intent unusedBroadcastIntent) {
+        WallpaperInfo newWallpaperInfo = mWallpaperManager.getWallpaperInfo();
+        boolean oldAllowSysuiScrims = mAllowSysuiScrims;
+        mAllowSysuiScrims = computeAllowSysuiScrims(newWallpaperInfo);
+        if (mAllowSysuiScrims != oldAllowSysuiScrims) {
+            // Reapply insets so scrim can be removed or re-added if necessary.
+            setInsets(mInsets);
+        }
+    }
+
+    /**
+     * Determines whether we can scrim the status bar and nav bar for the given wallpaper by
+     * checking against a list of live wallpapers that we don't show the scrims on.
+     */
+    private boolean computeAllowSysuiScrims(@Nullable WallpaperInfo newWallpaperInfo) {
+        if (newWallpaperInfo == null) {
+            // New wallpaper is static, not live. Thus, blacklist isn't applicable.
+            return true;
+        }
+        ComponentName newWallpaper = newWallpaperInfo.getComponent();
+        for (String wallpaperWithoutScrim : mWallpapersWithoutSysuiScrims) {
+            if (newWallpaper.equals(ComponentName.unflattenFromString(wallpaperWithoutScrim))) {
+                // New wallpaper is blacklisted from showing a scrim.
+                return false;
+            }
+        }
+        return true;
+    }
 }
diff --git a/src/com/android/launcher3/views/ClipIconView.java b/src/com/android/launcher3/views/ClipIconView.java
new file mode 100644
index 0000000..478141a
--- /dev/null
+++ b/src/com/android/launcher3/views/ClipIconView.java
@@ -0,0 +1,349 @@
+/*
+ * 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.Utilities.mapToRange;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Outline;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewOutlineProvider;
+
+import androidx.annotation.Nullable;
+import androidx.dynamicanimation.animation.FloatPropertyCompat;
+import androidx.dynamicanimation.animation.SpringAnimation;
+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;
+import com.android.launcher3.graphics.IconShape;
+
+/**
+ * A view used to draw both layers of an {@link AdaptiveIconDrawable}.
+ * Supports springing just the foreground layer.
+ * Supports clipping the icon to/from its icon shape.
+ */
+@TargetApi(Build.VERSION_CODES.Q)
+public class ClipIconView extends View implements ClipPathView {
+
+    private static final Rect sTmpRect = new Rect();
+
+    // We spring the foreground drawable relative to the icon's movement in the DragLayer.
+    // We then use these two factor values to scale the movement of the fg within this view.
+    private static final int FG_TRANS_X_FACTOR = 60;
+    private static final int FG_TRANS_Y_FACTOR = 75;
+
+    private static final FloatPropertyCompat<ClipIconView> mFgTransYProperty =
+            new FloatPropertyCompat<ClipIconView>("ClipIconViewFgTransY") {
+                @Override
+                public float getValue(ClipIconView view) {
+                    return view.mFgTransY;
+                }
+
+                @Override
+                public void setValue(ClipIconView view, float transY) {
+                    view.mFgTransY = transY;
+                    view.invalidate();
+                }
+            };
+
+    private static final FloatPropertyCompat<ClipIconView> mFgTransXProperty =
+            new FloatPropertyCompat<ClipIconView>("ClipIconViewFgTransX") {
+                @Override
+                public float getValue(ClipIconView view) {
+                    return view.mFgTransX;
+                }
+
+                @Override
+                public void setValue(ClipIconView view, float transX) {
+                    view.mFgTransX = transX;
+                    view.invalidate();
+                }
+            };
+
+    private final Launcher mLauncher;
+    private final int mBlurSizeOutline;
+    private final boolean mIsRtl;
+
+    private @Nullable Drawable mForeground;
+    private @Nullable Drawable mBackground;
+
+    private boolean mIsVerticalBarLayout = false;
+    private boolean mIsAdaptiveIcon = false;
+
+    private ValueAnimator mRevealAnimator;
+
+    private final Rect mStartRevealRect = new Rect();
+    private final Rect mEndRevealRect = new Rect();
+    private Path mClipPath;
+    private float mTaskCornerRadius;
+
+    private final Rect mOutline = new Rect();
+    private final Rect mFinalDrawableBounds = new Rect();
+
+    private final SpringAnimation mFgSpringY;
+    private float mFgTransY;
+    private final SpringAnimation mFgSpringX;
+    private float mFgTransX;
+
+    public ClipIconView(Context context) {
+        this(context, null);
+    }
+
+    public ClipIconView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    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());
+
+        mFgSpringX = new SpringAnimation(this, mFgTransXProperty)
+                .setSpring(new SpringForce()
+                        .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
+                        .setStiffness(SpringForce.STIFFNESS_LOW));
+        mFgSpringY = new SpringAnimation(this, mFgTransYProperty)
+                .setSpring(new SpringForce()
+                        .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
+                        .setStiffness(SpringForce.STIFFNESS_LOW));
+    }
+
+    void update(RectF rect, float progress, float shapeProgressStart, float cornerRadius,
+            boolean isOpening, float scale, float minSize, LayoutParams parentLp) {
+        DeviceProfile dp = mLauncher.getDeviceProfile();
+        float dX = mIsRtl
+                ? rect.left - (dp.widthPx - parentLp.getMarginStart() - parentLp.width)
+                : rect.left - parentLp.getMarginStart();
+        float dY = rect.top - parentLp.topMargin;
+
+        // shapeRevealProgress = 1 when progress = shapeProgressStart + SHAPE_PROGRESS_DURATION
+        float toMax = isOpening ? 1 / SHAPE_PROGRESS_DURATION : 1f;
+        float shapeRevealProgress = Utilities.boundToRange(mapToRange(
+                Math.max(shapeProgressStart, progress), shapeProgressStart, 1f, 0, toMax,
+                LINEAR), 0, 1);
+
+        if (mIsVerticalBarLayout) {
+            mOutline.right = (int) (rect.width() / scale);
+        } else {
+            mOutline.bottom = (int) (rect.height() / scale);
+        }
+
+        mTaskCornerRadius = cornerRadius / scale;
+        if (mIsAdaptiveIcon) {
+            if (!isOpening && progress >= shapeProgressStart) {
+                if (mRevealAnimator == null) {
+                    mRevealAnimator = (ValueAnimator) IconShape.getShape().createRevealAnimator(
+                            this, mStartRevealRect, mOutline, mTaskCornerRadius, !isOpening);
+                    mRevealAnimator.addListener(new AnimatorListenerAdapter() {
+                        @Override
+                        public void onAnimationEnd(Animator animation) {
+                            mRevealAnimator = null;
+                        }
+                    });
+                    mRevealAnimator.start();
+                    // We pause here so we can set the current fraction ourselves.
+                    mRevealAnimator.pause();
+                }
+                mRevealAnimator.setCurrentFraction(shapeRevealProgress);
+            }
+
+            float drawableScale = (mIsVerticalBarLayout ? mOutline.width() : mOutline.height())
+                    / minSize;
+            setBackgroundDrawableBounds(drawableScale);
+            if (isOpening) {
+                // Center align foreground
+                int height = mFinalDrawableBounds.height();
+                int width = mFinalDrawableBounds.width();
+                int diffY = mIsVerticalBarLayout ? 0
+                        : (int) (((height * drawableScale) - height) / 2);
+                int diffX = mIsVerticalBarLayout ? (int) (((width * drawableScale) - width) / 2)
+                        : 0;
+                sTmpRect.set(mFinalDrawableBounds);
+                sTmpRect.offset(diffX, diffY);
+                mForeground.setBounds(sTmpRect);
+            } else {
+                // Spring the foreground relative to the icon's movement within the DragLayer.
+                int diffX = (int) (dX / dp.availableWidthPx * FG_TRANS_X_FACTOR);
+                int diffY = (int) (dY / dp.availableHeightPx * FG_TRANS_Y_FACTOR);
+
+                mFgSpringX.animateToFinalPosition(diffX);
+                mFgSpringY.animateToFinalPosition(diffY);
+            }
+        }
+        invalidate();
+        invalidateOutline();
+    }
+
+    private void setBackgroundDrawableBounds(float scale) {
+        sTmpRect.set(mFinalDrawableBounds);
+        Utilities.scaleRectAboutCenter(sTmpRect, scale);
+        // Since the drawable is at the top of the view, we need to offset to keep it centered.
+        if (mIsVerticalBarLayout) {
+            sTmpRect.offsetTo((int) (mFinalDrawableBounds.left * scale), sTmpRect.top);
+        } else {
+            sTmpRect.offsetTo(sTmpRect.left, (int) (mFinalDrawableBounds.top * scale));
+        }
+        mBackground.setBounds(sTmpRect);
+    }
+
+    protected void endReveal() {
+        if (mRevealAnimator != null) {
+            mRevealAnimator.end();
+        }
+    }
+
+    void setIcon(@Nullable Drawable drawable, int iconOffset, LayoutParams lp, boolean isOpening) {
+        mIsAdaptiveIcon = drawable instanceof AdaptiveIconDrawable;
+        if (mIsAdaptiveIcon) {
+            boolean isFolderIcon = drawable instanceof FolderAdaptiveIcon;
+
+            AdaptiveIconDrawable adaptiveIcon = (AdaptiveIconDrawable) drawable;
+            Drawable background = adaptiveIcon.getBackground();
+            if (background == null) {
+                background = new ColorDrawable(Color.TRANSPARENT);
+            }
+            mBackground = background;
+            Drawable foreground = adaptiveIcon.getForeground();
+            if (foreground == null) {
+                foreground = new ColorDrawable(Color.TRANSPARENT);
+            }
+            mForeground = foreground;
+
+            final int originalHeight = lp.height;
+            final int originalWidth = lp.width;
+
+            int blurMargin = mBlurSizeOutline / 2;
+            mFinalDrawableBounds.set(0, 0, originalWidth, originalHeight);
+
+            if (!isFolderIcon) {
+                mFinalDrawableBounds.inset(iconOffset - blurMargin, iconOffset - blurMargin);
+            }
+            mForeground.setBounds(mFinalDrawableBounds);
+            mBackground.setBounds(mFinalDrawableBounds);
+
+            mStartRevealRect.set(0, 0, originalWidth, originalHeight);
+
+            if (!isFolderIcon) {
+                Utilities.scaleRectAboutCenter(mStartRevealRect, IconShape.getNormalizationScale());
+            }
+
+            float aspectRatio = mLauncher.getDeviceProfile().aspectRatio;
+            if (mIsVerticalBarLayout) {
+                lp.width = (int) Math.max(lp.width, lp.height * aspectRatio);
+            } else {
+                lp.height = (int) Math.max(lp.height, lp.width * aspectRatio);
+            }
+
+            int left = mIsRtl
+                    ? mLauncher.getDeviceProfile().widthPx - lp.getMarginStart() - lp.width
+                    : lp.leftMargin;
+            layout(left, lp.topMargin, left + lp.width, lp.topMargin + lp.height);
+
+            float scale = Math.max((float) lp.height / originalHeight,
+                    (float) lp.width / originalWidth);
+            float bgDrawableStartScale;
+            if (isOpening) {
+                bgDrawableStartScale = 1f;
+                mOutline.set(0, 0, originalWidth, originalHeight);
+            } else {
+                bgDrawableStartScale = scale;
+                mOutline.set(0, 0, lp.width, lp.height);
+            }
+            setBackgroundDrawableBounds(bgDrawableStartScale);
+            mEndRevealRect.set(0, 0, lp.width, lp.height);
+            setOutlineProvider(new ViewOutlineProvider() {
+                @Override
+                public void getOutline(View view, Outline outline) {
+                    outline.setRoundRect(mOutline, mTaskCornerRadius);
+                }
+            });
+            setClipToOutline(true);
+        } else {
+            setBackground(drawable);
+            setClipToOutline(false);
+        }
+
+        invalidate();
+        invalidateOutline();
+    }
+
+    @Override
+    public void setClipPath(Path clipPath) {
+        mClipPath = clipPath;
+        invalidate();
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        int count = canvas.save();
+        if (mClipPath != null) {
+            canvas.clipPath(mClipPath);
+        }
+        super.draw(canvas);
+        if (mBackground != null) {
+            mBackground.draw(canvas);
+        }
+        if (mForeground != null) {
+            int count2 = canvas.save();
+            canvas.translate(mFgTransX, mFgTransY);
+            mForeground.draw(canvas);
+            canvas.restoreToCount(count2);
+        }
+        canvas.restoreToCount(count);
+    }
+
+    void recycle() {
+        setBackground(null);
+        mIsAdaptiveIcon = false;
+        mForeground = null;
+        mBackground = null;
+        mClipPath = null;
+        mFinalDrawableBounds.setEmpty();
+        if (mRevealAnimator != null) {
+            mRevealAnimator.cancel();
+        }
+        mRevealAnimator = null;
+        mTaskCornerRadius = 0;
+        mOutline.setEmpty();
+        mFgTransY = 0;
+        mFgSpringX.cancel();
+        mFgTransX = 0;
+        mFgSpringY.cancel();
+    }
+}
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index fa625ed..3e2560f 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -18,8 +18,6 @@
 import static com.android.launcher3.LauncherAnimUtils.DRAWABLE_ALPHA;
 import static com.android.launcher3.Utilities.getBadge;
 import static com.android.launcher3.Utilities.getFullDrawable;
-import static com.android.launcher3.Utilities.mapToRange;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
 import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -28,17 +26,12 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Outline;
-import android.graphics.Path;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.AdaptiveIconDrawable;
-import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.CancellationSignal;
@@ -46,19 +39,17 @@
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewOutlineProvider;
 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+import android.widget.FrameLayout;
 import android.widget.ImageView;
 
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 import androidx.annotation.WorkerThread;
-import androidx.dynamicanimation.animation.FloatPropertyCompat;
-import androidx.dynamicanimation.animation.SpringAnimation;
-import androidx.dynamicanimation.animation.SpringForce;
 
 import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.InsettableFrameLayout.LayoutParams;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InsettableFrameLayout;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
@@ -66,8 +57,6 @@
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
 import com.android.launcher3.folder.FolderIcon;
-import com.android.launcher3.graphics.IconShape;
-import com.android.launcher3.graphics.ShiftedBitmapDrawable;
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.shortcuts.DeepShortcutView;
@@ -76,8 +65,8 @@
  * A view that is created to look like another view with the purpose of creating fluid animations.
  */
 @TargetApi(Build.VERSION_CODES.Q)
-public class FloatingIconView extends View implements
-        Animator.AnimatorListener, ClipPathView, OnGlobalLayoutListener {
+public class FloatingIconView extends FrameLayout implements
+        Animator.AnimatorListener, OnGlobalLayoutListener {
 
     private static final String TAG = FloatingIconView.class.getSimpleName();
 
@@ -86,81 +75,34 @@
 
     public static final float SHAPE_PROGRESS_DURATION = 0.10f;
     private static final int FADE_DURATION_MS = 200;
-    private static final Rect sTmpRect = new Rect();
     private static final RectF sTmpRectF = new RectF();
     private static final Object[] sTmpObjArray = new Object[1];
 
-    // We spring the foreground drawable relative to the icon's movement in the DragLayer.
-    // We then use these two factor values to scale the movement of the fg within this view.
-    private static final int FG_TRANS_X_FACTOR = 60;
-    private static final int FG_TRANS_Y_FACTOR = 75;
-
-    private static final FloatPropertyCompat<FloatingIconView> mFgTransYProperty
-            = new FloatPropertyCompat<FloatingIconView>("FloatingViewFgTransY") {
-        @Override
-        public float getValue(FloatingIconView view) {
-            return view.mFgTransY;
-        }
-
-        @Override
-        public void setValue(FloatingIconView view, float transY) {
-            view.mFgTransY = transY;
-            view.invalidate();
-        }
-    };
-
-    private static final FloatPropertyCompat<FloatingIconView> mFgTransXProperty
-            = new FloatPropertyCompat<FloatingIconView>("FloatingViewFgTransX") {
-        @Override
-        public float getValue(FloatingIconView view) {
-            return view.mFgTransX;
-        }
-
-        @Override
-        public void setValue(FloatingIconView view, float transX) {
-            view.mFgTransX = transX;
-            view.invalidate();
-        }
-    };
-
     private Runnable mEndRunnable;
     private CancellationSignal mLoadIconSignal;
 
     private final Launcher mLauncher;
-    private final int mBlurSizeOutline;
     private final boolean mIsRtl;
 
     private boolean mIsVerticalBarLayout = false;
-    private boolean mIsAdaptiveIcon = false;
     private boolean mIsOpening;
 
     private IconLoadResult mIconLoadResult;
 
+    private ClipIconView mClipIconView;
     private @Nullable Drawable mBadge;
-    private @Nullable Drawable mForeground;
-    private @Nullable Drawable mBackground;
+
     private float mRotation;
-    private ValueAnimator mRevealAnimator;
-    private final Rect mStartRevealRect = new Rect();
-    private final Rect mEndRevealRect = new Rect();
-    private Path mClipPath;
-    private float mTaskCornerRadius;
 
     private View mOriginalIcon;
     private RectF mPositionOut;
     private Runnable mOnTargetChangeRunnable;
 
-    private final Rect mOutline = new Rect();
     private final Rect mFinalDrawableBounds = new Rect();
 
     private AnimatorSet mFadeAnimatorSet;
     private ListenerView mListenerView;
 
-    private final SpringAnimation mFgSpringY;
-    private float mFgTransY;
-    private final SpringAnimation mFgSpringX;
-    private float mFgTransX;
-
     public FloatingIconView(Context context) {
         this(context, null);
     }
@@ -172,19 +114,11 @@
     public FloatingIconView(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());
         mListenerView = new ListenerView(context, attrs);
-
-        mFgSpringX = new SpringAnimation(this, mFgTransXProperty)
-                .setSpring(new SpringForce()
-                .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
-                .setStiffness(SpringForce.STIFFNESS_LOW));
-        mFgSpringY = new SpringAnimation(this, mFgTransYProperty)
-                .setSpring(new SpringForce()
-                        .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
-                        .setStiffness(SpringForce.STIFFNESS_LOW));
+        mClipIconView = new ClipIconView(context, attrs);
+        addView(mClipIconView);
+        setWillNotDraw(false);
     }
 
     @Override
@@ -213,10 +147,12 @@
             float cornerRadius, boolean isOpening) {
         setAlpha(alpha);
 
-        LayoutParams lp = (LayoutParams) getLayoutParams();
+        InsettableFrameLayout.LayoutParams lp =
+                (InsettableFrameLayout.LayoutParams) getLayoutParams();
+
+        DeviceProfile dp = mLauncher.getDeviceProfile();
         float dX = mIsRtl
-                ? rect.left
-                - (mLauncher.getDeviceProfile().widthPx - lp.getMarginStart() - lp.width)
+                ? rect.left - (dp.widthPx - lp.getMarginStart() - lp.width)
                 : rect.left - lp.getMarginStart();
         float dY = rect.top - lp.topMargin;
         setTranslationX(dX);
@@ -227,69 +163,15 @@
         float scaleY = rect.height() / minSize;
         float scale = Math.max(1f, Math.min(scaleX, scaleY));
 
+        mClipIconView.update(rect, progress, shapeProgressStart, cornerRadius, isOpening, scale,
+                minSize, lp);
+
         setPivotX(0);
         setPivotY(0);
         setScaleX(scale);
         setScaleY(scale);
 
-        // shapeRevealProgress = 1 when progress = shapeProgressStart + SHAPE_PROGRESS_DURATION
-        float toMax = isOpening ? 1 / SHAPE_PROGRESS_DURATION : 1f;
-        float shapeRevealProgress = Utilities.boundToRange(mapToRange(
-                Math.max(shapeProgressStart, progress), shapeProgressStart, 1f, 0, toMax,
-                LINEAR), 0, 1);
-
-        if (mIsVerticalBarLayout) {
-            mOutline.right = (int) (rect.width() / scale);
-        } else {
-            mOutline.bottom = (int) (rect.height() / scale);
-        }
-
-        mTaskCornerRadius = cornerRadius / scale;
-        if (mIsAdaptiveIcon) {
-            if (!isOpening && progress >= shapeProgressStart) {
-                if (mRevealAnimator == null) {
-                    mRevealAnimator = (ValueAnimator) IconShape.getShape().createRevealAnimator(
-                            this, mStartRevealRect, mOutline, mTaskCornerRadius, !isOpening);
-                    mRevealAnimator.addListener(new AnimatorListenerAdapter() {
-                        @Override
-                        public void onAnimationEnd(Animator animation) {
-                            mRevealAnimator = null;
-                        }
-                    });
-                    mRevealAnimator.start();
-                    // We pause here so we can set the current fraction ourselves.
-                    mRevealAnimator.pause();
-                }
-                mRevealAnimator.setCurrentFraction(shapeRevealProgress);
-            }
-
-            float drawableScale = (mIsVerticalBarLayout ? mOutline.width() : mOutline.height())
-                    / minSize;
-            setBackgroundDrawableBounds(drawableScale);
-            if (isOpening) {
-                // Center align foreground
-                int height = mFinalDrawableBounds.height();
-                int width = mFinalDrawableBounds.width();
-                int diffY = mIsVerticalBarLayout ? 0
-                        : (int) (((height * drawableScale) - height) / 2);
-                int diffX = mIsVerticalBarLayout ? (int) (((width * drawableScale) - width) / 2)
-                        : 0;
-                sTmpRect.set(mFinalDrawableBounds);
-                sTmpRect.offset(diffX, diffY);
-                mForeground.setBounds(sTmpRect);
-            } else {
-                // Spring the foreground relative to the icon's movement within the DragLayer.
-                int diffX = (int) (dX / mLauncher.getDeviceProfile().availableWidthPx
-                        * FG_TRANS_X_FACTOR);
-                int diffY = (int) (dY / mLauncher.getDeviceProfile().availableHeightPx
-                        * FG_TRANS_Y_FACTOR);
-
-                mFgSpringX.animateToFinalPosition(diffX);
-                mFgSpringY.animateToFinalPosition(diffY);
-            }
-        }
         invalidate();
-        invalidateOutline();
     }
 
     @Override
@@ -301,9 +183,7 @@
             mEndRunnable.run();
         } else {
             // End runnable also ends the reveal animator, so we manually handle it here.
-            if (mRevealAnimator != null) {
-                mRevealAnimator.end();
-            }
+            mClipIconView.endReveal();
         }
     }
 
@@ -315,23 +195,25 @@
      */
     private void matchPositionOf(Launcher launcher, View v, boolean isOpening, RectF positionOut) {
         float rotation = getLocationBoundsForView(launcher, v, isOpening, positionOut);
-        final LayoutParams lp = new LayoutParams(
+        final InsettableFrameLayout.LayoutParams lp = new InsettableFrameLayout.LayoutParams(
                 Math.round(positionOut.width()),
                 Math.round(positionOut.height()));
         updatePosition(rotation, positionOut, lp);
         setLayoutParams(lp);
+
+        mClipIconView.setLayoutParams(new FrameLayout.LayoutParams(lp.width, lp.height));
     }
 
-    private void updatePosition(float rotation, RectF position, LayoutParams lp) {
+    private void updatePosition(float rotation, RectF pos, InsettableFrameLayout.LayoutParams lp) {
         mRotation = rotation;
-        mPositionOut.set(position);
+        mPositionOut.set(pos);
         lp.ignoreInsets = true;
         // Position the floating view exactly on top of the original
-        lp.topMargin = Math.round(position.top);
+        lp.topMargin = Math.round(pos.top);
         if (mIsRtl) {
-            lp.setMarginStart(Math.round(mLauncher.getDeviceProfile().widthPx - position.right));
+            lp.setMarginStart(Math.round(mLauncher.getDeviceProfile().widthPx - pos.right));
         } else {
-            lp.setMarginStart(Math.round(position.left));
+            lp.setMarginStart(Math.round(pos.left));
         }
         // Set the properties here already to make sure they are available when running the first
         // animation frame.
@@ -412,9 +294,8 @@
                 drawable = originalView.getBackground();
             }
         } else {
-            boolean isFolderIcon = originalView instanceof FolderIcon;
-            int width = isFolderIcon ? originalView.getWidth() : (int) pos.width();
-            int height = isFolderIcon ? originalView.getHeight() : (int) pos.height();
+            int width = (int) pos.width();
+            int height = (int) pos.height();
             if (supportsAdaptiveIcons) {
                 drawable = getFullDrawable(l, info, width, height, sTmpObjArray);
                 if (drawable instanceof AdaptiveIconDrawable) {
@@ -451,110 +332,42 @@
     /**
      * Sets the drawables of the {@param originalView} onto this view.
      *
-     * @param originalView The View that the FloatingIconView will replace.
      * @param drawable The drawable of the original view.
      * @param badge The badge of the original view.
      * @param iconOffset The amount of offset needed to match this view with the original view.
      */
     @UiThread
-    private void setIcon(View originalView, @Nullable Drawable drawable, @Nullable Drawable badge,
-            int iconOffset) {
+    private void setIcon(@Nullable Drawable drawable, @Nullable Drawable badge, int iconOffset) {
+        final InsettableFrameLayout.LayoutParams lp =
+                (InsettableFrameLayout.LayoutParams) getLayoutParams();
         mBadge = badge;
-
-        mIsAdaptiveIcon = drawable instanceof AdaptiveIconDrawable;
-        if (mIsAdaptiveIcon) {
-            boolean isFolderIcon = drawable instanceof FolderAdaptiveIcon;
-
-            AdaptiveIconDrawable adaptiveIcon = (AdaptiveIconDrawable) drawable;
-            Drawable background = adaptiveIcon.getBackground();
-            if (background == null) {
-                background = new ColorDrawable(Color.TRANSPARENT);
-            }
-            mBackground = background;
-            Drawable foreground = adaptiveIcon.getForeground();
-            if (foreground == null) {
-                foreground = new ColorDrawable(Color.TRANSPARENT);
-            }
-            mForeground = foreground;
-
-            final LayoutParams lp = (LayoutParams) getLayoutParams();
+        mClipIconView.setIcon(drawable, iconOffset, lp, mIsOpening);
+        if (drawable instanceof AdaptiveIconDrawable) {
             final int originalHeight = lp.height;
             final int originalWidth = lp.width;
 
-            int blurMargin = mBlurSizeOutline / 2;
             mFinalDrawableBounds.set(0, 0, originalWidth, originalHeight);
 
-            if (!isFolderIcon) {
-                mFinalDrawableBounds.inset(iconOffset - blurMargin, iconOffset - blurMargin);
-            }
-            mForeground.setBounds(mFinalDrawableBounds);
-            mBackground.setBounds(mFinalDrawableBounds);
-
-            mStartRevealRect.set(0, 0, originalWidth, originalHeight);
-
-            if (mBadge != null) {
-                mBadge.setBounds(mStartRevealRect);
-                if (!mIsOpening && !isFolderIcon) {
-                    DRAWABLE_ALPHA.set(mBadge, 0);
-                }
-            }
-
-            if (isFolderIcon) {
-                ((FolderIcon) originalView).getPreviewBounds(sTmpRect);
-                float bgStroke = ((FolderIcon) originalView).getBackgroundStrokeWidth();
-                if (mForeground instanceof ShiftedBitmapDrawable) {
-                    ShiftedBitmapDrawable sbd = (ShiftedBitmapDrawable) mForeground;
-                    sbd.setShiftX(sbd.getShiftX() - sTmpRect.left - bgStroke);
-                    sbd.setShiftY(sbd.getShiftY() - sTmpRect.top - bgStroke);
-                }
-                if (mBadge instanceof ShiftedBitmapDrawable) {
-                    ShiftedBitmapDrawable sbd = (ShiftedBitmapDrawable) mBadge;
-                    sbd.setShiftX(sbd.getShiftX() - sTmpRect.left - bgStroke);
-                    sbd.setShiftY(sbd.getShiftY() - sTmpRect.top - bgStroke);
-                }
-            } else {
-                Utilities.scaleRectAboutCenter(mStartRevealRect,
-                        IconShape.getNormalizationScale());
-            }
-
             float aspectRatio = mLauncher.getDeviceProfile().aspectRatio;
             if (mIsVerticalBarLayout) {
                 lp.width = (int) Math.max(lp.width, lp.height * aspectRatio);
             } else {
                 lp.height = (int) Math.max(lp.height, lp.width * aspectRatio);
             }
+            setLayoutParams(lp);
 
-            int left = mIsRtl
-                    ? mLauncher.getDeviceProfile().widthPx - lp.getMarginStart() - lp.width
-                    : lp.leftMargin;
-            layout(left, lp.topMargin, left + lp.width, lp.topMargin + lp.height);
+            final LayoutParams clipViewLp = (LayoutParams) mClipIconView.getLayoutParams();
+            final int clipViewOgHeight = clipViewLp.height;
+            final int clipViewOgWidth = clipViewLp.width;
+            clipViewLp.width = lp.width;
+            clipViewLp.height = lp.height;
+            mClipIconView.setLayoutParams(clipViewLp);
 
-            float scale = Math.max((float) lp.height / originalHeight,
-                    (float) lp.width / originalWidth);
-            float bgDrawableStartScale;
-            if (mIsOpening) {
-                bgDrawableStartScale = 1f;
-                mOutline.set(0, 0, originalWidth, originalHeight);
-            } else {
-                bgDrawableStartScale = scale;
-                mOutline.set(0, 0, lp.width, lp.height);
+            if (mBadge != null) {
+                mBadge.setBounds(0, 0, clipViewOgWidth, clipViewOgHeight);
             }
-            setBackgroundDrawableBounds(bgDrawableStartScale);
-            mEndRevealRect.set(0, 0, lp.width, lp.height);
-            setOutlineProvider(new ViewOutlineProvider() {
-                @Override
-                public void getOutline(View view, Outline outline) {
-                    outline.setRoundRect(mOutline, mTaskCornerRadius);
-                }
-            });
-            setClipToOutline(true);
-        } else {
-            setBackground(drawable);
-            setClipToOutline(false);
         }
-
         invalidate();
-        invalidateOutline();
     }
 
     /**
@@ -571,7 +384,7 @@
 
         synchronized (mIconLoadResult) {
             if (mIconLoadResult.isIconLoaded) {
-                setIcon(originalView, mIconLoadResult.drawable, mIconLoadResult.badge,
+                setIcon(mIconLoadResult.drawable, mIconLoadResult.badge,
                         mIconLoadResult.iconOffset);
                 hideOriginalView(originalView);
             } else {
@@ -580,7 +393,7 @@
                         return;
                     }
 
-                    setIcon(originalView, mIconLoadResult.drawable, mIconLoadResult.badge,
+                    setIcon(mIconLoadResult.drawable, mIconLoadResult.badge,
                             mIconLoadResult.iconOffset);
 
                     setVisibility(VISIBLE);
@@ -600,23 +413,12 @@
         }
     }
 
-    private void setBackgroundDrawableBounds(float scale) {
-        sTmpRect.set(mFinalDrawableBounds);
-        Utilities.scaleRectAboutCenter(sTmpRect, scale);
-        // Since the drawable is at the top of the view, we need to offset to keep it centered.
-        if (mIsVerticalBarLayout) {
-            sTmpRect.offsetTo((int) (mFinalDrawableBounds.left * scale), sTmpRect.top);
-        } else {
-            sTmpRect.offsetTo(sTmpRect.left, (int) (mFinalDrawableBounds.top * scale));
-        }
-        mBackground.setBounds(sTmpRect);
-    }
-
     @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 (Build.VERSION.SDK_INT < Build.VERSION_CODES.O
+                || !(drawable instanceof AdaptiveIconDrawable)
+                || (drawable instanceof FolderAdaptiveIcon)) {
             return 0;
         }
         int blurSizeOutline =
@@ -640,29 +442,11 @@
     }
 
     @Override
-    public void setClipPath(Path clipPath) {
-        mClipPath = clipPath;
-        invalidate();
-    }
-
-    @Override
-    public void draw(Canvas canvas) {
+    protected void dispatchDraw(Canvas canvas) {
         int count = canvas.save();
         canvas.rotate(mRotation,
                 mFinalDrawableBounds.exactCenterX(), mFinalDrawableBounds.exactCenterY());
-        if (mClipPath != null) {
-            canvas.clipPath(mClipPath);
-        }
-        super.draw(canvas);
-        if (mBackground != null) {
-            mBackground.draw(canvas);
-        }
-        if (mForeground != null) {
-            int count2 = canvas.save();
-            canvas.translate(mFgTransX, mFgTransY);
-            mForeground.draw(canvas);
-            canvas.restoreToCount(count2);
-        }
+        super.dispatchDraw(canvas);
         if (mBadge != null) {
             mBadge.draw(canvas);
         }
@@ -706,7 +490,8 @@
             float rotation = getLocationBoundsForView(mLauncher, mOriginalIcon, mIsOpening,
                     sTmpRectF);
             if (rotation != mRotation || !sTmpRectF.equals(mPositionOut)) {
-                updatePosition(rotation, sTmpRectF, (LayoutParams) getLayoutParams());
+                updatePosition(rotation, sTmpRectF,
+                        (InsettableFrameLayout.LayoutParams) getLayoutParams());
                 if (mOnTargetChangeRunnable != null) {
                     mOnTargetChangeRunnable.run();
                 }
@@ -822,12 +607,6 @@
             }
         });
 
-        if (mBadge != null) {
-            ObjectAnimator badgeFade = ObjectAnimator.ofInt(mBadge, DRAWABLE_ALPHA, 255);
-            badgeFade.addUpdateListener(valueAnimator -> invalidate());
-            fade.play(badgeFade);
-        }
-
         if (originalView instanceof IconLabelDotView) {
             IconLabelDotView view = (IconLabelDotView) originalView;
             fade.addListener(new AnimatorListenerAdapter() {
@@ -869,21 +648,12 @@
         setScaleX(1);
         setScaleY(1);
         setAlpha(1);
-        setBackground(null);
         if (mLoadIconSignal != null) {
             mLoadIconSignal.cancel();
         }
         mLoadIconSignal = null;
         mEndRunnable = null;
-        mIsAdaptiveIcon = false;
-        mForeground = null;
-        mBackground = null;
-        mClipPath = null;
         mFinalDrawableBounds.setEmpty();
-        if (mRevealAnimator != null) {
-            mRevealAnimator.cancel();
-        }
-        mRevealAnimator = null;
         if (mFadeAnimatorSet != null) {
             mFadeAnimatorSet.cancel();
         }
@@ -892,15 +662,10 @@
         mListenerView.setListener(null);
         mOriginalIcon = null;
         mOnTargetChangeRunnable = null;
-        mTaskCornerRadius = 0;
-        mOutline.setEmpty();
-        mFgTransY = 0;
-        mFgSpringX.cancel();
-        mFgTransX = 0;
-        mFgSpringY.cancel();
         mBadge = null;
         sTmpObjArray[0] = null;
         mIconLoadResult = null;
+        mClipIconView.recycle();
     }
 
     private static class IconLoadResult {
@@ -911,7 +676,7 @@
         Runnable onIconLoaded;
         boolean isIconLoaded;
 
-        public IconLoadResult(ItemInfo itemInfo) {
+        IconLoadResult(ItemInfo itemInfo) {
             this.itemInfo = itemInfo;
         }
     }
diff --git a/src/com/android/launcher3/views/WorkFooterContainer.java b/src/com/android/launcher3/views/WorkFooterContainer.java
deleted file mode 100644
index d86d0ff..0000000
--- a/src/com/android/launcher3/views/WorkFooterContainer.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.views;
-
-import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
-
-import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.launcher3.Insettable;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.allapps.WorkModeSwitch;
-import com.android.launcher3.pm.UserCache;
-
-/**
- * Container to show work footer in all-apps.
- */
-public class WorkFooterContainer extends LinearLayout implements Insettable {
-    private Rect mInsets = new Rect();
-
-    private WorkModeSwitch mWorkModeSwitch;
-    private TextView mWorkModeLabel;
-
-    protected final ObjectAnimator mOpenCloseAnimator;
-
-    public WorkFooterContainer(Context context) {
-        this(context, null, 0);
-    }
-
-    public WorkFooterContainer(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public WorkFooterContainer(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-        mOpenCloseAnimator = ObjectAnimator.ofPropertyValuesHolder(this);
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int l, int t, int r, int b) {
-        super.onLayout(changed, l, t, r, b);
-        updateTranslation();
-        this.setVisibility(shouldShowWorkFooter() ? VISIBLE : GONE);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        mWorkModeSwitch = findViewById(R.id.work_mode_toggle);
-        mWorkModeLabel = findViewById(R.id.work_mode_label);
-    }
-
-    @Override
-    public void offsetTopAndBottom(int offset) {
-        super.offsetTopAndBottom(offset);
-        updateTranslation();
-    }
-
-    private void updateTranslation() {
-        if (getParent() instanceof View) {
-            View parent = (View) getParent();
-            int availableBot = parent.getHeight() - parent.getPaddingBottom();
-            setTranslationY(Math.max(0, availableBot - getBottom()));
-        }
-    }
-
-    @Override
-    public void setInsets(Rect insets) {
-        int bottomInset = insets.bottom - mInsets.bottom;
-        mInsets.set(insets);
-        setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(),
-                getPaddingBottom() + bottomInset);
-    }
-
-    /**
-     * Animates in/out work profile toggle panel based on the tab user is on
-     */
-    public void setWorkTabVisible(boolean workTabVisible) {
-        if (!shouldShowWorkFooter()) return;
-
-        mOpenCloseAnimator.setValues(PropertyValuesHolder.ofFloat(ALPHA, workTabVisible ? 1 : 0));
-        mOpenCloseAnimator.start();
-    }
-
-    /**
-     * Refreshes views based on current work profile enabled status
-     */
-    public void refresh() {
-        if (!shouldShowWorkFooter()) return;
-        boolean anyProfileQuietModeEnabled = UserCache.INSTANCE.get(
-                getContext()).isAnyProfileQuietModeEnabled();
-
-        mWorkModeLabel.setCompoundDrawablesWithIntrinsicBounds(
-                anyProfileQuietModeEnabled ? R.drawable.ic_corp_off : R.drawable.ic_corp, 0, 0, 0);
-        mWorkModeSwitch.refresh();
-    }
-
-    /**
-     * Returns work mode switch
-     */
-    public WorkModeSwitch getWorkModeSwitch() {
-        return mWorkModeSwitch;
-    }
-
-    private boolean shouldShowWorkFooter() {
-        Launcher launcher = Launcher.getLauncher(getContext());
-        return Utilities.ATLEAST_P && (hasShortcutsPermission(launcher)
-                || launcher.checkSelfPermission("android.permission.MODIFY_QUIET_MODE")
-                == PackageManager.PERMISSION_GRANTED);
-    }
-}
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index 73a0615..23c2160 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -33,6 +33,8 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.dragndrop.DragOptions;
 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;
@@ -92,6 +94,8 @@
 
     @Override
     public boolean onLongClick(View v) {
+        TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "Widgets.onLongClick");
+        v.cancelLongPress();
         if (!ItemLongClickListener.canStartDrag(mLauncher)) return false;
 
         if (v instanceof WidgetCell) {
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index f3fd7ca..78acc34 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -20,13 +20,13 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.PointF;
+import android.graphics.Rect;
 import android.os.Handler;
 import android.os.SystemClock;
 import android.util.SparseBooleanArray;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewConfiguration;
 import android.view.ViewDebug;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityNodeInfo;
@@ -40,10 +40,9 @@
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.R;
-import com.android.launcher3.SimpleOnStylusPressListener;
-import com.android.launcher3.StylusEventHelper;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.BaseDragLayer.TouchCompleteListener;
@@ -52,7 +51,7 @@
  * {@inheritDoc}
  */
 public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView
-        implements TouchCompleteListener, View.OnLongClickListener {
+        implements TouchCompleteListener, View.OnLongClickListener, DraggableView {
 
     // Related to the auto-advancing of widgets
     private static final long ADVANCE_INTERVAL = 20000;
@@ -64,14 +63,11 @@
     protected final LayoutInflater mInflater;
 
     private final CheckLongPressHelper mLongPressHelper;
-    private final StylusEventHelper mStylusEventHelper;
     protected final Launcher mLauncher;
 
     @ViewDebug.ExportedProperty(category = "launcher")
     private boolean mReinflateOnConfigChange;
 
-    private float mSlop;
-
     private boolean mIsScrollable;
     private boolean mIsAttachedToWindow;
     private boolean mIsAutoAdvanceRegistered;
@@ -91,7 +87,6 @@
         super(context);
         mLauncher = Launcher.getLauncher(context);
         mLongPressHelper = new CheckLongPressHelper(this, this);
-        mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
         mInflater = LayoutInflater.from(context);
         setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
         setBackgroundResource(R.drawable.widget_internal_focus_bg);
@@ -155,68 +150,19 @@
     }
 
     public boolean onInterceptTouchEvent(MotionEvent ev) {
-        // Just in case the previous long press hasn't been cleared, we make sure to start fresh
-        // on touch down.
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-            mLongPressHelper.cancelLongPress();
-        }
-
-        // Consume any touch events for ourselves after longpress is triggered
-        if (mLongPressHelper.hasPerformedLongPress()) {
-            mLongPressHelper.cancelLongPress();
-            return true;
-        }
-
-        // Watch for longpress or stylus button press events at this level to
-        // make sure users can always pick up this widget
-        if (mStylusEventHelper.onMotionEvent(ev)) {
-            mLongPressHelper.cancelLongPress();
-            return true;
-        }
-
-        switch (ev.getAction()) {
-            case MotionEvent.ACTION_DOWN: {
-                DragLayer dragLayer = Launcher.getLauncher(getContext()).getDragLayer();
-
-                if (mIsScrollable) {
-                     dragLayer.requestDisallowInterceptTouchEvent(true);
-                }
-                if (!mStylusEventHelper.inStylusButtonPressed()) {
-                    mLongPressHelper.postCheckForLongPress();
-                }
-                dragLayer.setTouchCompleteListener(this);
-                break;
+            DragLayer dragLayer = Launcher.getLauncher(getContext()).getDragLayer();
+            if (mIsScrollable) {
+                dragLayer.requestDisallowInterceptTouchEvent(true);
             }
-
-            case MotionEvent.ACTION_UP:
-            case MotionEvent.ACTION_CANCEL:
-                mLongPressHelper.cancelLongPress();
-                break;
-            case MotionEvent.ACTION_MOVE:
-                if (!Utilities.pointInView(this, ev.getX(), ev.getY(), mSlop)) {
-                    mLongPressHelper.cancelLongPress();
-                }
-                break;
+            dragLayer.setTouchCompleteListener(this);
         }
-
-        // Otherwise continue letting touch events fall through to children
-        return false;
+        mLongPressHelper.onTouchEvent(ev);
+        return mLongPressHelper.hasPerformedLongPress();
     }
 
     public boolean onTouchEvent(MotionEvent ev) {
-        // If the widget does not handle touch, then cancel
-        // long press when we release the touch
-        switch (ev.getAction()) {
-            case MotionEvent.ACTION_UP:
-            case MotionEvent.ACTION_CANCEL:
-                mLongPressHelper.cancelLongPress();
-                break;
-            case MotionEvent.ACTION_MOVE:
-                if (!Utilities.pointInView(this, ev.getX(), ev.getY(), mSlop)) {
-                    mLongPressHelper.cancelLongPress();
-                }
-                break;
-        }
+        mLongPressHelper.onTouchEvent(ev);
         // We want to keep receiving though events to be able to cancel long press on ACTION_UP
         return true;
     }
@@ -224,7 +170,6 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
 
         mIsAttachedToWindow = true;
         checkIfAutoAdvance();
@@ -412,4 +357,18 @@
         }
         return false;
     }
+
+    @Override
+    public int getViewType() {
+        return DRAGGABLE_WIDGET;
+    }
+
+    @Override
+    public void getVisualDragBounds(Rect bounds) {
+        int x = (int) (1 - getScaleToFit()) * getMeasuredWidth() / 2;
+        int y = (int) (1 - getScaleToFit()) * getMeasuredWidth() / 2;
+        int width = (int) getScaleToFit() * getMeasuredWidth();
+        int height = (int) getScaleToFit() * getMeasuredHeight();
+        bounds.set(x, y , x + width, y + height);
+    }
 }
diff --git a/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java b/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
index 68b1595..104ad77 100644
--- a/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
@@ -24,12 +24,15 @@
 import android.view.ViewDebug;
 import android.view.ViewGroup;
 
+import com.android.launcher3.dragndrop.DraggableView;
+
 import java.util.ArrayList;
 
 /**
  * Extension of AppWidgetHostView with support for controlled keyboard navigation.
  */
-public abstract class NavigableAppWidgetHostView extends AppWidgetHostView {
+public abstract class NavigableAppWidgetHostView extends AppWidgetHostView
+        implements DraggableView {
 
     @ViewDebug.ExportedProperty(category = "launcher")
     private boolean mChildrenFocused;
@@ -133,4 +136,14 @@
         // The host view's background changes when selected, to indicate the focus is inside.
         setSelected(childIsFocused);
     }
+
+    @Override
+    public int getViewType() {
+        return DRAGGABLE_WIDGET;
+    }
+
+    @Override
+    public void getVisualDragBounds(Rect bounds) {
+        bounds.set(0, 0 , getMeasuredWidth(), getMeasuredHeight());
+    }
 }
diff --git a/src/com/android/launcher3/widget/PendingItemDragHelper.java b/src/com/android/launcher3/widget/PendingItemDragHelper.java
index 662e627..3c11274 100644
--- a/src/com/android/launcher3/widget/PendingItemDragHelper.java
+++ b/src/com/android/launcher3/widget/PendingItemDragHelper.java
@@ -32,6 +32,7 @@
 import com.android.launcher3.PendingAddItemInfo;
 import com.android.launcher3.R;
 import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.dragndrop.LivePreviewWidgetCell;
 import com.android.launcher3.graphics.DragPreviewProvider;
 import com.android.launcher3.icons.LauncherIcons;
@@ -79,6 +80,8 @@
 
         mEstimatedCellSize = launcher.getWorkspace().estimateItemSize(mAddInfo);
 
+        DraggableView draggableView;
+
         if (mAddInfo instanceof PendingAddWidgetInfo) {
             PendingAddWidgetInfo createWidgetInfo = (PendingAddWidgetInfo) mAddInfo;
 
@@ -110,6 +113,7 @@
 
             dragOffset = null;
             dragRegion = null;
+            draggableView = DraggableView.ofType(DraggableView.DRAGGABLE_WIDGET);
         } else {
             PendingAddShortcutInfo createShortcutInfo = (PendingAddShortcutInfo) mAddInfo;
             Drawable icon = createShortcutInfo.activityInfo.getFullResIcon(app.getIconCache());
@@ -136,6 +140,7 @@
             dragRegion.top = (mEstimatedCellSize[1]
                     - iconSize - dp.iconTextSizePx - dp.iconDrawablePaddingPx) / 2;
             dragRegion.bottom = dragRegion.top + iconSize;
+            draggableView = DraggableView.ofType(DraggableView.DRAGGABLE_ICON);
         }
 
         // Since we are not going through the workspace for starting the drag, set drag related
@@ -148,8 +153,8 @@
                 + (int) ((scale * preview.getHeight() - preview.getHeight()) / 2);
 
         // Start the drag
-        launcher.getDragController().startDrag(preview, dragLayerX, dragLayerY, source, mAddInfo,
-                dragOffset, dragRegion, scale, scale, options);
+        launcher.getDragController().startDrag(preview, draggableView, dragLayerX, dragLayerY,
+                source, mAddInfo, dragOffset, dragRegion, scale, scale, options);
     }
 
     @Override
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index f055adf..4a0b4ef 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -31,10 +31,9 @@
 import android.widget.TextView;
 
 import com.android.launcher3.BaseActivity;
+import com.android.launcher3.CheckLongPressHelper;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
-import com.android.launcher3.SimpleOnStylusPressListener;
-import com.android.launcher3.StylusEventHelper;
 import com.android.launcher3.WidgetPreviewLoader;
 import com.android.launcher3.icons.BaseIconFactory;
 import com.android.launcher3.model.WidgetItem;
@@ -71,7 +70,6 @@
     protected WidgetItem mItem;
 
     private WidgetPreviewLoader mWidgetPreviewLoader;
-    private StylusEventHelper mStylusEventHelper;
 
     protected CancellationSignal mActiveRequest;
     private boolean mAnimatePreview = true;
@@ -80,7 +78,8 @@
     private Bitmap mDeferredBitmap;
 
     protected final BaseActivity mActivity;
-    protected DeviceProfile mDeviceProfile;
+    protected final DeviceProfile mDeviceProfile;
+    private final CheckLongPressHelper mLongPressHelper;
 
     public WidgetCell(Context context) {
         this(context, null);
@@ -95,8 +94,9 @@
 
         mActivity = BaseActivity.fromContext(context);
         mDeviceProfile = mActivity.getDeviceProfile();
-        mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
+        mLongPressHelper = new CheckLongPressHelper(this);
 
+        mLongPressHelper.setLongPressTimeoutFactor(1);
         setContainerWidth();
         setWillNotDraw(false);
         setClipToPadding(false);
@@ -210,11 +210,15 @@
 
     @Override
     public boolean onTouchEvent(MotionEvent ev) {
-        boolean handled = super.onTouchEvent(ev);
-        if (mStylusEventHelper.onMotionEvent(ev)) {
-            return true;
-        }
-        return handled;
+        super.onTouchEvent(ev);
+        mLongPressHelper.onTouchEvent(ev);
+        return true;
+    }
+
+    @Override
+    public void cancelLongPress() {
+        super.cancelLongPress();
+        mLongPressHelper.cancelLongPress();
     }
 
     /**
diff --git a/src/com/android/launcher3/widget/WidgetsFullSheet.java b/src/com/android/launcher3/widget/WidgetsFullSheet.java
index b07a4f4..aaebedd 100644
--- a/src/com/android/launcher3/widget/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsFullSheet.java
@@ -24,6 +24,7 @@
 import android.content.Context;
 import android.graphics.Rect;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.util.Pair;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
@@ -38,8 +39,10 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetHost.ProviderChangedListener;
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.views.RecyclerViewFastScroller;
 import com.android.launcher3.views.TopRoundedCornerView;
 
@@ -68,6 +71,14 @@
 
     }
 
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+            Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "WidgetsFullSheet: " + ev);
+        }
+        return super.dispatchTouchEvent(ev);
+    }
+
     public WidgetsFullSheet(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
     }
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/BackgroundBlurController.java b/src_ui_overrides/com/android/launcher3/uioverrides/BackgroundBlurController.java
deleted file mode 100644
index 75f99a9..0000000
--- a/src_ui_overrides/com/android/launcher3/uioverrides/BackgroundBlurController.java
+++ /dev/null
@@ -1,59 +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;
-
-
-import android.util.IntProperty;
-import android.view.View;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager;
-import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.states.StateAnimationConfig;
-
-/**
- * Controls the blur, for the Launcher surface only.
- */
-public class BackgroundBlurController implements LauncherStateManager.StateHandler {
-
-    public static final IntProperty<BackgroundBlurController> BACKGROUND_BLUR =
-            new IntProperty<BackgroundBlurController>("backgroundBlur") {
-                @Override
-                public void setValue(BackgroundBlurController blurController, int blurRadius) {}
-
-                @Override
-                public Integer get(BackgroundBlurController blurController) {
-                    return 0;
-                }
-            };
-
-    public BackgroundBlurController(Launcher l) {}
-
-    public int getFolderBackgroundBlurAdjustment() {
-        return 0;
-    }
-
-    public void setSurfaceToLauncher(View v) {}
-
-    @Override
-    public void setState(LauncherState toState) {}
-
-    @Override
-    public void setStateWithAnimation(LauncherState toState, StateAnimationConfig config,
-            PendingAnimation animation) { }
-}
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 3d12248..7cd656e 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -18,7 +18,6 @@
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
 import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
-import static com.android.launcher3.tapl.LauncherInstrumentation.ContainerType;
 import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
@@ -57,6 +56,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.model.AppLaunchTracker;
 import com.android.launcher3.tapl.LauncherInstrumentation;
+import com.android.launcher3.tapl.LauncherInstrumentation.ContainerType;
 import com.android.launcher3.tapl.TestHelpers;
 import com.android.launcher3.testcomponent.TestCommandReceiver;
 import com.android.launcher3.testing.TestProtocol;
@@ -102,6 +102,7 @@
 
     private static String sDetectedActivityLeak;
     private static boolean sActivityLeakReported;
+    private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
 
     protected LooperExecutor mMainThreadExecutor = MAIN_EXECUTOR;
     protected final UiDevice mDevice = UiDevice.getInstance(getInstrumentation());
@@ -213,8 +214,26 @@
         return mDevice;
     }
 
+    private boolean hasSystemUiObject(String resId) {
+        return mDevice.hasObject(By.res(SYSTEMUI_PACKAGE, resId));
+    }
+
     @Before
     public void setUp() throws Exception {
+        Log.d(TAG, "Before disabling battery defender");
+        mDevice.executeShellCommand("setprop vendor.battery.defender.disable 1");
+        Log.d(TAG, "Before enabling stay awake");
+        mDevice.executeShellCommand("settings put global stay_on_while_plugged_in 3");
+        for (int i = 0; i < 10 && hasSystemUiObject("keyguard_status_view"); ++i) {
+            Log.d(TAG, "Before unlocking the phone");
+            mDevice.executeShellCommand("input keyevent 82");
+            mDevice.waitForIdle();
+        }
+        Assert.assertTrue("Keyguard still visible",
+                mDevice.wait(
+                        Until.gone(By.res(SYSTEMUI_PACKAGE, "keyguard_status_view")), 60000));
+        Log.d(TAG, "Keyguard is not visible");
+
         final String launcherPackageName = mDevice.getLauncherPackageName();
         try {
             final Context context = InstrumentationRegistry.getContext();
@@ -275,7 +294,8 @@
     protected void resetLoaderState() {
         try {
             mMainThreadExecutor.execute(
-                    () -> LauncherAppState.getInstance(mTargetContext).getModel().forceReload());
+                    () -> LauncherAppState.getInstance(
+                            mTargetContext).getModel().forceReload());
         } catch (Throwable t) {
             throw new IllegalArgumentException(t);
         }
@@ -289,7 +309,8 @@
         ContentResolver resolver = mTargetContext.getContentResolver();
         int screenId = FIRST_SCREEN_ID;
         // Update the screen id counter for the provider.
-        LauncherSettings.Settings.call(resolver, LauncherSettings.Settings.METHOD_NEW_SCREEN_ID);
+        LauncherSettings.Settings.call(resolver,
+                LauncherSettings.Settings.METHOD_NEW_SCREEN_ID);
 
         if (screenId > FIRST_SCREEN_ID) {
             screenId = FIRST_SCREEN_ID;
@@ -303,7 +324,8 @@
         item.screenId = screenId;
         item.onAddToDatabase(writer);
         writer.put(LauncherSettings.Favorites._ID, item.id);
-        resolver.insert(LauncherSettings.Favorites.CONTENT_URI, writer.getValues(mTargetContext));
+        resolver.insert(LauncherSettings.Favorites.CONTENT_URI,
+                writer.getValues(mTargetContext));
         resetLoaderState();
 
         // Launch the home activity
@@ -334,7 +356,8 @@
         });
     }
 
-    // Cannot be used in TaplTests between a Tapl call injecting a gesture and a tapl call expecting
+    // Cannot be used in TaplTests between a Tapl call injecting a gesture and a tapl call
+    // expecting
     // the results of that gesture because the wait can hide flakeness.
     protected void waitForState(String message, Supplier<LauncherState> state) {
         waitForLauncherCondition(message,
@@ -347,7 +370,8 @@
 
     // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide
     // flakiness.
-    protected void waitForLauncherCondition(String message, Function<Launcher, Boolean> condition) {
+    protected void waitForLauncherCondition(String
+            message, Function<Launcher, Boolean> condition) {
         waitForLauncherCondition(message, condition, DEFAULT_ACTIVITY_TIMEOUT);
     }
 
@@ -423,7 +447,8 @@
 
         public Intent blockingGetExtraIntent() throws InterruptedException {
             Intent intent = blockingGetIntent();
-            return intent == null ? null : (Intent) intent.getParcelableExtra(Intent.EXTRA_INTENT);
+            return intent == null ? null : (Intent) intent.getParcelableExtra(
+                    Intent.EXTRA_INTENT);
         }
     }
 
@@ -450,7 +475,8 @@
         if (newTask) {
             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
         } else {
-            intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
+            intent.addFlags(
+                    Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
         }
         getInstrumentation().getTargetContext().startActivity(intent);
         assertTrue("App didn't start: " + selector,
@@ -487,7 +513,8 @@
 
     protected boolean isInState(Supplier<LauncherState> state) {
         if (!TestHelpers.isInLauncherProcess()) return true;
-        return getFromLauncher(launcher -> launcher.getStateManager().getState() == state.get());
+        return getFromLauncher(
+                launcher -> launcher.getStateManager().getState() == state.get());
     }
 
     protected int getAllAppsScroll(Launcher launcher) {
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index f8bbf21..de1ada4 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -314,7 +314,7 @@
                 switchToAllApps();
         allApps.freeze();
         try {
-            allApps.getAppIcon(APP_NAME).dragToWorkspace();
+            allApps.getAppIcon(APP_NAME).dragToWorkspace(false);
             mLauncher.getWorkspace().getWorkspaceAppIcon(APP_NAME).launch(getAppPackageName());
         } finally {
             allApps.unfreeze();
@@ -342,7 +342,7 @@
                     getMenuItem(0);
             final String shortcutName = menuItem.getText();
 
-            menuItem.dragToWorkspace();
+            menuItem.dragToWorkspace(false);
             mLauncher.getWorkspace().getWorkspaceAppIcon(shortcutName).launch(getAppPackageName());
         } finally {
             allApps.unfreeze();
diff --git a/tests/src/com/android/launcher3/ui/WorkTabTest.java b/tests/src/com/android/launcher3/ui/WorkTabTest.java
index db2d974..8d571ff 100644
--- a/tests/src/com/android/launcher3/ui/WorkTabTest.java
+++ b/tests/src/com/android/launcher3/ui/WorkTabTest.java
@@ -16,6 +16,7 @@
 package com.android.launcher3.ui;
 
 import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.NORMAL;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -31,9 +32,9 @@
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.AllAppsContainerView;
 import com.android.launcher3.allapps.AllAppsPagedView;
+import com.android.launcher3.allapps.WorkModeSwitch;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.views.WorkEduView;
-import com.android.launcher3.views.WorkFooterContainer;
 
 import org.junit.After;
 import org.junit.Before;
@@ -87,7 +88,7 @@
         executeOnLauncher(launcher -> launcher.getStateManager().goToState(ALL_APPS));
         waitForState("Launcher internal state didn't switch to All Apps", () -> ALL_APPS);
         getOnceNotNull("Apps view did not bind",
-                launcher -> launcher.getAppsView().getWorkFooterContainer(), 60000);
+                launcher -> launcher.getAppsView().getWorkModeSwitch(), 60000);
 
         UserManager userManager = getFromLauncher(l -> l.getSystemService(UserManager.class));
         assertEquals(2, userManager.getUserProfiles().size());
@@ -102,10 +103,10 @@
 
         assertTrue(userManager.isQuietModeEnabled(workProfile));
         executeOnLauncher(launcher -> {
-            WorkFooterContainer wf = launcher.getAppsView().getWorkFooterContainer();
+            WorkModeSwitch wf = launcher.getAppsView().getWorkModeSwitch();
             ((AllAppsPagedView) launcher.getAppsView().getContentView()).snapToPageImmediately(
                     AllAppsContainerView.AdapterHolder.WORK);
-            wf.getWorkModeSwitch().toggle();
+            wf.toggle();
         });
         waitForLauncherCondition("Work toggle did not work",
                 l -> l.getSystemService(UserManager.class).isQuietModeEnabled(workProfile));
@@ -158,9 +159,11 @@
 
         // dismiss personal edu
         mDevice.pressHome();
+        waitForState("Launcher did not go home", () -> NORMAL);
 
         // open work tab
         executeOnLauncher(launcher -> launcher.getStateManager().goToState(ALL_APPS));
+        waitForState("Launcher did not switch to all apps", () -> ALL_APPS);
         executeOnLauncher(launcher -> {
             AllAppsPagedView pagedView = (AllAppsPagedView) launcher.getAppsView().getContentView();
             pagedView.setCurrentPage(WORK_PAGE);
diff --git a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
index de9757f..d93915c 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
@@ -94,7 +94,7 @@
         WidgetConfigStartupMonitor monitor = new WidgetConfigStartupMonitor();
         widgets.
                 getWidget(mWidgetInfo.getLabel(mTargetContext.getPackageManager())).
-                dragToWorkspace();
+                dragToWorkspace(true);
         // Widget id for which the config activity was opened
         mWidgetId = monitor.getWidgetId();
 
diff --git a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
index f9d1d93..788e041 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
@@ -57,7 +57,7 @@
                 getWorkspace().
                 openAllWidgets().
                 getWidget(widgetInfo.getLabel(mTargetContext.getPackageManager())).
-                dragToWorkspace();
+                dragToWorkspace(false);
 
         assertTrue(mActivityMonitor.itemExists(
                 (info, view) -> info instanceof LauncherAppWidgetInfo &&
@@ -83,7 +83,7 @@
         mDevice.pressHome();
         mLauncher.getWorkspace().openAllWidgets()
                 .getWidget("com.android.launcher3.testcomponent.CustomShortcutConfigActivity")
-                .dragToWorkspace();
+                .dragToWorkspace(false);
         mLauncher.getWorkspace().getWorkspaceAppIcon("Shortcut")
                 .launch(getAppPackageName());
     }
diff --git a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
index 793af48..a3c70ec 100644
--- a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
@@ -45,6 +45,7 @@
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
 import com.android.launcher3.pm.InstallSessionHelper;
+import com.android.launcher3.tapl.Widget;
 import com.android.launcher3.tapl.Workspace;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.ui.TestViewHelpers;
@@ -267,8 +268,10 @@
     }
 
     private void verifyWidgetPresent(LauncherAppWidgetProviderInfo info) {
+        final Widget widget = mLauncher.getWorkspace().tryGetWidget(info.label, DEFAULT_UI_TIMEOUT);
+        if (widget == null) mLauncher.dumpViewHierarchy(); // b/152645831
         assertTrue("Widget is not present",
-                mLauncher.getWorkspace().tryGetWidget(info.label, DEFAULT_UI_TIMEOUT) != null);
+                widget != null);
     }
 
     private void verifyPendingWidgetPresent() {
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIcon.java b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
index 8932291..bdfd563 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
@@ -32,6 +32,7 @@
 public final class AppIcon extends Launchable {
 
     private static final Pattern START_EVENT = Pattern.compile("start:");
+    private static final Pattern LONG_CLICK_EVENT = Pattern.compile("onAllAppsItemLongClick");
 
     AppIcon(LauncherInstrumentation launcher, UiObject2 icon) {
         super(launcher, icon);
@@ -47,11 +48,16 @@
     public AppIconMenu openMenu() {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
             return new AppIconMenu(mLauncher, mLauncher.clickAndGet(
-                    mObject, "deep_shortcuts_container"));
+                    mObject, "deep_shortcuts_container", LONG_CLICK_EVENT));
         }
     }
 
     @Override
+    protected void addExpectedEventsForLongClick() {
+        mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, LONG_CLICK_EVENT);
+    }
+
+    @Override
     protected String getLongPressIndicator() {
         return "deep_shortcuts_container";
     }
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java b/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java
index f8dd89c..37a7b91 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java
@@ -41,6 +41,10 @@
     }
 
     @Override
+    protected void addExpectedEventsForLongClick() {
+    }
+
+    @Override
     protected String getLongPressIndicator() {
         return "drop_target_bar";
     }
diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java
index 2acab97..80b8e89 100644
--- a/tests/tapl/com/android/launcher3/tapl/Background.java
+++ b/tests/tapl/com/android/launcher3/tapl/Background.java
@@ -25,7 +25,6 @@
 import android.view.MotionEvent;
 
 import androidx.annotation.NonNull;
-import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.UiObject2;
 
 import com.android.launcher3.testing.TestProtocol;
@@ -72,6 +71,7 @@
     }
 
     protected void goToOverviewUnchecked() {
+        final boolean launcherWasVisible = mLauncher.isLauncherVisible();
         switch (mLauncher.getNavigationModel()) {
             case ZERO_BUTTON: {
                 final int centerX = mLauncher.getDevice().getDisplayWidth() / 2;
@@ -137,6 +137,15 @@
                         OVERVIEW_STATE_ORDINAL);
                 break;
         }
+        expectSwitchToOverviewEvents();
+
+        if (!launcherWasVisible) {
+            mLauncher.expectEvent(
+                    TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_START_ACTIVITY);
+        }
+    }
+
+    private void expectSwitchToOverviewEvents() {
     }
 
     /**
@@ -157,6 +166,7 @@
     }
 
     protected void quickSwitchToPreviousApp(int expectedState) {
+        final boolean launcherWasVisible = mLauncher.isLauncherVisible();
         boolean transposeInLandscape = false;
         switch (mLauncher.getNavigationModel()) {
             case TWO_BUTTON:
@@ -180,15 +190,17 @@
                     endX = startX;
                     endY = 0;
                 }
-                final boolean launcherIsVisible =
-                        mLauncher.hasLauncherObject(By.textStartsWith(""));
                 final boolean isZeroButton = mLauncher.getNavigationModel()
                         == LauncherInstrumentation.NavigationModel.ZERO_BUTTON;
+                if (!launcherWasVisible) {
+                    mLauncher.expectEvent(
+                            TestProtocol.SEQUENCE_MAIN,
+                            LauncherInstrumentation.EVENT_START_ACTIVITY);
+                }
                 mLauncher.swipeToState(startX, startY, endX, endY, 20, expectedState,
-                        launcherIsVisible && isZeroButton
+                        launcherWasVisible && isZeroButton
                                 ? LauncherInstrumentation.GestureScope.INSIDE_TO_OUTSIDE
-                                : LauncherInstrumentation.GestureScope.OUTSIDE
-                );
+                                : LauncherInstrumentation.GestureScope.OUTSIDE);
                 break;
             }
 
@@ -196,6 +208,11 @@
                 // Double press the recents button.
                 UiObject2 recentsButton = mLauncher.waitForSystemUiObject("recent_apps");
                 mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
+                if (!launcherWasVisible) {
+                    mLauncher.expectEvent(
+                            TestProtocol.SEQUENCE_MAIN,
+                            LauncherInstrumentation.EVENT_START_ACTIVITY);
+                }
                 mLauncher.runToState(() -> recentsButton.click(), OVERVIEW_STATE_ORDINAL);
                 mLauncher.getOverview();
                 mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
@@ -203,6 +220,8 @@
                 break;
         }
         mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, TASK_START_EVENT);
+        mLauncher.expectEvent(
+                TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_STOP_ACTIVITY);
     }
 
     protected String getSwipeHeightRequestName() {
diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java
index b20384e..2177032 100644
--- a/tests/tapl/com/android/launcher3/tapl/Launchable.java
+++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java
@@ -25,6 +25,8 @@
 import androidx.test.uiautomator.UiObject2;
 import androidx.test.uiautomator.Until;
 
+import com.android.launcher3.testing.TestProtocol;
+
 /**
  * Ancestor for AppIcon and AppMenuItem.
  */
@@ -62,6 +64,8 @@
                 event -> event.getEventType() == TYPE_WINDOW_STATE_CHANGED,
                 () -> "Launching an app didn't open a new window: " + mObject.getText());
         expectActivityStartEvents();
+        mLauncher.expectEvent(
+                TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_STOP_ACTIVITY);
 
         mLauncher.assertTrue(
                 "App didn't start: " + selector,
@@ -72,8 +76,9 @@
 
     /**
      * Drags an object to the center of homescreen.
+     * @param startsActivity whether it's expected to start an activity.
      */
-    public void dragToWorkspace() {
+    public void dragToWorkspace(boolean startsActivity) {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
             final Point launchableCenter = getObject().getVisibleCenter();
             final Point displaySize = mLauncher.getRealDisplaySize();
@@ -86,9 +91,13 @@
                                     ? launchableCenter.x - width / 2
                                     : launchableCenter.x + width / 2,
                             displaySize.y / 2),
-                    getLongPressIndicator());
+                    getLongPressIndicator(),
+                    startsActivity,
+                    () -> addExpectedEventsForLongClick());
         }
     }
 
+    protected abstract void addExpectedEventsForLongClick();
+
     protected abstract String getLongPressIndicator();
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index cac89e6..3ddc0d2 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -97,6 +97,8 @@
     private static final Pattern EVENT_TOUCH_UP = getTouchEventPattern("ACTION_UP");
     private static final Pattern EVENT_TOUCH_CANCEL = getTouchEventPattern("ACTION_CANCEL");
     private static final Pattern EVENT_PILFER_POINTERS = Pattern.compile("pilferPointers");
+    static final Pattern EVENT_START_ACTIVITY = Pattern.compile("Activity\\.onStart");
+    static final Pattern EVENT_STOP_ACTIVITY = Pattern.compile("Activity\\.onStop");
 
     static final Pattern EVENT_TOUCH_DOWN_TIS = getTouchEventPatternTIS("ACTION_DOWN");
     static final Pattern EVENT_TOUCH_UP_TIS = getTouchEventPatternTIS("ACTION_UP");
@@ -316,7 +318,7 @@
         };
     }
 
-    private void dumpViewHierarchy() {
+    public void dumpViewHierarchy() {
         final ByteArrayOutputStream stream = new ByteArrayOutputStream();
         try {
             mDevice.dumpWindowHierarchy(stream);
@@ -332,15 +334,21 @@
 
     private String getSystemAnomalyMessage() {
         try {
+            final StringBuilder sb = new StringBuilder();
+
             UiObject2 object = mDevice.findObject(By.res("android", "alertTitle"));
             if (object != null) {
-                return "System alert popup is visible: " + object.getText();
+                sb.append("TITLE: ").append(object.getText());
             }
 
             object = mDevice.findObject(By.res("android", "message"));
             if (object != null) {
-                return "Message popup by " + object.getApplicationPackage() + " is visible: "
-                        + object.getText();
+                sb.append(" PACKAGE: ").append(object.getApplicationPackage())
+                        .append(" MESSAGE: ").append(object.getText());
+            }
+
+            if (sb.length() != 0) {
+                return "System alert popup is visible: " + sb;
             }
 
             if (hasSystemUiObject("keyguard_status_view")) return "Phone is locked";
@@ -637,6 +645,7 @@
             // otherwise waitForIdle may return immediately in case when there was a big enough
             // pause in accessibility events prior to pressing Home.
             final String action;
+            final boolean launcherWasVisible = isLauncherVisible();
             if (getNavigationModel() == NavigationModel.ZERO_BUTTON) {
                 checkForAnomaly();
 
@@ -665,12 +674,18 @@
                                 displaySize.x / 2, displaySize.y - 1,
                                 displaySize.x / 2, 0,
                                 ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME, NORMAL_STATE_ORDINAL,
-                                hasLauncherObject(By.textStartsWith(""))
+                                launcherWasVisible
                                         ? GestureScope.INSIDE_TO_OUTSIDE
                                         : GestureScope.OUTSIDE);
                     }
+                    if (!launcherWasVisible) {
+                        expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_START_ACTIVITY);
+                    }
                 }
             } else {
+                if (!launcherWasVisible) {
+                    expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_START_ACTIVITY);
+                }
                 log("Hierarchy before clicking home:");
                 dumpViewHierarchy();
                 log(action = "clicking home button from " + getVisibleStateMessage());
@@ -681,6 +696,7 @@
                         expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_DOWN_TIS);
                         expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_UP_TIS);
                     }
+
                     runToState(
                             waitForSystemUiObject("home")::click,
                             NORMAL_STATE_ORDINAL,
@@ -697,6 +713,11 @@
         }
     }
 
+    boolean isLauncherVisible() {
+        mDevice.waitForIdle();
+        return hasLauncherObject(By.textStartsWith(""));
+    }
+
     /**
      * Gets the Workspace object if the current state is "active home", i.e. workspace. Fails if the
      * launcher is not in that state.
@@ -1116,7 +1137,7 @@
                 break;
             case MotionEvent.ACTION_UP:
                 if (notLauncher3 && gestureScope != GestureScope.INSIDE) {
-                    expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_PILFER_POINTERS);
+                    expectEvent(TestProtocol.SEQUENCE_PILFER, EVENT_PILFER_POINTERS);
                 }
                 if (gestureScope != GestureScope.OUTSIDE) {
                     expectEvent(TestProtocol.SEQUENCE_MAIN, gestureScope == GestureScope.INSIDE
@@ -1158,10 +1179,12 @@
     }
 
     @NonNull
-    UiObject2 clickAndGet(@NonNull final UiObject2 target, @NonNull String resName) {
+    UiObject2 clickAndGet(
+            @NonNull final UiObject2 target, @NonNull String resName, Pattern longClickEvent) {
         final Point targetCenter = target.getVisibleCenter();
         final long downTime = SystemClock.uptimeMillis();
         sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, targetCenter, GestureScope.INSIDE);
+        expectEvent(TestProtocol.SEQUENCE_MAIN, longClickEvent);
         final UiObject2 result = waitForLauncherObject(resName);
         sendPointer(downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, targetCenter,
                 GestureScope.INSIDE);
diff --git a/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java b/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java
index 0fc88ee..49901ea 100644
--- a/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java
+++ b/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java
@@ -149,23 +149,24 @@
         finishSync(waitForExpectedCountMs);
 
         final StringBuilder sb = new StringBuilder();
+        boolean hasMismatches = false;
         for (Map.Entry<String, List<Pattern>> expectedEvents : mExpectedEvents.entrySet()) {
             String sequence = expectedEvents.getKey();
 
             List<String> actual = new ArrayList<>(mEvents.getNonNull(sequence));
             final int mismatchPosition = getMismatchPosition(expectedEvents.getValue(), actual);
-            if (mismatchPosition != -1) {
-                formatSequenceWithMismatch(
-                        sb,
-                        sequence,
-                        expectedEvents.getValue(),
-                        actual,
-                        mismatchPosition);
-            }
+            hasMismatches = hasMismatches || mismatchPosition != -1;
+            formatSequenceWithMismatch(
+                    sb,
+                    sequence,
+                    expectedEvents.getValue(),
+                    actual,
+                    mismatchPosition);
         }
         // Check for unexpected event sequences in the actual data.
         for (String actualNamedSequence : mEvents.keySet()) {
             if (!mExpectedEvents.containsKey(actualNamedSequence)) {
+                hasMismatches = true;
                 formatSequenceWithMismatch(
                         sb,
                         actualNamedSequence,
@@ -175,7 +176,7 @@
             }
         }
 
-        return sb.length() != 0 ? "mismatching events: " + sb.toString() : null;
+        return hasMismatches ? "mismatching events: " + sb.toString() : null;
     }
 
     // If the list of actual events matches the list of expected events, returns -1, otherwise
@@ -199,10 +200,11 @@
             List<Pattern> expected,
             List<String> actualEvents,
             int mismatchPosition) {
-        sb.append("\n>> Sequence " + sequenceName);
-        sb.append("\n  Expected:");
+        sb.append("\n>> SEQUENCE " + sequenceName + " - "
+                + (mismatchPosition == -1 ? "MATCH" : "MISMATCH"));
+        sb.append("\n  EXPECTED:");
         formatEventListWithMismatch(sb, expected, mismatchPosition);
-        sb.append("\n  Actual:");
+        sb.append("\n  ACTUAL:");
         formatEventListWithMismatch(sb, actualEvents, mismatchPosition);
     }
 
diff --git a/tests/tapl/com/android/launcher3/tapl/OptionsPopupMenuItem.java b/tests/tapl/com/android/launcher3/tapl/OptionsPopupMenuItem.java
index c2f701b..b8e6c0e 100644
--- a/tests/tapl/com/android/launcher3/tapl/OptionsPopupMenuItem.java
+++ b/tests/tapl/com/android/launcher3/tapl/OptionsPopupMenuItem.java
@@ -15,11 +15,15 @@
  */
 package com.android.launcher3.tapl;
 
+import android.os.Build;
+
 import androidx.annotation.NonNull;
 import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.UiObject2;
 import androidx.test.uiautomator.Until;
 
+import com.android.launcher3.testing.TestProtocol;
+
 public class OptionsPopupMenuItem {
 
     private final LauncherInstrumentation mLauncher;
@@ -39,6 +43,12 @@
             LauncherInstrumentation.log("OptionsPopupMenuItem before click "
                     + mObject.getVisibleCenter() + " in " + mObject.getVisibleBounds());
             mLauncher.clickLauncherObject(mObject);
+            if (!Build.MODEL.contains("Cuttlefish") ||
+                    Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q &&
+                            !"R".equals(Build.VERSION.CODENAME)) {
+                mLauncher.expectEvent(
+                        TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_STOP_ACTIVITY);
+            }
             mLauncher.assertTrue(
                     "App didn't start: " + By.pkg(expectedPackageName),
                     mLauncher.getDevice().wait(Until.hasObject(By.pkg(expectedPackageName)),
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index f955cf2..5c51782 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -79,6 +79,8 @@
                         () -> "Launching task didn't open a new window: "
                                 + mTask.getParent().getContentDescription());
                 mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, TASK_START_EVENT);
+                mLauncher.expectEvent(
+                        TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_STOP_ACTIVITY);
             }
             return new Background(mLauncher);
         }
diff --git a/tests/tapl/com/android/launcher3/tapl/Widget.java b/tests/tapl/com/android/launcher3/tapl/Widget.java
index a658f16..53ef796 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widget.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widget.java
@@ -18,11 +18,17 @@
 
 import androidx.test.uiautomator.UiObject2;
 
+import com.android.launcher3.testing.TestProtocol;
+
+import java.util.regex.Pattern;
+
 /**
  * Widget in workspace or a widget list.
  */
 public final class Widget extends Launchable {
 
+    private static final Pattern LONG_CLICK_EVENT = Pattern.compile("Widgets.onLongClick");
+
     Widget(LauncherInstrumentation launcher, UiObject2 icon) {
         super(launcher, icon);
     }
@@ -35,4 +41,9 @@
     @Override
     protected void expectActivityStartEvents() {
     }
+
+    @Override
+    protected void addExpectedEventsForLongClick() {
+        mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, LONG_CLICK_EVENT);
+    }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/Widgets.java b/tests/tapl/com/android/launcher3/tapl/Widgets.java
index 084138c..a14d2f0 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widgets.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widgets.java
@@ -88,47 +88,50 @@
     }
 
     public Widget getWidget(String labelText) {
-        final UiObject2 widgetsContainer = verifyActiveContainer();
-        final Point displaySize = mLauncher.getRealDisplaySize();
-        final BySelector labelSelector = By.clazz("android.widget.TextView").text(labelText);
+        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                "getting widget " + labelText + " in widgets list")) {
+            final UiObject2 widgetsContainer = verifyActiveContainer();
+            final Point displaySize = mLauncher.getRealDisplaySize();
+            final BySelector labelSelector = By.clazz("android.widget.TextView").text(labelText);
 
-        int i = 0;
-        for (; ; ) {
-            final Collection<UiObject2> cells = mLauncher.getObjectsInContainer(
-                    widgetsContainer, "widgets_scroll_container");
-            mLauncher.assertTrue("Widgets doesn't have 2 rows", cells.size() >= 2);
-            for (UiObject2 cell : cells) {
-                final UiObject2 label = cell.findObject(labelSelector);
-                if (label == null) continue;
+            int i = 0;
+            for (; ; ) {
+                final Collection<UiObject2> cells = mLauncher.getObjectsInContainer(
+                        widgetsContainer, "widgets_scroll_container");
+                mLauncher.assertTrue("Widgets doesn't have 2 rows", cells.size() >= 2);
+                for (UiObject2 cell : cells) {
+                    final UiObject2 label = cell.findObject(labelSelector);
+                    if (label == null) continue;
 
-                final UiObject2 widget = label.getParent().getParent();
-                mLauncher.assertEquals(
-                        "View is not WidgetCell",
-                        "com.android.launcher3.widget.WidgetCell",
-                        widget.getClassName());
+                    final UiObject2 widget = label.getParent().getParent();
+                    mLauncher.assertEquals(
+                            "View is not WidgetCell",
+                            "com.android.launcher3.widget.WidgetCell",
+                            widget.getClassName());
 
-                int maxWidth = 0;
-                for (UiObject2 sibling : widget.getParent().getChildren()) {
-                    maxWidth = Math.max(sibling.getVisibleBounds().width(), maxWidth);
+                    int maxWidth = 0;
+                    for (UiObject2 sibling : widget.getParent().getChildren()) {
+                        maxWidth = Math.max(sibling.getVisibleBounds().width(), maxWidth);
+                    }
+
+                    int visibleDelta = maxWidth - widget.getVisibleBounds().width();
+                    if (visibleDelta > 0) {
+                        Rect parentBounds = cell.getVisibleBounds();
+                        mLauncher.linearGesture(parentBounds.centerX() + visibleDelta
+                                        + mLauncher.getTouchSlop(),
+                                parentBounds.centerY(), parentBounds.centerX(),
+                                parentBounds.centerY(), 10, true, GestureScope.INSIDE);
+                    }
+
+                    if (widget.getVisibleBounds().bottom
+                            <= displaySize.y - mLauncher.getBottomGestureSize()) {
+                        return new Widget(mLauncher, widget);
+                    }
                 }
 
-                int visibleDelta = maxWidth - widget.getVisibleBounds().width();
-                if (visibleDelta > 0) {
-                    Rect parentBounds = cell.getVisibleBounds();
-                    mLauncher.linearGesture(parentBounds.centerX() + visibleDelta
-                                    + mLauncher.getTouchSlop(),
-                            parentBounds.centerY(), parentBounds.centerX(),
-                            parentBounds.centerY(), 10, true, GestureScope.INSIDE);
-                }
-
-                if (widget.getVisibleBounds().bottom
-                        <= displaySize.y - mLauncher.getBottomGestureSize()) {
-                    return new Widget(mLauncher, widget);
-                }
+                mLauncher.assertTrue("Too many attempts", ++i <= 40);
+                mLauncher.scrollToLastVisibleRow(widgetsContainer, cells, 0);
             }
-
-            mLauncher.assertTrue("Too many attempts", ++i <= 40);
-            mLauncher.scrollToLastVisibleRow(widgetsContainer, cells, 0);
         }
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 3f5dc8d..86aafc2 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -52,6 +52,7 @@
     static final Pattern EVENT_CTRL_W_UP = Pattern.compile(
             "Key event: KeyEvent.*?action=ACTION_UP.*?keyCode=KEYCODE_W"
                     + ".*?metaState=META_CTRL_ON");
+    private static final Pattern LONG_CLICK_EVENT = Pattern.compile("onWorkspaceItemLongClick");
 
     private final UiObject2 mHotseat;
 
@@ -177,7 +178,10 @@
                             getHotseatAppIcon("Chrome"),
                             new Point(mLauncher.getDevice().getDisplayWidth(),
                                     workspace.getVisibleBounds().centerY()),
-                            "deep_shortcuts_container");
+                            "deep_shortcuts_container",
+                            false,
+                            () -> mLauncher.expectEvent(
+                                    TestProtocol.SEQUENCE_MAIN, LONG_CLICK_EVENT));
                     verifyActiveContainer();
                 }
             }
@@ -198,7 +202,7 @@
 
     static void dragIconToWorkspace(
             LauncherInstrumentation launcher, Launchable launchable, Point dest,
-            String longPressIndicator) {
+            String longPressIndicator, boolean startsActivity, Runnable expectLongClickEvents) {
         LauncherInstrumentation.log("dragIconToWorkspace: begin");
         final Point launchableCenter = launchable.getObject().getVisibleCenter();
         final long downTime = SystemClock.uptimeMillis();
@@ -207,6 +211,7 @@
                     launcher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN,
                             launchableCenter, LauncherInstrumentation.GestureScope.INSIDE);
                     LauncherInstrumentation.log("dragIconToWorkspace: sent down");
+                    expectLongClickEvents.run();
                     launcher.waitForLauncherObject(longPressIndicator);
                     LauncherInstrumentation.log("dragIconToWorkspace: indicator");
                     launcher.movePointer(launchableCenter, dest, 10, downTime, true,
@@ -219,6 +224,10 @@
                         downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, dest,
                         LauncherInstrumentation.GestureScope.INSIDE),
                 NORMAL_STATE_ORDINAL);
+        if (startsActivity) {
+            launcher.expectEvent(
+                    TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_STOP_ACTIVITY);
+        }
         LauncherInstrumentation.log("dragIconToWorkspace: end");
         launcher.waitUntilGone("drop_target_bar");
     }
@@ -281,16 +290,22 @@
 
     @Nullable
     public Widget tryGetWidget(String label, long timeout) {
-        final UiObject2 widget = mLauncher.tryWaitForLauncherObject(
-                By.clazz("com.android.launcher3.widget.LauncherAppWidgetHostView").desc(label),
-                timeout);
-        return widget != null ? new Widget(mLauncher, widget) : null;
+        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                "getting widget " + label + " on workspace with timeout " + timeout)) {
+            final UiObject2 widget = mLauncher.tryWaitForLauncherObject(
+                    By.clazz("com.android.launcher3.widget.LauncherAppWidgetHostView").desc(label),
+                    timeout);
+            return widget != null ? new Widget(mLauncher, widget) : null;
+        }
     }
 
     @Nullable
     public Widget tryGetPendingWidget(long timeout) {
-        final UiObject2 widget = mLauncher.tryWaitForLauncherObject(
-                By.clazz("com.android.launcher3.widget.PendingAppWidgetHostView"), timeout);
-        return widget != null ? new Widget(mLauncher, widget) : null;
+        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                "getting pending widget on workspace with timeout " + timeout)) {
+            final UiObject2 widget = mLauncher.tryWaitForLauncherObject(
+                    By.clazz("com.android.launcher3.widget.PendingAppWidgetHostView"), timeout);
+            return widget != null ? new Widget(mLauncher, widget) : null;
+        }
     }
 }
\ No newline at end of file