Merging from ub-launcher3-master @ build 6248758

Test: manual, presubmit on the source branch
http: //x20/teams/android-launcher/merge/ub-launcher3-master_6248758.html
Change-Id: I1c64ab220493793ba10cbbecf38a4df8381d8a54
Bug: 150504032
diff --git a/Android.bp b/Android.bp
index cb695df..e132854 100644
--- a/Android.bp
+++ b/Android.bp
@@ -47,3 +47,14 @@
     },
     static_libs: ["libprotobuf-java-lite"],
 }
+
+java_library {
+    name: "LauncherPluginLib",
+
+    static_libs: ["PluginCoreLib"],
+
+    srcs: ["src_plugins/**/*.java"],
+
+    sdk_version: "current",
+    min_sdk_version: "28",
+}
diff --git a/Android.mk b/Android.mk
index c066a12..9cfcf17 100644
--- a/Android.mk
+++ b/Android.mk
@@ -17,24 +17,6 @@
 LOCAL_PATH := $(call my-dir)
 
 #
-# Build rule for plugin lib (needed to write a plugin).
-#
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_AAPT2_ONLY := true
-LOCAL_MODULE_TAGS := optional
-LOCAL_STATIC_JAVA_LIBRARIES:= PluginCoreLib
-
-LOCAL_SRC_FILES := \
-    $(call all-java-files-under, src_plugins)
-
-LOCAL_SDK_VERSION := current
-LOCAL_MIN_SDK_VERSION := 28
-LOCAL_MODULE := LauncherPluginLib
-
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
-#
 # Build rule for Launcher3 dependencies lib.
 #
 include $(CLEAR_VARS)
diff --git a/buglist.txt b/buglist.txt
new file mode 100644
index 0000000..9e3e977
--- /dev/null
+++ b/buglist.txt
@@ -0,0 +1,37 @@
+148867106
+139828243
+133381284
+145297320
+149969889
+label,
+144052839
+148099851
+148099851
+139750033
+148896221
+149993849
+149935239
+149967272
+148900990
+142753423
+149870691
+149870691
+149870691
+149422395
+145253300
+149927292
+144854916
+139828243
+149867607
+144052839
+148542211
+149200572
+149197172
+149199058
+149215103
+149198955
+145595763
+149481723
+139750033
+148542211
+145647019
diff --git a/buglist_unique.txt b/buglist_unique.txt
new file mode 100644
index 0000000..4e7b1be
--- /dev/null
+++ b/buglist_unique.txt
@@ -0,0 +1,30 @@
+133381284
+139750033
+139828243
+142753423
+144052839
+144854916
+145253300
+145297320
+145595763
+145647019
+148099851
+148542211
+148867106
+148896221
+148900990
+149197172
+149198955
+149199058
+149200572
+149215103
+149422395
+149481723
+149867607
+149870691
+149927292
+149935239
+149967272
+149969889
+149993849
+label,
diff --git a/buglist_with_title.txt b/buglist_with_title.txt
new file mode 100644
index 0000000..31e57e2
--- /dev/null
+++ b/buglist_with_title.txt
@@ -0,0 +1,29 @@
+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 ----
diff --git a/commitlist.txt b/commitlist.txt
new file mode 100644
index 0000000..778b5a1
--- /dev/null
+++ b/commitlist.txt
@@ -0,0 +1,965 @@
+COMMAND>> git log 6a8d59d9649cde5626f57935dfeba27cb866583f..d8995a6fdaf363c5cbef2c7209b4b199c7e083af(B
+commit d8995a6fdaf363c5cbef2c7209b4b199c7e083af
+Merge: 3be0443d3 3dd64ca3a
+Author: Vadim Tryshev <vadimt@google.com>
+Date:   Fri Feb 28 22:29:47 2020 +0000
+
+    Merge "Revert "TAPL: Verifying activity start/stop events"" into ub-launcher3-master
+
+commit 3dd64ca3a8a43c4b0fcb28a761d9379c0de0e1de
+Author: Vadim Tryshev <vadimt@google.com>
+Date:   Fri Feb 28 22:28:16 2020 +0000
+
+    Revert "TAPL: Verifying activity start/stop events"
+    
+    This reverts commit 1186700c9a9d282824bad4b9cc566974c287a49d.
+    
+    Reason for revert: Fails in the lab
+    
+    Change-Id: I6715a058c76d96752b904331968b46a07ce93f85
+
+commit 3be0443d377ee78af88bbe5d9551cfeefd280e06
+Merge: 23ceafe3d f450b2500
+Author: Vadim Tryshev <vadimt@google.com>
+Date:   Fri Feb 28 21:34:25 2020 +0000
+
+    Merge "Revert "Enabling flaky tests per our new flaky test policy"" into ub-launcher3-master
+
+commit f450b2500c6854f59ad9b8ccc373199c631e681b
+Author: Vadim Tryshev <vadimt@google.com>
+Date:   Fri Feb 28 21:33:16 2020 +0000
+
+    Revert "Enabling flaky tests per our new flaky test policy"
+    
+    This reverts commit 983145c5944ae8c44166a1bccce94f077e3e9106.
+    
+    Reason for revert: Too flaky
+    
+    Change-Id: Ifabb638d6ce0a539518bc0b6b667a9783b6ca29d
+
+commit 23ceafe3d5d1766f458c5cfe3234e92ea35db85a
+Merge: 64a932ed2 dc24231e4
+Author: Zak Cohen <zakcohen@google.com>
+Date:   Fri Feb 28 21:21:36 2020 +0000
+
+    Merge "TAPL tests - make all apps in overview checks less fragile." into ub-launcher3-master
+
+commit dc24231e4faff53750a06e64a16d596add296dbe
+Author: Zak Cohen <zakcohen@google.com>
+Date:   Fri Feb 28 11:59:33 2020 -0800
+
+    TAPL tests - make all apps in overview checks less fragile.
+    
+    Tests that check for all apps in overview mode were only checking for
+    actions being enabled. This isn't sufficient logic, two button mode also
+    uses all apps.
+    
+    Centralize the check for all apps.
+    
+    Test: local tapl tests
+    Change-Id: If1bf98019e6f1aea8f7967883aba6780743e9d6b
+
+commit 64a932ed2a7e166257c107676f35eb2e3c3c4c7c
+Merge: 1fabbf5a2 1186700c9
+Author: Vadim Tryshev <vadimt@google.com>
+Date:   Fri Feb 28 19:22:32 2020 +0000
+
+    Merge "TAPL: Verifying activity start/stop events" into ub-launcher3-master
+
+commit 1fabbf5a23f2bb0aa83a2b9ceb7b255274e9ff03
+Merge: 7ff297a81 983145c59
+Author: Vadim Tryshev <vadimt@google.com>
+Date:   Fri Feb 28 18:29:23 2020 +0000
+
+    Merge "Enabling flaky tests per our new flaky test policy" into ub-launcher3-master
+
+commit 983145c5944ae8c44166a1bccce94f077e3e9106
+Author: vadimt <vadimt@google.com>
+Date:   Thu Feb 27 17:58:59 2020 -0800
+
+    Enabling flaky tests per our new flaky test policy
+    
+    I'll check whether the tests are still flaky and assign to the test
+    owner if they are.
+    
+    Bug: 148867106
+    Change-Id: Ic850abc862b0f266b100aa6bfff4e89b3802e50d
+
+commit 7ff297a81faee4815a1feba6d64e6dd464bb7f0d
+Author: Becky Qiu <xuqiu@google.com>
+Date:   Fri Feb 28 01:38:44 2020 +0000
+
+    Revert "[Overview Actions] Disable swiping for TaskView if it's in select mode."
+    
+    This reverts commit 0499fb2060af0b7d065d77b22c81f4f77ee24eed.
+    
+    Reason for revert: This might break the post submit test. See b/150401176
+    
+    Change-Id: Iafc420fff72de8e83ce5e325fc1e5b1a1efe7350
+
+commit 2fe6fb967163e4c91f90a1680b2004ec45e0730e
+Merge: eecb258cc 3d64ffd4a
+Author: Vadim Tryshev <vadimt@google.com>
+Date:   Thu Feb 27 22:28:59 2020 +0000
+
+    Merge "Fixing activity leak via alarm in SecondaryDropTarget" into ub-launcher3-master
+
+commit 3d64ffd4a53e73c6fb4c04d6f144878ce4710d65
+Author: vadimt <vadimt@google.com>
+Date:   Thu Feb 27 11:28:47 2020 -0800
+
+    Fixing activity leak via alarm in SecondaryDropTarget
+    
+    Change-Id: I12c099ee1bb55d9db81e14d27d2eb09fbbc83e0f
+
+commit eecb258cc7c81ce04feb903b15267257d480c8bc
+Merge: 0e0b17636 9a4df4dba
+Author: Zak Cohen <zakcohen@google.com>
+Date:   Thu Feb 27 19:48:51 2020 +0000
+
+    Merge changes from topic "enable" into ub-launcher3-master
+    
+    * changes:
+      Overview Actions - Enable by default.
+      TAPL test update - don't run all apps in overview tests with actions.
+
+commit 0e0b1763612778dc94d3c29e4d32018c854dbcc7
+Merge: 3ba680055 da12b805c
+Author: Vadim Tryshev <vadimt@google.com>
+Date:   Thu Feb 27 19:45:39 2020 +0000
+
+    Merge "Not showing "mismatched events" and other diags after Launcher crashes" into ub-launcher3-master
+
+commit 3ba6800552f699f6937e7ff6d24ff66c20d6c2cc
+Merge: 9f62bc10a 1779a3829
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Thu Feb 27 19:19:14 2020 +0000
+
+    Merge "Using StrictMode to detect activity leaks" into ub-launcher3-master
+
+commit 1186700c9a9d282824bad4b9cc566974c287a49d
+Author: vadimt <vadimt@google.com>
+Date:   Fri Feb 14 14:42:35 2020 -0800
+
+    TAPL: Verifying activity start/stop events
+    
+    It also turned out that Pilfer event seems to come in a
+    non-deterministic order relative to the events from the Main and TIS
+    sequences. So I moved it to its own sequence.
+    
+    Change-Id: I5851aafb6d04398c5727712eaf8561916a30c4c5
+
+commit 9f62bc10afe375032453d5361707acd6c9df439b
+Merge: b5565fb62 dbc758305
+Author: Vadim Tryshev <vadimt@google.com>
+Date:   Thu Feb 27 19:08:29 2020 +0000
+
+    Merge "Remove unnecessary PilferPointer events and enable testPressHomeOnAllAppsContextMenu" into ub-launcher3-master
+
+commit b5565fb62fb0256395c4a2fd9abf07c83451f26b
+Merge: 39c610192 0499fb206
+Author: Becky Qiu <xuqiu@google.com>
+Date:   Thu Feb 27 18:48:09 2020 +0000
+
+    Merge "[Overview Actions] Disable swiping for TaskView if it's in select mode." into ub-launcher3-master
+
+commit 39c610192a8350adba64fdf6869f9b560163cb80
+Merge: de9823243 6f9637662
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Thu Feb 27 01:14:52 2020 +0000
+
+    Merge "Catching OOM exceptions in auto-investigator" into ub-launcher3-master
+
+commit de9823243a73662c211a5d405511d6bcf4f1f0a6
+Merge: 833cd4320 f2a15f1be
+Author: Zak Cohen <zakcohen@google.com>
+Date:   Wed Feb 26 21:25:36 2020 +0000
+
+    Merge "Make RectFSpringAnim resilient to negative height starting rect." into ub-launcher3-master
+
+commit da12b805cc1c02b03048430840d4772399bcb733
+Author: vadimt <vadimt@google.com>
+Date:   Wed Feb 26 12:17:47 2020 -0800
+
+    Not showing "mismatched events" and other diags after Launcher crashes
+    
+    Change-Id: Iee5f8813c3610314d486589bc361466a05da0ecb
+
+commit f2a15f1be54735ef41fe3a1dfea7f196a2e93b04
+Author: Zak Cohen <zakcohen@google.com>
+Date:   Wed Feb 26 10:53:53 2020 -0800
+
+    Make RectFSpringAnim resilient to negative height starting rect.
+    
+    Test: ran tapl tests, failed before, passed after
+    Change-Id: I10b82dd348b5c12550e60b145c76dad542aa56e8
+
+commit 833cd43206cefdc2d653d2ab42cb5b6c9317b278
+Merge: 5991eb833 65ed9c3d9
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Wed Feb 26 17:47:59 2020 +0000
+
+    Merge "Adding a failure investigator" into ub-launcher3-master
+
+commit 5991eb83377e84011d90cc40eb3ebe7617292a49
+Merge: b066af18a 5bb63c916
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Wed Feb 26 17:42:40 2020 +0000
+
+    Merge changes from topic "new-task-snapshots-ub-launcher3-master" into ub-launcher3-master
+    
+    * changes:
+      Toggle loading state based on config_lowResTaskSnapshotScale
+      Add config to enable snapshot preloading
+
+commit b066af18a51a7ab6ac167c3b9c5532c6aed1f294
+Merge: 0c5f58afb e6981a606
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Wed Feb 26 07:59:20 2020 +0000
+
+    Merge "Update mEndRect when closing ArrowPopup" into ub-launcher3-master
+
+commit dbc7583054405ab0f87a9394c56ecbf4241d8159
+Author: vadimt <vadimt@google.com>
+Date:   Wed Feb 19 19:17:28 2020 -0800
+
+    Remove unnecessary PilferPointer events and enable testPressHomeOnAllAppsContextMenu
+    
+    Change-Id: I04199f46024b05f05d04ba2be7a797842bb48527
+
+commit 0499fb2060af0b7d065d77b22c81f4f77ee24eed
+Author: Becky Qiu <xuqiu@google.com>
+Date:   Tue Feb 25 15:12:55 2020 -0800
+
+    [Overview Actions] Disable swiping for TaskView if it's in select mode.
+    
+    This including swiping up to dismiss, down to launch app, left and right to switch apps.
+    
+    Test: local
+    Bug: 139828243
+    Change-Id: Ib588c8725fb765097d03a5def719abdffd603708
+
+commit 0c5f58afb716d22fa3adb3a1962483f4430ae63c
+Merge: f17241918 e7e91fd60
+Author: Tracy Zhou <tracyzhou@google.com>
+Date:   Wed Feb 26 01:01:24 2020 +0000
+
+    Merge "Connect split-screen things to systemui divider" into ub-launcher3-master
+
+commit f1724191896786e5e957489b34ef5692d43aed46
+Merge: 37f435d62 bb18f4c9c
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Wed Feb 26 00:50:47 2020 +0000
+
+    Merge "Fixing log message" into ub-launcher3-master
+
+commit 37f435d62462c31c99c32d9222f539b104338a06
+Merge: 43a161579 20cc7a3b5
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Wed Feb 26 00:35:40 2020 +0000
+
+    Merge "Update proguard flags." into ub-launcher3-master
+
+commit e7e91fd60e5f78e63dd1b92fe21cb38a9f0cc494
+Author: Evan Rosky <erosky@google.com>
+Date:   Wed Nov 20 17:00:06 2019 -0800
+
+    Connect split-screen things to systemui divider
+    
+    Since divider stuff lives in sysui instead of framework
+    
+    Bug: 133381284
+    Test: Manual, open 2 apps in split and drag-up to show
+          recents.
+    Change-Id: If6740b7ee4829bf4cac6e829e81943f16a41f977
+    (cherry picked from commit 3ef159becdbda1041781920eb816d041bd88da44)
+
+commit bb18f4c9c6acfeef5666493ae904020b55414fd9
+Author: Sunny Goyal <sunnygoyal@google.com>
+Date:   Tue Feb 25 15:27:06 2020 -0800
+
+    Fixing log message
+    
+    Change-Id: I0668ea273a54ea741b1b871a50a8738c0ec4788b
+
+commit 9a4df4dba1512f4aa67da5c7f9d69a1ad22f3805
+Author: Zak Cohen <zakcohen@google.com>
+Date:   Wed Feb 19 14:52:10 2020 -0800
+
+    Overview Actions - Enable by default.
+    
+    Enable the Overview Actions feature flag by default.
+    
+    Test: local
+    Bug: 145297320
+    Change-Id: I6f4a2b6e90704b78d1ff1f77bbfea2d8789169ff
+
+commit 43a161579600c2f7cbce8d96e2474f97983f7d15
+Author: Zak Cohen <zakcohen@google.com>
+Date:   Tue Feb 25 14:29:37 2020 -0800
+
+    Hide apps view in transitions if none of its parts are visible.
+    
+    Check where the all apps view has any pieces visible, if not hide it.
+    
+    Test: local
+    Change-Id: I1d9b957b6c10dfbe4beaa56c4fee920844699a6c
+
+commit fe935dfbc198192780f9c3bdd94fdfb1b849d33b
+Author: Zak Cohen <zakcohen@google.com>
+Date:   Thu Feb 20 15:08:10 2020 -0800
+
+    TAPL test update - don't run all apps in overview tests with actions.
+    
+    Overview actions removes the all apps from overview. Don't run the tests
+    that depend on these.
+    Ultimately need to add more tests for the actions.
+    
+    Test: tapl local
+    Change-Id: I2471d10af7bc03a40a94f99aa16354b85bdb3ad7
+
+commit 1779a38290933a2b72df87795b5c8f1758da2ece
+Author: vadimt <vadimt@google.com>
+Date:   Tue Feb 25 14:41:01 2020 -0800
+
+    Using StrictMode to detect activity leaks
+    
+    Change-Id: I615f641897d57be0cd31be944796c6931ef9ab00
+
+commit 6f96376620b34b68c5896d25a315fc0f8b72ac80
+Author: vadimt <vadimt@google.com>
+Date:   Tue Feb 25 11:05:39 2020 -0800
+
+    Catching OOM exceptions in auto-investigator
+    
+    In the development environment, the device may stay for long time
+    without reboot, and the logcat size may turn to be long enough to
+    overflow the memory.
+    
+    Suppressing OOM exceptions.
+    
+    Change-Id: I562e2c03312e88f669d9cb660af6e8849dc0c8ef
+
+commit d71c3b185de62a50b730bbf389d48ead9ccc6315
+Merge: 86430dd2d 93c4de741
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Tue Feb 25 18:23:19 2020 +0000
+
+    Merge "Setting interpolator beforehand, instead of overriding it after the pending animaiton is created" into ub-launcher3-master
+
+commit 86430dd2d25ba69220f20e5cc2f5379cadcef27c
+Merge: 6382b39ff b79614cc9
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Tue Feb 25 18:20:39 2020 +0000
+
+    Merge "Performing a check for a crashed Launcher process for each UI test" into ub-launcher3-master
+
+commit 65ed9c3d9bb6c0086613b3e6512bdb0a1f673936
+Author: vadimt <vadimt@google.com>
+Date:   Mon Feb 24 19:10:50 2020 -0800
+
+    Adding a failure investigator
+    
+    Change-Id: Ie1c9820ecb7373a1f4428a24867c51332a84e65c
+
+commit b79614cc93f7a54dead7c19e6e225cc4a832ba53
+Author: vadimt <vadimt@google.com>
+Date:   Mon Feb 24 18:24:05 2020 -0800
+
+    Performing a check for a crashed Launcher process for each UI test
+
+commit 6382b39ff4605fbe30d8edf9ba25d32a7c04d7e5
+Author: Tony Wickham <twickham@google.com>
+Date:   Mon Feb 24 17:06:17 2020 -0800
+
+    Add null check to sendCustomAccessibilityEvent()
+    
+    ag/10273975 caused a regression where launcher crashes if RecentsView is
+    empty.
+    
+    Change-Id: I1607627be0a9748fd552adc757296d7b6bbfd1e2
+
+commit e6981a606abac6fe70cbb9fdefd16a25fb2f2f58
+Author: Tony Wickham <twickham@google.com>
+Date:   Mon Feb 24 17:33:20 2020 -0800
+
+    Update mEndRect when closing ArrowPopup
+    
+    Generally mEndRect is still correct from when we set it in
+    animateOpen(), but there are cases where it is no longer valid and
+    should be recomputed in animateClose(). For example, if you dismiss
+    notifications, the height of the popup changes between opening and
+    closing it.
+    
+    Test: Long press an icon with a notification dot, dismiss all
+    notifications from the popup and then close the popup. The shadow
+    follows the outline of the popup, instead of extending well below it.
+    
+    Change-Id: Ic5d6ad2807ea1ec8aff5e3d6ce8abaa11ce5d33c
+
+commit 93c4de7413f40a3660fdecb3a19fe5271e93d360
+Author: Sunny Goyal <sunnygoyal@google.com>
+Date:   Mon Feb 24 16:39:36 2020 -0800
+
+    Setting interpolator beforehand, instead of overriding it after the pending
+    animaiton is created
+    
+    Change-Id: I7caec2eeb86084591fa627518d064b7ebb895d65
+
+commit 20cc7a3b5b2ef2e49b85d2cd51320c4c0183e244
+Author: Amos Bianchi <amosbianchi@google.com>
+Date:   Mon Feb 24 15:35:49 2020 -0800
+
+    Update proguard flags.
+    
+    Fully R8 optimized variants may crash with:
+    java.lang.AbstractMethodError exception.
+    
+    Test: build, manual test on device.
+    Change-Id: I18a7b011ddd3536e3d33b991954a52e5fc5c7d56
+
+commit 436573401765cdc97d8cf672ace3ccbea16badeb
+Merge: 6b7fbd22d be13d109b
+Author: Tracy Zhou <tracyzhou@google.com>
+Date:   Mon Feb 24 20:01:58 2020 +0000
+
+    Merge "Render user's actual workspace in ThemePicker preview (Part 3)" into ub-launcher3-master
+
+commit 6b7fbd22dda79a31e75da18da4b0bf05cd25a85a
+Merge: d2c56525b 27d3c595c
+Author: Andy Wickham <awickham@google.com>
+Date:   Mon Feb 24 18:49:12 2020 +0000
+
+    Merge "Makes all ArrowPopups AccessibilityTargets." into ub-launcher3-master
+
+commit d2c56525bb18c5e971aced5f60857c2f70d502d6
+Merge: 7fa947cb3 a173193af
+Author: Sunny Goyal <sunnygoyal@google.com>
+Date:   Mon Feb 24 18:30:13 2020 +0000
+
+    Merge "Using LeastSquaresVelocityTrackerStrategy for calculating velocity when detecting motion pause events. This is the same logic used by the platform implementation VelocityTracker" into ub-launcher3-master
+
+commit 7fa947cb316945f40cab5cf7b5ed20e8b82dc219
+Merge: bc5056391 5a93eb3ba
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Mon Feb 24 18:24:22 2020 +0000
+
+    Merge "Fix NPE on app launch log" into ub-launcher3-master
+
+commit 5a93eb3bafbe38bccd34c59c4843c318135f7975
+Author: Samuel Fufa <sfufa@google.com>
+Date:   Fri Feb 21 18:51:36 2020 -0800
+
+    Fix NPE on app launch log
+    
+    Bug: 149969889
+    Change-Id: I60fd283ac082fab1ebf6ea90568e054525c414cc
+
+commit bc50563915e92d3eec594c45af365f327d277996
+Merge: 218f5276a a241df66d
+Author: Sreyas Rangaraju <sreyasr@google.com>
+Date:   Sat Feb 22 01:11:47 2020 +0000
+
+    Merge "OverviewActions: Creating parent view to house RecentsView and Overview Actions." into ub-launcher3-master
+
+commit 218f5276a3b8e7ac3a5d3dc95c30087149e52e5b
+Merge: 3310b76a2 15d79a9b8
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Sat Feb 22 00:55:55 2020 +0000
+
+    Merge "Fixes incorrect FromFolderLabelState in clearcut log when updating folder label." into ub-launcher3-master
+
+commit 3310b76a27ef0f7bdb332e82faea498e868c5f9a
+Merge: 6b029c30b a754374c3
+Author: Tony Wickham <twickham@google.com>
+Date:   Sat Feb 22 00:34:09 2020 +0000
+
+    Merge "Remove mSourceWindowClipInsetsForLiveTile" into ub-launcher3-master
+
+commit 15d79a9b85cb58319e51ffa364ab38e804da307f
+Author: thiruram <thiruram@google.com>
+Date:   Fri Feb 21 14:31:02 2020 -0800
+
+    Fixes incorrect FromFolderLabelState in clearcut log when updating folder label.
+    
+    Bug description: When folder label is updated from a suggested label to a custom label,
+    EditFolderLabelEvent log expected to have FromFolderLabelState = FROM_SUGGESTED.
+    Currently FromFolderLabelState is logged as FROM_CUSTOM in such scenarios.
+    
+    Root cause: Folder label state is decided based on whether IME has entered into compose mode while editing.
+    This works fine for initial folder creation and also updated to non-empty label(to both custom or suggested).
+    But when folder is edited from suggested to empty label, its internal state changes from SUGGESTED to CUSTOM as expected.
+    Later when empty label is updated to suggested again, its internal state remains custom forever.
+    
+    Fix: Instead of setting folder's internal state based on IME's compose mode, comparing actual folder label with suggestions
+    and set it to SUGGESTED if any suggestion matches else CUSTOM.
+    
+    Change-Id: Ieea572ee93fd5567e3128c9bbcea3b670f0f01d9
+
+commit a241df66df203e1fcb5133cb93f0968591003b25
+Author: Sreyas <sreyasr@google.com>
+Date:   Wed Feb 5 16:58:34 2020 -0800
+
+    OverviewActions: Creating parent view to house RecentsView and Overview Actions.
+    
+    Creating RecentsViewHolder to be contain LauncherRecentsView in OverviewPanel so Overview Actions View can be created only once.
+    
+    Change-Id: I111f88903d2ff80275cc2e07b761577260073c17
+
+commit be13d109b7d422e89a994554d2c4be30a86338b2
+Author: Tracy Zhou <tracyzhou@google.com>
+Date:   Sun Jan 12 01:07:59 2020 -0800
+
+    Render user's actual workspace in ThemePicker preview (Part 3)
+    
+    go/grid-migration-preview
+    
+    With this change, we can see actual grid migration in wallpaper preview.
+    
+    The approach here: we use a tmp table (favorites_preview) here specifically for this preview (to write off the migration results), and load from this tmp table workspace items if migration is necessary and successful. Otherwise, we load from the current workspace.
+    
+    UPDATED: this change should be completely compatible with the new multi-db grid migration algorithm. Here is why
+    1. In LauncherPreviewRender#renderScreenShot, I added a check to decide which grid migration preview method we should call. Once v2 preview method is implemented, it should be integrated with other parts of this change perfectly (the reason will be mentioned below).
+    2. While we have multiple DBs, mOpenHelper in LauncherProvider always points to the current db we are using. Queries using CONTENT_URI is routed to whatever DB mOpenHelper points to, so it works perfectly to directly operate on CONTENT_URI even when we use multi-db underneath the hood.
+    3. With 1 and 2 mentioned, I believe in order for this preview change to support multi-db, we only need to implement the V2 grid migration algorithm. Because most of what we are doing in this change is wrapped in GridSizeMigrationTask, it's perfectly safeguarded.
+    
+    Bug: 144052839
+    Change-Id: Ie6d6048d77326f96546c8a180a7cd8f15b47e4c4
+
+commit 5bb63c916e757ca90a3ad56ccb6a8e340bb4fcab
+Author: Peter Kalauskas <peskal@google.com>
+Date:   Fri Jan 24 18:51:13 2020 -0800
+
+    Toggle loading state based on config_lowResTaskSnapshotScale
+    
+    Change high res loading state implementation to toggle on/off based on
+    config_lowResTaskSnapshotScale!=0 instead of ro.config.low_ram=true
+    
+    Test: TaskSnapshotCacheTest
+    Test: TaskSnapshotControllerTest
+    Test: TaskSnapshotPersisterLoaderTest
+    Test: TaskSnapshotSurfaceTest
+    Bug: 148099851
+    Change-Id: I316e553a8a13fe38f1224c38f7aa717a0f628f57
+
+commit 6b029c30b1e36435f4fd6c65bbe6d9f6f6b4d0c0
+Merge: 977940502 8605be316
+Author: Hyunyoung Song <hyunyoungs@google.com>
+Date:   Fri Feb 21 23:36:29 2020 +0000
+
+    Merge "Convert LauncherPluginLib to soong" into ub-launcher3-master
+
+commit 03c8f296cd9ed6a95c4de07213c0ba0fda277023
+Author: Peter Kalauskas <peskal@google.com>
+Date:   Tue Feb 18 16:20:27 2020 -0800
+
+    Add config to enable snapshot preloading
+    
+    Enable preloading if config_enableTaskSnapshotPreloading instead of
+    assuming it should be off for all low ram devices. This allows it to be
+    configured via an overlay.
+    
+    Test: TaskSnapshotCacheTest
+    Test: TaskSnapshotControllerTest
+    Test: TaskSnapshotPersisterLoaderTest
+    Test: TaskSnapshotSurfaceTest
+    Bug: 148099851
+    Change-Id: I9e3320dcff5b710e110cc7199c41c5a004991592
+
+commit a173193afa082647db3cd44ce34403773aacad6a
+Author: Sunny Goyal <sunnygoyal@google.com>
+Date:   Thu Feb 20 12:03:39 2020 -0800
+
+    Using LeastSquaresVelocityTrackerStrategy for calculating velocity
+    when detecting motion pause events. This is the same logic used by
+    the platform implementation VelocityTracker
+    
+    Bug: 139750033
+    Change-Id: I87aabd46e58a5caa1395be3a63fd38d0ee75e355
+
+commit 8605be3162fab552a0b950a2c4016986abe62148
+Author: Hyunyoung Song <hyunyoungs@google.com>
+Date:   Fri Feb 21 14:52:25 2020 -0800
+
+    Convert LauncherPluginLib to soong
+    
+    Bug: 148896221
+    Change-Id: I0c09ba2994cf2a8e230aebc51eb52a8acadf8686
+
+commit 9779405023996bbe6bc2f9a050d66f4dbd9d5385
+Merge: 621c1a8be 4e3eaa5a7
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Fri Feb 21 20:36:35 2020 +0000
+
+    Merge "[Overview Actions] Add ControlType for overview action buttons." into ub-launcher3-master
+
+commit 621c1a8be7a0ee1ffff4af7a3f1e8c0e5a3c10ee
+Merge: b47a08ad1 6d3e5465e
+Author: Hyunyoung Song <hyunyoungs@google.com>
+Date:   Fri Feb 21 20:08:20 2020 +0000
+
+    Merge "Update the suggestFolderName when items are added and deleted from folders" into ub-launcher3-master
+
+commit b47a08ad1eb7ce8b7b6ef7eb00b02f45ba6f88d6
+Merge: fb5d542f5 888b516a2
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Fri Feb 21 20:05:38 2020 +0000
+
+    Merge "Fixes NPE when creating folder with null suggestedFolderNames." into ub-launcher3-master
+
+commit 888b516a22ebb0a33be0656ede18606c037e9db3
+Author: thiruram <thiruram@google.com>
+Date:   Fri Feb 21 10:42:23 2020 -0800
+
+    Fixes NPE when creating folder with null suggestedFolderNames.
+    
+    Bug: 149993849
+    
+    Change-Id: Ia2758b76aef1812dab225c8a95e7141fcdeefab4
+
+commit 6d3e5465e23ef48bdd7a572c985ada779cbb9f42
+Author: Hyunyoung Song <hyunyoungs@google.com>
+Date:   Wed Feb 19 23:32:04 2020 -0800
+
+    Update the suggestFolderName when items are added and deleted from folders
+    
+    Bug: 149935239
+    Bug: 149967272
+    Bug: 148900990
+    
+    Change-Id: I0ed27236ad22579a1f3dcfd35a32252c5b1f6691
+
+commit fb5d542f51cb2a4532bb006bd36cf5f71a9e68f3
+Merge: 1562d104b 46b4f5816
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Fri Feb 21 17:33:27 2020 +0000
+
+    Merge "Increase FileLog days" into ub-launcher3-master
+
+commit 46b4f581610d322287473e8070a10f649c6985d6
+Author: Samuel Fufa <sfufa@google.com>
+Date:   Wed Feb 19 15:56:12 2020 -0800
+
+    Increase FileLog days
+    
+    Bug: 142753423
+    Change-Id: I43ccfe540cb0899a4a4ab6722b6670e830457f60
+
+commit 1562d104b86fa4f231dbe823d96c61d27fb65057
+Merge: f1030cf56 2f41808e9
+Author: Vadim Tryshev <vadimt@google.com>
+Date:   Fri Feb 21 00:27:46 2020 +0000
+
+    Merge "Updating logcat reading logic" into ub-launcher3-master
+
+commit f1030cf5642d8d21b1d1c47d3b3518fa7a388b64
+Merge: aeaad97eb d1d342b1c
+Author: Tony Wickham <twickham@google.com>
+Date:   Thu Feb 20 22:53:10 2020 +0000
+
+    Merge changes Ie48f4b66,Ia48f4453 into ub-launcher3-master
+    
+    * changes:
+      Always update clip rect before applying surface params
+      Clean up AppWindowAnimationHelper and TransformParams
+
+commit a754374c30195ecac5f54b3243f5a23e8dfaa8d2
+Author: Tony Wickham <twickham@google.com>
+Date:   Wed Feb 19 13:30:34 2020 -0800
+
+    Remove mSourceWindowClipInsetsForLiveTile
+    
+    It seems that the only time it is used, it is the same as
+    mSourceWindowClipInsets anyway.
+    
+    Bug: 149870691
+    Change-Id: I3b1a5a47a0d49cdd6eb8a3271ca24da6c17109cb
+
+commit d1d342b1c72c1dd500208098d82be18f4b23cf62
+Author: Tony Wickham <twickham@google.com>
+Date:   Wed Feb 19 16:04:25 2020 -0800
+
+    Always update clip rect before applying surface params
+    
+    Previously, we were only updating the clip rect when params.currentRect
+    == null, meaning the clip would be stale once the caller started
+    providing its own rect (e.g. when swiping to home).
+    
+    Also 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.
+    
+    Bug: 149870691
+    Change-Id: Ie48f4b665a5bf3cbef76bdf7f043febe99fb84a0
+
+commit badab61ace41602c78dd59732a1b954c7c0cf4aa
+Author: Tony Wickham <twickham@google.com>
+Date:   Wed Feb 19 12:59:37 2020 -0800
+
+    Clean up AppWindowAnimationHelper and TransformParams
+    
+    AppWindowAnimationHelper and TransformParams were being very tightly
+    intertwined, to the point that you really had to understand subtle
+    nuances of both in order to get a desired behavior. This makes making
+    changes really difficult, because there are lots of "traps" to know
+    about and navigate. To help alleviate this burden, cleaned up some of
+    these traps and give AWAH and TP distinct roles:
+    - A caller who needs to animate an app window needs both AWAH and TP.
+      TP defines specific parameters of how the app window should be
+      controlled, and AWAH simply reads from TP in order to provide the
+      desired behavior.
+    - Only the caller should write to TP; AWAH should no longer change
+      anything in a TP that is passed to it. For instance, instead of
+      repurposing TP.currentRect, AWAH now has its own mCurrentRect to
+      update based on passed parameters.
+    - TP is a very basic class that just holds various values that callers
+      can set one at a time. The order should not matter (setting one value
+      will never set another one), and defaults make sense.
+    - Commented the setter methods in TP so developers shouldn't have to
+      follow the code to see how they are used.
+    
+    Bug: 149870691
+    Change-Id: Ia48f4453c29549271a3fc6538128a1a77439e015
+
+commit 2f41808e9b7e361341c70c6978301f90bbacfc03
+Author: vadimt <vadimt@google.com>
+Date:   Wed Feb 19 14:19:17 2020 -0800
+
+    Updating logcat reading logic
+    
+    Start a thread to read logcat synchronusly instead
+    of back-tracking at the end of the test
+    
+    Also:
+    * Reusing the same logcat process for all checks. This eliminates
+    reading the log from its start for every gesture. We don’t kill that
+    process at the end, and don’t stop the thread. I’ve verified that
+    “am instrument” doesn’t hang, and the logcat process gets automatically
+    killed after the test process exits.
+    * Not using mStarted latch, as there is no need to wait until the reader
+    reaches the start mark.
+    
+    Bug: 149422395
+    Change-Id: Ide4ed19ad8d099c41918f38c2b073b8b2e143b69
+
+commit 27d3c595cfd3c462b93364c357253c2f4b686f37
+Author: Andy Wickham <awickham@google.com>
+Date:   Thu Feb 20 20:32:07 2020 +0000
+
+    Makes all ArrowPopups AccessibilityTargets.
+    
+    This currently includes the menu that pops up when
+    long pressing an app icon in Launcher or Search, or
+    long pressing Smartspace. Previously, only the menu
+    for Launcher apps grabbed accessibility focus after
+    the open animation completed, and now all three do.
+    
+    Fixes: 145253300
+    Change-Id: I147b45d38a04ab9a55eee9b5bd5551b4c7185fcf
+
+commit aeaad97eb7ac5610491c35d2bc7bd15758d22966
+Author: Samuel Fufa <sfufa@google.com>
+Date:   Thu Feb 20 11:21:26 2020 -0800
+
+    Increase timeout for WorkTabTest.toggleWorks test
+    
+    Bug: 149927292
+    Change-Id: I6dcd0bfa2fe584be9e46f653992f9c748e795614
+
+commit d446f984576fa7fbd5ed0555bdcb6a42440f12a4
+Merge: 7be160170 f3b7246bf
+Author: Winson Chung <winsonc@google.com>
+Date:   Thu Feb 20 06:37:46 2020 +0000
+
+    Merge "Adding new tracing call from SysUI" into ub-launcher3-master
+
+commit 7be160170d1f1e81f0eea4305a8d01031a8633be
+Merge: 91f97cb2d 10a285dc2
+Author: Tracy Zhou <tracyzhou@google.com>
+Date:   Thu Feb 20 05:53:45 2020 +0000
+
+    Merge "Render user's actual workspace in ThemePicker preview (Part 5)" into ub-launcher3-master
+
+commit f3b7246bf273b69a1a7390b4fbb7794ed57168ee
+Author: Winson Chung <winsonc@google.com>
+Date:   Wed Nov 20 12:27:06 2019 -0800
+
+    Adding new tracing call from SysUI
+    
+    Bug: 144854916
+    Test: This change is only to ensure the changes build
+    Change-Id: I62b247b45454861328b6062ed571c854a374aa34
+
+commit 91f97cb2d1e5be3a17b040e34732b10df14c975f
+Merge: 6e82910a4 d1c077972
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Thu Feb 20 01:49:30 2020 +0000
+
+    Merge "Increase timeout for flaky work test" into ub-launcher3-master
+
+commit 4e3eaa5a71d127f30927e07cc42ddfa7afcb4887
+Author: Becky Qiu <xuqiu@google.com>
+Date:   Wed Feb 19 17:37:22 2020 -0800
+
+    [Overview Actions] Add ControlType for overview action buttons.
+    
+    Design: go/logging_for_overview_actions
+    
+    Test:local, logging result: https://paste.googleplex.com/6489148393259008
+    
+    Bug:139828243
+    
+    Change-Id: I61fa66f8ec6edae43f6242599f8d4ea8e28d735d
+
+commit 6e82910a45539afab26f60d60261c1b56c3885d7
+Merge: 9fb7d28b6 619daaf82
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Thu Feb 20 01:16:42 2020 +0000
+
+    Merge "Give current TaskView accessibility focus" into ub-launcher3-master
+
+commit d1c07797269afc77608809b72088f89fad71acf9
+Author: Samuel Fufa <sfufa@google.com>
+Date:   Wed Feb 19 17:07:00 2020 -0800
+
+    Increase timeout for flaky work test
+    
+    Bug:149867607
+    Change-Id: Ic746a4857a47fc29372d19419ddfa6eaebd06f87
+
+commit 10a285dc24b75cb7469f284c945ea348ea305139
+Author: Tracy Zhou <tracyzhou@google.com>
+Date:   Tue Feb 18 11:52:53 2020 -0800
+
+    Render user's actual workspace in ThemePicker preview (Part 5)
+    
+    This change takes care of rendering widgets using widget provider's layout info.
+    
+    Test: manual
+    Bug: 144052839
+    
+    Change-Id: I7002d8bf653513cdd317736d550a47f61f0ee474
+
+commit 9fb7d28b638d70be07337ce2c73038bdcfaf8b5d
+Author: Andy Wickham <awickham@google.com>
+Date:   Thu Feb 20 00:07:06 2020 +0000
+
+    Removes exported="true" for RecentsActivity.
+    
+    Change-Id: Iea9ad04886c3221cefa709c14b6501b2af83af06
+
+commit 2eac20e3789bbd3d47d07a291d85fec9a7c53772
+Merge: b3c991f7d 63b7f3633
+Author: Andy Wickham <awickham@google.com>
+Date:   Wed Feb 19 23:51:20 2020 +0000
+
+    Merge "Renames BackGestureTutorialActivity to GestureSandboxActivity." into ub-launcher3-master
+
+commit b3c991f7d344966a1892efd1a4077ad9b6d91161
+Merge: 1e33123a0 9ac4b580a
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Wed Feb 19 21:45:38 2020 +0000
+
+    Merge "null check in showLableSuggestion()." into ub-launcher3-master
+
+commit 63b7f36338c5ccce05b97d8fa16e740142360d4a
+Author: Andy Wickham <awickham@google.com>
+Date:   Tue Feb 18 23:19:42 2020 +0000
+
+    Renames BackGestureTutorialActivity to GestureSandboxActivity.
+    
+    Bug: 148542211
+    Change-Id: Iaac537380b02f1c52f967748c30fe49ac36bb91e
+
+commit 1e33123a0ba651ccd9f020d333e6787948344bd5
+Merge: 6eebaab5d 3b0c1f3ac
+Author: Andy Wickham <awickham@google.com>
+Date:   Wed Feb 19 21:11:20 2020 +0000
+
+    Merge "Allows multiple gesture blocking activities to be specified." into ub-launcher3-master
+
+commit 9ac4b580a1a5caabab15cb5b1eafa584da5f9504
+Author: jayaprakashs <jayaprakashs@google.com>
+Date:   Wed Feb 19 12:38:05 2020 -0800
+
+    null check in showLableSuggestion().
+    
+    It throws NPE when the data is missing for all the packages and all the suggestions are null.
+    
+    Change-Id: I4aa36969e1aee00bcfc577a839d6da01d4e67a75
+
+commit 6eebaab5d6ba9094715652f4b1107e43e5cb6035
+Merge: aeb185e74 c3bd4d0e7
+Author: TreeHugger Robot <treehugger-gerrit@google.com>
+Date:   Wed Feb 19 20:10:59 2020 +0000
+
+    Merge "Updating Pause detection to use motion events directly" into ub-launcher3-master
+
+commit aeb185e744f8d1edac11dad6bd254bd0b741d9fb
+Merge: b8d1e3526 ccebfbe27
+Author: Samuel Fufa <sfufa@google.com>
+Date:   Wed Feb 19 19:53:38 2020 +0000
+
+    Merge "Clean up work profile" into ub-launcher3-master
+
+commit ccebfbe2733e6570d3af0622262a1eb2e5633478
+Author: Samuel Fufa <sfufa@google.com>
+Date:   Mon Feb 10 09:26:20 2020 -0800
+
+    Clean up work profile
+    
+    This includes
+    - Dismiss work edu on launcher state change
+    - Remove work tab flash on first setup
+    - Make edu bottom sheet adopt theme color
+    - Fix Work toggle bottom inset
+    
+    
+    Bug: 149200572
+    Bug: 149197172
+    Bug: 149199058
+    Bug: 149215103
+    Bug: 149198955
+    Bug: 145595763
+    Bug:149481723
+    
+    Test: Manual
+    Change-Id: I39a30782b80fd3a66bede55754fa30a940d2caee
+
+commit c3bd4d0e75e150a44169551eba27db48bc66a462
+Author: Sunny Goyal <sunnygoyal@google.com>
+Date:   Tue Feb 18 17:32:57 2020 -0800
+
+    Updating Pause detection to use motion events directly
+    
+    Bug: 139750033
+    Change-Id: Ib261b203fec941f3c5303eafc1d435efdf3dbcaf
+
+commit 3b0c1f3acaea80c332da2b49b35a58f1d5a1f53c
+Author: Andy Wickham <awickham@google.com>
+Date:   Fri Feb 14 00:31:54 2020 +0000
+
+    Allows multiple gesture blocking activities to be specified.
+    
+    Bug: 148542211
+    Change-Id: Ie299359eb2df60f5f08d156dc6882fa133fab27c
+
+commit 619daaf82a65cbd1190a676b8582f1a7eb1cb774
+Author: Tony Wickham <twickham@google.com>
+Date:   Fri Feb 7 12:29:06 2020 -0800
+
+    Give current TaskView accessibility focus
+    
+    Send TYPE_VIEW_FOCUSED in the following places:
+    - When page transition ends on a task
+    - When finishing state transition to overview
+    - When un-hiding the current running task view
+    
+    Bug: 145647019
+    Change-Id: I7bb357ea60e1dea79daf2ad50efa51071e064da8
diff --git a/go/src/com/android/launcher3/model/WidgetsModel.java b/go/src/com/android/launcher3/model/WidgetsModel.java
index 7690b9d..3b3dc01 100644
--- a/go/src/com/android/launcher3/model/WidgetsModel.java
+++ b/go/src/com/android/launcher3/model/WidgetsModel.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.model;
 
+import android.content.ComponentName;
 import android.content.Context;
 import android.os.UserHandle;
 
@@ -68,4 +69,9 @@
     public void onPackageIconsUpdated(Set<String> packageNames, UserHandle user,
             LauncherAppState app) {
     }
+
+    public WidgetItem getWidgetProviderInfoByProviderName(
+            ComponentName providerName) {
+        return null;
+    }
 }
\ No newline at end of file
diff --git a/proguard.flags b/proguard.flags
index e556c94..37b8093 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -50,4 +50,13 @@
 -dontwarn android.graphics.**
 
 # Ignore warnings for hidden utility classes referenced from the shared lib
--dontwarn com.android.internal.util.**
\ No newline at end of file
+-dontwarn com.android.internal.util.**
+
+################ Do not optimize recents lib #############
+-keep class com.android.systemui.** {
+  *;
+}
+
+-keep class com.android.quickstep.** {
+  *;
+}
diff --git a/protos/launcher_log.proto b/protos/launcher_log.proto
index ec1d55b..d08d851 100644
--- a/protos/launcher_log.proto
+++ b/protos/launcher_log.proto
@@ -151,6 +151,10 @@
   DISMISS_PREDICTION = 21;
   HYBRID_HOTSEAT_ACCEPTED = 22;
   HYBRID_HOTSEAT_CANCELED = 23;
+  OVERVIEW_ACTIONS_SHARE_BUTTON = 24;
+  OVERVIEW_ACTIONS_SCREENSHOT_BUTTON = 25;
+  OVERVIEW_ACTIONS_SELECT_BUTTON = 26;
+  SELECT_MODE_CLOSE_BUTTON = 27;
 }
 
 enum TipType {
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index d3cec28..a06a2dd 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -47,10 +47,7 @@
             </intent-filter>
         </service>
 
-        <!-- STOPSHIP: Change exported to false once all the integration is complete.
-        It is set to true so that the activity can be started from command line -->
         <activity android:name="com.android.quickstep.RecentsActivity"
-            android:exported="true"
             android:excludeFromRecents="true"
             android:launchMode="singleTask"
             android:clearTaskOnLaunch="true"
@@ -93,12 +90,12 @@
                   android:directBootAware="true" />
 
         <activity
-            android:name="com.android.quickstep.interaction.BackGestureTutorialActivity"
+            android:name="com.android.quickstep.interaction.GestureSandboxActivity"
             android:autoRemoveFromRecents="true"
             android:excludeFromRecents="true"
             android:screenOrientation="portrait">
             <intent-filter>
-                <action android:name="com.android.quickstep.action.BACK_GESTURE_TUTORIAL" />
+                <action android:name="com.android.quickstep.action.GESTURE_SANDBOX" />
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
diff --git a/quickstep/recents_ui_overrides/res/drawable/hotseat_edu_notification_icon.xml b/quickstep/recents_ui_overrides/res/drawable/hotseat_edu_notification_icon.xml
index fa3a0f8..4fda2a9 100644
--- a/quickstep/recents_ui_overrides/res/drawable/hotseat_edu_notification_icon.xml
+++ b/quickstep/recents_ui_overrides/res/drawable/hotseat_edu_notification_icon.xml
@@ -15,5 +15,5 @@
 -->
 <vector android:height="24dp" android:viewportHeight="24"
     android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
-    <path android:fillColor="@color/bottom_panel_background" android:pathData="M19 9l1.25-2.75L23 5l-2.75-1.25L19 1l-1.25 2.75L15 5l2.75 1.25L19 9zm-7.5.5L9 4 6.5 9.5 1 12l5.5 2.5L9 20l2.5-5.5L17 12l-5.5-2.5zM19 15l-1.25 2.75L15 19l2.75 1.25L19 23l1.25-2.75L23 19l-2.75-1.25L19 15z"/>
+    <path android:fillColor="?android:attr/colorAccent" android:pathData="M19 9l1.25-2.75L23 5l-2.75-1.25L19 1l-1.25 2.75L15 5l2.75 1.25L19 9zm-7.5.5L9 4 6.5 9.5 1 12l5.5 2.5L9 20l2.5-5.5L17 12l-5.5-2.5zM19 15l-1.25 2.75L15 19l2.75 1.25L19 23l1.25-2.75L23 19l-2.75-1.25L19 15z"/>
 </vector>
diff --git a/quickstep/recents_ui_overrides/res/layout/overview_panel.xml b/quickstep/recents_ui_overrides/res/layout/overview_panel.xml
index 7f1425b..a572cad 100644
--- a/quickstep/recents_ui_overrides/res/layout/overview_panel.xml
+++ b/quickstep/recents_ui_overrides/res/layout/overview_panel.xml
@@ -14,12 +14,17 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<com.android.quickstep.views.LauncherRecentsView
+<com.android.launcher3.InsettableFrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:theme="@style/HomeScreenElementTheme"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:clipChildren="false"
-    android:clipToPadding="false"
-    android:accessibilityPaneTitle="@string/accessibility_recent_apps"
-    android:visibility="invisible" />
\ No newline at end of file
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content">
+    <com.android.quickstep.views.LauncherRecentsView
+        android:id="@+id/overview_panel_recents"
+        android:theme="@style/HomeScreenElementTheme"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:clipChildren="false"
+        android:clipToPadding="false"
+        android:accessibilityPaneTitle="@string/accessibility_recent_apps"
+        android:visibility="invisible" />
+</com.android.launcher3.InsettableFrameLayout>
diff --git a/quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml b/quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml
index d94c665..fe99037 100644
--- a/quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml
+++ b/quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml
@@ -24,13 +24,13 @@
     <View
         android:layout_width="match_parent"
         android:layout_height="32dp"
-        android:backgroundTint="@color/bottom_panel_background"
+        android:backgroundTint="?android:attr/colorAccent"
         android:background="@drawable/bottom_sheet_top_border" />
 
     <LinearLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:background="@color/bottom_panel_background"
+        android:background="?android:attr/colorAccent"
         android:orientation="vertical">
 
         <TextView
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 bfbd00e..e7290a4 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
@@ -34,6 +34,7 @@
 import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.launcher3.util.ActivityTracker;
+import com.android.launcher3.util.Themes;
 
 import java.util.List;
 
@@ -109,7 +110,7 @@
                 NOTIFICATION_CHANNEL_ID)
                 .setContentTitle(name)
                 .setOngoing(true)
-                .setColor(mLauncher.getColor(R.color.bottom_panel_background))
+                .setColor(Themes.getColorAccent(mLauncher))
                 .setContentIntent(PendingIntent.getActivity(mLauncher, 0, intent,
                         PendingIntent.FLAG_CANCEL_CURRENT))
                 .setSmallIcon(R.drawable.hotseat_edu_notification_icon)
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 00e72b1..8926246 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
@@ -191,7 +191,7 @@
                 || predictions.size() < mLauncher.getDeviceProfile().inv.numHotseatIcons) {
             return;
         }
-        mLauncher.getDragLayer().addView(this);
+        attachToContainer();
         logOnBoardingSeen();
         animateOpen();
         for (int i = 0; i < mLauncher.getDeviceProfile().inv.numHotseatIcons; i++) {
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 a1c8378..7895bac 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
@@ -37,6 +37,7 @@
 
 import android.graphics.Rect;
 import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.Interpolator;
 
 import com.android.launcher3.AbstractFloatingView;
@@ -47,6 +48,7 @@
 import com.android.launcher3.Workspace;
 import com.android.launcher3.allapps.DiscoveryBounce;
 import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.quickstep.SysUINavigationMode;
@@ -143,6 +145,10 @@
     public void onStateTransitionEnd(Launcher launcher) {
         launcher.getRotationHelper().setCurrentStateRequest(REQUEST_ROTATE);
         DiscoveryBounce.showForOverviewIfNeeded(launcher);
+        RecentsView recentsView = launcher.getOverviewPanel();
+        AccessibilityManagerCompat.sendCustomAccessibilityEvent(
+                recentsView.getPageAt(recentsView.getCurrentPage()),
+                AccessibilityEvent.TYPE_VIEW_FOCUSED, null);
     }
 
     @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 b80830a..519939e 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
@@ -172,7 +172,7 @@
         mMotionPauseDetector.setDisallowPause(!handlingOverviewAnim()
                 || upDisplacement < mMotionPauseMinDisplacement
                 || upDisplacement > mMotionPauseMaxDisplacement);
-        mMotionPauseDetector.addPosition(displacement, event.getEventTime());
+        mMotionPauseDetector.addPosition(event);
         return super.onDrag(displacement, event);
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 8628db0..799f1ad 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -317,7 +317,7 @@
         // home screen elements will appear in the shelf on motion pause.
         mMotionPauseDetector.setDisallowPause(mIsHomeScreenVisible
                 || -displacement.y < mMotionPauseMinDisplacement);
-        mMotionPauseDetector.addPosition(displacement.y, ev.getEventTime());
+        mMotionPauseDetector.addPosition(ev);
 
         if (mIsHomeScreenVisible) {
             // Cancel the shelf anim so it doesn't clobber mNonOverviewAnim.
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
index 03862db..1f5228a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
@@ -19,6 +19,7 @@
 import static com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController.isTouchOverHotseat;
 
 import android.view.MotionEvent;
+import android.view.animation.Interpolator;
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.util.PendingAnimation;
@@ -74,12 +75,12 @@
      * @param duration how long the animation should be
      * @return the animation
      */
-    PendingAnimation createSwipeDownToTaskAppAnimation(long duration) {
+    PendingAnimation createSwipeDownToTaskAppAnimation(long duration, Interpolator interpolator) {
         mRecentsView.setCurrentPage(mRecentsView.getPageNearestToCenterOfScreen());
         TaskView taskView = mRecentsView.getCurrentPageTaskView();
         if (taskView == null) {
             throw new IllegalStateException("There is no task view to animate to.");
         }
-        return mRecentsView.createTaskLauncherAnimation(taskView, duration);
+        return mRecentsView.createTaskLaunchAnimation(taskView, duration, interpolator);
     }
 }
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 32855d7..947a861 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
@@ -191,9 +191,8 @@
 
             mEndDisplacement = -mTaskBeingDragged.getHeight();
         } else {
-            mPendingAnimation = mRecentsView.createTaskLauncherAnimation(
-                    mTaskBeingDragged, maxDuration);
-            mPendingAnimation.anim.setInterpolator(Interpolators.ZOOM_IN);
+            mPendingAnimation = mRecentsView.createTaskLaunchAnimation(
+                    mTaskBeingDragged, maxDuration, Interpolators.ZOOM_IN);
 
             mTempCords[1] = mTaskBeingDragged.getHeight();
             dl.getDescendantCoordRelativeToSelf(mTaskBeingDragged, mTempCords);
@@ -203,8 +202,8 @@
         if (mCurrentAnimation != null) {
             mCurrentAnimation.setOnCancelRunnable(null);
         }
-        mCurrentAnimation = AnimatorPlaybackController
-                .wrap(mPendingAnimation.anim, maxDuration, this::clearState);
+        mCurrentAnimation = AnimatorPlaybackController.wrap(
+                mPendingAnimation.anim, maxDuration, this::clearState);
         onUserControlledAnimationCreated(mCurrentAnimation);
         mCurrentAnimation.getTarget().addListener(this);
         mCurrentAnimation.dispatchOnStart();
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 7ff8969..3601af2 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
@@ -428,6 +428,10 @@
         // rounding at the end of the animation.
         float startRadius = mAppWindowAnimationHelper.getCurrentCornerRadius();
         float endRadius = startRect.width() / 6f;
+
+        float startTransformProgress = mTransformParams.getProgress();
+        float endTransformProgress = 1;
+
         // We want the window alpha to be 0 once this threshold is met, so that the
         // FolderIconView can be seen morphing into the icon shape.
         final float windowAlphaThreshold = isFloatingIconView ? 1f - SHAPE_PROGRESS_DURATION : 1f;
@@ -437,8 +441,10 @@
             public void onUpdate(RectF currentRect, float progress) {
                 homeAnim.setPlayFraction(progress);
 
-                mTransformParams.setProgress(progress)
-                        .setCurrentRectAndTargetAlpha(currentRect, getWindowAlpha(progress));
+                mTransformParams.setProgress(
+                        Utilities.mapRange(progress, startTransformProgress, endTransformProgress))
+                        .setCurrentRect(currentRect)
+                        .setTargetAlpha(getWindowAlpha(progress));
                 if (isFloatingIconView) {
                     mTransformParams.setCornerRadius(endRadius * progress + startRadius
                             * (1f - progress));
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 34b2bdb..d4c746f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -3,8 +3,8 @@
 import android.app.Activity;
 import android.content.Context;
 import android.os.Bundle;
-import android.util.Log;
 
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.testing.TestInformationHandler;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
@@ -64,6 +64,12 @@
                         taskBaseIntentComponents);
                 return response;
             }
+
+            case TestProtocol.REQUEST_OVERVIEW_ACTIONS_ENABLED: {
+                response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD,
+                        FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get());
+                return response;
+            }
         }
 
         return super.call(method);
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 bfe5738..8d73591 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
@@ -163,7 +163,7 @@
                 if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
                     List<SurfaceParams> surfaceParamsList = new ArrayList<>();
                     // Append the surface transform params for the app that's being opened.
-                    Collections.addAll(surfaceParamsList, inOutHelper.getSurfaceParams(params));
+                    Collections.addAll(surfaceParamsList, inOutHelper.computeSurfaceParams(params));
 
                     AppWindowAnimationHelper liveTileAnimationHelper =
                             v.getRecentsView().getClipAnimationHelper();
@@ -173,14 +173,14 @@
                                 v.getRecentsView().getLiveTileParams(true /* mightNeedToRefill */);
                         if (liveTileParams != null) {
                             SurfaceParams[] liveTileSurfaceParams =
-                                    liveTileAnimationHelper.getSurfaceParams(liveTileParams);
+                                    liveTileAnimationHelper.computeSurfaceParams(liveTileParams);
                             if (liveTileSurfaceParams != null) {
                                 Collections.addAll(surfaceParamsList, liveTileSurfaceParams);
                             }
                         }
                     }
                     // Apply surface transform using the surface params list.
-                    AppWindowAnimationHelper.applySurfaceParams(params.syncTransactionApplier,
+                    AppWindowAnimationHelper.applySurfaceParams(params.getSyncTransactionApplier(),
                             surfaceParamsList.toArray(new SurfaceParams[surfaceParamsList.size()]));
                     // Get the task bounds for the app that's being opened after surface transform
                     // update.
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 5820029..28e8fb6 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -772,7 +772,7 @@
 
     protected boolean shouldNotifyBackGesture() {
         return mBackGestureNotificationCounter > 0 &&
-                mDeviceState.getGestureBlockedActivityPackage() != null;
+                !mDeviceState.getGestureBlockedActivityPackages().isEmpty();
     }
 
     @WorkerThread
@@ -781,8 +781,8 @@
             mBackGestureNotificationCounter--;
             Utilities.getDevicePrefs(this).edit()
                     .putInt(KEY_BACK_NOTIFICATION_COUNT, mBackGestureNotificationCounter).apply();
-            sendBroadcast(new Intent(NOTIFY_ACTION_BACK).setPackage(
-                    mDeviceState.getGestureBlockedActivityPackage()));
+            mDeviceState.getGestureBlockedActivityPackages().forEach(blockedPackage ->
+                    sendBroadcast(new Intent(NOTIFY_ACTION_BACK).setPackage(blockedPackage)));
         }
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
index d3765c5..5ad48eb 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
@@ -31,8 +31,8 @@
 import com.android.launcher3.R;
 import com.android.quickstep.InputConsumer;
 import com.android.quickstep.RecentsAnimationDeviceState;
-import com.android.quickstep.util.MotionPauseDetector;
 import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.util.MotionPauseDetector;
 import com.android.systemui.shared.system.InputMonitorCompat;
 
 /**
@@ -117,9 +117,7 @@
                     if (pointerIndex == -1) {
                         break;
                     }
-
-                    mMotionPauseDetector.addPosition(ev.getY(pointerIndex) - mDownY,
-                            ev.getEventTime());
+                    mMotionPauseDetector.addPosition(ev, pointerIndex);
                 }
                 break;
             }
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 3ee3c2d..8e7074d 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
@@ -59,6 +59,7 @@
 import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.CachedEventDispatcher;
 import com.android.quickstep.util.MotionPauseDetector;
+import com.android.quickstep.util.NavBarPosition;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.InputMonitorCompat;
 
@@ -77,6 +78,7 @@
     public static final float QUICKSTEP_TOUCH_SLOP_RATIO = 3;
 
     private final RecentsAnimationDeviceState mDeviceState;
+    private final NavBarPosition mNavBarPosition;
     private final TaskAnimationManager mTaskAnimationManager;
     private final GestureState mGestureState;
     private RecentsAnimationCallbacks mActiveCallbacks;
@@ -126,13 +128,16 @@
             Factory handlerFactory) {
         super(base);
         mDeviceState = deviceState;
+        mNavBarPosition = mDeviceState.getNavBarPosition();
         mTaskAnimationManager = taskAnimationManager;
         mGestureState = gestureState;
         mMainThreadHandler = new Handler(Looper.getMainLooper());
         mHandlerFactory = handlerFactory;
         mActivityInterface = mGestureState.getActivityInterface();
 
-        mMotionPauseDetector = new MotionPauseDetector(base);
+        mMotionPauseDetector = new MotionPauseDetector(base, false,
+                mNavBarPosition.isLeftEdge() || mNavBarPosition.isRightEdge()
+                        ? MotionEvent.AXIS_X : MotionEvent.AXIS_Y);
         mMotionPauseMinDisplacement = base.getResources().getDimension(
                 R.dimen.motion_pause_detector_min_displacement_from_app);
         mOnCompleteCallback = onCompleteCallback;
@@ -172,7 +177,7 @@
         if (mPassedWindowMoveSlop && mInteractionHandler != null
                 && !mRecentsViewDispatcher.hasConsumer()) {
             mRecentsViewDispatcher.setConsumer(mInteractionHandler.getRecentsViewDispatcher(
-                    mDeviceState.getNavBarPosition().getRotationMode()));
+                    mNavBarPosition.getRotationMode()));
         }
         int edgeFlags = ev.getEdgeFlags();
         ev.setEdgeFlags(edgeFlags | EDGE_NAV_BAR);
@@ -285,7 +290,7 @@
                     if (mDeviceState.isFullyGesturalNavMode()) {
                         mMotionPauseDetector.setDisallowPause(upDist < mMotionPauseMinDisplacement
                                 || isLikelyToStartNewTask);
-                        mMotionPauseDetector.addPosition(displacement, ev.getEventTime());
+                        mMotionPauseDetector.addPosition(ev);
                         mInteractionHandler.setIsLikelyToStartNewTask(isLikelyToStartNewTask);
                     }
                 }
@@ -354,9 +359,9 @@
                         ViewConfiguration.get(this).getScaledMaximumFlingVelocity());
                 float velocityX = mVelocityTracker.getXVelocity(mActivePointerId);
                 float velocityY = mVelocityTracker.getYVelocity(mActivePointerId);
-                float velocity = mDeviceState.getNavBarPosition().isRightEdge()
+                float velocity = mNavBarPosition.isRightEdge()
                         ? velocityX
-                        : mDeviceState.getNavBarPosition().isLeftEdge()
+                        : mNavBarPosition.isLeftEdge()
                                 ? -velocityX
                                 : velocityY;
 
@@ -410,9 +415,9 @@
     }
 
     private float getDisplacement(MotionEvent ev) {
-        if (mDeviceState.getNavBarPosition().isRightEdge()) {
+        if (mNavBarPosition.isRightEdge()) {
             return ev.getX() - mDownPos.x;
-        } else if (mDeviceState.getNavBarPosition().isLeftEdge()) {
+        } else if (mNavBarPosition.isLeftEdge()) {
             return mDownPos.x - ev.getX();
         } else {
             return ev.getY() - mDownPos.y;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java
index d5ed321..b9827ff 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java
@@ -23,8 +23,8 @@
 import com.android.launcher3.R;
 import com.android.quickstep.GestureState;
 import com.android.quickstep.InputConsumer;
-import com.android.quickstep.util.MotionPauseDetector;
 import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.util.MotionPauseDetector;
 
 /**
  * An input consumer that detects swipe up and hold to exit screen pinning mode.
@@ -72,7 +72,7 @@
             case MotionEvent.ACTION_MOVE:
                 float displacement = mTouchDownY - y;
                 mMotionPauseDetector.setDisallowPause(displacement < mMotionPauseMinDisplacement);
-                mMotionPauseDetector.addPosition(y, ev.getEventTime());
+                mMotionPauseDetector.addPosition(ev);
                 break;
             case MotionEvent.ACTION_CANCEL:
             case MotionEvent.ACTION_UP:
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
index 9e29238..5a9c2fe 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
@@ -60,23 +60,23 @@
     private final Rect mSourceStackBounds = new Rect();
     // The insets of the source app
     private final Rect mSourceInsets = new Rect();
-    // The source app bounds with the source insets applied, in the source app window coordinates
+    // The source app bounds with the source insets applied, in the device coordinates
     private final RectF mSourceRect = new RectF();
-    // The bounds of the task view in launcher window coordinates
+    // The bounds of the task view in device coordinates
     private final RectF mTargetRect = new RectF();
+    // The bounds of the app window (between mSourceRect and mTargetRect) in device coordinates
+    private final RectF mCurrentRect = new RectF();
     // The insets to be used for clipping the app window, which can be larger than mSourceInsets
     // if the aspect ratio of the target is smaller than the aspect ratio of the source rect. In
     // app window coordinates.
     private final RectF mSourceWindowClipInsets = new RectF();
-    // The insets to be used for clipping the app window. For live tile, we don't transform the clip
-    // relative to the target rect.
-    private final RectF mSourceWindowClipInsetsForLiveTile = new RectF();
+    // The clip rect in source app window coordinates. The app window surface will only be drawn
+    // within these bounds. This clip rect starts at the full mSourceStackBounds, and insets by
+    // mSourceWindowClipInsets as the transform progress goes to 1.
+    private final RectF mCurrentClipRectF = new RectF();
 
     // The bounds of launcher (not including insets) in device coordinates
     public final Rect mHomeStackBounds = new Rect();
-
-    // The clip rect in source app window coordinates
-    private final RectF mClipRectF = new RectF();
     private final RectFEvaluator mRectFEvaluator = new RectFEvaluator();
     private final Matrix mTmpMatrix = new Matrix();
     private final Rect mTmpRect = new Rect();
@@ -146,7 +146,6 @@
                 Math.max(scaledTargetRect.top, 0),
                 Math.max(mSourceStackBounds.width() - scaledTargetRect.right, 0),
                 Math.max(mSourceStackBounds.height() - scaledTargetRect.bottom, 0));
-        mSourceWindowClipInsetsForLiveTile.set(mSourceWindowClipInsets);
         mSourceRect.set(scaledTargetRect);
     }
 
@@ -156,25 +155,29 @@
     }
 
     public RectF applyTransform(TransformParams params) {
-        SurfaceParams[] surfaceParams = getSurfaceParams(params);
+        SurfaceParams[] surfaceParams = computeSurfaceParams(params);
         if (surfaceParams == null) {
             return null;
         }
-        applySurfaceParams(params.syncTransactionApplier, surfaceParams);
-        return params.currentRect;
+        applySurfaceParams(params.mSyncTransactionApplier, surfaceParams);
+        return mCurrentRect;
     }
 
-    public SurfaceParams[] getSurfaceParams(TransformParams params) {
-        if (params.targetSet == null) {
+    /**
+     * Updates this AppWindowAnimationHelper's state based on the given TransformParams, and returns
+     * the SurfaceParams to apply via {@link SyncRtSurfaceTransactionApplierCompat#applyParams}.
+     */
+    public SurfaceParams[] computeSurfaceParams(TransformParams params) {
+        if (params.mTargetSet == null) {
             return null;
         }
 
-        float progress = Utilities.boundToRange(params.progress, 0, 1);
+        float progress = Utilities.boundToRange(params.mProgress, 0, 1);
         updateCurrentRect(params);
 
-        SurfaceParams[] surfaceParams = new SurfaceParams[params.targetSet.unfilteredApps.length];
-        for (int i = 0; i < params.targetSet.unfilteredApps.length; i++) {
-            RemoteAnimationTargetCompat app = params.targetSet.unfilteredApps[i];
+        SurfaceParams[] surfaceParams = new SurfaceParams[params.mTargetSet.unfilteredApps.length];
+        for (int i = 0; i < params.mTargetSet.unfilteredApps.length; i++) {
+            RemoteAnimationTargetCompat app = params.mTargetSet.unfilteredApps[i];
             mTmpMatrix.setTranslate(app.position.x, app.position.y);
             Rect crop = mTmpRect;
             crop.set(app.sourceContainerBounds);
@@ -182,17 +185,17 @@
             float alpha;
             int layer = RemoteAnimationProvider.getLayer(app, mBoostModeTargetLayers);
             float cornerRadius = 0f;
-            float scale = Math.max(params.currentRect.width(), mTargetRect.width()) / crop.width();
-            if (app.mode == params.targetSet.targetMode) {
-                alpha = mTaskAlphaCallback.getAlpha(app, params.targetAlpha);
+            float scale = Math.max(mCurrentRect.width(), mTargetRect.width()) / crop.width();
+            if (app.mode == params.mTargetSet.targetMode) {
+                alpha = mTaskAlphaCallback.getAlpha(app, params.mTargetAlpha);
                 if (app.activityType != RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
-                    mTmpMatrix.setRectToRect(mSourceRect, params.currentRect, ScaleToFit.FILL);
+                    mTmpMatrix.setRectToRect(mSourceRect, mCurrentRect, ScaleToFit.FILL);
                     mTmpMatrix.postTranslate(app.position.x, app.position.y);
-                    mClipRectF.roundOut(crop);
+                    mCurrentClipRectF.roundOut(crop);
                     if (mSupportsRoundedCornersOnWindows) {
-                        if (params.cornerRadius > -1) {
-                            cornerRadius = params.cornerRadius;
-                            scale = params.currentRect.width() / crop.width();
+                        if (params.mCornerRadius > -1) {
+                            cornerRadius = params.mCornerRadius;
+                            scale = mCurrentRect.width() / crop.width();
                         } else {
                             float windowCornerRadius = mUseRoundedCornersOnWindows
                                     ? mWindowCornerRadius : 0;
@@ -206,14 +209,14 @@
                             && app.isNotInRecents) {
                         alpha = 1 - Interpolators.DEACCEL_2_5.getInterpolation(progress);
                     }
-                } else if (params.targetSet.hasRecents) {
+                } else if (params.mTargetSet.hasRecents) {
                     // If home has a different target then recents, reverse anim the
                     // home target.
-                    alpha = 1 - (progress * params.targetAlpha);
+                    alpha = 1 - (progress * params.mTargetAlpha);
                 }
             } else {
                 alpha = mBaseAlphaCallback.getAlpha(app, progress);
-                if (ENABLE_QUICKSTEP_LIVE_TILE.get() && params.launcherOnTop) {
+                if (ENABLE_QUICKSTEP_LIVE_TILE.get() && params.mLauncherOnTop) {
                     crop = null;
                     layer = Integer.MAX_VALUE;
                 }
@@ -228,31 +231,33 @@
     }
 
     public RectF updateCurrentRect(TransformParams params) {
-        float progress = params.progress;
-        if (params.currentRect == null) {
-            RectF currentRect;
+        if (params.mCurrentRect != null) {
+            mCurrentRect.set(params.mCurrentRect);
+        } else {
             mTmpRectF.set(mTargetRect);
-            Utilities.scaleRectFAboutCenter(mTmpRectF, params.offsetScale);
-            currentRect = mRectFEvaluator.evaluate(progress, mSourceRect, mTmpRectF);
-            currentRect.offset(params.offsetX, 0);
-
-            // Don't clip past progress > 1.
-            progress = Math.min(1, progress);
-            final RectF sourceWindowClipInsets = params.forLiveTile
-                    ? mSourceWindowClipInsetsForLiveTile : mSourceWindowClipInsets;
-            mClipRectF.left = sourceWindowClipInsets.left * progress;
-            mClipRectF.top = sourceWindowClipInsets.top * progress;
-            mClipRectF.right =
-                    mSourceStackBounds.width() - (sourceWindowClipInsets.right * progress);
-            mClipRectF.bottom =
-                    mSourceStackBounds.height() - (sourceWindowClipInsets.bottom * progress);
-            params.setCurrentRectAndTargetAlpha(currentRect, 1);
+            Utilities.scaleRectFAboutCenter(mTmpRectF, params.mOffsetScale);
+            mCurrentRect.set(mRectFEvaluator.evaluate(params.mProgress, mSourceRect, mTmpRectF));
+            mCurrentRect.offset(params.mOffsetX, 0);
         }
-        return params.currentRect;
+
+        updateClipRect(params);
+
+        return mCurrentRect;
+    }
+
+    private void updateClipRect(TransformParams params) {
+        // Don't clip past progress > 1.
+        float progress = Math.min(1, params.mProgress);
+        mCurrentClipRectF.left = mSourceWindowClipInsets.left * progress;
+        mCurrentClipRectF.top = mSourceWindowClipInsets.top * progress;
+        mCurrentClipRectF.right =
+                mSourceStackBounds.width() - (mSourceWindowClipInsets.right * progress);
+        mCurrentClipRectF.bottom =
+                mSourceStackBounds.height() - (mSourceWindowClipInsets.bottom * progress);
     }
 
     public RectF getCurrentRectWithInsets() {
-        mTmpMatrix.mapRect(mCurrentRectWithInsets, mClipRectF);
+        mTmpMatrix.mapRect(mCurrentRectWithInsets, mCurrentClipRectF);
         return mCurrentRectWithInsets;
     }
 
@@ -384,75 +389,152 @@
     }
 
     public static class TransformParams {
-        float progress;
-        public float offsetX;
-        public float offsetScale;
-        public @Nullable RectF currentRect;
-        float targetAlpha;
-        boolean forLiveTile;
-        float cornerRadius;
-        boolean launcherOnTop;
-
-        public RemoteAnimationTargets targetSet;
-        public SyncRtSurfaceTransactionApplierCompat syncTransactionApplier;
+        private float mProgress;
+        private float mOffsetX;
+        private float mOffsetScale;
+        private @Nullable RectF mCurrentRect;
+        private float mTargetAlpha;
+        private float mCornerRadius;
+        private boolean mLauncherOnTop;
+        private RemoteAnimationTargets mTargetSet;
+        private SyncRtSurfaceTransactionApplierCompat mSyncTransactionApplier;
 
         public TransformParams() {
-            progress = 0;
-            offsetX = 0;
-            offsetScale = 1;
-            currentRect = null;
-            targetAlpha = 0;
-            forLiveTile = false;
-            cornerRadius = -1;
-            launcherOnTop = false;
+            mProgress = 0;
+            mOffsetX = 0;
+            mOffsetScale = 1;
+            mCurrentRect = null;
+            mTargetAlpha = 1;
+            mCornerRadius = -1;
+            mLauncherOnTop = false;
         }
 
+        /**
+         * Sets the progress of the transformation, where 0 is the source and 1 is the target. We
+         * automatically adjust properties such as currentRect and cornerRadius based on this
+         * progress, unless they are manually overridden by setting them on this TransformParams.
+         */
         public TransformParams setProgress(float progress) {
-            this.progress = progress;
-            this.currentRect = null;
+            mProgress = progress;
             return this;
         }
 
+        /**
+         * Sets the corner radius of the transformed window, in pixels. If unspecified (-1), we
+         * simply interpolate between the window's corner radius to the task view's corner radius,
+         * based on {@link #mProgress}.
+         */
         public TransformParams setCornerRadius(float cornerRadius) {
-            this.cornerRadius = cornerRadius;
+            mCornerRadius = cornerRadius;
             return this;
         }
 
-        public TransformParams setCurrentRectAndTargetAlpha(RectF currentRect, float targetAlpha) {
-            this.currentRect = currentRect;
-            this.targetAlpha = targetAlpha;
+        /**
+         * Sets the current rect to show the transformed window, in device coordinates. This gives
+         * the caller manual control of where to show the window. If unspecified (null), we
+         * interpolate between {@link AppWindowAnimationHelper#mSourceRect} and
+         * {@link AppWindowAnimationHelper#mTargetRect}, based on {@link #mProgress}.
+         */
+        public TransformParams setCurrentRect(RectF currentRect) {
+            mCurrentRect = currentRect;
             return this;
         }
 
+        /**
+         * Specifies the alpha of the transformed window. Default is 1.
+         */
+        public TransformParams setTargetAlpha(float targetAlpha) {
+            mTargetAlpha = targetAlpha;
+            return this;
+        }
+
+        /**
+         * If {@link #mCurrentRect} is null (i.e. {@link #setCurrentRect(RectF)} hasn't overridden
+         * the default), then offset the current rect by this amount after computing the rect based
+         * on {@link #mProgress}.
+         */
         public TransformParams setOffsetX(float offsetX) {
-            this.offsetX = offsetX;
+            mOffsetX = offsetX;
             return this;
         }
 
+        /**
+         * If {@link #mCurrentRect} is null (i.e. {@link #setCurrentRect(RectF)} hasn't overridden
+         * the default), then scale the current rect by this amount after computing the rect based
+         * on {@link #mProgress}.
+         */
         public TransformParams setOffsetScale(float offsetScale) {
-            this.offsetScale = offsetScale;
+            mOffsetScale = offsetScale;
             return this;
         }
 
-        public TransformParams setForLiveTile(boolean forLiveTile) {
-            this.forLiveTile = forLiveTile;
-            return this;
-        }
-
+        /**
+         * If true, sets the crop = null and layer = Integer.MAX_VALUE for targets that don't match
+         * {@link #mTargetSet}.targetMode. (Currently only does this when live tiles are enabled.)
+         */
         public TransformParams setLauncherOnTop(boolean launcherOnTop) {
-            this.launcherOnTop = launcherOnTop;
+            mLauncherOnTop = launcherOnTop;
             return this;
         }
 
+        /**
+         * Specifies the set of RemoteAnimationTargetCompats that are included in the transformation
+         * that these TransformParams help compute. These TransformParams generally only apply to
+         * the targetSet.apps which match the targetSet.targetMode (e.g. the MODE_CLOSING app when
+         * swiping to home).
+         */
         public TransformParams setTargetSet(RemoteAnimationTargets targetSet) {
-            this.targetSet = targetSet;
+            mTargetSet = targetSet;
             return this;
         }
 
+        /**
+         * Sets the SyncRtSurfaceTransactionApplierCompat that will apply the SurfaceParams that
+         * are computed based on these TransformParams.
+         */
         public TransformParams setSyncTransactionApplier(
                 SyncRtSurfaceTransactionApplierCompat applier) {
-            this.syncTransactionApplier = applier;
+            mSyncTransactionApplier = applier;
             return this;
         }
+
+        // Pubic getters so outside packages can read the values.
+
+        public float getProgress() {
+            return mProgress;
+        }
+
+        public float getOffsetX() {
+            return mOffsetX;
+        }
+
+        public float getOffsetScale() {
+            return mOffsetScale;
+        }
+
+        @Nullable
+        public RectF getCurrentRect() {
+            return mCurrentRect;
+        }
+
+        public float getTargetAlpha() {
+            return mTargetAlpha;
+        }
+
+        public float getCornerRadius() {
+            return mCornerRadius;
+        }
+
+        public boolean isLauncherOnTop() {
+            return mLauncherOnTop;
+        }
+
+        public RemoteAnimationTargets getTargetSet() {
+            return mTargetSet;
+        }
+
+        public SyncRtSurfaceTransactionApplierCompat getSyncTransactionApplier() {
+            return mSyncTransactionApplier;
+        }
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
index c6eafe6..a59c99c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
@@ -165,7 +165,7 @@
         mRectYAnim = new FlingSpringAnim(this, RECT_Y, startY, endY, startVelocityY,
                 mMinVisChange, minYValue, maxYValue, springVelocityFactor, onYEndListener);
 
-        float minVisibleChange = 1f / mStartRect.height();
+        float minVisibleChange = Math.abs(1f / mStartRect.height());
         mRectScaleAnim = new SpringAnimation(this, RECT_SCALE_PROGRESS)
                 .setSpring(new SpringForce(1f)
                 .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
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 1bbb3f5..3e106aa 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
@@ -200,8 +200,8 @@
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             if (tv.isRunningTask()) {
                 mTransformParams.setProgress(1 - progress)
-                        .setSyncTransactionApplier(mSyncTransactionApplier)
-                        .setForLiveTile(true);
+                        .setCurrentRect(null)
+                        .setSyncTransactionApplier(mSyncTransactionApplier);
                 mAppWindowAnimationHelper.applyTransform(mTransformParams);
             } else {
                 redrawLiveTile(true);
@@ -267,7 +267,8 @@
             }
             mTempRectF.set(mTempRect);
             mTransformParams.setProgress(1f)
-                    .setCurrentRectAndTargetAlpha(mTempRectF, taskView.getAlpha())
+                    .setCurrentRect(mTempRectF)
+                    .setTargetAlpha(taskView.getAlpha())
                     .setSyncTransactionApplier(mSyncTransactionApplier)
                     .setTargetSet(mRecentsAnimationTargets)
                     .setLauncherOnTop(true);
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 a7968ca..bc89859 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
@@ -76,6 +76,7 @@
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.animation.Interpolator;
 import android.widget.ListView;
 
 import androidx.annotation.Nullable;
@@ -92,6 +93,7 @@
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PropertyListBuilder;
 import com.android.launcher3.anim.SpringObjectAnimator;
+import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -980,6 +982,10 @@
         TaskView runningTask = getRunningTaskView();
         if (runningTask != null) {
             runningTask.setStableAlpha(isHidden ? 0 : mContentAlpha);
+            if (!isHidden) {
+                AccessibilityManagerCompat.sendCustomAccessibilityEvent(runningTask,
+                        AccessibilityEvent.TYPE_VIEW_FOCUSED, null);
+            }
         }
     }
 
@@ -1602,7 +1608,8 @@
         return anim;
     }
 
-    public PendingAnimation createTaskLauncherAnimation(TaskView tv, long duration) {
+    public PendingAnimation createTaskLaunchAnimation(
+            TaskView tv, long duration, Interpolator interpolator) {
         if (FeatureFlags.IS_STUDIO_BUILD && mPendingAnimation != null) {
             throw new IllegalStateException("Another pending animation is still running");
         }
@@ -1615,7 +1622,6 @@
         int targetSysUiFlags = tv.getThumbnail().getSysUiStatusNavFlags();
         final boolean[] passedOverviewThreshold = new boolean[] {false};
         ValueAnimator progressAnim = ValueAnimator.ofFloat(0, 1);
-        progressAnim.setInterpolator(LINEAR);
         progressAnim.addUpdateListener(animator -> {
             // Once we pass a certain threshold, update the sysui flags to match the target
             // tasks' flags
@@ -1641,7 +1647,7 @@
         appWindowAnimationHelper.prepareAnimation(mActivity.getDeviceProfile(), true /* isOpening */);
         AnimatorSet anim = createAdjacentPageAnimForTaskLaunch(tv, appWindowAnimationHelper);
         anim.play(progressAnim);
-        anim.setDuration(duration);
+        anim.setDuration(duration).setInterpolator(interpolator);
 
         Consumer<Boolean> onTaskLaunchFinish = this::onTaskLaunched;
 
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 8b7ce10..dce92ff 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
@@ -21,6 +21,7 @@
 import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
 
@@ -280,11 +281,10 @@
     }
 
     public AnimatorPlaybackController createLaunchAnimationForRunningTask() {
-        final PendingAnimation pendingAnimation =
-                getRecentsView().createTaskLauncherAnimation(this, RECENTS_LAUNCH_DURATION);
-        pendingAnimation.anim.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR);
-        AnimatorPlaybackController currentAnimation = AnimatorPlaybackController
-                .wrap(pendingAnimation.anim, RECENTS_LAUNCH_DURATION, null);
+        final PendingAnimation pendingAnimation = getRecentsView().createTaskLaunchAnimation(
+                this, RECENTS_LAUNCH_DURATION, TOUCH_RESPONSE_INTERPOLATOR);
+        AnimatorPlaybackController currentAnimation = AnimatorPlaybackController.wrap(
+                pendingAnimation.anim, RECENTS_LAUNCH_DURATION);
         currentAnimation.setEndAction(() -> {
             pendingAnimation.finish(true, Touch.SWIPE);
             launchTask(false);
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index 327bb14..a688f9a 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -13,11 +13,13 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<resources>
-    <string name="task_overlay_factory_class" translatable="false"></string>
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <string name="task_overlay_factory_class" translatable="false"/>
 
-    <!-- Activity which blocks home gesture -->
-    <string name="gesture_blocking_activity" translatable="false"></string>
+    <!-- Activities which block home gesture -->
+    <string-array name="gesture_blocking_activities" tools:ignore="InconsistentArrays">
+        <item>com.android.launcher3/com.android.quickstep.interaction.GestureSandboxActivity</item>
+    </string-array>
 
     <string name="stats_log_manager_class" translatable="false">com.android.quickstep.logging.StatsLogCompatManager</string>
 
@@ -32,5 +34,5 @@
     <integer name="assistant_gesture_min_time_threshold">200</integer>
     <integer name="assistant_gesture_corner_deg_threshold">20</integer>
 
-    <string name="wellbeing_provider_pkg" translatable="false"></string>
+    <string name="wellbeing_provider_pkg" translatable="false"/>
 </resources>
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 4470407..84774fc 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -201,7 +201,7 @@
 
         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();
+            getOverviewPanelContainer().bringToFront();
         }
     }
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
index e88a8a4..b5e05ee 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -54,6 +54,12 @@
     }
 
     @Override
+    public void onStateDisabled(Launcher launcher) {
+        super.onStateDisabled(launcher);
+        AbstractFloatingView.closeAllOpenViews(launcher);
+    }
+
+    @Override
     public String getDescription(Launcher launcher) {
         AllAppsContainerView appsView = launcher.getAppsView();
         return appsView.getDescription();
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index d5ce734..fe830d2 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -228,15 +228,13 @@
             // Reset the state manager, when changing the interaction mode
             mLauncher.getStateManager().goToState(OVERVIEW, false /* animate */);
             mPendingAnimation = mOverviewPortraitStateTouchHelper
-                    .createSwipeDownToTaskAppAnimation(maxAccuracy);
-            mPendingAnimation.anim.setInterpolator(Interpolators.LINEAR);
-
+                    .createSwipeDownToTaskAppAnimation(maxAccuracy, Interpolators.LINEAR);
             Runnable onCancelRunnable = () -> {
                 cancelPendingAnim();
                 clearState();
             };
-            mCurrentAnimation = AnimatorPlaybackController.wrap(mPendingAnimation.anim, maxAccuracy,
-                    onCancelRunnable);
+            mCurrentAnimation = AnimatorPlaybackController.wrap(
+                    mPendingAnimation.anim, maxAccuracy, onCancelRunnable);
             mLauncher.getStateManager().setCurrentUserControlledAnimation(mCurrentAnimation);
             totalShift = LayoutUtils.getShelfTrackingDistance(mLauncher,
                     mLauncher.getDeviceProfile());
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 4b33d21..abe1592 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -66,6 +66,8 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * Manages the state of the system during a swipe up gesture.
@@ -107,7 +109,7 @@
     private Region mExclusionRegion;
     private SystemGestureExclusionListenerCompat mExclusionListener;
 
-    private ComponentName mGestureBlockedActivity;
+    private final List<ComponentName> mGestureBlockedActivities;
 
     public RecentsAnimationDeviceState(Context context) {
         final ContentResolver resolver = context.getContentResolver();
@@ -142,9 +144,19 @@
         runOnDestroy(() -> mSysUiNavMode.removeModeChangeListener(this));
 
         // Add any blocked activities
-        String blockingActivity = context.getString(R.string.gesture_blocking_activity);
-        if (!TextUtils.isEmpty(blockingActivity)) {
-            mGestureBlockedActivity = ComponentName.unflattenFromString(blockingActivity);
+        String[] blockingActivities;
+        try {
+            blockingActivities =
+                    context.getResources().getStringArray(R.array.gesture_blocking_activities);
+        } catch (Resources.NotFoundException e) {
+            blockingActivities = new String[0];
+        }
+        mGestureBlockedActivities = new ArrayList<>(blockingActivities.length);
+        for (String blockingActivity : blockingActivities) {
+            if (!TextUtils.isEmpty(blockingActivity)) {
+                mGestureBlockedActivities.add(
+                        ComponentName.unflattenFromString(blockingActivity));
+            }
         }
     }
 
@@ -272,17 +284,16 @@
      * @return whether the given running task info matches the gesture-blocked activity.
      */
     public boolean isGestureBlockedActivity(ActivityManager.RunningTaskInfo runningTaskInfo) {
-        return runningTaskInfo != null && mGestureBlockedActivity != null
-                && mGestureBlockedActivity.equals(runningTaskInfo.topActivity);
+        return runningTaskInfo != null
+                && mGestureBlockedActivities.contains(runningTaskInfo.topActivity);
     }
 
     /**
-     * @return the package of the gesture-blocked activity or {@code null} if there is none.
+     * @return the packages of gesture-blocked activities.
      */
-    public String getGestureBlockedActivityPackage() {
-        return (mGestureBlockedActivity != null)
-                ? mGestureBlockedActivity.getPackageName()
-                : null;
+    public List<String> getGestureBlockedActivityPackages() {
+        return mGestureBlockedActivities.stream().map(ComponentName::getPackageName)
+                .collect(Collectors.toList());
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialActivity.java b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
similarity index 95%
rename from quickstep/src/com/android/quickstep/interaction/BackGestureTutorialActivity.java
rename to quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
index d2a0951..8081ad7 100644
--- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
@@ -32,8 +32,8 @@
 import java.util.List;
 import java.util.Optional;
 
-/** Shows the Back gesture interactive tutorial in full screen mode. */
-public class BackGestureTutorialActivity extends FragmentActivity {
+/** Shows the gesture interactive sandbox in full screen mode. */
+public class GestureSandboxActivity extends FragmentActivity {
 
     Optional<BackGestureTutorialFragment> mFragment = Optional.empty();
 
diff --git a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
index 801a560..7d52571 100644
--- a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
+++ b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
@@ -15,6 +15,8 @@
  */
 package com.android.quickstep.util;
 
+import static com.android.launcher3.config.FeatureFlags.ENABLE_LSQ_VELOCITY_PROVIDER;
+
 import android.content.Context;
 import android.content.res.Resources;
 import android.view.MotionEvent;
@@ -37,8 +39,7 @@
     private static final long FORCE_PAUSE_TIMEOUT = 300;
 
     /**
-     * After {@link #makePauseHarderToTrigger()}, must
-     * move slowly for this long to trigger a pause.
+     * After {@link #mMakePauseHarderToTrigger}, must move slowly for this long to trigger a pause.
      */
     private static final long HARDER_TRIGGER_TIMEOUT = 400;
 
@@ -49,13 +50,10 @@
     private final Alarm mForcePauseTimeout;
     private final boolean mMakePauseHarderToTrigger;
     private final Context mContext;
+    private final VelocityProvider mVelocityProvider;
 
-    private Long mPreviousTime = null;
-    private Float mPreviousPosition = null;
     private Float mPreviousVelocity = null;
 
-    private Float mFirstPosition = null;
-
     private OnMotionPauseListener mOnMotionPauseListener;
     private boolean mIsPaused;
     // Bias more for the first pause to make it feel extra responsive.
@@ -73,6 +71,13 @@
      * @param makePauseHarderToTrigger Used for gestures that require a more explicit pause.
      */
     public MotionPauseDetector(Context context, boolean makePauseHarderToTrigger) {
+        this(context, makePauseHarderToTrigger, MotionEvent.AXIS_Y);
+    }
+
+    /**
+     * @param makePauseHarderToTrigger Used for gestures that require a more explicit pause.
+     */
+    public MotionPauseDetector(Context context, boolean makePauseHarderToTrigger, int axis) {
         mContext = context;
         Resources res = context.getResources();
         mSpeedVerySlow = res.getDimension(R.dimen.motion_pause_detector_speed_very_slow);
@@ -82,6 +87,8 @@
         mForcePauseTimeout = new Alarm();
         mForcePauseTimeout.setOnAlarmListener(alarm -> updatePaused(true /* isPaused */));
         mMakePauseHarderToTrigger = makePauseHarderToTrigger;
+        mVelocityProvider = ENABLE_LSQ_VELOCITY_PROVIDER.get()
+                ? new LSqVelocityProvider(axis) : new LinearVelocityProvider(axis);
     }
 
     /**
@@ -101,28 +108,26 @@
 
     /**
      * Computes velocity and acceleration to determine whether the motion is paused.
-     * @param position The x or y component of the motion being tracked.
-     *
-     * TODO: Use historical positions as well, e.g. {@link MotionEvent#getHistoricalY(int, int)}.
+     * @param ev The motion being tracked.
      */
-    public void addPosition(float position, long time) {
-        if (mFirstPosition == null) {
-            mFirstPosition = position;
-        }
+    public void addPosition(MotionEvent ev) {
+        addPosition(ev, 0);
+    }
+
+    /**
+     * Computes velocity and acceleration to determine whether the motion is paused.
+     * @param ev The motion being tracked.
+     * @param pointerIndex Index for the pointer being tracked in the motion event
+     */
+    public void addPosition(MotionEvent ev, int pointerIndex) {
         mForcePauseTimeout.setAlarm(mMakePauseHarderToTrigger
                 ? HARDER_TRIGGER_TIMEOUT
                 : FORCE_PAUSE_TIMEOUT);
-        if (mPreviousTime != null && mPreviousPosition != null) {
-            long changeInTime = Math.max(1, time - mPreviousTime);
-            float changeInPosition = position - mPreviousPosition;
-            float velocity = changeInPosition / changeInTime;
-            if (mPreviousVelocity != null) {
-                checkMotionPaused(velocity, mPreviousVelocity, time);
-            }
-            mPreviousVelocity = velocity;
+        Float newVelocity = mVelocityProvider.addMotionEvent(ev, pointerIndex);
+        if (newVelocity != null && mPreviousVelocity != null) {
+            checkMotionPaused(newVelocity, mPreviousVelocity, ev.getEventTime());
         }
-        mPreviousTime = time;
-        mPreviousPosition = position;
+        mPreviousVelocity = newVelocity;
     }
 
     private void checkMotionPaused(float velocity, float prevVelocity, long time) {
@@ -178,10 +183,8 @@
     }
 
     public void clear() {
-        mPreviousTime = null;
-        mPreviousPosition = null;
+        mVelocityProvider.clear();
         mPreviousVelocity = null;
-        mFirstPosition = null;
         setOnMotionPauseListener(null);
         mIsPaused = mHasEverBeenPaused = false;
         mSlowStartTime = 0;
@@ -195,4 +198,188 @@
     public interface OnMotionPauseListener {
         void onMotionPauseChanged(boolean isPaused);
     }
+
+    /**
+     * Interface to abstract out velocity calculations
+     */
+    protected interface VelocityProvider {
+
+        /**
+         * Adds a new motion events, and returns the velocity at this point, or null if
+         * the velocity is not available
+         */
+        Float addMotionEvent(MotionEvent ev, int pointer);
+
+        /**
+         * Clears all stored motion event records
+         */
+        void clear();
+    }
+
+    private static class LinearVelocityProvider implements VelocityProvider {
+
+        private Long mPreviousTime = null;
+        private Float mPreviousPosition = null;
+
+        private final int mAxis;
+
+        LinearVelocityProvider(int axis) {
+            mAxis = axis;
+        }
+
+        @Override
+        public Float addMotionEvent(MotionEvent ev, int pointer) {
+            long time = ev.getEventTime();
+            float position = ev.getAxisValue(mAxis, pointer);
+            Float velocity = null;
+
+            if (mPreviousTime != null && mPreviousPosition != null) {
+                long changeInTime = Math.max(1, time - mPreviousTime);
+                float changeInPosition = position - mPreviousPosition;
+                velocity = changeInPosition / changeInTime;
+            }
+            mPreviousTime = time;
+            mPreviousPosition = position;
+            return velocity;
+        }
+
+        @Override
+        public void clear() {
+            mPreviousTime = null;
+            mPreviousPosition = null;
+        }
+    }
+
+    /**
+     * Java implementation of {@link android.view.VelocityTracker} using the Least Square (deg 2)
+     * algorithm.
+     */
+    private static class LSqVelocityProvider implements VelocityProvider {
+
+        // Maximum age of a motion event to be considered when calculating the velocity.
+        private static final long HORIZON_MS = 100;
+        // Number of samples to keep.
+        private static final int HISTORY_SIZE = 20;
+
+        // Position history are stored in a circular array
+        private final float[] mHistoricTimes = new float[HISTORY_SIZE];
+        private final float[] mHistoricPos = new float[HISTORY_SIZE];
+        private int mHistoryCount = 0;
+        private int mHistoryStart = 0;
+
+        private final int mAxis;
+
+        LSqVelocityProvider(int axis) {
+            mAxis = axis;
+        }
+
+        @Override
+        public void clear() {
+            mHistoryCount = mHistoryStart = 0;
+        }
+
+        private void addPositionAndTime(float eventTime, float eventPosition) {
+            mHistoricTimes[mHistoryStart] = eventTime;
+            mHistoricPos[mHistoryStart] = eventPosition;
+            mHistoryStart++;
+            if (mHistoryStart >= HISTORY_SIZE) {
+                mHistoryStart = 0;
+            }
+            mHistoryCount = Math.min(HISTORY_SIZE, mHistoryCount + 1);
+        }
+
+        @Override
+        public Float addMotionEvent(MotionEvent ev, int pointer) {
+            // Add all historic points
+            int historyCount = ev.getHistorySize();
+            for (int i = 0; i < historyCount; i++) {
+                addPositionAndTime(
+                        ev.getHistoricalEventTime(i), ev.getHistoricalAxisValue(mAxis, pointer, i));
+            }
+
+            // Start index for the last position (about to be added)
+            int eventStartIndex = mHistoryStart;
+            addPositionAndTime(ev.getEventTime(), ev.getAxisValue(mAxis, pointer));
+            return solveUnweightedLeastSquaresDeg2(eventStartIndex);
+        }
+
+        /**
+         * Solves the instantaneous velocity.
+         * Based on solveUnweightedLeastSquaresDeg2 in VelocityTracker.cpp
+         */
+        private Float solveUnweightedLeastSquaresDeg2(final int pointPos) {
+            final float eventTime = mHistoricTimes[pointPos];
+
+            float sxi = 0, sxiyi = 0, syi = 0, sxi2 = 0, sxi3 = 0, sxi2yi = 0, sxi4 = 0;
+            int count = 0;
+            for (int i = 0; i < mHistoryCount; i++) {
+                int index = pointPos - i;
+                if (index < 0) {
+                    index += HISTORY_SIZE;
+                }
+
+                float time = mHistoricTimes[index];
+                float age = eventTime - time;
+                if (age > HORIZON_MS) {
+                    break;
+                }
+                count++;
+                float xi = -age;
+
+                float yi = mHistoricPos[index];
+                float xi2 = xi * xi;
+                float xi3 = xi2 * xi;
+                float xi4 = xi3 * xi;
+                float xiyi = xi * yi;
+                float xi2yi = xi2 * yi;
+
+                sxi += xi;
+                sxi2 += xi2;
+                sxiyi += xiyi;
+                sxi2yi += xi2yi;
+                syi += yi;
+                sxi3 += xi3;
+                sxi4 += xi4;
+            }
+
+            if (count < 3) {
+                // Too few samples
+                if (count == 2) {
+                    int endPos = pointPos - 1;
+                    if (endPos < 0) {
+                        endPos += HISTORY_SIZE;
+                    }
+                    float denominator = eventTime - mHistoricTimes[endPos];
+                    if (denominator != 0) {
+                        return (eventTime - mHistoricPos[endPos]) / denominator;
+
+                    }
+                }
+                return null;
+            }
+
+            float Sxx = sxi2 - sxi * sxi / count;
+            float Sxy = sxiyi - sxi * syi / count;
+            float Sxx2 = sxi3 - sxi * sxi2 / count;
+            float Sx2y = sxi2yi - sxi2 * syi / count;
+            float Sx2x2 = sxi4 - sxi2 * sxi2 / count;
+
+            float denominator = Sxx * Sx2x2 - Sxx2 * Sxx2;
+            if (denominator == 0) {
+                // division by 0 when computing velocity
+                return null;
+            }
+            // Compute a
+            // float numerator = Sx2y*Sxx - Sxy*Sxx2;
+
+            // Compute b
+            float numerator = Sxy * Sx2x2 - Sx2y * Sxx2;
+            float b = numerator / denominator;
+
+            // Compute c
+            // float c = syi/count - b * sxi/count - a * sxi2/count;
+
+            return b;
+        }
+    }
 }
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
index 1229a63..cd94704 100644
--- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
+++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
@@ -35,6 +35,7 @@
 
 import com.android.launcher3.tapl.LauncherInstrumentation;
 import com.android.launcher3.tapl.TestHelpers;
+import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.util.Wait;
 import com.android.launcher3.util.rule.FailureWatcher;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -199,7 +200,7 @@
                         + launcher.getNavigationModeMismatchError(),
                 () -> launcher.getNavigationModeMismatchError() == null,
                 60000 /* b/148422894 */, launcher);
-
+        AbstractLauncherUiTest.checkDetectedLeaks();
         return true;
     }
 
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 724af66..03d7c3e 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -45,7 +45,6 @@
 import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
 import com.android.quickstep.views.RecentsView;
 
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -53,21 +52,10 @@
 @LargeTest
 @RunWith(AndroidJUnit4.class)
 public class TaplTestsQuickstep extends AbstractQuickStepTest {
-    private int mLauncherPid;
-
     @Before
     public void setUp() throws Exception {
-        mLauncherPid = 0;
         super.setUp();
         TaplTestsLauncher3.initialize(this);
-        mLauncherPid = mLauncher.getPid();
-    }
-
-    @After
-    public void teardown() {
-        if (mLauncherPid != 0) {
-            assertEquals("Launcher crashed, pid mismatch:", mLauncherPid, mLauncher.getPid());
-        }
     }
 
     private void startTestApps() throws Exception {
@@ -93,6 +81,10 @@
 
     @Test
     public void testAllAppsFromOverview() throws Exception {
+        if (!mLauncher.hasAllAppsInOverview()) {
+            return;
+        }
+
         // Test opening all apps from Overview.
         assertNotNull("switchToAllApps() returned null",
                 mLauncher.getWorkspace().switchToOverview().switchToAllApps());
@@ -153,8 +145,8 @@
                 launcher -> assertEquals("Dismissing a task didn't remove 1 task from Overview",
                         numTasks - 1, getTaskCount(launcher)));
 
-        if (!TestHelpers.isInLauncherProcess() ||
-                getFromLauncher(launcher -> !launcher.getDeviceProfile().isLandscape)) {
+        if (mLauncher.hasAllAppsInOverview() && (!TestHelpers.isInLauncherProcess()
+                || getFromLauncher(launcher -> !launcher.getDeviceProfile().isLandscape))) {
             // Test switching to all apps and back.
             final AllAppsFromOverview allApps = overview.switchToAllApps();
             assertNotNull("overview.switchToAllApps() returned null (1)", allApps);
@@ -197,6 +189,10 @@
 
     @Test
     public void testAppIconLaunchFromAllAppsFromOverview() throws Exception {
+        if (!mLauncher.hasAllAppsInOverview()) {
+            return;
+        }
+
         final AllApps allApps =
                 mLauncher.getWorkspace().switchToOverview().switchToAllApps();
         assertTrue("Launcher internal state is not All Apps",
diff --git a/res/layout/launcher.xml b/res/layout/launcher.xml
index cca899b..6c66897 100644
--- a/res/layout/launcher.xml
+++ b/res/layout/launcher.xml
@@ -44,9 +44,8 @@
             layout="@layout/hotseat" />
 
         <include
-            android:id="@+id/overview_panel"
-            layout="@layout/overview_panel"
-            android:visibility="gone" />
+            android:id="@+id/overview_panel_container"
+            layout="@layout/overview_panel"/>
 
         <!-- Keep these behind the workspace so that they are not visible when
          we go into AllApps -->
diff --git a/res/layout/overview_panel.xml b/res/layout/overview_panel.xml
index bdd5d23..7fff711 100644
--- a/res/layout/overview_panel.xml
+++ b/res/layout/overview_panel.xml
@@ -14,7 +14,9 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<Space
+<FrameLayout
       xmlns:android="http://schemas.android.com/apk/res/android"
+      android:id="@+id/overview_panel_recents"
       android:layout_width="0dp"
-      android:layout_height="0dp" />
\ No newline at end of file
+      android:layout_height="0dp"
+      android:visibility="gone" />
\ No newline at end of file
diff --git a/res/layout/work_apps_paused.xml b/res/layout/work_apps_paused.xml
index 33502d0..1c18076 100644
--- a/res/layout/work_apps_paused.xml
+++ b/res/layout/work_apps_paused.xml
@@ -15,9 +15,9 @@
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
+    android:background="?attr/allAppsScrimColor"
     android:padding="8dp"
     android:orientation="vertical"
-    android:background="?attr/allAppsScrimColor"
     android:gravity="center">
 
     <ImageView
@@ -38,7 +38,7 @@
         android:layout_marginBottom="8dp"
         android:text="@string/work_apps_paused_title"
         android:textAlignment="center"
-        android:textSize="24sp" />
+        android:textSize="20sp" />
 
     <TextView
         android:layout_width="wrap_content"
diff --git a/res/layout/work_profile_edu.xml b/res/layout/work_profile_edu.xml
index f7a529d..04094c4 100644
--- a/res/layout/work_profile_edu.xml
+++ b/res/layout/work_profile_edu.xml
@@ -21,22 +21,22 @@
 
     <View
         android:layout_width="match_parent"
-        android:backgroundTint="@color/bottom_panel_background"
         android:layout_height="32dp"
-        android:background="@drawable/bottom_sheet_top_border" />
+        android:background="@drawable/bottom_sheet_top_border"
+        android:backgroundTint="?android:attr/colorAccent" />
 
     <LinearLayout
         android:id="@+id/view_wrapper"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:background="@color/bottom_panel_background"
+        android:background="?android:attr/colorAccent"
         android:orientation="vertical"
         android:paddingLeft="@dimen/bottom_sheet_edu_padding"
         android:paddingRight="@dimen/bottom_sheet_edu_padding">
 
         <TextView
-            style="@style/TextHeadline"
             android:id="@+id/content_text"
+            style="@style/TextHeadline"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_marginTop="48dp"
@@ -57,5 +57,4 @@
             android:textAlignment="center"
             android:textColor="@android:color/white" />
     </LinearLayout>
-
 </com.android.launcher3.views.WorkEduView>
\ No newline at end of file
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 36f8468..194ef2c 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -45,5 +45,4 @@
     <color name="back_gesture_tutorial_primary_color">#1A73E8</color> <!-- Blue -->
 
 
-    <color name="bottom_panel_background">#f01A73E8</color>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 0775c0c..9d0fb56 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -330,7 +330,7 @@
     <!--- User onboarding title for personal apps -->
     <string name="work_profile_edu_personal_apps">Personal apps are private &amp; can\'t be seen by IT</string>
     <!--- User onboarding title for work profile apps -->
-    <string name="work_profile_edu_work_apps">Work apps are badged and monitored by IT</string>
+    <string name="work_profile_edu_work_apps">Work apps are badged &amp; visible to IT</string>
     <!-- Action label to proceed to the next work profile edu section-->
     <string name="work_profile_edu_next">Next</string>
     <!-- Action label to finish work profile edu-->
diff --git a/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java b/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java
index 1ed4bca..c426dc5 100644
--- a/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java
@@ -65,7 +65,7 @@
         };
 
         mIdp.numHotseatIcons = 3;
-        new GridSizeMigrationTask(mContext, mDb, mValidPackages, 5, 3)
+        new GridSizeMigrationTask(mContext, mDb, mValidPackages, false, 5, 3)
                 .migrateHotseat();
         // First item is dropped as it has the least weight.
         verifyHotseat(hotseatItems[1], hotseatItems[3], hotseatItems[4]);
@@ -82,7 +82,7 @@
         };
 
         mIdp.numHotseatIcons = 3;
-        new GridSizeMigrationTask(mContext, mDb, mValidPackages, 5, 3)
+        new GridSizeMigrationTask(mContext, mDb, mValidPackages, false, 5, 3)
                 .migrateHotseat();
         // First item is dropped as it has the least weight.
         verifyHotseat(hotseatItems[1], hotseatItems[3], hotseatItems[4]);
@@ -127,7 +127,7 @@
                 {  5,  2, -1,  6},
         }});
 
-        new GridSizeMigrationTask(mContext, mDb, mValidPackages,
+        new GridSizeMigrationTask(mContext, mDb, mValidPackages, false,
                 new Point(4, 4), new Point(3, 3)).migrateWorkspace();
 
         // Column 2 and row 2 got removed.
@@ -147,7 +147,7 @@
                 {  5,  2, -1,  6},
         }});
 
-        new GridSizeMigrationTask(mContext, mDb, mValidPackages,
+        new GridSizeMigrationTask(mContext, mDb, mValidPackages, false,
                 new Point(4, 4), new Point(3, 3)).migrateWorkspace();
 
         // Items in the second column get moved to new screen
@@ -172,7 +172,7 @@
                 {  3,  1, -1,  4},
         }});
 
-        new GridSizeMigrationTask(mContext, mDb, mValidPackages,
+        new GridSizeMigrationTask(mContext, mDb, mValidPackages, false,
                 new Point(4, 4), new Point(3, 3)).migrateWorkspace();
 
         // Items in the second column of the first screen should get placed on the 3rd
@@ -204,7 +204,7 @@
                 {  5,  2, -1,  6},
         }});
 
-        new GridSizeMigrationTask(mContext, mDb, mValidPackages,
+        new GridSizeMigrationTask(mContext, mDb, mValidPackages, false,
                 new Point(4, 4), new Point(3, 3)).migrateWorkspace();
 
         // Items in the second column of the first screen should get placed on a new screen.
@@ -235,7 +235,7 @@
                 {  5,  2,  7, -1},
         }}, 0);
 
-        new GridSizeMigrationTask(mContext, mDb, mValidPackages,
+        new GridSizeMigrationTask(mContext, mDb, mValidPackages, false,
                 new Point(4, 4), new Point(3, 4)).migrateWorkspace();
 
         // Items in the second column of the first screen should get placed on a new screen.
@@ -262,7 +262,7 @@
                 {  5,  6,  7, -1},
         }}, 0);
 
-        new GridSizeMigrationTask(mContext, mDb, mValidPackages,
+        new GridSizeMigrationTask(mContext, mDb, mValidPackages, false,
                 new Point(4, 4), new Point(3, 3)).migrateWorkspace();
 
         // Items in the second column of the first screen should get placed on a new screen.
@@ -282,7 +282,7 @@
      *            represent the workspace grid.
      */
     private void verifyWorkspace(int[][][] ids) {
-        IntArray allScreens = getWorkspaceScreenIds(mDb);
+        IntArray allScreens = getWorkspaceScreenIds(mDb, LauncherSettings.Favorites.TABLE_NAME);
         assertEquals(ids.length, allScreens.size());
         int total = 0;
 
@@ -351,7 +351,7 @@
         private final LinkedList<Point> mPoints;
 
         public MultiStepMigrationTaskVerifier(int... points) {
-            super(null, null, null);
+            super(null, null, null, false);
 
             mPoints = new LinkedList<>();
             for (int i = 0; i < points.length; i += 2) {
diff --git a/robolectric_tests/src/com/android/launcher3/model/LoaderCursorTest.java b/robolectric_tests/src/com/android/launcher3/model/LoaderCursorTest.java
index 7fa3ee9..6e41a4f 100644
--- a/robolectric_tests/src/com/android/launcher3/model/LoaderCursorTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/LoaderCursorTest.java
@@ -52,6 +52,7 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.LauncherRoboTestRunner;
@@ -91,7 +92,7 @@
                 SCREEN, CELLX, CELLY, RESTORED, INTENT
         });
 
-        mLoaderCursor = new LoaderCursor(mCursor, mApp);
+        mLoaderCursor = new LoaderCursor(mCursor, LauncherSettings.Favorites.CONTENT_URI, mApp);
         mLoaderCursor.allUsers.put(0, Process.myUserHandle());
     }
 
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 857db8e..2ad84b9 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -47,6 +47,7 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.graphics.IconShape;
+import com.android.launcher3.graphics.LauncherPreviewRenderer;
 import com.android.launcher3.util.ConfigMonitor;
 import com.android.launcher3.util.DefaultDisplay;
 import com.android.launcher3.util.DefaultDisplay.Info;
@@ -156,6 +157,11 @@
 
     @TargetApi(23)
     private InvariantDeviceProfile(Context context) {
+        if (context instanceof LauncherPreviewRenderer.PreviewContext) {
+            throw new IllegalArgumentException(
+                    "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;
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 7e8377a..06d0914 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -80,6 +80,7 @@
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.OvershootInterpolator;
+import android.widget.FrameLayout;
 import android.widget.Toast;
 
 import androidx.annotation.Nullable;
@@ -273,6 +274,7 @@
 
     // UI and state for the overview panel
     private View mOverviewPanel;
+    private FrameLayout mOverviewPanelContainer;
 
     @Thunk
     boolean mWorkspaceLoading = true;
@@ -482,7 +484,6 @@
     @Override
     public void onEnterAnimationComplete() {
         super.onEnterAnimationComplete();
-        mAllAppsController.highlightWorkTabIfNecessary();
         mRotationHelper.setCurrentTransitionRequest(REQUEST_NONE);
     }
 
@@ -1161,7 +1162,8 @@
         mFocusHandler = mDragLayer.getFocusIndicatorHelper();
         mWorkspace = mDragLayer.findViewById(R.id.workspace);
         mWorkspace.initParentViews(mDragLayer);
-        mOverviewPanel = findViewById(R.id.overview_panel);
+        mOverviewPanel = findViewById(R.id.overview_panel_recents);
+        mOverviewPanelContainer = findViewById(R.id.overview_panel_container);
         mHotseat = findViewById(R.id.hotseat);
 
         mLauncherView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
@@ -1409,6 +1411,10 @@
         return (T) mOverviewPanel;
     }
 
+    public FrameLayout getOverviewPanelContainer() {
+        return mOverviewPanelContainer;
+    }
+
     public DropTargetBar getDropTargetBar() {
         return mDropTargetBar;
     }
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index d77285d..2f38037 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -27,6 +27,8 @@
 import android.os.Handler;
 import android.util.Log;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.icons.IconProvider;
@@ -55,12 +57,12 @@
     private final IconCache mIconCache;
     private final WidgetPreviewLoader mWidgetCache;
     private final InvariantDeviceProfile mInvariantDeviceProfile;
-    private final SecureSettingsObserver mNotificationDotsObserver;
 
-    private final InstallSessionTracker mInstallSessionTracker;
-    private final SimpleBroadcastReceiver mModelChangeReceiver;
-    private final SafeCloseable mCalendarChangeTracker;
-    private final SafeCloseable mUserChangeListener;
+    private SecureSettingsObserver mNotificationDotsObserver;
+    private InstallSessionTracker mInstallSessionTracker;
+    private SimpleBroadcastReceiver mModelChangeReceiver;
+    private SafeCloseable mCalendarChangeTracker;
+    private SafeCloseable mUserChangeListener;
 
     public static LauncherAppState getInstance(final Context context) {
         return INSTANCE.get(context);
@@ -74,15 +76,8 @@
         return mContext;
     }
 
-    private LauncherAppState(Context context) {
-        Log.v(Launcher.TAG, "LauncherAppState initiated");
-        Preconditions.assertUIThread();
-        mContext = context;
-
-        mInvariantDeviceProfile = InvariantDeviceProfile.INSTANCE.get(mContext);
-        mIconCache = new IconCache(mContext, mInvariantDeviceProfile);
-        mWidgetCache = new WidgetPreviewLoader(mContext, mIconCache);
-        mModel = new LauncherModel(this, mIconCache, AppFilter.newInstance(mContext));
+    public LauncherAppState(Context context) {
+        this(context, LauncherFiles.APP_ICONS_DB);
 
         mModelChangeReceiver = new SimpleBroadcastReceiver(mModel::onBroadcastIntent);
 
@@ -123,6 +118,17 @@
         }
     }
 
+    public LauncherAppState(Context context, @Nullable String iconCacheFileName) {
+        Log.v(Launcher.TAG, "LauncherAppState initiated");
+        Preconditions.assertUIThread();
+        mContext = context;
+
+        mInvariantDeviceProfile = InvariantDeviceProfile.INSTANCE.get(context);
+        mIconCache = new IconCache(mContext, mInvariantDeviceProfile, iconCacheFileName);
+        mWidgetCache = new WidgetPreviewLoader(mContext, mIconCache);
+        mModel = new LauncherModel(this, mIconCache, AppFilter.newInstance(mContext));
+    }
+
     protected void onNotificationSettingsChanged(boolean areNotificationDotsEnabled) {
         if (areNotificationDotsEnabled) {
             NotificationListener.requestRebind(new ComponentName(
@@ -148,11 +154,19 @@
      * Call from Application.onTerminate(), which is not guaranteed to ever be called.
      */
     public void onTerminate() {
-        mContext.unregisterReceiver(mModelChangeReceiver);
+        if (mModelChangeReceiver != null) {
+            mContext.unregisterReceiver(mModelChangeReceiver);
+        }
         mContext.getSystemService(LauncherApps.class).unregisterCallback(mModel);
-        mInstallSessionTracker.unregister();
-        mCalendarChangeTracker.close();
-        mUserChangeListener.close();
+        if (mInstallSessionTracker != null) {
+            mInstallSessionTracker.unregister();
+        }
+        if (mCalendarChangeTracker != null) {
+            mCalendarChangeTracker.close();
+        }
+        if (mUserChangeListener != null) {
+            mUserChangeListener.close();
+        }
         CustomWidgetManager.INSTANCE.get(mContext).setWidgetRefreshCallback(null);
 
         if (mNotificationDotsObserver != null) {
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index f618fe1..e61b7a8 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -153,7 +153,7 @@
 
     public void onPackagesRemoved(UserHandle user, String... packages) {
         int op = PackageUpdatedTask.OP_REMOVE;
-        FileLog.d(TAG, "package removed received " + String.join("," + packages));
+        FileLog.d(TAG, "package removed received " + TextUtils.join(",", packages));
         enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packages));
     }
 
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index 49831f6..216c221 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -93,15 +93,26 @@
         public static final String TABLE_NAME = "favorites";
 
         /**
-         * Backup table created when when the favorites table is modified during grid migration
+         * Backup table created when the favorites table is modified during grid migration
          */
         public static final String BACKUP_TABLE_NAME = "favorites_bakup";
 
         /**
-         * The content:// style URL for this table
+         * Temporary table used specifically for grid migrations during wallpaper preview
          */
-        public static final Uri CONTENT_URI = Uri.parse("content://" +
-                LauncherProvider.AUTHORITY + "/" + TABLE_NAME);
+        public static final String PREVIEW_TABLE_NAME = "favorites_preview";
+
+        /**
+         * The content:// style URL for "favorites" table
+         */
+        public static final Uri CONTENT_URI = Uri.parse("content://"
+                + LauncherProvider.AUTHORITY + "/" + TABLE_NAME);
+
+        /**
+         * The content:// style URL for "favorites_preview" table
+         */
+        public static final Uri PREVIEW_CONTENT_URI = Uri.parse("content://"
+                + LauncherProvider.AUTHORITY + "/" + PREVIEW_TABLE_NAME);
 
         /**
          * The content:// style URL for a given row, identified by its id.
@@ -111,8 +122,8 @@
          * @return The unique content URL for the specified row.
          */
         public static Uri getContentUri(int id) {
-            return Uri.parse("content://" + LauncherProvider.AUTHORITY +
-                    "/" + TABLE_NAME + "/" + id);
+            return Uri.parse("content://" + LauncherProvider.AUTHORITY
+                    + "/" + TABLE_NAME + "/" + id);
         }
 
         /**
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 16be391..36440c9 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -74,6 +74,10 @@
     public static final int VERTICAL_SWIPE_INDICATOR = 1 << 5;
     public static final int RECENTS_CLEAR_ALL_BUTTON = 1 << 6;
 
+    /** Mask of all the items that are contained in the apps view. */
+    public static final int APPS_VIEW_ITEM_MASK =
+            HOTSEAT_SEARCH_BOX | ALL_APPS_HEADER | ALL_APPS_HEADER_EXTRA | ALL_APPS_CONTENT;
+
     protected static final int FLAG_MULTI_PAGE = 1 << 0;
     protected static final int FLAG_DISABLE_ACCESSIBILITY = 1 << 1;
     protected static final int FLAG_DISABLE_RESTORE = 1 << 2;
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 4c6e1f3..01893e9 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -374,6 +374,8 @@
     protected void onPageEndTransition() {
         mWasInOverscroll = false;
         AccessibilityManagerCompat.sendScrollFinishedEventToTest(getContext());
+        AccessibilityManagerCompat.sendCustomAccessibilityEvent(getPageAt(mCurrentPage),
+                AccessibilityEvent.TYPE_VIEW_FOCUSED, null);
     }
 
     protected int getUnboundedScrollX() {
diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java
index 1dbe195..114c491 100644
--- a/src/com/android/launcher3/SecondaryDropTarget.java
+++ b/src/com/android/launcher3/SecondaryDropTarget.java
@@ -56,6 +56,7 @@
     private final ArrayMap<UserHandle, Boolean> mUninstallDisabledCache = new ArrayMap<>(1);
 
     private final Alarm mCacheExpireAlarm;
+    private boolean mHadPendingAlarm;
 
     protected int mCurrentAccessibilityAction = -1;
     public SecondaryDropTarget(Context context, AttributeSet attrs) {
@@ -70,6 +71,24 @@
     }
 
     @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        if (mHadPendingAlarm) {
+            mCacheExpireAlarm.setAlarm(CACHE_EXPIRE_TIMEOUT);
+            mHadPendingAlarm = false;
+        }
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        if (mCacheExpireAlarm.alarmPending()) {
+            mCacheExpireAlarm.cancelAlarm();
+            mHadPendingAlarm = true;
+        }
+    }
+
+    @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
         setupUi(UNINSTALL);
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 4e5b031..fcccc59 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -103,6 +103,8 @@
 
     private final MultiValueAlpha mMultiValueAlpha;
 
+    Rect mInsets = new Rect();
+
     public AllAppsContainerView(Context context) {
         this(context, null);
     }
@@ -325,6 +327,7 @@
 
     @Override
     public void setInsets(Rect insets) {
+        mInsets.set(insets);
         DeviceProfile grid = mLauncher.getDeviceProfile();
         int leftRightPadding = grid.desiredWorkspaceLeftRightMarginPx
                 + grid.cellLayoutPaddingLeftRightPx;
@@ -416,6 +419,7 @@
                 new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                         ViewGroup.LayoutParams.WRAP_CONTENT));
         this.addView(mWorkFooterContainer);
+        mWorkFooterContainer.setInsets(mInsets);
         mWorkFooterContainer.post(() -> mAH[AdapterHolder.WORK].applyPadding());
     }
 
@@ -553,17 +557,6 @@
         return mHeader != null && mHeader.getVisibility() == View.VISIBLE;
     }
 
-    public void onScrollUpEnd() {
-        highlightWorkTabIfNecessary();
-    }
-
-    void highlightWorkTabIfNecessary() {
-        if (mUsingTabs) {
-            ((PersonalWorkSlidingTabStrip) findViewById(R.id.tabs))
-                    .highlightWorkTabIfNecessary();
-        }
-    }
-
     /**
      * Adds an update listener to {@param animator} that adds springs to the animation.
      */
@@ -648,9 +641,9 @@
                         R.layout.work_apps_paused, null);
                 recyclerView.post(() -> {
                     int width = recyclerView.getWidth();
-                    int height = recyclerView.getHeight();
-                    pausedOverlay.measure(makeMeasureSpec(width, EXACTLY),
-                            makeMeasureSpec(height, EXACTLY));
+                    int height = recyclerView.getHeight() -  mWorkFooterContainer.getHeight();
+                    pausedOverlay.measure(makeMeasureSpec(recyclerView.getWidth(), EXACTLY),
+                            makeMeasureSpec(recyclerView.getHeight(), EXACTLY));
                     pausedOverlay.layout(0, 0, width, height);
                     applyPadding();
                 });
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 93bdac9..e49ff30 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -2,6 +2,7 @@
 
 import static com.android.launcher3.LauncherState.ALL_APPS_CONTENT;
 import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA;
+import static com.android.launcher3.LauncherState.APPS_VIEW_ITEM_MASK;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.LauncherState.VERTICAL_SWIPE_INDICATOR;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE;
@@ -203,6 +204,8 @@
         boolean hasHeaderExtra = (visibleElements & ALL_APPS_HEADER_EXTRA) != 0;
         boolean hasAllAppsContent = (visibleElements & ALL_APPS_CONTENT) != 0;
 
+        boolean hasAnyVisibleItem = (visibleElements & APPS_VIEW_ITEM_MASK) != 0;
+
         Interpolator allAppsFade = builder.getInterpolator(ANIM_ALL_APPS_FADE, LINEAR);
         Interpolator headerFade = builder.getInterpolator(ANIM_ALL_APPS_HEADER_FADE, allAppsFade);
         setter.setViewAlpha(mAppsView.getContentView(), hasAllAppsContent ? 1 : 0, allAppsFade);
@@ -213,6 +216,8 @@
 
         setter.setInt(mScrimView, ScrimView.DRAG_HANDLE_ALPHA,
                 (visibleElements & VERTICAL_SWIPE_INDICATOR) != 0 ? 255 : 0, allAppsFade);
+
+        setter.setViewAlpha(mAppsView, hasAnyVisibleItem ? 1 : 0, allAppsFade);
     }
 
     public AnimatorListenerAdapter getProgressAnimatorListener() {
@@ -248,18 +253,6 @@
     private void onProgressAnimationEnd() {
         if (Float.compare(mProgress, 1f) == 0) {
             mAppsView.reset(false /* animate */);
-        } else if (isAllAppsExpanded()) {
-            mAppsView.onScrollUpEnd();
-        }
-    }
-
-    private boolean isAllAppsExpanded() {
-        return Float.compare(mProgress, 0f) == 0;
-    }
-
-    public void highlightWorkTabIfNecessary() {
-        if (isAllAppsExpanded()) {
-            mAppsView.highlightWorkTabIfNecessary();
         }
     }
 }
diff --git a/src/com/android/launcher3/allapps/DiscoveryBounce.java b/src/com/android/launcher3/allapps/DiscoveryBounce.java
index e8035eb..0f0fc3a 100644
--- a/src/com/android/launcher3/allapps/DiscoveryBounce.java
+++ b/src/com/android/launcher3/allapps/DiscoveryBounce.java
@@ -35,7 +35,6 @@
 import com.android.launcher3.LauncherStateManager.StateListener;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.pm.UserCache;
 
 /**
  * Abstract base class of floating view responsible for showing discovery bounce animation
@@ -144,8 +143,7 @@
 
     private static void showForHomeIfNeeded(Launcher launcher, boolean withDelay) {
         if (!launcher.isInState(NORMAL)
-                || (launcher.getSharedPrefs().getBoolean(HOME_BOUNCE_SEEN, false)
-                && !shouldShowForWorkProfile(launcher))
+                || launcher.getSharedPrefs().getBoolean(HOME_BOUNCE_SEEN, false)
                 || AbstractFloatingView.getTopOpenView(launcher) != null
                 || launcher.getSystemService(UserManager.class).isDemoUser()
                 || Utilities.IS_RUNNING_IN_TEST_HARNESS) {
@@ -170,8 +168,7 @@
                 || !launcher.hasBeenResumed()
                 || launcher.isForceInvisible()
                 || launcher.getDeviceProfile().isVerticalBarLayout()
-                || (launcher.getSharedPrefs().getBoolean(SHELF_BOUNCE_SEEN, false)
-                && !shouldShowForWorkProfile(launcher))
+                || launcher.getSharedPrefs().getBoolean(SHELF_BOUNCE_SEEN, false)
                 || launcher.getSystemService(UserManager.class).isDemoUser()
                 || Utilities.IS_RUNNING_IN_TEST_HARNESS) {
             return;
@@ -213,12 +210,6 @@
         }
     }
 
-    private static boolean shouldShowForWorkProfile(Launcher launcher) {
-        return !launcher.getSharedPrefs().getBoolean(
-                PersonalWorkSlidingTabStrip.KEY_SHOWED_PEEK_WORK_TAB, false)
-                && UserCache.INSTANCE.get(launcher).hasWorkProfile();
-    }
-
     private static void incrementShelfBounceCount(Launcher launcher) {
         SharedPreferences sharedPrefs = launcher.getSharedPrefs();
         int count = sharedPrefs.getInt(SHELF_BOUNCE_COUNT, 0);
diff --git a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
index 9d0ecd3..25db0e7 100644
--- a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
@@ -64,6 +64,14 @@
     }
 
     @Override
+    public void setupHeader() {
+        super.setupHeader();
+        if (mWorkTabListener != null && !mUsingTabs) {
+            mLauncher.getStateManager().removeStateListener(mWorkTabListener);
+        }
+    }
+
+    @Override
     public void onTabChanged(int pos) {
         super.onTabChanged(pos);
         if (mUsingTabs) {
diff --git a/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java b/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
index 6204f31..0e39bbe 100644
--- a/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
+++ b/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
@@ -16,7 +16,6 @@
 package com.android.launcher3.allapps;
 
 import android.content.Context;
-import android.content.SharedPreferences;
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.util.AttributeSet;
@@ -39,11 +38,8 @@
     private static final int POSITION_PERSONAL = 0;
     private static final int POSITION_WORK = 1;
 
-    public static final String KEY_SHOWED_PEEK_WORK_TAB = "showed_peek_work_tab";
-
     private final Paint mSelectedIndicatorPaint;
     private final Paint mDividerPaint;
-    private final SharedPreferences mSharedPreferences;
 
     private int mSelectedIndicatorHeight;
     private int mIndicatorLeft = -1;
@@ -72,7 +68,6 @@
         mDividerPaint.setStrokeWidth(
                 getResources().getDimensionPixelSize(R.dimen.all_apps_divider_height));
 
-        mSharedPreferences = Utilities.getPrefs(context);
         mIsRtl = Utilities.isRtl(getResources());
     }
 
@@ -128,25 +123,6 @@
             mIndicatorRight, getHeight(), mSelectedIndicatorPaint);
     }
 
-    public void highlightWorkTabIfNecessary() {
-        if (mSharedPreferences.getBoolean(KEY_SHOWED_PEEK_WORK_TAB, false)) {
-            return;
-        }
-        if (mLastActivePage != POSITION_PERSONAL) {
-            return;
-        }
-        highlightWorkTab();
-        mSharedPreferences.edit().putBoolean(KEY_SHOWED_PEEK_WORK_TAB, true).apply();
-    }
-
-    private void highlightWorkTab() {
-        View v = getChildAt(POSITION_WORK);
-        v.post(() -> {
-            v.setPressed(true);
-            v.setPressed(false);
-        });
-    }
-
     @Override
     public void setScroll(int currentScroll, int totalScroll) {
         float scrollOffset = ((float) currentScroll) / totalScroll;
diff --git a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
index 28579c1..1d32d1d 100644
--- a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
+++ b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
@@ -18,11 +18,14 @@
 
 import android.content.Context;
 import android.os.Bundle;
+import android.text.TextUtils;
 import android.util.Log;
 import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.Utilities;
 import com.android.launcher3.testing.TestProtocol;
 
@@ -37,11 +40,21 @@
         return isAccessibilityEnabled(context);
     }
 
-    public static void sendCustomAccessibilityEvent(View target, int type, String text) {
-        if (isObservedEventType(target.getContext(), type)) {
+    /**
+     *
+     * @param target The view the accessibility event is initialized on.
+     *               If null, this method has no effect.
+     * @param type See TYPE_ constants defined in {@link AccessibilityEvent}.
+     * @param text Optional text to add to the event, which will be announced to the user.
+     */
+    public static void sendCustomAccessibilityEvent(@Nullable View target, int type,
+            @Nullable String text) {
+        if (target != null && isObservedEventType(target.getContext(), type)) {
             AccessibilityEvent event = AccessibilityEvent.obtain(type);
             target.onInitializeAccessibilityEvent(event);
-            event.getText().add(text);
+            if (!TextUtils.isEmpty(text)) {
+                event.getText().add(text);
+            }
             getManager(target.getContext()).sendAccessibilityEvent(event);
         }
     }
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 6413044..d7498a5 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -124,7 +124,7 @@
             "Show launcher preview in grid picker");
 
     public static final BooleanFlag ENABLE_OVERVIEW_ACTIONS = getDebugFlag(
-            "ENABLE_OVERVIEW_ACTIONS", false, "Show app actions instead of the shelf in Overview."
+            "ENABLE_OVERVIEW_ACTIONS", true, "Show app actions instead of the shelf in Overview."
             + " As part of this decoupling, also distinguish swipe up from nav bar vs above it.");
 
     public static final BooleanFlag ENABLE_DATABASE_RESTORE = getDebugFlag(
@@ -135,6 +135,10 @@
             "ENABLE_UNIVERSAL_SMARTSPACE", false,
             "Replace Smartspace with a version rendered by System UI.");
 
+    public static final BooleanFlag ENABLE_LSQ_VELOCITY_PROVIDER = getDebugFlag(
+            "ENABLE_LSQ_VELOCITY_PROVIDER", false,
+            "Use Least Square algorithm for motion pause detection.");
+
     public static void initialize(Context context) {
         synchronized (sDebugFlags) {
             for (DebugFlag flag : sDebugFlags) {
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 1d79e01..e33d89f 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -33,6 +33,7 @@
 
 import static java.util.Arrays.asList;
 import static java.util.Arrays.stream;
+import static java.util.Optional.ofNullable;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -95,6 +96,7 @@
 import com.android.launcher3.userevent.LauncherLogProto.LauncherEvent;
 import com.android.launcher3.userevent.LauncherLogProto.Target;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.views.ClipPathView;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
@@ -322,12 +324,11 @@
         post(() -> {
             if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
                 if (isEmpty(mFolderName.getText())) {
-                    FolderNameInfo[] nameInfos =
-                            (FolderNameInfo[]) mInfo.suggestedFolderNames.getParcelableArrayExtra(
-                                    FolderInfo.EXTRA_FOLDER_SUGGESTIONS);
-                    if (nameInfos != null) {
-                        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("");
@@ -345,7 +346,7 @@
         }
 
         mInfo.title = newTitle;
-        mInfo.setOption(FLAG_MANUAL_FOLDER_NAME, mFolderName.isEnteredCompose(),
+        mInfo.setOption(FLAG_MANUAL_FOLDER_NAME, getAcceptedSuggestionIndex() < 0,
                 mLauncher.getModelWriter());
         mFolderIcon.onTitleChanged(newTitle);
         mLauncher.getModelWriter().updateItemInDatabase(mInfo);
@@ -426,7 +427,7 @@
         mInfo = info;
         ArrayList<WorkspaceItemInfo> children = info.contents;
         Collections.sort(children, ITEM_POS_COMPARATOR);
-        updateItemLocationsInDatabaseBatch();
+        updateItemLocationsInDatabaseBatch(true);
 
         DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
         if (lp == null) {
@@ -436,19 +437,15 @@
         }
         mItemsInvalidated = true;
         mInfo.addListener(this);
+        mPreviousLabel = mInfo.title.toString();
+        mIsPreviousLabelSuggested = !mInfo.hasOption(FLAG_MANUAL_FOLDER_NAME);
 
         if (!isEmpty(mInfo.title)) {
             mFolderName.setText(mInfo.title);
-            mPreviousLabel = mInfo.title.toString();
-            mIsPreviousLabelSuggested = !mInfo.hasOption(FLAG_MANUAL_FOLDER_NAME);
             mFolderName.setHint(null);
         } else {
             mFolderName.setText("");
-            if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
-                mFolderName.setHint("");
-            } else {
-                mFolderName.setHint(R.string.folder_hint_text);
-            }
+            mFolderName.setHint(R.string.folder_hint_text);
         }
         // In case any children didn't come across during loading, clean up the folder accordingly
         mFolderIcon.post(() -> {
@@ -464,8 +461,6 @@
      */
     public void showSuggestedTitle(FolderNameInfo[] nameInfos) {
         if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
-            mInfo.suggestedFolderNames = new Intent().putExtra(FolderInfo.EXTRA_FOLDER_SUGGESTIONS,
-                    nameInfos);
             if (isEmpty(mFolderName.getText().toString())
                     && !mInfo.hasOption(FLAG_MANUAL_FOLDER_NAME)) {
                 showLabelSuggestion(nameInfos, true);
@@ -487,9 +482,9 @@
                 nameInfos[0].getLabel())
                 || nameInfos.length > 1 && nameInfos[1] != null && !isEmpty(
                 nameInfos[1].getLabel());
-        CharSequence firstLabel = nameInfos[0].getLabel();
 
         if (shouldOpen) {
+            CharSequence firstLabel = nameInfos[0] == null ? "" : nameInfos[0].getLabel();
             if (!isEmpty(firstLabel)) {
                 mFolderName.setHint("");
                 mFolderName.setText(firstLabel);
@@ -985,7 +980,7 @@
 
         // Reordering may have occured, and we need to save the new item locations. We do this once
         // at the end to prevent unnecessary database operations.
-        updateItemLocationsInDatabaseBatch();
+        updateItemLocationsInDatabaseBatch(false);
         // Use the item count to check for multi-page as the folder UI may not have
         // been refreshed yet.
         if (getItemCount() <= mContent.itemsPerPage()) {
@@ -995,7 +990,7 @@
         }
     }
 
-    private void updateItemLocationsInDatabaseBatch() {
+    private void updateItemLocationsInDatabaseBatch(boolean isBind) {
         FolderGridOrganizer verifier = new FolderGridOrganizer(
                 mLauncher.getDeviceProfile().inv).setFolderInfo(mInfo);
 
@@ -1011,6 +1006,18 @@
         if (!items.isEmpty()) {
             mLauncher.getModelWriter().moveItemsInDatabase(items, mInfo.id, 0);
         }
+        if (FeatureFlags.FOLDER_NAME_SUGGEST.get() && !isBind) {
+            Executors.MODEL_EXECUTOR.post(() -> {
+                FolderNameInfo[] nameInfos =
+                        new FolderNameInfo[FolderNameProvider.SUGGEST_MAX];
+                FolderNameProvider fnp = FolderNameProvider.newInstance(getContext());
+                fnp.getSuggestedFolderName(
+                        getContext(), mInfo.contents, nameInfos);
+                mInfo.suggestedFolderNames = new Intent().putExtra(
+                        FolderInfo.EXTRA_FOLDER_SUGGESTIONS,
+                        nameInfos);
+            });
+        }
     }
 
     public void notifyDrop() {
@@ -1315,7 +1322,7 @@
             // We only need to update the locations if it doesn't get handled in
             // #onDropCompleted.
             if (d.dragSource != this) {
-                updateItemLocationsInDatabaseBatch();
+                updateItemLocationsInDatabaseBatch(false);
             }
         }
 
@@ -1356,7 +1363,7 @@
         verifier.updateRankAndPos(item, rank);
         mLauncher.getModelWriter().addOrMoveItemInDatabase(item, mInfo.id, 0, item.cellX,
                 item.cellY);
-        updateItemLocationsInDatabaseBatch();
+        updateItemLocationsInDatabaseBatch(false);
 
         if (mContent.areViewsBound()) {
             mContent.createAndAddViewForRank(item, rank);
@@ -1647,26 +1654,8 @@
                 checkNotNull(mFolderName.getText().toString(),
                         "Expected valid folder label, but found null");
 
-        Optional<String[]> suggestedLabels = Optional.ofNullable(
-                (FolderNameInfo[]) mInfo.suggestedFolderNames
-                        .getParcelableArrayExtra(FolderInfo.EXTRA_FOLDER_SUGGESTIONS))
-                .map(folderNameInfoArray ->
-                        stream(folderNameInfoArray)
-                                .filter(Objects::nonNull)
-                                .map(FolderNameInfo::getLabel)
-                                .map(CharSequence::toString)
-                                .toArray(String[]::new));
-
-
-        int accepted_suggestion_index = suggestedLabels
-                .map(folderNameInfoArray ->
-                        IntStream.range(0, folderNameInfoArray.length)
-                                .filter(index -> newLabel.equalsIgnoreCase(
-                                        folderNameInfoArray[index]))
-                                .findFirst()
-                                .orElse(-1)
-                ).orElse(-1);
-
+        Optional<String[]> suggestedLabels = getSuggestedLabels();
+        int accepted_suggestion_index = getAcceptedSuggestionIndex();
         boolean hasValidPrimary = suggestedLabels
                 .map(labels -> labels.length > 0 && !isEmpty(labels[0]))
                 .orElse(false);
@@ -1695,6 +1684,39 @@
                         : Target.ToFolderLabelState.valueOf("TO_CUSTOM" + suggestionsSuffix);
     }
 
+    private Optional<String[]> getSuggestedLabels() {
+        return ofNullable(mInfo)
+            .map(info -> info.suggestedFolderNames)
+            .map(
+                folderNames ->
+                    (FolderNameInfo[])
+                        folderNames.getParcelableArrayExtra(FolderInfo.EXTRA_FOLDER_SUGGESTIONS))
+            .map(
+                folderNameInfoArray ->
+                    stream(folderNameInfoArray)
+                        .filter(Objects::nonNull)
+                        .map(FolderNameInfo::getLabel)
+                        .filter(Objects::nonNull)
+                        .map(CharSequence::toString)
+                        .toArray(String[]::new));
+    }
+
+    private int getAcceptedSuggestionIndex() {
+        String newLabel =
+                checkNotNull(mFolderName.getText().toString(),
+                        "Expected valid folder label, but found null");
+
+        return getSuggestedLabels()
+                .map(suggestionsArray ->
+                        IntStream.range(0, suggestionsArray.length)
+                                .filter(index -> newLabel.equalsIgnoreCase(
+                                        suggestionsArray[index]))
+                                .findFirst()
+                                .orElse(-1)
+                ).orElse(-1);
+
+    }
+
 
     private Target.Builder newEditTextTargetBuilder() {
         return Target.newBuilder().setType(Target.Type.ITEM).setItemType(ItemType.EDITTEXT);
diff --git a/src/com/android/launcher3/folder/FolderNameEditText.java b/src/com/android/launcher3/folder/FolderNameEditText.java
index 7e11b18..edf2c70 100644
--- a/src/com/android/launcher3/folder/FolderNameEditText.java
+++ b/src/com/android/launcher3/folder/FolderNameEditText.java
@@ -102,13 +102,6 @@
         mEnteredCompose = value;
     }
 
-    protected boolean isEnteredCompose() {
-        if (DEBUG) {
-            Log.d(TAG, "isEnteredCompose " + mEnteredCompose);
-        }
-        return mEnteredCompose;
-    }
-
     private class FolderNameEditTextInputConnection extends InputConnectionWrapper {
 
         FolderNameEditTextInputConnection(InputConnection target, boolean mutable) {
diff --git a/src/com/android/launcher3/folder/FolderNameInfo.java b/src/com/android/launcher3/folder/FolderNameInfo.java
index eb9da90..1287219 100644
--- a/src/com/android/launcher3/folder/FolderNameInfo.java
+++ b/src/com/android/launcher3/folder/FolderNameInfo.java
@@ -80,4 +80,10 @@
     public int describeContents() {
         return 0;
     }
+
+    @Override
+    @NonNull
+    public String toString() {
+        return String.format("%s:%.2f", mLabel, mScore);
+    }
 }
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index def76e8..0927b26 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -20,13 +20,19 @@
 import static android.view.View.VISIBLE;
 
 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;
 
 import android.annotation.TargetApi;
 import android.app.Fragment;
+import android.appwidget.AppWidgetHostView;
 import android.content.Context;
+import android.content.ContextWrapper;
 import android.content.Intent;
+import android.content.pm.ShortcutInfo;
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -55,6 +61,7 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherSettings.Favorites;
@@ -68,15 +75,31 @@
 import com.android.launcher3.icons.BaseIconFactory;
 import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.icons.BitmapRenderer;
+import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.model.AllAppsList;
 import com.android.launcher3.model.BgDataModel;
 import com.android.launcher3.model.BgDataModel.Callbacks;
+import com.android.launcher3.model.GridSizeMigrationTask;
+import com.android.launcher3.model.GridSizeMigrationTaskV2;
 import com.android.launcher3.model.LoaderResults;
+import com.android.launcher3.model.LoaderTask;
+import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.model.WidgetsModel;
+import com.android.launcher3.pm.InstallSessionHelper;
+import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.views.BaseDragLayer;
 
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
@@ -97,6 +120,81 @@
 
     private static final String TAG = "LauncherPreviewRenderer";
 
+    /**
+     * Context used just for preview. It also provides a few objects (e.g. UserCache) just for
+     * preview purposes.
+     */
+    public static class PreviewContext extends ContextWrapper {
+
+        private static final Set<MainThreadInitializedObject> WHITELIST = new HashSet<>(
+                Arrays.asList(UserCache.INSTANCE, InstallSessionHelper.INSTANCE,
+                        LauncherAppState.INSTANCE, InvariantDeviceProfile.INSTANCE));
+
+        private final InvariantDeviceProfile mIdp;
+        private final Map<MainThreadInitializedObject, Object> mObjectMap = new HashMap<>();
+        private final ConcurrentLinkedQueue<LauncherIconsForPreview> mIconPool =
+                new ConcurrentLinkedQueue<>();
+
+        public PreviewContext(Context base, InvariantDeviceProfile idp) {
+            super(base);
+            mIdp = idp;
+        }
+
+        @Override
+        public Context getApplicationContext() {
+            return this;
+        }
+
+        /**
+         * Find a cached object from mObjectMap if we have already created one. If not, generate
+         * an object using the provider.
+         */
+        public <T> T getObject(MainThreadInitializedObject<T> mainThreadInitializedObject,
+                MainThreadInitializedObject.ObjectProvider<T> provider) {
+            if (!WHITELIST.contains(mainThreadInitializedObject)) {
+                throw new IllegalStateException("Leaking unknown objects");
+            }
+            if (mainThreadInitializedObject == LauncherAppState.INSTANCE) {
+                throw new IllegalStateException(
+                        "Should not use MainThreadInitializedObject to initialize this with "
+                                + "PreviewContext");
+            }
+            if (mainThreadInitializedObject == InvariantDeviceProfile.INSTANCE) {
+                return (T) mIdp;
+            }
+            if (mObjectMap.containsKey(mainThreadInitializedObject)) {
+                return (T) mObjectMap.get(mainThreadInitializedObject);
+            }
+            T t = provider.get(this);
+            mObjectMap.put(mainThreadInitializedObject, t);
+            return t;
+        }
+
+        public LauncherIcons newLauncherIcons(Context context, boolean shapeDetection) {
+            LauncherIconsForPreview launcherIconsForPreview = mIconPool.poll();
+            if (launcherIconsForPreview != null) {
+                return launcherIconsForPreview;
+            }
+            return new LauncherIconsForPreview(context, mIdp.fillResIconDpi, mIdp.iconBitmapSize,
+                    -1 /* poolId */, shapeDetection);
+        }
+
+        private final class LauncherIconsForPreview extends LauncherIcons {
+
+            private LauncherIconsForPreview(Context context, int fillResIconDpi, int iconBitmapSize,
+                    int poolId, boolean shapeDetection) {
+                super(context, fillResIconDpi, iconBitmapSize, poolId, shapeDetection);
+            }
+
+            @Override
+            public void recycle() {
+                // Clear any temporary state variables
+                clear();
+                mIconPool.offer(this);
+            }
+        }
+    }
+
     private final Handler mUiHandler;
     private final Context mContext;
     private final InvariantDeviceProfile mIdp;
@@ -248,6 +346,16 @@
             addInScreenFromBind(folderIcon, info);
         }
 
+        private void inflateAndAddWidgets(LauncherAppWidgetInfo info, WidgetsModel widgetsModel) {
+            WidgetItem widgetItem = widgetsModel.getWidgetProviderInfoByProviderName(
+                    info.providerName);
+            AppWidgetHostView view = new AppWidgetHostView(mContext);
+            view.setAppWidget(-1, widgetItem.widgetInfo);
+            view.updateAppWidget(null);
+            view.setTag(info);
+            addInScreenFromBind(view, info);
+        }
+
         private void dispatchVisibilityAggregated(View view, boolean isVisible) {
             // Similar to View.dispatchVisibilityAggregated implementation.
             final boolean thisVisible = view.getVisibility() == VISIBLE;
@@ -268,15 +376,28 @@
 
         private void renderScreenShot(Canvas canvas) {
             if (ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER.get()) {
-                final LauncherModel launcherModel = LauncherAppState.getInstance(
-                        mContext).getModel();
-                final WorkspaceItemsInfoFetcher fetcher = new WorkspaceItemsInfoFetcher();
-                launcherModel.enqueueModelUpdateTask(fetcher);
-                ArrayList<ItemInfo> workspaceItems;
-                try {
-                    workspaceItems = fetcher.mTask.get(5, TimeUnit.SECONDS);
-                } catch (InterruptedException | ExecutionException | TimeoutException e) {
-                    Log.d(TAG, "Error fetching workspace items info", e);
+                boolean needsToMigrate = needsToMigrate(mContext, mIdp);
+                boolean success = false;
+                if (needsToMigrate) {
+                    success = MULTI_DB_GRID_MIRATION_ALGO.get()
+                            ? GridSizeMigrationTaskV2.migrateGridIfNeeded(mContext, mIdp)
+                            : GridSizeMigrationTask.migrateGridIfNeeded(mContext, mIdp);
+                }
+
+                WorkspaceFetcher fetcher;
+                if (needsToMigrate && success) {
+                    LauncherAppState appForPreview = new LauncherAppState(
+                            new PreviewContext(mContext, mIdp), null /* iconCacheFileName */);
+                    fetcher = new WorkspaceItemsInfoFromPreviewFetcher(appForPreview);
+                    MODEL_EXECUTOR.execute(fetcher);
+                } else {
+                    fetcher = new WorkspaceItemsInfoFetcher();
+                    LauncherAppState.getInstance(mContext).getModel().enqueueModelUpdateTask(
+                            (LauncherModel.ModelUpdateTask) fetcher);
+                }
+                WorkspaceResult workspaceResult = fetcher.get();
+
+                if (workspaceResult == null) {
                     return;
                 }
 
@@ -284,9 +405,14 @@
                 // items
                 ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>();
                 ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>();
+                ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>();
+                ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>();
 
-                filterCurrentWorkspaceItems(0 /* currentScreenId */, workspaceItems,
-                        currentWorkspaceItems, otherWorkspaceItems);
+                filterCurrentWorkspaceItems(0 /* currentScreenId */,
+                        workspaceResult.mWorkspaceItems, currentWorkspaceItems,
+                        otherWorkspaceItems);
+                filterCurrentWorkspaceItems(0 /* currentScreenId */, workspaceResult.mAppWidgets,
+                        currentAppWidgets, otherAppWidgets);
                 sortWorkspaceItemsSpatially(mIdp, currentWorkspaceItems);
 
                 for (ItemInfo itemInfo : currentWorkspaceItems) {
@@ -303,6 +429,17 @@
                             break;
                     }
                 }
+                for (ItemInfo itemInfo : currentAppWidgets) {
+                    switch (itemInfo.itemType) {
+                        case Favorites.ITEM_TYPE_APPWIDGET:
+                        case Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
+                            inflateAndAddWidgets((LauncherAppWidgetInfo) itemInfo,
+                                    workspaceResult.mWidgetsModel);
+                            break;
+                        default:
+                            break;
+                    }
+                }
             } else {
                 // Add hotseat icons
                 for (int i = 0; i < mIdp.numHotseatIcons; i++) {
@@ -349,10 +486,15 @@
         }
     }
 
-    private static class WorkspaceItemsInfoFetcher implements Callable<ArrayList<ItemInfo>>,
-            LauncherModel.ModelUpdateTask {
+    private static void measureView(View view, int width, int height) {
+        view.measure(makeMeasureSpec(width, EXACTLY), makeMeasureSpec(height, EXACTLY));
+        view.layout(0, 0, width, height);
+    }
 
-        private final FutureTask<ArrayList<ItemInfo>> mTask = new FutureTask<>(this);
+    private static class WorkspaceItemsInfoFetcher implements LauncherModel.ModelUpdateTask,
+            WorkspaceFetcher {
+
+        private final FutureTask<WorkspaceResult> mTask = new FutureTask<>(this);
 
         private LauncherAppState mApp;
         private LauncherModel mModel;
@@ -369,24 +511,80 @@
         }
 
         @Override
+        public FutureTask<WorkspaceResult> getTask() {
+            return mTask;
+        }
+
+        @Override
         public void run() {
             mTask.run();
         }
 
         @Override
-        public ArrayList<ItemInfo> call() throws Exception {
+        public WorkspaceResult call() throws Exception {
             if (!mModel.isModelLoaded()) {
                 Log.d(TAG, "Workspace not loaded, loading now");
                 mModel.startLoaderForResults(
                         new LoaderResults(mApp, mBgDataModel, mAllAppsList, new Callbacks[0]));
-                return new ArrayList<>();
+                return null;
             }
-            return mBgDataModel.workspaceItems;
+
+            return new WorkspaceResult(mBgDataModel.workspaceItems, mBgDataModel.appWidgets,
+                    mBgDataModel.widgetsModel);
         }
     }
 
-    private static void measureView(View view, int width, int height) {
-        view.measure(makeMeasureSpec(width, EXACTLY), makeMeasureSpec(height, EXACTLY));
-        view.layout(0, 0, width, height);
+    private static class WorkspaceItemsInfoFromPreviewFetcher extends LoaderTask implements
+            WorkspaceFetcher {
+
+        private final FutureTask<WorkspaceResult> mTask = new FutureTask<>(this);
+
+        WorkspaceItemsInfoFromPreviewFetcher(LauncherAppState app) {
+            super(app, null, new BgDataModel(), null);
+        }
+
+        @Override
+        public FutureTask<WorkspaceResult> getTask() {
+            return mTask;
+        }
+
+        @Override
+        public void run() {
+            mTask.run();
+        }
+
+        @Override
+        public WorkspaceResult call() throws Exception {
+            List<ShortcutInfo> allShortcuts = new ArrayList<>();
+            loadWorkspace(allShortcuts, LauncherSettings.Favorites.PREVIEW_CONTENT_URI);
+            return new WorkspaceResult(mBgDataModel.workspaceItems, mBgDataModel.appWidgets,
+                    mBgDataModel.widgetsModel);
+        }
+    }
+
+    private interface WorkspaceFetcher extends Runnable, Callable<WorkspaceResult> {
+        FutureTask<WorkspaceResult> getTask();
+
+        default WorkspaceResult get() {
+            try {
+                return getTask().get(5, TimeUnit.SECONDS);
+            } catch (InterruptedException | ExecutionException | TimeoutException e) {
+                Log.d(TAG, "Error fetching workspace items info", e);
+                return null;
+            }
+        }
+    }
+
+    private static class WorkspaceResult {
+        private final ArrayList<ItemInfo> mWorkspaceItems;
+        private final ArrayList<LauncherAppWidgetInfo> mAppWidgets;
+        private final WidgetsModel mWidgetsModel;
+
+        private WorkspaceResult(ArrayList<ItemInfo> workspaceItems,
+                ArrayList<LauncherAppWidgetInfo> appWidgets, WidgetsModel widgetsModel) {
+            mWorkspaceItems = workspaceItems;
+            mAppWidgets = appWidgets;
+            mWidgetsModel = widgetsModel;
+        }
     }
 }
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index 804acc3..f27c9da 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -81,9 +81,13 @@
 
     private int mPendingIconRequestCount = 0;
 
-    public IconCache(Context context, InvariantDeviceProfile inv) {
-        super(context, LauncherFiles.APP_ICONS_DB, MODEL_EXECUTOR.getLooper(),
-                inv.fillResIconDpi, inv.iconBitmapSize, true /* inMemoryCache */);
+    public IconCache(Context context, InvariantDeviceProfile idp) {
+        this(context, idp, LauncherFiles.APP_ICONS_DB);
+    }
+
+    public IconCache(Context context, InvariantDeviceProfile idp, String dbFileName) {
+        super(context, dbFileName, MODEL_EXECUTOR.getLooper(),
+                idp.fillResIconDpi, idp.iconBitmapSize, true /* inMemoryCache */);
         mComponentWithLabelCachingLogic = new ComponentCachingLogic(context, false);
         mLauncherActivityInfoCachingLogic = LauncherActivityCachingLogic.newInstance(context);
         mShortcutCachingLogic = new ShortcutCachingLogic();
diff --git a/src/com/android/launcher3/icons/LauncherIcons.java b/src/com/android/launcher3/icons/LauncherIcons.java
index cbd7c53..bf7897e 100644
--- a/src/com/android/launcher3/icons/LauncherIcons.java
+++ b/src/com/android/launcher3/icons/LauncherIcons.java
@@ -19,8 +19,8 @@
 import android.content.Context;
 
 import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.graphics.IconShape;
+import com.android.launcher3.graphics.LauncherPreviewRenderer;
 
 /**
  * Wrapper class to provide access to {@link BaseIconFactory} and also to provide pool of this class
@@ -41,6 +41,11 @@
      * avoid allocating new objects in many cases.
      */
     public static LauncherIcons obtain(Context context, boolean shapeDetection) {
+        if (context instanceof LauncherPreviewRenderer.PreviewContext) {
+            return ((LauncherPreviewRenderer.PreviewContext) context).newLauncherIcons(context,
+                    shapeDetection);
+        }
+
         int poolId;
         synchronized (sPoolSync) {
             if (sPool != null) {
@@ -52,7 +57,7 @@
             poolId = sPoolId;
         }
 
-        InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
+        InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(context);
         return new LauncherIcons(context, idp.fillResIconDpi, idp.iconBitmapSize, poolId,
                 shapeDetection);
     }
@@ -68,7 +73,7 @@
 
     private LauncherIcons next;
 
-    private LauncherIcons(Context context, int fillResIconDpi, int iconBitmapSize, int poolId,
+    protected LauncherIcons(Context context, int fillResIconDpi, int iconBitmapSize, int poolId,
             boolean shapeDetection) {
         super(context, fillResIconDpi, iconBitmapSize, shapeDetection);
         mPoolId = poolId;
diff --git a/src/com/android/launcher3/logging/FileLog.java b/src/com/android/launcher3/logging/FileLog.java
index 67f07b1..a3fdf8d 100644
--- a/src/com/android/launcher3/logging/FileLog.java
+++ b/src/com/android/launcher3/logging/FileLog.java
@@ -10,6 +10,7 @@
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.IOUtils;
 
 import java.io.BufferedReader;
@@ -42,7 +43,7 @@
     private static Handler sHandler = null;
     private static File sLogsDirectory = null;
 
-    public static final int LOG_DAYS = 4;
+    public static final int LOG_DAYS = FeatureFlags.ENABLE_HYBRID_HOTSEAT.get() ? 10 : 4;
 
     public static void setDir(File logsDir) {
         if (ENABLED) {
diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java
index 199d13f..afa3f6d 100644
--- a/src/com/android/launcher3/logging/UserEventDispatcher.java
+++ b/src/com/android/launcher3/logging/UserEventDispatcher.java
@@ -147,7 +147,7 @@
             fillIntentInfo(event.srcTarget[0], intent, userHandle);
         }
         ItemInfo info = (ItemInfo) v.getTag();
-        if (Utilities.IS_DEBUG_DEVICE && FeatureFlags.ENABLE_HYBRID_HOTSEAT.get()) {
+        if (info != null && Utilities.IS_DEBUG_DEVICE && FeatureFlags.ENABLE_HYBRID_HOTSEAT.get()) {
             FileLog.d(TAG, "appLaunch: packageName:" + info.getTargetComponent().getPackageName()
                     + ",isWorkApp:" + (info.user != null && !Process.myUserHandle().equals(
                     userHandle)) + ",launchLocation:" + info.container);
diff --git a/src/com/android/launcher3/model/GridBackupTable.java b/src/com/android/launcher3/model/GridBackupTable.java
index 07a7551..4a1bc4d 100644
--- a/src/com/android/launcher3/model/GridBackupTable.java
+++ b/src/com/android/launcher3/model/GridBackupTable.java
@@ -100,12 +100,24 @@
                     Process.myUserHandle()), 0);
             return false;
         }
+        return restoreIfBackupExists(Favorites.TABLE_NAME);
+    }
+
+    public boolean restoreToPreviewIfBackupExists() {
+        if (!tableExists(mDb, BACKUP_TABLE_NAME)) {
+            return false;
+        }
+
+        return restoreIfBackupExists(Favorites.PREVIEW_TABLE_NAME);
+    }
+
+    private boolean restoreIfBackupExists(String toTableName) {
         if (loadDBProperties() != STATE_SANITIZED) {
             return false;
         }
         long userSerial = UserCache.INSTANCE.get(mContext).getSerialNumberForUser(
                 Process.myUserHandle());
-        copyTable(mDb, BACKUP_TABLE_NAME, Favorites.TABLE_NAME, userSerial);
+        copyTable(mDb, BACKUP_TABLE_NAME, toTableName, userSerial);
         Log.d(TAG, "Backup table found");
         return true;
     }
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTask.java b/src/com/android/launcher3/model/GridSizeMigrationTask.java
index c35c4b9..3ba740d 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTask.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTask.java
@@ -3,6 +3,7 @@
 import static com.android.launcher3.LauncherSettings.Settings.EXTRA_VALUE;
 import static com.android.launcher3.Utilities.getPointString;
 import static com.android.launcher3.Utilities.parsePoint;
+import static com.android.launcher3.provider.LauncherDbUtils.copyTable;
 
 import android.content.ComponentName;
 import android.content.ContentValues;
@@ -14,6 +15,7 @@
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.graphics.Point;
+import android.os.SystemClock;
 import android.util.Log;
 import android.util.SparseArray;
 
@@ -29,6 +31,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.graphics.LauncherPreviewRenderer;
 import com.android.launcher3.pm.InstallSessionHelper;
 import com.android.launcher3.provider.LauncherDbUtils;
 import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
@@ -69,6 +72,7 @@
 
     private final SparseArray<ContentValues> mUpdateOperations = new SparseArray<>();
     private final HashSet<String> mValidPackages;
+    private final String mTableName;
 
     private final int mSrcX, mSrcY;
     private final int mTrgX, mTrgY;
@@ -78,10 +82,12 @@
     private final int mDestHotseatSize;
 
     protected GridSizeMigrationTask(Context context, SQLiteDatabase db,
-            HashSet<String> validPackages, Point sourceSize, Point targetSize) {
+            HashSet<String> validPackages, boolean usePreviewTable, Point sourceSize,
+            Point targetSize) {
         mContext = context;
         mDb = db;
         mValidPackages = validPackages;
+        mTableName = usePreviewTable ? Favorites.PREVIEW_TABLE_NAME : Favorites.TABLE_NAME;
 
         mSrcX = sourceSize.x;
         mSrcY = sourceSize.y;
@@ -97,10 +103,12 @@
     }
 
     protected GridSizeMigrationTask(Context context, SQLiteDatabase db,
-            HashSet<String> validPackages, int srcHotseatSize, int destHotseatSize) {
+            HashSet<String> validPackages, boolean usePreviewTable, int srcHotseatSize,
+            int destHotseatSize) {
         mContext = context;
         mDb = db;
         mValidPackages = validPackages;
+        mTableName = usePreviewTable ? Favorites.PREVIEW_TABLE_NAME : Favorites.TABLE_NAME;
 
         mSrcHotseatSize = srcHotseatSize;
 
@@ -120,7 +128,7 @@
         // Update items
         int updateCount = mUpdateOperations.size();
         for (int i = 0; i < updateCount; i++) {
-            mDb.update(Favorites.TABLE_NAME, mUpdateOperations.valueAt(i),
+            mDb.update(mTableName, mUpdateOperations.valueAt(i),
                     "_id=" + mUpdateOperations.keyAt(i), null);
         }
 
@@ -128,8 +136,8 @@
             if (DEBUG) {
                 Log.d(TAG, "Removing items: " + mEntryToRemove.toConcatString());
             }
-            mDb.delete(Favorites.TABLE_NAME, Utilities.createDbSelectionQuery(
-                    Favorites._ID, mEntryToRemove), null);
+            mDb.delete(mTableName, Utilities.createDbSelectionQuery(Favorites._ID, mEntryToRemove),
+                    null);
         }
 
         return updateCount > 0 || !mEntryToRemove.isEmpty();
@@ -182,8 +190,8 @@
     }
 
     @VisibleForTesting
-    static IntArray getWorkspaceScreenIds(SQLiteDatabase db) {
-        return LauncherDbUtils.queryIntArray(db, Favorites.TABLE_NAME, Favorites.SCREEN,
+    static IntArray getWorkspaceScreenIds(SQLiteDatabase db, String tableName) {
+        return LauncherDbUtils.queryIntArray(db, tableName, Favorites.SCREEN,
                 Favorites.CONTAINER + " = " + Favorites.CONTAINER_DESKTOP,
                 Favorites.SCREEN, Favorites.SCREEN);
     }
@@ -192,7 +200,7 @@
      * @return true if any DB change was made
      */
     protected boolean migrateWorkspace() throws Exception {
-        IntArray allScreens = getWorkspaceScreenIds(mDb);
+        IntArray allScreens = getWorkspaceScreenIds(mDb, mTableName);
         if (allScreens.isEmpty()) {
             throw new Exception("Unable to get workspace screens");
         }
@@ -244,12 +252,12 @@
     /**
      * Migrate a particular screen id.
      * Strategy:
-     *   1) For all possible combinations of row and column, pick the one which causes the least
-     *      data loss: {@link #tryRemove(int, int, int, ArrayList, float[])}
-     *   2) Maintain a list of all lost items before this screen, and add any new item lost from
-     *      this screen to that list as well.
-     *   3) If all those items from the above list can be placed on this screen, place them
-     *      (otherwise they are placed on a new screen).
+     *  1) For all possible combinations of row and column, pick the one which causes the least
+     *    data loss: {@link #tryRemove(int, int, int, ArrayList, float[])}
+     *  2) Maintain a list of all lost items before this screen, and add any new item lost from
+     *    this screen to that list as well.
+     *  3) If all those items from the above list can be placed on this screen, place them
+     *    (otherwise they are placed on a new screen).
      */
     protected void migrateScreen(int screenId) {
         // If we are migrating the first screen, do not touch the first row.
@@ -362,9 +370,9 @@
     /**
      * Tries the remove the provided row and column.
      *
-     * @param items all the items on the screen under operation
+     * @param items   all the items on the screen under operation
      * @param outLoss array of size 2. The first entry is filled with weight loss, and the second
-     * with the overall item movement.
+     *                with the overall item movement.
      */
     private ArrayList<DbEntry> tryRemove(int col, int row, int startY,
             ArrayList<DbEntry> items, float[] outLoss) {
@@ -379,13 +387,13 @@
 
         for (DbEntry item : items) {
             if ((item.cellX <= col && (item.spanX + item.cellX) > col)
-                || (item.cellY <= row && (item.spanY + item.cellY) > row)) {
+                    || (item.cellY <= row && (item.spanY + item.cellY) > row)) {
                 removedItems.add(item);
-                if (item.cellX >= col) item.cellX --;
-                if (item.cellY >= row) item.cellY --;
+                if (item.cellX >= col) item.cellX--;
+                if (item.cellY >= row) item.cellY--;
             } else {
-                if (item.cellX > col) item.cellX --;
-                if (item.cellY > row) item.cellY --;
+                if (item.cellX > col) item.cellX--;
+                if (item.cellY > row) item.cellY--;
                 finalItems.add(item);
                 occupied.markCells(item, true);
             }
@@ -438,9 +446,9 @@
         /**
          * Recursively finds a placement for the provided items.
          *
-         * @param index the position in {@link #itemsToPlace} to start looking at.
-         * @param weightLoss total weight loss upto this point
-         * @param moveCost total move cost upto this point
+         * @param index       the position in {@link #itemsToPlace} to start looking at.
+         * @param weightLoss  total weight loss upto this point
+         * @param moveCost    total move cost upto this point
          * @param itemsPlaced all the items already placed upto this point
          */
         public void find(int index, float weightLoss, float moveCost,
@@ -481,11 +489,11 @@
                         float newMoveCost = moveCost;
                         if (x != myX) {
                             me.cellX = x;
-                            newMoveCost ++;
+                            newMoveCost++;
                         }
                         if (y != myY) {
                             me.cellY = y;
-                            newMoveCost ++;
+                            newMoveCost++;
                         }
                         if (ignoreMove) {
                             newMoveCost = moveCost;
@@ -500,35 +508,35 @@
 
                         // Try resizing horizontally
                         if (myW > me.minSpanX && occupied.isRegionVacant(x, y, myW - 1, myH)) {
-                            me.spanX --;
+                            me.spanX--;
                             occupied.markCells(me, true);
                             // 1 extra move cost
                             find(index + 1, weightLoss, newMoveCost + 1, itemsIncludingMe);
                             occupied.markCells(me, false);
-                            me.spanX ++;
+                            me.spanX++;
                         }
 
                         // Try resizing vertically
                         if (myH > me.minSpanY && occupied.isRegionVacant(x, y, myW, myH - 1)) {
-                            me.spanY --;
+                            me.spanY--;
                             occupied.markCells(me, true);
                             // 1 extra move cost
                             find(index + 1, weightLoss, newMoveCost + 1, itemsIncludingMe);
                             occupied.markCells(me, false);
-                            me.spanY ++;
+                            me.spanY++;
                         }
 
                         // Try resizing horizontally & vertically
                         if (myH > me.minSpanY && myW > me.minSpanX &&
                                 occupied.isRegionVacant(x, y, myW - 1, myH - 1)) {
-                            me.spanX --;
-                            me.spanY --;
+                            me.spanX--;
+                            me.spanY--;
                             occupied.markCells(me, true);
                             // 2 extra move cost
                             find(index + 1, weightLoss, newMoveCost + 2, itemsIncludingMe);
                             occupied.markCells(me, false);
-                            me.spanX ++;
-                            me.spanY ++;
+                            me.spanX++;
+                            me.spanY++;
                         }
                         me.cellX = myX;
                         me.cellY = myY;
@@ -565,11 +573,11 @@
                     float newMoveCost = moveCost;
                     if (newX != myX) {
                         me.cellX = newX;
-                        newMoveCost ++;
+                        newMoveCost++;
                     }
                     if (newY != myY) {
                         me.cellY = newY;
-                        newMoveCost ++;
+                        newMoveCost++;
                     }
                     if (ignoreMove) {
                         newMoveCost = moveCost;
@@ -602,7 +610,7 @@
     }
 
     private ArrayList<DbEntry> loadHotseatEntries() {
-        Cursor c =  queryWorkspace(
+        Cursor c = queryWorkspace(
                 new String[]{
                         Favorites._ID,                  // 0
                         Favorites.ITEM_TYPE,            // 1
@@ -787,7 +795,7 @@
     }
 
     protected Cursor queryWorkspace(String[] columns, String where) {
-        return mDb.query(Favorites.TABLE_NAME, columns, where, null, null, null, null);
+        return mDb.query(mTableName, columns, where, null, null, null, null);
     }
 
     /**
@@ -879,24 +887,44 @@
     }
 
     /**
-     * Migrates the workspace and hotseat in case their sizes changed.
+     * Check given a new IDP, if migration is necessary.
+     */
+    public static boolean needsToMigrate(Context context, InvariantDeviceProfile idp) {
+        SharedPreferences prefs = Utilities.getPrefs(context);
+        String gridSizeString = getPointString(idp.numColumns, idp.numRows);
+
+        return !gridSizeString.equals(prefs.getString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, ""))
+                || idp.numHotseatIcons != prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT,
+                idp.numHotseatIcons);
+    }
+
+    /** See {@link #migrateGridIfNeeded(Context, InvariantDeviceProfile)} */
+    public static boolean migrateGridIfNeeded(Context context) {
+        if (context instanceof LauncherPreviewRenderer.PreviewContext) {
+            return true;
+        }
+        return migrateGridIfNeeded(context, null);
+    }
+
+    /**
+     * 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.
      *
      * @return false if the migration failed.
      */
-    public static boolean migrateGridIfNeeded(Context context) {
-        SharedPreferences prefs = Utilities.getPrefs(context);
-        InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
+    public static boolean migrateGridIfNeeded(Context context, InvariantDeviceProfile idp) {
+        boolean migrateForPreview = idp != null;
+        if (!migrateForPreview) {
+            idp = LauncherAppState.getIDP(context);
+        }
 
-        String gridSizeString = getPointString(idp.numColumns, idp.numRows);
-
-        if (gridSizeString.equals(prefs.getString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, "")) &&
-                idp.numHotseatIcons == prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT,
-                        idp.numHotseatIcons)) {
-            // Skip if workspace and hotseat sizes have not changed.
+        if (!needsToMigrate(context, idp)) {
             return true;
         }
 
-        long migrationStartTime = System.currentTimeMillis();
+        SharedPreferences prefs = Utilities.getPrefs(context);
+        String gridSizeString = getPointString(idp.numColumns, idp.numRows);
+        long migrationStartTime = SystemClock.elapsedRealtime();
         try (SQLiteTransaction transaction = (SQLiteTransaction) Settings.call(
                 context.getContentResolver(), Settings.METHOD_NEW_TRANSACTION)
                 .getBinder(Settings.EXTRA_VALUE)) {
@@ -907,33 +935,39 @@
                     KEY_MIGRATION_SRC_WORKSPACE_SIZE, gridSizeString));
 
             boolean dbChanged = false;
+            if (migrateForPreview) {
+                copyTable(transaction.getDb(), Favorites.TABLE_NAME, Favorites.PREVIEW_TABLE_NAME,
+                        context);
+            }
 
             GridBackupTable backupTable = new GridBackupTable(context, transaction.getDb(),
                     srcHotseatCount, sourceSize.x, sourceSize.y);
-            if (backupTable.backupOrRestoreAsNeeded()) {
+            if (migrateForPreview ? backupTable.restoreToPreviewIfBackupExists()
+                    : backupTable.backupOrRestoreAsNeeded()) {
                 dbChanged = true;
                 srcHotseatCount = backupTable.getRestoreHotseatAndGridSize(sourceSize);
             }
 
             HashSet<String> validPackages = getValidPackages(context);
-            // Hotseat
+            // Hotseat.
             if (srcHotseatCount != idp.numHotseatIcons) {
                 // Migrate hotseat.
-                dbChanged = new GridSizeMigrationTask(context, transaction.getDb(),
-                        validPackages, srcHotseatCount, idp.numHotseatIcons).migrateHotseat();
+                dbChanged = new GridSizeMigrationTask(context, transaction.getDb(), validPackages,
+                        migrateForPreview, srcHotseatCount, idp.numHotseatIcons).migrateHotseat();
             }
 
             // Grid size
             Point targetSize = new Point(idp.numColumns, idp.numRows);
-            if (new MultiStepMigrationTask(validPackages, context, transaction.getDb())
-                    .migrate(sourceSize, targetSize)) {
+            if (new MultiStepMigrationTask(validPackages, context, transaction.getDb(),
+                    migrateForPreview).migrate(sourceSize, targetSize)) {
                 dbChanged = true;
             }
 
             if (dbChanged) {
                 // Make sure we haven't removed everything.
                 final Cursor c = context.getContentResolver().query(
-                        Favorites.CONTENT_URI, null, null, null, null);
+                        migrateForPreview ? Favorites.PREVIEW_CONTENT_URI : Favorites.CONTENT_URI,
+                        null, null, null, null);
                 boolean hasData = c.moveToNext();
                 c.close();
                 if (!hasData) {
@@ -942,21 +976,25 @@
             }
 
             transaction.commit();
-            Settings.call(context.getContentResolver(), Settings.METHOD_REFRESH_BACKUP_TABLE);
+            if (!migrateForPreview) {
+                Settings.call(context.getContentResolver(), Settings.METHOD_REFRESH_BACKUP_TABLE);
+            }
             return true;
         } catch (Exception e) {
-            Log.e(TAG, "Error during grid migration", e);
+            Log.e(TAG, "Error during preview grid migration", e);
 
             return false;
         } finally {
-            Log.v(TAG, "Workspace migration completed in "
-                    + (System.currentTimeMillis() - migrationStartTime));
+            Log.v(TAG, "Preview workspace migration completed in "
+                    + (SystemClock.elapsedRealtime() - 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();
+            }
         }
     }
 
@@ -989,7 +1027,7 @@
                 .getBinder(Settings.EXTRA_VALUE)) {
             GridSizeMigrationTask task = new GridSizeMigrationTask(
                     context, transaction.getDb(), getValidPackages(context),
-                    Integer.MAX_VALUE, Integer.MAX_VALUE);
+                    false /* usePreviewTable */, Integer.MAX_VALUE, Integer.MAX_VALUE);
 
             // Load all the valid entries
             ArrayList<DbEntry> items = task.loadHotseatEntries();
@@ -1011,12 +1049,14 @@
         private final HashSet<String> mValidPackages;
         private final Context mContext;
         private final SQLiteDatabase mDb;
+        private final boolean mUsePreviewTable;
 
         public MultiStepMigrationTask(HashSet<String> validPackages, Context context,
-                SQLiteDatabase db) {
+                SQLiteDatabase db, boolean usePreviewTable) {
             mValidPackages = validPackages;
             mContext = context;
             mDb = db;
+            mUsePreviewTable = usePreviewTable;
         }
 
         public boolean migrate(Point sourceSize, Point targetSize) throws Exception {
@@ -1052,8 +1092,8 @@
         }
 
         protected boolean runStepTask(Point sourceSize, Point nextSize) throws Exception {
-            return new GridSizeMigrationTask(mContext, mDb,
-                    mValidPackages, sourceSize, nextSize).migrateWorkspace();
+            return new GridSizeMigrationTask(mContext, mDb, mValidPackages, mUsePreviewTable,
+                    sourceSize, nextSize).migrateWorkspace();
         }
     }
 }
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
index 63b7191..197b29c 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
@@ -18,6 +18,8 @@
 
 import android.content.Context;
 
+import com.android.launcher3.InvariantDeviceProfile;
+
 /**
  * This class takes care of shrinking the workspace (by maximum of one row and one column), as a
  * result of restoring from a larger device or device density change.
@@ -28,12 +30,19 @@
 
     }
 
+    /** See {@link #migrateGridIfNeeded(Context, InvariantDeviceProfile)} */
+    public static boolean migrateGridIfNeeded(Context context) {
+        // To be implemented.
+        return true;
+    }
+
     /**
-     * Migrates the workspace and hotseat in case their sizes changed.
+     * 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.
      *
      * @return false if the migration failed.
      */
-    public static boolean migrateGridIfNeeded(Context context) {
+    public static boolean migrateGridIfNeeded(Context context, InvariantDeviceProfile idp) {
         // To be implemented.
         return true;
     }
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 4961432..2311dcc 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -28,6 +28,7 @@
 import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.database.CursorWrapper;
+import android.net.Uri;
 import android.os.UserHandle;
 import android.provider.BaseColumns;
 import android.text.TextUtils;
@@ -64,6 +65,7 @@
 
     public final LongSparseArray<UserHandle> allUsers = new LongSparseArray<>();
 
+    private final Uri mContentUri;
     private final Context mContext;
     private final PackageManager mPM;
     private final IconCache mIconCache;
@@ -96,8 +98,10 @@
     public int itemType;
     public int restoreFlag;
 
-    public LoaderCursor(Cursor c, LauncherAppState app) {
-        super(c);
+    public LoaderCursor(Cursor cursor, Uri contentUri, LauncherAppState app) {
+        super(cursor);
+
+        mContentUri = contentUri;
         mContext = app.getContext();
         mIconCache = app.getIconCache();
         mIDP = app.getInvariantDeviceProfile();
@@ -312,9 +316,8 @@
     public boolean commitDeleted() {
         if (itemsToRemove.size() > 0) {
             // Remove dead items
-            mContext.getContentResolver().delete(LauncherSettings.Favorites.CONTENT_URI,
-                    Utilities.createDbSelectionQuery(
-                            LauncherSettings.Favorites._ID, itemsToRemove), null);
+            mContext.getContentResolver().delete(mContentUri, Utilities.createDbSelectionQuery(
+                    LauncherSettings.Favorites._ID, itemsToRemove), null);
             return true;
         }
         return false;
@@ -339,7 +342,7 @@
             // Update restored items that no longer require special handling
             ContentValues values = new ContentValues();
             values.put(LauncherSettings.Favorites.RESTORED, 0);
-            mContext.getContentResolver().update(LauncherSettings.Favorites.CONTENT_URI, values,
+            mContext.getContentResolver().update(mContentUri, values,
                     Utilities.createDbSelectionQuery(
                             LauncherSettings.Favorites._ID, restoredRows), null);
         }
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 6223a23..fbf01fc 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -36,6 +36,7 @@
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageInstaller.SessionInfo;
 import android.content.pm.ShortcutInfo;
+import android.net.Uri;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.text.TextUtils;
@@ -76,7 +77,6 @@
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.shortcuts.ShortcutRequest;
 import com.android.launcher3.shortcuts.ShortcutRequest.QueryResult;
-import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.IOUtils;
 import com.android.launcher3.util.LooperIdleLock;
@@ -106,7 +106,7 @@
 
     private final LauncherAppState mApp;
     private final AllAppsList mBgAllAppsList;
-    private final BgDataModel mBgDataModel;
+    protected final BgDataModel mBgDataModel;
 
     private FirstScreenBroadcast mFirstScreenBroadcast;
 
@@ -284,6 +284,10 @@
 
     @VisibleForTesting
     void loadWorkspace(List<ShortcutInfo> allDeepShortcuts) {
+        loadWorkspace(allDeepShortcuts, LauncherSettings.Favorites.CONTENT_URI);
+    }
+
+    protected void loadWorkspace(List<ShortcutInfo> allDeepShortcuts, Uri contentUri) {
         final Context context = mApp.getContext();
         final ContentResolver contentResolver = context.getContentResolver();
         final PackageManagerHelper pmHelper = new PackageManagerHelper(context);
@@ -327,8 +331,8 @@
             mFirstScreenBroadcast = new FirstScreenBroadcast(installingPkgs);
 
             Map<ShortcutKey, ShortcutInfo> shortcutKeyToPinnedShortcuts = new HashMap<>();
-            final LoaderCursor c = new LoaderCursor(contentResolver.query(
-                    LauncherSettings.Favorites.CONTENT_URI, null, null, null, null), mApp);
+            final LoaderCursor c = new LoaderCursor(
+                    contentResolver.query(contentUri, null, null, null, null), contentUri, mApp);
 
             Map<ComponentKey, AppWidgetProviderInfo> widgetProvidersMap = null;
 
diff --git a/src/com/android/launcher3/model/PackageItemInfo.java b/src/com/android/launcher3/model/PackageItemInfo.java
index 3ef48cd..2fc064c 100644
--- a/src/com/android/launcher3/model/PackageItemInfo.java
+++ b/src/com/android/launcher3/model/PackageItemInfo.java
@@ -19,6 +19,8 @@
 import com.android.launcher3.ItemInfoWithIcon;
 import com.android.launcher3.LauncherSettings;
 
+import java.util.Objects;
+
 /**
  * Represents a {@link Package} in the widget tray section.
  */
@@ -48,4 +50,17 @@
     public PackageItemInfo clone() {
         return new PackageItemInfo(this);
     }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        PackageItemInfo that = (PackageItemInfo) o;
+        return Objects.equals(packageName, that.packageName);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(packageName);
+    }
 }
diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java
index d9bd714..1c2acfd 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -32,6 +32,7 @@
 import android.graphics.Rect;
 import android.graphics.drawable.ShapeDrawable;
 import android.util.AttributeSet;
+import android.util.Pair;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -358,6 +359,11 @@
         }
     }
 
+    @Override
+    protected Pair<View, String> getAccessibilityTarget() {
+        return Pair.create(this, "");
+    }
+
     private void animateOpen() {
         setVisibility(View.VISIBLE);
 
@@ -413,6 +419,8 @@
         }
         if (getOutlineProvider() instanceof RevealOutlineAnimation) {
             ((RevealOutlineAnimation) getOutlineProvider()).getOutline(mEndRect);
+        } else {
+            mEndRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
         }
         if (mOpenCloseAnimator != null) {
             mOpenCloseAnimator.cancel();
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 05ea694..445acca 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -37,7 +37,6 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.util.AttributeSet;
-import android.util.Pair;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
@@ -330,11 +329,6 @@
     }
 
     @Override
-    protected Pair<View, String> getAccessibilityTarget() {
-        return Pair.create(this, "");
-    }
-
-    @Override
     protected void getTargetObjectLocation(Rect outPos) {
         getPopupContainer().getDescendantRectRelativeToSelf(mOriginalIcon, outPos);
         outPos.top += mOriginalIcon.getPaddingTop();
diff --git a/src/com/android/launcher3/provider/LauncherDbUtils.java b/src/com/android/launcher3/provider/LauncherDbUtils.java
index 2c843f9..f7ecc3f 100644
--- a/src/com/android/launcher3/provider/LauncherDbUtils.java
+++ b/src/com/android/launcher3/provider/LauncherDbUtils.java
@@ -22,10 +22,12 @@
 import android.database.DatabaseUtils;
 import android.database.sqlite.SQLiteDatabase;
 import android.os.Binder;
+import android.os.Process;
 import android.util.Log;
 
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.util.IntArray;
 
 import java.util.Locale;
@@ -116,6 +118,15 @@
         db.execSQL("DROP TABLE IF EXISTS " + tableName);
     }
 
+    /** Copy from table to the to table. */
+    public static void copyTable(SQLiteDatabase db, String from, String to, Context context) {
+        long userSerial = UserCache.INSTANCE.get(context).getSerialNumberForUser(
+                Process.myUserHandle());
+        dropTable(db, to);
+        Favorites.addTableToDb(db, userSerial, false, to);
+        db.execSQL("INSERT INTO " + to + " SELECT * FROM " + from);
+    }
+
     /**
      * Utility class to simplify managing sqlite transactions
      */
diff --git a/src/com/android/launcher3/provider/LossyScreenMigrationTask.java b/src/com/android/launcher3/provider/LossyScreenMigrationTask.java
index 6d839f3..c0111b9 100644
--- a/src/com/android/launcher3/provider/LossyScreenMigrationTask.java
+++ b/src/com/android/launcher3/provider/LossyScreenMigrationTask.java
@@ -18,7 +18,6 @@
 
 import android.content.ContentValues;
 import android.content.Context;
-import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.graphics.Point;
 
@@ -43,7 +42,7 @@
     protected LossyScreenMigrationTask(
             Context context, InvariantDeviceProfile idp, SQLiteDatabase db) {
         // Decrease the rows count by 1
-        super(context, db, getValidPackages(context),
+        super(context, db, getValidPackages(context), false /* usePreviewTable */,
                 new Point(idp.numColumns, idp.numRows + 1),
                 new Point(idp.numColumns, idp.numRows));
 
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index f995c61..97ce65e 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -89,6 +89,8 @@
     public static final String REQUEST_ENABLE_DEBUG_TRACING = "enable-debug-tracing";
     public static final String REQUEST_DISABLE_DEBUG_TRACING = "disable-debug-tracing";
 
+    public static final String REQUEST_OVERVIEW_ACTIONS_ENABLED = "overview-actions-enabled";
+
     public static final String PERMANENT_DIAG_TAG = "TaplTarget";
 
     public static final String NO_BACKGROUND_TO_OVERVIEW_TAG = "b/138251824";
diff --git a/src/com/android/launcher3/util/MainThreadInitializedObject.java b/src/com/android/launcher3/util/MainThreadInitializedObject.java
index 520a9ed..fc9f8f7 100644
--- a/src/com/android/launcher3/util/MainThreadInitializedObject.java
+++ b/src/com/android/launcher3/util/MainThreadInitializedObject.java
@@ -22,6 +22,7 @@
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.launcher3.graphics.LauncherPreviewRenderer.PreviewContext;
 import com.android.launcher3.util.ResourceBasedOverride.Overrides;
 
 import java.util.concurrent.ExecutionException;
@@ -39,6 +40,10 @@
     }
 
     public T get(Context context) {
+        if (context instanceof PreviewContext) {
+            return ((PreviewContext) context).getObject(this, mProvider);
+        }
+
         if (mValue == null) {
             if (Looper.myLooper() == Looper.getMainLooper()) {
                 mValue = TraceHelper.whitelistIpcs("main.thread.object",
diff --git a/src/com/android/launcher3/views/AbstractSlideInView.java b/src/com/android/launcher3/views/AbstractSlideInView.java
index 9334c46..bdba39c 100644
--- a/src/com/android/launcher3/views/AbstractSlideInView.java
+++ b/src/com/android/launcher3/views/AbstractSlideInView.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.views;
 
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+
 import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
 
 import android.animation.Animator;
@@ -62,6 +64,7 @@
     protected final ObjectAnimator mOpenCloseAnimator;
 
     protected View mContent;
+    private final View mColorScrim;
     protected Interpolator mScrollInterpolator;
 
     // range [0, 1], 0=> completely open, 1=> completely closed
@@ -85,11 +88,30 @@
                 announceAccessibilityChanges();
             }
         });
+        int scrimColor = getScrimColor(mLauncher);
+        mColorScrim = scrimColor != -1 ? createColorScrim(mLauncher, scrimColor) : null;
+    }
+
+    protected void attachToContainer() {
+        if (mColorScrim != null) {
+            getPopupContainer().addView(mColorScrim);
+        }
+        getPopupContainer().addView(this);
+    }
+
+    /**
+     * Returns a scrim color for a sliding view. if returned value is -1, no scrim is added.
+     */
+    protected int getScrimColor(Context context) {
+        return -1;
     }
 
     protected void setTranslationShift(float translationShift) {
         mTranslationShift = translationShift;
         mContent.setTranslationY(mTranslationShift * mContent.getHeight());
+        if (mColorScrim != null) {
+            mColorScrim.setAlpha(1 - mTranslationShift);
+        }
     }
 
     @Override
@@ -127,7 +149,8 @@
     /* SingleAxisSwipeDetector.Listener */
 
     @Override
-    public void onDragStart(boolean start) { }
+    public void onDragStart(boolean start) {
+    }
 
     @Override
     public boolean onDrag(float displacement) {
@@ -185,9 +208,25 @@
     protected void onCloseComplete() {
         mIsOpen = false;
         getPopupContainer().removeView(this);
+        if (mColorScrim != null) {
+            getPopupContainer().removeView(mColorScrim);
+        }
     }
 
     protected BaseDragLayer getPopupContainer() {
         return mLauncher.getDragLayer();
     }
+
+
+    protected static View createColorScrim(Context context, int bgColor) {
+        View view = new View(context);
+        view.forceHasOverlappingRendering(false);
+        view.setBackgroundColor(bgColor);
+
+        BaseDragLayer.LayoutParams lp = new BaseDragLayer.LayoutParams(MATCH_PARENT, MATCH_PARENT);
+        lp.ignoreInsets = true;
+        view.setLayoutParams(lp);
+
+        return view;
+    }
 }
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index cae2c3a..254655c 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -29,7 +29,6 @@
 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;
@@ -41,8 +40,8 @@
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.InsettableFrameLayout;
+import com.android.launcher3.Launcher;
 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.TouchController;
@@ -150,10 +149,20 @@
         return findActiveController(ev);
     }
 
+    private boolean isEventInLauncher(MotionEvent ev) {
+        final float x = ev.getX();
+        final float y = ev.getY();
+
+        return x >= mSystemGestureRegion.left && x < getWidth() - mSystemGestureRegion.right
+                && y >= mSystemGestureRegion.top && y < getHeight() - mSystemGestureRegion.bottom;
+    }
+
     private TouchController findControllerToHandleTouch(MotionEvent ev) {
-        AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
-        if (topView != null && topView.onControllerInterceptTouchEvent(ev)) {
-            return topView;
+        if (isEventInLauncher(ev)) {
+            AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
+            if (topView != null && topView.onControllerInterceptTouchEvent(ev)) {
+                return topView;
+            }
         }
 
         for (TouchController controller : mControllers) {
@@ -245,17 +254,12 @@
     public boolean dispatchTouchEvent(MotionEvent ev) {
         switch (ev.getAction()) {
             case ACTION_DOWN: {
-                float x = ev.getX();
-                float y = ev.getY();
                 mTouchDispatchState |= TOUCH_DISPATCHING_VIEW;
 
-                if ((y < mSystemGestureRegion.top
-                        || x < mSystemGestureRegion.left
-                        || x > (getWidth() - mSystemGestureRegion.right)
-                        || y > (getHeight() - mSystemGestureRegion.bottom))) {
-                    mTouchDispatchState |= TOUCH_DISPATCHING_GESTURE;
-                } else {
+                if (isEventInLauncher(ev)) {
                     mTouchDispatchState &= ~TOUCH_DISPATCHING_GESTURE;
+                } else {
+                    mTouchDispatchState |= TOUCH_DISPATCHING_GESTURE;
                 }
                 break;
             }
diff --git a/src/com/android/launcher3/views/WorkEduView.java b/src/com/android/launcher3/views/WorkEduView.java
index b6c81ae..81f8327 100644
--- a/src/com/android/launcher3/views/WorkEduView.java
+++ b/src/com/android/launcher3/views/WorkEduView.java
@@ -52,11 +52,15 @@
     private static final int WORK_EDU_PERSONAL_APPS = 1;
     private static final int WORK_EDU_WORK_APPS = 2;
 
+    protected static final int FINAL_SCRIM_BG_COLOR = 0x88000000;
+
+
     private Rect mInsets = new Rect();
     private View mViewWrapper;
     private Button mProceedButton;
     private TextView mContentText;
     private AllAppsPagedView mAllAppsPagedView;
+
     private int mNextWorkEduStep = WORK_EDU_PERSONAL_APPS;
 
 
@@ -141,10 +145,15 @@
     }
 
     private void show() {
-        mLauncher.getDragLayer().addView(this);
+        attachToContainer();
         animateOpen();
     }
 
+    @Override
+    protected int getScrimColor(Context context) {
+        return FINAL_SCRIM_BG_COLOR;
+    }
+
     private void goToFirstPage() {
         if (mAllAppsPagedView != null) {
             mAllAppsPagedView.snapToPageImmediately(AllAppsContainerView.AdapterHolder.MAIN);
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index 6cae43d..df1a469 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -15,8 +15,6 @@
  */
 package com.android.launcher3.widget;
 
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-
 import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
 import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
 
@@ -42,7 +40,6 @@
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.AbstractSlideInView;
-import com.android.launcher3.views.BaseDragLayer;
 
 /**
  * Base class for various widgets popup
@@ -55,11 +52,14 @@
     /* Touch handling related member variables. */
     private Toast mWidgetInstructionToast;
 
-    protected final View mColorScrim;
-
     public BaseWidgetSheet(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
-        mColorScrim = createColorScrim(context);
+    }
+
+    protected int getScrimColor(Context context) {
+        WallpaperColorInfo colors = WallpaperColorInfo.INSTANCE.get(context);
+        int alpha = context.getResources().getInteger(R.integer.extracted_color_gradient_alpha);
+        return setColorAlphaBound(colors.getSecondaryColor(), alpha);
     }
 
     @Override
@@ -98,16 +98,6 @@
         return true;
     }
 
-    protected void attachToContainer() {
-        getPopupContainer().addView(mColorScrim);
-        getPopupContainer().addView(this);
-    }
-
-    protected void setTranslationShift(float translationShift) {
-        super.setTranslationShift(translationShift);
-        mColorScrim.setAlpha(1 - mTranslationShift);
-    }
-
     private boolean beginDraggingWidget(WidgetCell v) {
         // Get the widget preview as the drag representation
         WidgetImageView image = v.getWidgetView();
@@ -138,7 +128,6 @@
 
     protected void onCloseComplete() {
         super.onCloseComplete();
-        getPopupContainer().removeView(mColorScrim);
         clearNavBarColor();
     }
 
@@ -177,19 +166,4 @@
     protected SystemUiController getSystemUiController() {
         return mLauncher.getSystemUiController();
     }
-
-    private static View createColorScrim(Context context) {
-        View view = new View(context);
-        view.forceHasOverlappingRendering(false);
-
-        WallpaperColorInfo colors = WallpaperColorInfo.INSTANCE.get(context);
-        int alpha = context.getResources().getInteger(R.integer.extracted_color_gradient_alpha);
-        view.setBackgroundColor(setColorAlphaBound(colors.getSecondaryColor(), alpha));
-
-        BaseDragLayer.LayoutParams lp = new BaseDragLayer.LayoutParams(MATCH_PARENT, MATCH_PARENT);
-        lp.ignoreInsets = true;
-        view.setLayoutParams(lp);
-
-        return view;
-    }
 }
diff --git a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
index 282867a..ae32692 100644
--- a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
+++ b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
@@ -6,6 +6,7 @@
 import static com.android.launcher3.pm.ShortcutConfigActivityInfo.queryList;
 
 import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.os.Process;
@@ -243,4 +244,16 @@
             }
         }
     }
+
+    public WidgetItem getWidgetProviderInfoByProviderName(
+            ComponentName providerName) {
+        ArrayList<WidgetItem> widgetsList = mWidgetsList.get(
+                new PackageItemInfo(providerName.getPackageName()));
+        for (WidgetItem item : widgetsList) {
+            if (item.componentName.equals(providerName)) {
+                return item;
+            }
+        }
+        return null;
+    }
 }
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 4ffc251..e93df96 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -22,10 +22,9 @@
 import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
-import static java.lang.System.exit;
-
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -38,7 +37,7 @@
 import android.content.pm.PackageManager;
 import android.os.Process;
 import android.os.RemoteException;
-import android.util.Log;
+import android.os.StrictMode;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.uiautomator.By;
@@ -97,11 +96,50 @@
     public static final long DEFAULT_UI_TIMEOUT = 10000;
     private static final String TAG = "AbstractLauncherUiTest";
 
+    private static String sDetectedActivityLeak;
+    private static boolean sActivityLeakReported;
+
     protected LooperExecutor mMainThreadExecutor = MAIN_EXECUTOR;
     protected final UiDevice mDevice = UiDevice.getInstance(getInstrumentation());
     protected final LauncherInstrumentation mLauncher = new LauncherInstrumentation();
     protected Context mTargetContext;
     protected String mTargetPackage;
+    private int mLauncherPid;
+
+    static {
+        if (TestHelpers.isInLauncherProcess()) {
+            StrictMode.VmPolicy.Builder builder =
+                    new StrictMode.VmPolicy.Builder()
+                            .detectActivityLeaks()
+                            .penaltyLog()
+                            .penaltyListener(Runnable::run, violation -> {
+                                // Runs in the main thread. We can't dumpheap in the main thread,
+                                // so let's just mark the fact that the leak has happened.
+                                if (sDetectedActivityLeak == null) {
+                                    sDetectedActivityLeak = violation.toString();
+                                }
+                            });
+            StrictMode.setVmPolicy(builder.build());
+        }
+    }
+
+    public static void checkDetectedLeaks() {
+        if (sDetectedActivityLeak != null && !sActivityLeakReported) {
+            sActivityLeakReported = true;
+
+            final UiDevice device = UiDevice.getInstance(getInstrumentation());
+            try {
+                device.executeShellCommand(
+                        "am dumpheap "
+                                + device.getLauncherPackageName()
+                                + " "
+                                + getInstrumentation().getTargetContext().getFilesDir().getPath()
+                                + "/ActivityLeakHeapDump.hprof");
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
 
     protected AbstractLauncherUiTest() {
         mLauncher.enableCheckEventsForSuccessfulGestures();
@@ -121,6 +159,8 @@
                                     checkLauncherIntegrity(launcher, containerType)));
         }
         mLauncher.enableDebugTracing();
+        // Avoid double-reporting of Launcher crashes.
+        mLauncher.setOnLauncherCrashed(() -> mLauncherPid = 0);
     }
 
     protected final LauncherActivityRule mActivityMonitor = new LauncherActivityRule();
@@ -158,7 +198,7 @@
 
         return TestHelpers.isInLauncherProcess()
                 ? RuleChain.outerRule(ShellCommandRule.setDefaultLauncher())
-                        .around(inner) :
+                .around(inner) :
                 inner;
     }
 
@@ -175,17 +215,23 @@
 
     @Before
     public void setUp() throws Exception {
+        mLauncherPid = 0;
         // Disable app tracker
         AppLaunchTracker.INSTANCE.initializeForTesting(new AppLaunchTracker());
 
         mTargetContext = InstrumentationRegistry.getTargetContext();
         mTargetPackage = mTargetContext.getPackageName();
+        mLauncherPid = mLauncher.getPid();
     }
 
     @After
     public void verifyLauncherState() {
         // Limits UI tests affecting tests running after them.
         mLauncher.waitForLauncherInitialized();
+        if (mLauncherPid != 0) {
+            assertEquals("Launcher crashed, pid mismatch:", mLauncherPid, mLauncher.getPid());
+        }
+        checkDetectedLeaks();
     }
 
     protected void clearLauncherData() throws IOException, InterruptedException {
@@ -196,6 +242,7 @@
         } else {
             clearPackageData(mDevice.getLauncherPackageName());
             mLauncher.enableDebugTracing();
+            mLauncherPid = mLauncher.getPid();
         }
     }
 
diff --git a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
index 1a68122..38f50c1 100644
--- a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
+++ b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
@@ -56,6 +56,7 @@
             private void evaluateInPortrait() throws Throwable {
                 mTest.mDevice.setOrientationNatural();
                 mTest.mLauncher.setExpectedRotation(Surface.ROTATION_0);
+                AbstractLauncherUiTest.checkDetectedLeaks();
                 base.evaluate();
                 mTest.getDevice().pressHome();
             }
@@ -63,6 +64,7 @@
             private void evaluateInLandscape() throws Throwable {
                 mTest.mDevice.setOrientationLeft();
                 mTest.mLauncher.setExpectedRotation(Surface.ROTATION_90);
+                AbstractLauncherUiTest.checkDetectedLeaks();
                 base.evaluate();
                 mTest.getDevice().pressHome();
             }
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 54caf1e..f8bbf21 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -40,9 +40,7 @@
 import com.android.launcher3.widget.WidgetsFullSheet;
 import com.android.launcher3.widget.WidgetsRecyclerView;
 
-import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -51,21 +49,10 @@
 public class TaplTestsLauncher3 extends AbstractLauncherUiTest {
     private static final String APP_NAME = "LauncherTestApp";
 
-    private int mLauncherPid;
-
     @Before
     public void setUp() throws Exception {
-        mLauncherPid = 0;
         super.setUp();
         initialize(this);
-        mLauncherPid = mLauncher.getPid();
-    }
-
-    @After
-    public void teardown() {
-        if (mLauncherPid != 0) {
-            assertEquals("Launcher crashed, pid mismatch:", mLauncherPid, mLauncher.getPid());
-        }
     }
 
     public static void initialize(AbstractLauncherUiTest test) throws Exception {
@@ -77,6 +64,7 @@
         test.waitForResumed("Launcher internal state is still Background");
         // Check that we switched to home.
         test.mLauncher.getWorkspace();
+        AbstractLauncherUiTest.checkDetectedLeaks();
     }
 
     // Please don't add negative test cases for methods that fail only after a long wait.
@@ -122,7 +110,6 @@
     }
 
     @Test
-    @Ignore
     public void testPressHomeOnAllAppsContextMenu() throws Exception {
         final AllApps allApps = mLauncher.getWorkspace().switchToAllApps();
         allApps.freeze();
diff --git a/tests/src/com/android/launcher3/ui/WorkTabTest.java b/tests/src/com/android/launcher3/ui/WorkTabTest.java
index 5aa0090..6fe6739 100644
--- a/tests/src/com/android/launcher3/ui/WorkTabTest.java
+++ b/tests/src/com/android/launcher3/ui/WorkTabTest.java
@@ -73,11 +73,10 @@
         mDevice.pressHome();
         waitForLauncherCondition("Launcher didn't start", Objects::nonNull);
         executeOnLauncher(launcher -> launcher.getStateManager().goToState(ALL_APPS));
-
         waitForLauncherCondition("Personal tab is missing",
-                launcher -> launcher.getAppsView().isPersonalTabVisible());
+                launcher -> launcher.getAppsView().isPersonalTabVisible(), 60000);
         waitForLauncherCondition("Work tab is missing",
-                launcher -> launcher.getAppsView().isWorkTabVisible());
+                launcher -> launcher.getAppsView().isWorkTabVisible(), 60000);
     }
 
     @Test
@@ -89,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());
+                launcher -> launcher.getAppsView().getWorkFooterContainer(), 60000);
 
         UserManager userManager = getFromLauncher(l -> l.getSystemService(UserManager.class));
         assertEquals(2, userManager.getUserProfiles().size());
diff --git a/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java b/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java
index 5880eb6..e4f520f 100644
--- a/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java
+++ b/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java
@@ -102,10 +102,15 @@
             logSinceBoot =
                     UiDevice.getInstance(getInstrumentation())
                             .executeShellCommand("logcat -d -t " + systemBootTime.replace(" ", ""));
-        } catch (IOException e) {
+        } catch (IOException | OutOfMemoryError e) {
             return 0;
         }
 
+        if (matches("android\\:\\:uirenderer\\:\\:renderthread\\:\\:EglManager\\:\\:swapBuffers",
+                logSinceBoot)) {
+            return 148529608;
+        }
+
         for (ExceptionMatch exceptionMatch : EXCEPTION_MATCHES) {
             if (matches(exceptionMatch.exceptionPattern, exception)) {
                 for (LogcatMatch logcatMatch : exceptionMatch.logcatMatches) {
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 60ec3a4..6b2fb49 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -69,22 +69,16 @@
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.lang.ref.WeakReference;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Date;
 import java.util.Deque;
-import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.Map;
 import java.util.concurrent.TimeoutException;
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Supplier;
-import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
@@ -97,16 +91,8 @@
     private static final String TAG = "Tapl";
     private static final int ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME = 20;
     private static final int GESTURE_STEP_MS = 16;
-    private static final SimpleDateFormat DATE_TIME_FORMAT =
-            new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
     private static long START_TIME = System.currentTimeMillis();
 
-    static final Pattern EVENT_LOG_ENTRY = Pattern.compile(
-            "(?<time>[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] "
-                    + "[0-9][0-9]:[0-9][0-9]:[0-9][0-9]\\.[0-9][0-9][0-9])"
-                    + ".*" + TestProtocol.TAPL_EVENTS_TAG + ": (?<sequence>[a-zA-Z]+) / "
-                    + "(?<event>.*)");
-
     private static final Pattern EVENT_TOUCH_DOWN = getTouchEventPattern("ACTION_DOWN");
     private static final Pattern EVENT_TOUCH_UP = getTouchEventPattern("ACTION_UP");
     private static final Pattern EVENT_TOUCH_CANCEL = getTouchEventPattern("ACTION_CANCEL");
@@ -159,7 +145,7 @@
 
     private static final String WORKSPACE_RES_ID = "workspace";
     private static final String APPS_RES_ID = "apps_view";
-    private static final String OVERVIEW_RES_ID = "overview_panel";
+    private static final String OVERVIEW_RES_ID = "overview_panel_recents";
     private static final String WIDGETS_RES_ID = "widgets_list_view";
     private static final String CONTEXT_MENU_RES_ID = "deep_shortcuts_container";
     public static final int WAIT_TIME_MS = 10000;
@@ -176,12 +162,13 @@
 
     private Consumer<ContainerType> mOnSettledStateAction;
 
-    // Map from an event sequence name to an ordered list of expected events in that sequence.
-    // Not null when we are collecting expected events to compare with actual ones.
-    private Map<String, List<Pattern>> mExpectedEvents;
+    private static LogEventChecker sEventChecker;
+    // True if there is an gesture in progress that needs event verification.
+    private static boolean sCheckingEvents;
 
-    private Date mStartRecordingTime;
     private boolean mCheckEventsForSuccessfulGestures = false;
+    private int mExpectedPid;
+    private Runnable mOnLauncherCrashed;
 
     private static Pattern getTouchEventPattern(String prefix, String action) {
         // The pattern includes sanity checks that we don't get a multi-touch events or other
@@ -264,6 +251,10 @@
         mCheckEventsForSuccessfulGestures = true;
     }
 
+    public void setOnLauncherCrashed(Runnable onLauncherCrashed) {
+        mOnLauncherCrashed = onLauncherCrashed;
+    }
+
     Context getContext() {
         return mInstrumentation.getContext();
     }
@@ -339,7 +330,7 @@
         }
     }
 
-    private String getAnomalyMessage() {
+    private String getSystemAnomalyMessage() {
         UiObject2 object = mDevice.findObject(By.res("android", "alertTitle"));
         if (object != null) {
             return "System alert popup is visible: " + object.getText();
@@ -358,15 +349,33 @@
         return null;
     }
 
+    private String getAnomalyMessage() {
+        if (mExpectedPid != 0 && mExpectedPid != getPid()) {
+            mExpectedPid = 0;
+            if (mOnLauncherCrashed != null) mOnLauncherCrashed.run();
+            return "Launcher crashed";
+        }
+
+        final String systemAnomalyMessage = getSystemAnomalyMessage();
+        if (systemAnomalyMessage != null) {
+            return "http://go/tapl : Tests are broken by a non-Launcher system error: "
+                    + systemAnomalyMessage;
+        }
+
+        return null;
+    }
+
     public void checkForAnomaly() {
         final String anomalyMessage = getAnomalyMessage();
         if (anomalyMessage != null) {
-            String message = "http://go/tapl : Tests are broken by a non-Launcher system error: "
-                    + anomalyMessage;
-            log("Hierarchy dump for: " + message);
+            if (sCheckingEvents) {
+                sCheckingEvents = false;
+                sEventChecker.finishNoWait();
+            }
+            log("Hierarchy dump for: " + anomalyMessage);
             dumpViewHierarchy();
 
-            Assert.fail(formatSystemHealthMessage(message));
+            Assert.fail(formatSystemHealthMessage(anomalyMessage));
         }
     }
 
@@ -434,10 +443,12 @@
         log("Hierarchy dump for: " + message);
         dumpViewHierarchy();
 
-        final String eventMismatch = getEventMismatchMessage(false);
-
-        if (eventMismatch != null) {
-            message = message + ", having produced " + eventMismatch;
+        if (sCheckingEvents) {
+            sCheckingEvents = false;
+            final String eventMismatch = sEventChecker.verify(0);
+            if (eventMismatch != null) {
+                message = message + ", having produced " + eventMismatch;
+            }
         }
 
         Assert.fail(formatSystemHealthMessage(message));
@@ -554,7 +565,7 @@
                     return waitForLauncherObject(APPS_RES_ID);
                 }
                 case OVERVIEW: {
-                    if (mDevice.isNaturalOrientation()) {
+                    if (hasAllAppsInOverview()) {
                         waitForLauncherObject(APPS_RES_ID);
                     } else {
                         waitUntilGone(APPS_RES_ID);
@@ -663,14 +674,6 @@
                     mDevice.waitForIdle();
 
                     if (getNavigationModel() == NavigationModel.TWO_BUTTON) {
-                        if (hasLauncherObject(CONTEXT_MENU_RES_ID) ||
-                                hasLauncherObject(WIDGETS_RES_ID)
-                                        && !mDevice.isNaturalOrientation()) {
-                            expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_PILFER_POINTERS);
-                        }
-                    }
-
-                    if (getNavigationModel() == NavigationModel.TWO_BUTTON) {
                         expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_DOWN_TIS);
                         expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_UP_TIS);
                     }
@@ -1210,6 +1213,28 @@
         getTestInfo(TestProtocol.REQUEST_ENABLE_DEBUG_TRACING);
     }
 
+    public boolean hasAllAppsInOverview() {
+        // Vertical bar layouts don't contain all apps
+        if (!mDevice.isNaturalOrientation()) {
+            return false;
+        }
+        // Portrait two button (quickstep) always has all apps.
+        if (getNavigationModel() == NavigationModel.TWO_BUTTON) {
+            return true;
+        }
+        // Overview actions hide all apps
+        if (overviewActionsEnabled()) {
+            return false;
+        }
+        // ...otherwise there should be all apps
+        return true;
+    }
+
+    private boolean overviewActionsEnabled() {
+        return getTestInfo(TestProtocol.REQUEST_OVERVIEW_ACTIONS_ENABLED).getBoolean(
+                TestProtocol.TEST_INFO_RESPONSE_FIELD);
+    }
+
     public void disableDebugTracing() {
         getTestInfo(TestProtocol.REQUEST_DISABLE_DEBUG_TRACING);
     }
@@ -1245,204 +1270,37 @@
         return tasks;
     }
 
-    // Returns actual events retrieved from logcat. The return value's key set is the set of all
-    // sequence names that actually had at least one event, and the values are lists of events in
-    // the given sequence, in the order they were recorded.
-    private Map<String, List<String>> getEvents() {
-        final Map<String, List<String>> events = new HashMap<>();
-        try {
-            // Logcat may skip events after the specified time. Querying for events starting 1 min
-            // earlier.
-            final Date startTime = new Date(mStartRecordingTime.getTime() - 60000);
-            final String logcatCommand = "logcat -d -v year --pid=" + getPid() + " -t "
-                    + DATE_TIME_FORMAT.format(startTime).replaceAll(" ", "")
-                    + " -s " + TestProtocol.TAPL_EVENTS_TAG;
-            log("Events query command: " + logcatCommand);
-            final String logcatEvents = mDevice.executeShellCommand(logcatCommand);
-            final Matcher matcher = EVENT_LOG_ENTRY.matcher(logcatEvents);
-            while (matcher.find()) {
-                // Skip events before recording start time.
-                if (DATE_TIME_FORMAT.parse(matcher.group("time"))
-                        .compareTo(mStartRecordingTime) < 0) {
-                    continue;
-                }
-
-                eventsListForSequence(matcher.group("sequence"), events).add(
-                        matcher.group("event"));
-            }
-            return events;
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        } catch (ParseException e) {
-            throw new AssertionError(e);
-        }
-    }
-
-    // Returns an event list for a given sequence, adding it to the map as needed.
-    private static <T> List<T> eventsListForSequence(
-            String sequenceName, Map<String, List<T>> events) {
-        List<T> eventSequence = events.get(sequenceName);
-        if (eventSequence == null) {
-            eventSequence = new ArrayList<>();
-            events.put(sequenceName, eventSequence);
-        }
-        return eventSequence;
-    }
-
-    private void startRecordingEvents() {
-        Assert.assertTrue("Already recording events", mExpectedEvents == null);
-        mExpectedEvents = new HashMap<>();
-        mStartRecordingTime = new Date();
-        log("startRecordingEvents: " + DATE_TIME_FORMAT.format(mStartRecordingTime));
-    }
-
-    private void stopRecordingEvents() {
-        mExpectedEvents = null;
-        mStartRecordingTime = null;
-    }
-
     public Closable eventsCheck() {
+        Assert.assertTrue("Nested event checking", !sCheckingEvents);
         if ("com.android.launcher3".equals(getLauncherPackageName())) {
             // Not checking specific Launcher3 event sequences.
             return () -> {
             };
         }
-
-        // Entering events check block.
-        startRecordingEvents();
+        sCheckingEvents = true;
+        mExpectedPid = getPid();
+        if (sEventChecker == null) sEventChecker = new LogEventChecker();
+        sEventChecker.start();
 
         return () -> {
-            // Leaving events check block.
-            if (mExpectedEvents == null) {
-                return; // There was a failure. Noo need to report another one.
-            }
+            checkForAnomaly();
 
-            if (!mCheckEventsForSuccessfulGestures) {
-                stopRecordingEvents();
-                return;
-            }
-
-            final String message = getEventMismatchMessage(true);
-            if (message != null) {
-                Assert.fail(formatSystemHealthMessage(
-                        "http://go/tapl : successful gesture produced " + message));
+            if (sCheckingEvents) {
+                sCheckingEvents = false;
+                if (mCheckEventsForSuccessfulGestures) {
+                    final String message = sEventChecker.verify(WAIT_TIME_MS);
+                    if (message != null) {
+                        Assert.fail(formatSystemHealthMessage(
+                                "http://go/tapl : successful gesture produced " + message));
+                    }
+                } else {
+                    sEventChecker.finishNoWait();
+                }
             }
         };
     }
 
     void expectEvent(String sequence, Pattern expected) {
-        if (mExpectedEvents != null) {
-            eventsListForSequence(sequence, mExpectedEvents).add(expected);
-        }
-    }
-
-    // Returns non-null error message if the actual events in logcat don't match expected events.
-    // If we are not checking events, returns null.
-    private String getEventMismatchMessage(boolean waitForExpectedCount) {
-        if (mExpectedEvents == null) return null;
-
-        try {
-            Map<String, List<String>> actual = getEvents();
-
-            if (waitForExpectedCount) {
-                // Wait until Launcher generates the expected number of events.
-                final long endTime = SystemClock.uptimeMillis() + WAIT_TIME_MS;
-                while (SystemClock.uptimeMillis() < endTime
-                        && !receivedEnoughEvents(actual)) {
-                    SystemClock.sleep(100);
-                    actual = getEvents();
-                }
-            }
-
-            return getEventMismatchErrorMessage(actual);
-        } finally {
-            stopRecordingEvents();
-        }
-    }
-
-    // Returns whether there is a sufficient number of events in the logcat to match the expected
-    // events.
-    private boolean receivedEnoughEvents(Map<String, List<String>> actual) {
-        for (Map.Entry<String, List<Pattern>> expectedNamedSequence : mExpectedEvents.entrySet()) {
-            final List<String> actualEventSequence = actual.get(expectedNamedSequence.getKey());
-            if (actualEventSequence == null
-                    || actualEventSequence.size() < expectedNamedSequence.getValue().size()) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    // If the list of actual events matches the list of expected events, returns -1, otherwise
-    // the position of the mismatch.
-    private static int getMismatchPosition(List<Pattern> expected, List<String> actual) {
-        for (int i = 0; i < expected.size(); ++i) {
-            if (i >= actual.size()
-                    || !expected.get(i).matcher(actual.get(i)).find()) {
-                return i;
-            }
-        }
-
-        if (actual.size() > expected.size()) return expected.size();
-
-        return -1;
-    }
-
-    // Returns non-null error message if the actual events passed as a param don't match expected
-    // events.
-    private String getEventMismatchErrorMessage(Map<String, List<String>> actualEvents) {
-        final StringBuilder sb = new StringBuilder();
-
-        // Check that all expected even sequences match the actual data.
-        for (Map.Entry<String, List<Pattern>> expectedNamedSequence : mExpectedEvents.entrySet()) {
-            List<String> actualEventSequence = actualEvents.get(expectedNamedSequence.getKey());
-            if (actualEventSequence == null) actualEventSequence = new ArrayList<>();
-            final int mismatchPosition = getMismatchPosition(
-                    expectedNamedSequence.getValue(), actualEventSequence);
-            if (mismatchPosition != -1) {
-                formatSequenceWithMismatch(
-                        sb,
-                        expectedNamedSequence.getKey(),
-                        expectedNamedSequence.getValue(),
-                        actualEventSequence,
-                        mismatchPosition);
-            }
-        }
-
-        // Check for unexpected event sequences in the actual data.
-        for (Map.Entry<String, List<String>> actualNamedSequence : actualEvents.entrySet()) {
-            if (!mExpectedEvents.containsKey(actualNamedSequence.getKey())) {
-                formatSequenceWithMismatch(
-                        sb,
-                        actualNamedSequence.getKey(),
-                        new ArrayList<>(),
-                        actualNamedSequence.getValue(),
-                        0);
-            }
-        }
-
-        return sb.length() != 0 ? "mismatching events: " + sb.toString() : null;
-    }
-
-    private static void formatSequenceWithMismatch(
-            StringBuilder sb,
-            String sequenceName,
-            List<Pattern> expected,
-            List<String> actualEvents,
-            int mismatchPosition) {
-        sb.append("\n>> Sequence " + sequenceName);
-        sb.append("\n  Expected:");
-        formatEventListWithMismatch(sb, expected, mismatchPosition);
-        sb.append("\n  Actual:");
-        formatEventListWithMismatch(sb, actualEvents, mismatchPosition);
-    }
-
-    private static void formatEventListWithMismatch(StringBuilder sb, List events, int position) {
-        for (int i = 0; i < events.size(); ++i) {
-            sb.append("\n  | ");
-            sb.append(i == position ? "---> " : "     ");
-            sb.append(events.get(i).toString());
-        }
-        if (position == events.size()) sb.append("\n  | ---> (end)");
+        if (sCheckingEvents) sEventChecker.expectPattern(sequence, expected);
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java b/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java
new file mode 100644
index 0000000..0fc88ee
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.tapl;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.util.Log;
+
+import com.android.launcher3.testing.TestProtocol;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Semaphore;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Utility class to read log on a background thread.
+ */
+public class LogEventChecker {
+
+    private static final Pattern EVENT_LOG_ENTRY = Pattern.compile(
+            ".*" + TestProtocol.TAPL_EVENTS_TAG + ": (?<sequence>[a-zA-Z]+) / (?<event>.*)");
+
+    private static final String START_PREFIX = "START_READER ";
+    private static final String FINISH_PREFIX = "FINISH_READER ";
+
+    private volatile CountDownLatch mFinished;
+
+    // Map from an event sequence name to an ordered list of expected events in that sequence.
+    private final ListMap<Pattern> mExpectedEvents = new ListMap<>();
+
+    private final ListMap<String> mEvents = new ListMap<>();
+    private final Semaphore mEventsCounter = new Semaphore(0);
+
+    private volatile String mStartCommand;
+    private volatile String mFinishCommand;
+
+    LogEventChecker() {
+        final Thread thread = new Thread(this::onRun, "log-reader-thread");
+        thread.setPriority(Thread.NORM_PRIORITY);
+        thread.start();
+    }
+
+    void start() {
+        if (mFinished != null) {
+            try {
+                mFinished.await();
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+            mFinished = null;
+        }
+        mEvents.clear();
+        mExpectedEvents.clear();
+        mEventsCounter.drainPermits();
+        final String id = UUID.randomUUID().toString();
+        mStartCommand = START_PREFIX + id;
+        mFinishCommand = FINISH_PREFIX + id;
+        Log.d(TestProtocol.TAPL_EVENTS_TAG, mStartCommand);
+    }
+
+    private void onRun() {
+        try {
+            // Note that we use Runtime.exec to start the log reading process instead of running
+            // it via UIAutomation, so that we can directly access the "Process" object and
+            // ensure that the instrumentation is not stuck forever.
+            final String cmd = "logcat -s " + TestProtocol.TAPL_EVENTS_TAG;
+
+            try (BufferedReader reader = new BufferedReader(new InputStreamReader(
+                    Runtime.getRuntime().exec(cmd).getInputStream()))) {
+                for (;;) {
+                    // Skip everything before the next start command.
+                    for (;;) {
+                        final String event = reader.readLine();
+                        if (event.contains(TestProtocol.TAPL_EVENTS_TAG)
+                                && event.contains(mStartCommand)) {
+                            break;
+                        }
+                    }
+
+                    // Store all actual events until the finish command.
+                    for (;;) {
+                        final String event = reader.readLine();
+                        if (event.contains(TestProtocol.TAPL_EVENTS_TAG)) {
+                            if (event.contains(mFinishCommand)) {
+                                mFinished.countDown();
+                                break;
+                            } else {
+                                final Matcher matcher = EVENT_LOG_ENTRY.matcher(event);
+                                if (matcher.find()) {
+                                    mEvents.add(matcher.group("sequence"), matcher.group("event"));
+                                    mEventsCounter.release();
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    void expectPattern(String sequence, Pattern pattern) {
+        mExpectedEvents.add(sequence, pattern);
+    }
+
+    private void finishSync(long waitForExpectedCountMs) {
+        try {
+            // Wait until Launcher generates the expected number of events.
+            int expectedCount = mExpectedEvents.entrySet()
+                    .stream().mapToInt(e -> e.getValue().size()).sum();
+            mEventsCounter.tryAcquire(expectedCount, waitForExpectedCountMs, MILLISECONDS);
+            finishNoWait();
+            mFinished.await();
+            mFinished = null;
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    void finishNoWait() {
+        mFinished = new CountDownLatch(1);
+        Log.d(TestProtocol.TAPL_EVENTS_TAG, mFinishCommand);
+    }
+
+    String verify(long waitForExpectedCountMs) {
+        finishSync(waitForExpectedCountMs);
+
+        final StringBuilder sb = new StringBuilder();
+        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);
+            }
+        }
+        // Check for unexpected event sequences in the actual data.
+        for (String actualNamedSequence : mEvents.keySet()) {
+            if (!mExpectedEvents.containsKey(actualNamedSequence)) {
+                formatSequenceWithMismatch(
+                        sb,
+                        actualNamedSequence,
+                        new ArrayList<>(),
+                        mEvents.get(actualNamedSequence),
+                        0);
+            }
+        }
+
+        return sb.length() != 0 ? "mismatching events: " + sb.toString() : null;
+    }
+
+    // If the list of actual events matches the list of expected events, returns -1, otherwise
+    // the position of the mismatch.
+    private static int getMismatchPosition(List<Pattern> expected, List<String> actual) {
+        for (int i = 0; i < expected.size(); ++i) {
+            if (i >= actual.size()
+                    || !expected.get(i).matcher(actual.get(i)).find()) {
+                return i;
+            }
+        }
+
+        if (actual.size() > expected.size()) return expected.size();
+
+        return -1;
+    }
+
+    private static void formatSequenceWithMismatch(
+            StringBuilder sb,
+            String sequenceName,
+            List<Pattern> expected,
+            List<String> actualEvents,
+            int mismatchPosition) {
+        sb.append("\n>> Sequence " + sequenceName);
+        sb.append("\n  Expected:");
+        formatEventListWithMismatch(sb, expected, mismatchPosition);
+        sb.append("\n  Actual:");
+        formatEventListWithMismatch(sb, actualEvents, mismatchPosition);
+    }
+
+    private static void formatEventListWithMismatch(StringBuilder sb, List events, int position) {
+        for (int i = 0; i < events.size(); ++i) {
+            sb.append("\n  | ");
+            sb.append(i == position ? "---> " : "     ");
+            sb.append(events.get(i).toString());
+        }
+        if (position == events.size()) sb.append("\n  | ---> (end)");
+    }
+
+    private static class ListMap<T> extends HashMap<String, List<T>> {
+
+        void add(String key, T value) {
+            getNonNull(key).add(value);
+        }
+
+        List<T> getNonNull(String key) {
+            List<T> list = get(key);
+            if (list == null) {
+                list = new ArrayList<>();
+                put(key, list);
+            }
+            return list;
+        }
+    }
+}