Merge "Update notification dot color" into udc-dev
diff --git a/go/quickstep/res/values-ky/strings.xml b/go/quickstep/res/values-ky/strings.xml
index 55e70c8..dcc1e4e 100644
--- a/go/quickstep/res/values-ky/strings.xml
+++ b/go/quickstep/res/values-ky/strings.xml
@@ -11,9 +11,9 @@
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Экрандагы текстти которуу же угуу"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Экрандагы текст, веб-даректер жана скриншоттор сыяктуу маалымат Google менен бөлүшүлүшү мүмкүн.\n\nБөлүшүлгөн маалыматты өзгөртүү үчүн"<b>"Параметрлер &gt; Колдонмолор &gt; Демейки колдонмолор &gt; Санариптик жардамчы колдонмосуна өтүңүз"</b>"."</string>
     <string name="assistant_not_selected_title" msgid="5017072974603345228">"Бул функцияны колдонуу үчүн жардамчыны тандаңыз"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"Экраныңыздагы текстти угуу же которуу үчүн Жөндөөлөрдөн санариптик жардамчы колдонмосун тандаңыз"</string>
+    <string name="assistant_not_selected_text" msgid="3244613673884359276">"Экраныңыздагы текстти угуу же которуу үчүн Параметрлерден санариптик жардамчы колдонмосун тандаңыз"</string>
     <string name="assistant_not_supported_title" msgid="1675788067597484142">"Бул функцияны колдонуу үчүн жардамчыңызды өзгөртүңүз"</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"Экраныңыздагы текстти угуу же которуу үчүн Жөндөөлөрдөн санариптик жардамчы колдонмосун өзгөртүңүз"</string>
+    <string name="assistant_not_supported_text" msgid="1708031078549268884">"Экраныңыздагы текстти угуу же которуу үчүн Параметрлерден санариптик жардамчы колдонмосун өзгөртүңүз"</string>
     <string name="tooltip_listen" msgid="7634466447860989102">"Бул экрандагы текстти угуу үчүн бул жерди басыңыз"</string>
     <string name="tooltip_translate" msgid="4184845868901542567">"Бул экрандагы текстти которуу үчүн бул жерди басыңыз"</string>
     <string name="toast_p2p_app_not_shareable" msgid="7229739094132131536">"Бул колдонмону бөлүшүүгө болбойт"</string>
diff --git a/quickstep/res/drawable-sw600dp-land/gesture_tutorial_home_step_shape.xml b/quickstep/res/drawable-sw600dp-land/gesture_tutorial_home_step_shape.xml
index 4cccd09..fd14d34 100644
--- a/quickstep/res/drawable-sw600dp-land/gesture_tutorial_home_step_shape.xml
+++ b/quickstep/res/drawable-sw600dp-land/gesture_tutorial_home_step_shape.xml
@@ -17,11 +17,7 @@
     android:height="67dp"
     android:viewportWidth="232"
     android:viewportHeight="67">
-  <group>
-    <clip-path
-        android:pathData="M0,0h232v67h-232z"/>
-    <path
-        android:pathData="M180.9,0.6H51.1C22.9,0.6 0,23.4 0,51.7V67.6H232V51.7C232,23.4 209.1,0.6 180.9,0.6Z"
-        android:fillColor="#4B67AE"/>
-  </group>
+  <path
+      android:pathData="M180.9,0.6H51.1C22.9,0.6 0,23.4 0,51.7V67.6H232V51.7C232,23.4 209.1,0.6 180.9,0.6Z"
+      android:fillColor="#4B67AE"/>
 </vector>
diff --git a/quickstep/res/drawable-sw600dp-land/gesture_tutorial_overview_step_shape.xml b/quickstep/res/drawable-sw600dp-land/gesture_tutorial_overview_step_shape.xml
index 7011f6c..f271c47 100644
--- a/quickstep/res/drawable-sw600dp-land/gesture_tutorial_overview_step_shape.xml
+++ b/quickstep/res/drawable-sw600dp-land/gesture_tutorial_overview_step_shape.xml
@@ -17,11 +17,7 @@
     android:height="94dp"
     android:viewportWidth="194"
     android:viewportHeight="94">
-  <group>
-    <clip-path
-        android:pathData="M0,0h194v94.09h-194z"/>
-    <path
-        android:pathData="M185.56,76.95C184.79,75.3 184.3,73.56 184.21,71.81L182.85,55.81C182.46,51.34 180.13,47.27 176.45,44.65L163.25,35.44C161.8,34.37 160.54,33.11 159.47,31.65L150.16,18.46C147.54,14.77 143.47,12.45 139,12.06L123,10.6C121.25,10.41 119.51,10.02 117.86,9.24L103.31,2.45C101.27,1.49 99.04,1 96.91,1C94.77,1 92.54,1.49 90.5,2.45L75.95,9.24C74.31,10.02 72.56,10.51 70.81,10.6L54.81,11.96C50.35,12.35 46.27,14.68 43.65,18.36L34.44,31.56C33.37,33.01 32.11,34.27 30.66,35.34L17.27,44.65C13.58,47.27 11.26,51.34 10.87,55.81L9.41,71.81C9.22,73.56 8.83,75.3 8.05,76.95L1.26,91.5C0.78,92.67 0.39,93.83 0.1,94.99H193.52C193.32,93.83 192.94,92.57 192.35,91.5L185.56,76.95Z"
-        android:fillColor="#7E44AD"/>
-  </group>
+  <path
+      android:pathData="M185.56,76.95C184.79,75.3 184.3,73.56 184.21,71.81L182.85,55.81C182.46,51.34 180.13,47.27 176.45,44.65L163.25,35.44C161.8,34.37 160.54,33.11 159.47,31.65L150.16,18.46C147.54,14.77 143.47,12.45 139,12.06L123,10.6C121.25,10.41 119.51,10.02 117.86,9.24L103.31,2.45C101.27,1.49 99.04,1 96.91,1C94.77,1 92.54,1.49 90.5,2.45L75.95,9.24C74.31,10.02 72.56,10.51 70.81,10.6L54.81,11.96C50.35,12.35 46.27,14.68 43.65,18.36L34.44,31.56C33.37,33.01 32.11,34.27 30.66,35.34L17.27,44.65C13.58,47.27 11.26,51.34 10.87,55.81L9.41,71.81C9.22,73.56 8.83,75.3 8.05,76.95L1.26,91.5C0.78,92.67 0.39,93.83 0.1,94.99H193.52C193.32,93.83 192.94,92.57 192.35,91.5L185.56,76.95Z"
+      android:fillColor="#7E44AD"/>
 </vector>
diff --git a/quickstep/res/drawable-sw720dp-land/gesture_tutorial_home_step_shape.xml b/quickstep/res/drawable-sw720dp-land/gesture_tutorial_home_step_shape.xml
index 5becb8b..3e71a3d 100644
--- a/quickstep/res/drawable-sw720dp-land/gesture_tutorial_home_step_shape.xml
+++ b/quickstep/res/drawable-sw720dp-land/gesture_tutorial_home_step_shape.xml
@@ -17,11 +17,7 @@
     android:height="73dp"
     android:viewportWidth="362"
     android:viewportHeight="73">
-  <group>
-    <clip-path
-        android:pathData="M0,0h362v73h-362z"/>
-    <path
-        android:pathData="M282.3,0H79.7C38,0 3.7,32.1 0.3,73H361.7C358.3,32.1 324,0 282.3,0Z"
-        android:fillColor="#4B67AE"/>
-  </group>
+  <path
+      android:pathData="M282.3,0H79.7C38,0 3.7,32.1 0.3,73H361.7C358.3,32.1 324,0 282.3,0Z"
+      android:fillColor="#4B67AE"/>
 </vector>
diff --git a/quickstep/res/drawable-sw720dp-land/gesture_tutorial_overview_step_shape.xml b/quickstep/res/drawable-sw720dp-land/gesture_tutorial_overview_step_shape.xml
index 7143089..2f11192 100644
--- a/quickstep/res/drawable-sw720dp-land/gesture_tutorial_overview_step_shape.xml
+++ b/quickstep/res/drawable-sw720dp-land/gesture_tutorial_overview_step_shape.xml
@@ -17,11 +17,7 @@
     android:height="144dp"
     android:viewportWidth="297"
     android:viewportHeight="144">
-  <group>
-    <clip-path
-        android:pathData="M0,0h297v144.04h-297z"/>
-    <path
-        android:pathData="M284.38,116.48C283.19,113.95 282.45,111.28 282.3,108.61L280.22,84.1C279.63,77.27 276.06,71.03 270.42,67.03L250.22,52.92C247.99,51.28 246.06,49.35 244.43,47.13L230.18,26.93C226.16,21.29 219.93,17.72 213.1,17.13L188.6,14.9C185.92,14.6 183.25,14.01 180.72,12.82L158.45,2.43C155.33,0.94 151.91,0.2 148.65,0.2C145.38,0.2 141.97,0.94 138.85,2.43L116.57,12.82C114.05,14.01 111.38,14.75 108.7,14.9L84.2,16.98C77.37,17.57 71.13,21.14 67.12,26.78L53.01,46.98C51.38,49.21 49.45,51.14 47.22,52.77L26.73,67.03C21.09,71.03 17.52,77.27 16.93,84.1L14.7,108.61C14.4,111.28 13.81,113.95 12.62,116.48L2.23,138.75C1.48,140.53 0.89,142.32 0.45,144.1H296.55C296.26,142.32 295.66,140.38 294.77,138.75L284.38,116.48Z"
-        android:fillColor="#7E44AD"/>
-  </group>
+  <path
+      android:pathData="M284.38,116.48C283.19,113.95 282.45,111.28 282.3,108.61L280.22,84.1C279.63,77.27 276.06,71.03 270.42,67.03L250.22,52.92C247.99,51.28 246.06,49.35 244.43,47.13L230.18,26.93C226.16,21.29 219.93,17.72 213.1,17.13L188.6,14.9C185.92,14.6 183.25,14.01 180.72,12.82L158.45,2.43C155.33,0.94 151.91,0.2 148.65,0.2C145.38,0.2 141.97,0.94 138.85,2.43L116.57,12.82C114.05,14.01 111.38,14.75 108.7,14.9L84.2,16.98C77.37,17.57 71.13,21.14 67.12,26.78L53.01,46.98C51.38,49.21 49.45,51.14 47.22,52.77L26.73,67.03C21.09,71.03 17.52,77.27 16.93,84.1L14.7,108.61C14.4,111.28 13.81,113.95 12.62,116.48L2.23,138.75C1.48,140.53 0.89,142.32 0.45,144.1H296.55C296.26,142.32 295.66,140.38 294.77,138.75L284.38,116.48Z"
+      android:fillColor="#7E44AD"/>
 </vector>
diff --git a/quickstep/res/drawable/gesture_tutorial_back_step_shape.xml b/quickstep/res/drawable/gesture_tutorial_back_step_shape.xml
index 68c5eb1..5f951e4 100644
--- a/quickstep/res/drawable/gesture_tutorial_back_step_shape.xml
+++ b/quickstep/res/drawable/gesture_tutorial_back_step_shape.xml
@@ -17,11 +17,7 @@
     android:height="208dp"
     android:viewportWidth="83"
     android:viewportHeight="208">
-  <group>
-    <clip-path
-        android:pathData="M0,0h83.95v208h-83.95z"/>
-    <path
-        android:pathData="M23.53,169.2L31.09,165.56C76.7,143.55 76.7,64.45 31.09,42.35L23.53,38.71C13.55,33.95 5.06,25.56 -1,14.92V193.08C5.06,182.44 13.55,174.05 23.53,169.2Z"
-        android:fillColor="#217500"/>
-  </group>
+  <path
+      android:pathData="M23.53,169.2L31.09,165.56C76.7,143.55 76.7,64.45 31.09,42.35L23.53,38.71C13.55,33.95 5.06,25.56 -1,14.92V193.08C5.06,182.44 13.55,174.05 23.53,169.2Z"
+      android:fillColor="#217500"/>
 </vector>
diff --git a/quickstep/res/layout-sw600dp-land/gesture_tutorial_step_menu.xml b/quickstep/res/layout-sw600dp-land/gesture_tutorial_step_menu.xml
index 39c7e73..d24219d 100644
--- a/quickstep/res/layout-sw600dp-land/gesture_tutorial_step_menu.xml
+++ b/quickstep/res/layout-sw600dp-land/gesture_tutorial_step_menu.xml
@@ -28,11 +28,13 @@
         android:layout_width="0dp"
         android:layout_height="@dimen/gesture_tutorial_menu_button_height"
         android:layout_marginEnd="@dimen/gesture_tutorial_menu_button_spacing"
+        android:layout_marginBottom="24dp"
         android:background="@drawable/gesture_tutorial_menu_button_background"
         android:clipToOutline="true"
         android:backgroundTint="@color/gesture_home_tutorial_background"
 
         app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toTopOf="@id/guideline"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintEnd_toStartOf="@id/gesture_tutorial_menu_back_button">
 
@@ -40,6 +42,8 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:src="@drawable/gesture_tutorial_home_step_shape"
+            android:scaleType="fitXY"
+            android:adjustViewBounds="true"
 
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintStart_toStartOf="parent"
@@ -64,11 +68,13 @@
         android:layout_width="0dp"
         android:layout_height="@dimen/gesture_tutorial_menu_button_height"
         android:layout_marginEnd="@dimen/gesture_tutorial_menu_button_spacing"
+        android:layout_marginBottom="24dp"
         android:background="@drawable/gesture_tutorial_menu_button_background"
         android:clipToOutline="true"
         android:backgroundTint="@color/gesture_back_tutorial_exiting_app"
 
         app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toTopOf="@id/guideline"
         app:layout_constraintStart_toEndOf="@id/gesture_tutorial_menu_home_button"
         app:layout_constraintEnd_toStartOf="@id/gesture_tutorial_menu_overview_button">
 
@@ -77,6 +83,8 @@
             android:layout_height="wrap_content"
             android:src="@drawable/gesture_tutorial_back_step_shape"
             android:layout_marginBottom="@dimen/gesture_tutorial_menu_back_shape_bottom_margin"
+            android:scaleType="fitXY"
+            android:adjustViewBounds="true"
 
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintStart_toStartOf="parent"/>
@@ -99,11 +107,13 @@
         android:id="@+id/gesture_tutorial_menu_overview_button"
         android:layout_width="0dp"
         android:layout_height="@dimen/gesture_tutorial_menu_button_height"
+        android:layout_marginBottom="24dp"
         android:background="@drawable/gesture_tutorial_menu_button_background"
         android:clipToOutline="true"
         android:backgroundTint="@color/gesture_overview_tutorial_background"
 
         app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toTopOf="@id/guideline"
         app:layout_constraintStart_toEndOf="@id/gesture_tutorial_menu_back_button"
         app:layout_constraintEnd_toEndOf="parent">
 
@@ -111,6 +121,8 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:src="@drawable/gesture_tutorial_overview_step_shape"
+            android:scaleType="fitXY"
+            android:adjustViewBounds="true"
 
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintStart_toStartOf="parent"
@@ -142,10 +154,8 @@
         style="@style/TextAppearance.GestureTutorial.ButtonLabel"
         android:id="@+id/gesture_tutorial_menu_done_button"
         android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:paddingVertical="16dp"
-        android:paddingHorizontal="26dp"
-        android:layout_marginVertical="@dimen/gesture_tutorial_menu_done_button_margin"
+        android:layout_height="40dp"
+        android:layout_marginVertical="16dp"
         android:text="@string/gesture_tutorial_action_button_label"
         android:background="@drawable/gesture_tutorial_action_button_background"
         android:stateListAnimator="@null"
diff --git a/quickstep/res/layout/gesture_tutorial_step_menu.xml b/quickstep/res/layout/gesture_tutorial_step_menu.xml
index 2836259..cf1e4d7 100644
--- a/quickstep/res/layout/gesture_tutorial_step_menu.xml
+++ b/quickstep/res/layout/gesture_tutorial_step_menu.xml
@@ -42,6 +42,8 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:src="@drawable/gesture_tutorial_home_step_shape"
+            android:scaleType="fitXY"
+            android:adjustViewBounds="true"
 
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintStart_toStartOf="parent"
@@ -79,6 +81,8 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:src="@drawable/gesture_tutorial_back_step_shape"
+            android:scaleType="fitXY"
+            android:adjustViewBounds="true"
 
             app:layout_constraintTop_toTopOf="parent"
             app:layout_constraintBottom_toBottomOf="parent"
@@ -116,6 +120,8 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:src="@drawable/gesture_tutorial_overview_step_shape"
+            android:scaleType="fitXY"
+            android:adjustViewBounds="true"
 
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintStart_toStartOf="parent"
@@ -147,9 +153,8 @@
         style="@style/TextAppearance.GestureTutorial.ButtonLabel"
         android:id="@+id/gesture_tutorial_menu_done_button"
         android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:paddingVertical="16dp"
-        android:paddingHorizontal="26dp"
+        android:layout_height="40dp"
+        android:layout_marginVertical="16dp"
         android:text="@string/gesture_tutorial_action_button_label"
         android:background="@drawable/gesture_tutorial_action_button_background"
         android:stateListAnimator="@null"
diff --git a/quickstep/res/values-cs/strings.xml b/quickstep/res/values-cs/strings.xml
index c27585a..786e162 100644
--- a/quickstep/res/values-cs/strings.xml
+++ b/quickstep/res/values-cs/strings.xml
@@ -88,7 +88,7 @@
     <string name="action_share" msgid="2648470652637092375">"Sdílet"</string>
     <string name="action_screenshot" msgid="8171125848358142917">"Snímek obrazovky"</string>
     <string name="action_split" msgid="2098009717623550676">"Rozdělit"</string>
-    <string name="toast_split_select_app" msgid="8464310533320556058">"Klepnutím na jinou aplikaci rozdělíte obrazovku"</string>
+    <string name="toast_split_select_app" msgid="8464310533320556058">"Obrazovku rozdělíte klepnutím na jinou aplikaci"</string>
     <string name="toast_split_app_unsupported" msgid="2360229567007828914">"Vyberte podporovanou aplikaci"</string>
     <string name="blocked_by_policy" msgid="2071401072261365546">"Aplikace nebo organizace zakazuje tuto akci"</string>
     <string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Přeskočit výukový program k navigaci?"</string>
diff --git a/quickstep/res/values-es/strings.xml b/quickstep/res/values-es/strings.xml
index 43fe5b1..06078e3 100644
--- a/quickstep/res/values-es/strings.xml
+++ b/quickstep/res/values-es/strings.xml
@@ -88,7 +88,7 @@
     <string name="action_share" msgid="2648470652637092375">"Compartir"</string>
     <string name="action_screenshot" msgid="8171125848358142917">"Hacer captura"</string>
     <string name="action_split" msgid="2098009717623550676">"Dividir"</string>
-    <string name="toast_split_select_app" msgid="8464310533320556058">"Toca otra app para usar la pantalla dividida"</string>
+    <string name="toast_split_select_app" msgid="8464310533320556058">"Toca otra aplicación para usar la pantalla dividida"</string>
     <string name="toast_split_app_unsupported" msgid="2360229567007828914">"Elige otra app para usar la pantalla dividida"</string>
     <string name="blocked_by_policy" msgid="2071401072261365546">"No puedes hacerlo porque la aplicación o tu organización no lo permiten"</string>
     <string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"¿Saltar tutorial de navegación?"</string>
diff --git a/quickstep/res/values-eu/strings.xml b/quickstep/res/values-eu/strings.xml
index 9f6e8e1..fa624e7 100644
--- a/quickstep/res/values-eu/strings.xml
+++ b/quickstep/res/values-eu/strings.xml
@@ -116,7 +116,7 @@
     <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Zereginen barra itxita dago"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Nabigazio-barra"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Erakutsi beti zereginen barra"</string>
-    <string name="change_navigation_mode" msgid="9088393078736808968">"Aldatu nabigatzeko modua"</string>
+    <string name="change_navigation_mode" msgid="9088393078736808968">"Aldatu nabigazio modua"</string>
     <string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Zereginen barraren zatitzailea"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Eraman gora, ezkerretara"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Eraman behera, eskuinetara"</string>
diff --git a/quickstep/res/values-in/strings.xml b/quickstep/res/values-in/strings.xml
index db880b3..0d4e375 100644
--- a/quickstep/res/values-in/strings.xml
+++ b/quickstep/res/values-in/strings.xml
@@ -88,7 +88,7 @@
     <string name="action_share" msgid="2648470652637092375">"Bagikan"</string>
     <string name="action_screenshot" msgid="8171125848358142917">"Screenshot"</string>
     <string name="action_split" msgid="2098009717623550676">"Pisahkan"</string>
-    <string name="toast_split_select_app" msgid="8464310533320556058">"Ketuk apl lain untuk menggunakan layar terpisah"</string>
+    <string name="toast_split_select_app" msgid="8464310533320556058">"Ketuk aplikasi lain untuk memakai layar terpisah"</string>
     <string name="toast_split_app_unsupported" msgid="2360229567007828914">"Pilih aplikasi lain untuk memakai layar terpisah"</string>
     <string name="blocked_by_policy" msgid="2071401072261365546">"Tindakan ini tidak diizinkan oleh aplikasi atau organisasi Anda"</string>
     <string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Lewati tutorial gestur?"</string>
diff --git a/quickstep/res/values-ml/strings.xml b/quickstep/res/values-ml/strings.xml
index ab77317..3d87fb6 100644
--- a/quickstep/res/values-ml/strings.xml
+++ b/quickstep/res/values-ml/strings.xml
@@ -88,7 +88,7 @@
     <string name="action_share" msgid="2648470652637092375">"പങ്കിടുക"</string>
     <string name="action_screenshot" msgid="8171125848358142917">"സ്ക്രീൻഷോട്ട്"</string>
     <string name="action_split" msgid="2098009717623550676">"വിഭജിക്കുക"</string>
-    <string name="toast_split_select_app" msgid="8464310533320556058">"സ്പ്ലിറ്റ് സ്ക്രീനിനായി മറ്റൊരു ആപ്പ് ടാപ്പുചെയ്യൂ"</string>
+    <string name="toast_split_select_app" msgid="8464310533320556058">"സ്പ്ലിറ്റ് സ്ക്രീനിന് മറ്റൊരു ആപ്പിൽ ടാപ്പ് ചെയ്യൂ"</string>
     <string name="toast_split_app_unsupported" msgid="2360229567007828914">"സ്ക്രീൻ വിഭജന മോഡിന് മറ്റൊരു ആപ്പ് തിരഞ്ഞെടുക്കൂ"</string>
     <string name="blocked_by_policy" msgid="2071401072261365546">"ഈ നടപടി എടുക്കുന്നത് ആപ്പോ നിങ്ങളുടെ സ്ഥാപനമോ അനുവദിക്കുന്നില്ല"</string>
     <string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"നാവിഗേഷൻ ട്യൂട്ടോറിയൽ ഒഴിവാക്കണോ?"</string>
diff --git a/quickstep/res/values-or/strings.xml b/quickstep/res/values-or/strings.xml
index 5a04d97..72729bf 100644
--- a/quickstep/res/values-or/strings.xml
+++ b/quickstep/res/values-or/strings.xml
@@ -21,7 +21,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="recent_task_option_pin" msgid="7929860679018978258">"ପିନ୍‍"</string>
     <string name="recent_task_option_freeform" msgid="48863056265284071">"ଫ୍ରିଫର୍ମ"</string>
-    <string name="recents_empty_message" msgid="7040467240571714191">"କୌଣସି ସାମ୍ପ୍ରତିକ ଆଇଟମ୍ ନାହିଁ"</string>
+    <string name="recents_empty_message" msgid="7040467240571714191">"ବର୍ତ୍ତମାନର କୌଣସି ଆଇଟମ ନାହିଁ"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ଆପ ବ୍ୟବହାର ସେଟିଂସ"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"ସବୁ ଖାଲି କରନ୍ତୁ"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"ବର୍ତ୍ତମାନର ଆପ୍‌"</string>
diff --git a/quickstep/res/values-pa/strings.xml b/quickstep/res/values-pa/strings.xml
index 939f699..3da7e42 100644
--- a/quickstep/res/values-pa/strings.xml
+++ b/quickstep/res/values-pa/strings.xml
@@ -97,7 +97,7 @@
     <string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"ਛੱਡੋ"</string>
     <string name="accessibility_rotate_button" msgid="4771825231336502943">"ਸਕ੍ਰੀਨ ਘੁਮਾਓ"</string>
     <string name="taskbar_edu_a11y_title" msgid="5417986057866415355">"ਟਾਸਕਬਾਰ ਸਿੱਖਿਆ"</string>
-    <string name="taskbar_edu_splitscreen" msgid="5605512479258053350">"ਇੱਕ ਵਾਰ ਵਿੱਚ 2 ਐਪਾਂ ਵਰਤਣ ਲਈ, ਐਪ ਨੂੰ ਪਾਸੇ ਵੱਲ ਘਸੀਟੋ"</string>
+    <string name="taskbar_edu_splitscreen" msgid="5605512479258053350">"ਇੱਕੋ ਸਮੇਂ \'ਤੇ 2 ਐਪਾਂ ਵਰਤਣ ਲਈ, ਐਪ ਨੂੰ ਪਾਸੇ ਵੱਲ ਘਸੀਟੋ"</string>
     <string name="taskbar_edu_stashing" msgid="5645461372669217294">"ਟਾਸਕਬਾਰ ਦਿਖਾਉਣ ਲਈ ਥੋੜ੍ਹਾ ਉੱਪਰ ਵੱਲ ਸਵਾਈਪ ਕਰੋ"</string>
     <string name="taskbar_edu_suggestions" msgid="8215044496435527982">"ਤੁਹਾਡੇ ਨਿਯਮਬੱਧ ਕੰਮ ਦੇ ਆਧਾਰ \'ਤੇ ਐਪ ਸੁਝਾਅ ਪ੍ਰਾਪਤ ਕਰੋ"</string>
     <string name="taskbar_edu_settings_persistent" msgid="1387372982791296151">"ਟਾਸਕਬਾਰ ਨੂੰ ਸਵੈ-ਲੁਕਾਉਣ ਲਈ ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਇਸ਼ਾਰਾ ਨੈਵੀਗੇਸ਼ਨ ਨੂੰ ਚਾਲੂ ਕਰੋ"</string>
diff --git a/quickstep/res/values-sk/strings.xml b/quickstep/res/values-sk/strings.xml
index 1577f4a..b3f1c29 100644
--- a/quickstep/res/values-sk/strings.xml
+++ b/quickstep/res/values-sk/strings.xml
@@ -88,7 +88,7 @@
     <string name="action_share" msgid="2648470652637092375">"Zdieľať"</string>
     <string name="action_screenshot" msgid="8171125848358142917">"Snímka obrazovky"</string>
     <string name="action_split" msgid="2098009717623550676">"Rozdeliť"</string>
-    <string name="toast_split_select_app" msgid="8464310533320556058">"Rozdelenú obrazovku spustíte klep. na inú aplik."</string>
+    <string name="toast_split_select_app" msgid="8464310533320556058">"Obrazovku rozdelíte klepnutím na inú aplikáciu"</string>
     <string name="toast_split_app_unsupported" msgid="2360229567007828914">"Na použitie rozd. obrazovky vyberte inú aplikáciu"</string>
     <string name="blocked_by_policy" msgid="2071401072261365546">"Aplikácia alebo vaša organizácia túto akciu nepovoľuje"</string>
     <string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Chcete preskočiť návod na navigáciu?"</string>
diff --git a/quickstep/res/values-sw600dp-land/dimens.xml b/quickstep/res/values-sw600dp-land/dimens.xml
index 9853140..9cb3fec 100644
--- a/quickstep/res/values-sw600dp-land/dimens.xml
+++ b/quickstep/res/values-sw600dp-land/dimens.xml
@@ -26,6 +26,5 @@
     <dimen name="gesture_tutorial_menu_button_spacing">24dp</dimen>
     <dimen name="gesture_tutorial_menu_done_button_top_spacing">40dp</dimen>
     <dimen name="gesture_tutorial_menu_back_shape_bottom_margin">49dp</dimen>
-    <dimen name="gesture_tutorial_menu_done_button_margin">16dp</dimen>
 
 </resources>
diff --git a/quickstep/res/values-sw720dp-land/dimens.xml b/quickstep/res/values-sw720dp-land/dimens.xml
index 1d02ab5..4634a2d 100644
--- a/quickstep/res/values-sw720dp-land/dimens.xml
+++ b/quickstep/res/values-sw720dp-land/dimens.xml
@@ -21,7 +21,5 @@
     <dimen name="gesture_tutorial_menu_button_spacing">49dp</dimen>
     <dimen name="gesture_tutorial_menu_done_button_top_spacing">24dp</dimen>
     <dimen name="gesture_tutorial_menu_back_shape_bottom_margin">21dp</dimen>
-    <dimen name="gesture_tutorial_menu_done_button_spacing">80dp</dimen>
-    <dimen name="gesture_tutorial_menu_done_button_margin">0dp</dimen>
 
 </resources>
diff --git a/quickstep/res/values-te/strings.xml b/quickstep/res/values-te/strings.xml
index 9fe46ef..9cdd2fa 100644
--- a/quickstep/res/values-te/strings.xml
+++ b/quickstep/res/values-te/strings.xml
@@ -88,7 +88,7 @@
     <string name="action_share" msgid="2648470652637092375">"షేర్ చేయండి"</string>
     <string name="action_screenshot" msgid="8171125848358142917">"స్క్రీన్‌షాట్"</string>
     <string name="action_split" msgid="2098009717623550676">"స్ప్లిట్ చేయండి"</string>
-    <string name="toast_split_select_app" msgid="8464310533320556058">"మరొక యాప్‌ను ట్యాప్ చేసి, స్ప్లిట్ స్క్రీన్ వాడండి"</string>
+    <string name="toast_split_select_app" msgid="8464310533320556058">"స్ప్లిట్ స్క్రీన్ కోసం మరొక యాప్‌ను ట్యాప్ చేయండి"</string>
     <string name="toast_split_app_unsupported" msgid="2360229567007828914">"స్ప్లిట్ స్క్రీన్ ఉపయోగానికి మరొక యాప్ ఎంచుకోండి"</string>
     <string name="blocked_by_policy" msgid="2071401072261365546">"ఈ చర్యను యాప్ గానీ, మీ సంస్థ గానీ అనుమతించవు"</string>
     <string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"నావిగేషన్ ట్యుటోరియల్‌ను స్కిప్ చేయాలా?"</string>
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 5d2df70..d69b155 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -134,7 +134,6 @@
     <dimen name="gesture_tutorial_menu_done_button_top_spacing">0dp</dimen>
     <dimen name="gesture_tutorial_menu_back_shape_bottom_margin">0dp</dimen>
     <dimen name="gesture_tutorial_menu_done_button_spacing">72dp</dimen>
-    <dimen name="gesture_tutorial_menu_done_button_margin">0dp</dimen>
 
     <!-- Gesture Tutorial mock conversations -->
     <dimen name="gesture_tutorial_message_icon_size">44dp</dimen>
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 682fccd..977163b 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -1448,6 +1448,7 @@
      */
     private Animator getFallbackClosingWindowAnimators(RemoteAnimationTarget[] appTargets) {
         final int rotationChange = getRotationChange(appTargets);
+        SurfaceTransactionApplier surfaceApplier = new SurfaceTransactionApplier(mDragLayer);
         Matrix matrix = new Matrix();
         Point tmpPos = new Point();
         Rect tmpRect = new Rect();
@@ -1503,7 +1504,7 @@
                                 .setAlpha(1f);
                     }
                 }
-                transaction.getTransaction().apply();
+                surfaceApplier.scheduleApply(transaction);
             }
         });
 
@@ -1591,8 +1592,7 @@
             boolean playFallBackAnimation = (launcherView == null
                     && launcherIsForceInvisibleOrOpening)
                     || mLauncher.getWorkspace().isOverlayShown()
-                    || hasMultipleTargetsWithMode(appTargets, MODE_CLOSING)
-                    || mLauncher.isDestroyed();
+                    || hasMultipleTargetsWithMode(appTargets, MODE_CLOSING);
 
             boolean playWorkspaceReveal = true;
             boolean skipAllAppsScale = false;
diff --git a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
index ed4a212..f981610 100644
--- a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
@@ -21,12 +21,15 @@
 
 import android.animation.Animator;
 
-import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.statemanager.StateManager;
 import com.android.quickstep.RecentsActivity;
+import com.android.quickstep.TopTaskTracker;
 import com.android.quickstep.fallback.RecentsState;
 import com.android.quickstep.views.RecentsView;
 
+import java.util.stream.Stream;
+
 /**
  * A data source which integrates with the fallback RecentsActivity instance (for 3P launchers).
  */
@@ -81,18 +84,15 @@
      * Currently this animation just force stashes the taskbar in Overview.
      */
     public Animator createAnimToRecentsState(RecentsState toState, long duration) {
-        // Force stash the taskbar in overview modal state or when going home. We do not force
-        // stash on home when running in a test as 3p launchers rely on taskbar instead of hotseat.
-        boolean isGoingHome = toState == RecentsState.HOME && !isRunningInTestHarness();
-        boolean useStashedLauncherState = toState.hasOverviewActions() || isGoingHome;
-        boolean stashedLauncherState = useStashedLauncherState && (
-                (FeatureFlags.ENABLE_GRID_ONLY_OVERVIEW.get() && toState == RecentsState.MODAL_TASK)
-                        || isGoingHome);
+        // Force stash taskbar (disallow unstashing) when:
+        // - in a 3P launcher or overview task.
+        // - not running in a test harness (unstash is needed for tests)
+        boolean forceStash = isIn3pHomeOrRecents() && !isRunningInTestHarness();
         TaskbarStashController stashController = mControllers.taskbarStashController;
         // Set both FLAG_IN_STASHED_LAUNCHER_STATE and FLAG_IN_APP to ensure the state is respected.
         // For all other states, just use the current stashed-in-app setting (e.g. if long clicked).
-        stashController.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE, stashedLauncherState);
-        stashController.updateStateForFlag(FLAG_IN_APP, !useStashedLauncherState);
+        stashController.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE, forceStash);
+        stashController.updateStateForFlag(FLAG_IN_APP, !forceStash);
         return stashController.createApplyStateAnimator(duration);
     }
 
@@ -108,4 +108,20 @@
     public RecentsView getRecentsView() {
         return mRecentsActivity.getOverviewPanel();
     }
+
+    @Override
+    Stream<SystemShortcut.Factory<BaseTaskbarContext>> getSplitMenuOptions() {
+        if (isIn3pHomeOrRecents()) {
+            // Split from Taskbar is not supported in fallback launcher, so return empty stream
+            return Stream.empty();
+        } else {
+            return super.getSplitMenuOptions();
+        }
+    }
+
+    private boolean isIn3pHomeOrRecents() {
+        TopTaskTracker.CachedTaskInfo topTask = TopTaskTracker.INSTANCE
+                .get(mControllers.taskbarActivityContext).getCachedTopTask(true);
+        return topTask.isHomeTask() || topTask.isRecentsTask();
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index fdef39f..ba6f165 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -197,6 +197,10 @@
         return mTaskbarLauncherStateController.applyState(fromInit ? 0 : duration, startAnimation);
     }
 
+    public void refreshResumedState() {
+        onLauncherResumedOrPaused(mLauncher.hasBeenResumed());
+    }
+
     /**
      * Create Taskbar animation when going from an app to Launcher as part of recents transition.
      * @param toState If known, the state we will end up in when reaching Launcher.
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index d94d8f7..cf82900 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -1145,6 +1145,10 @@
         return mControllers.taskbarStashController.isInApp();
     }
 
+    public boolean isInStashedLauncherState() {
+        return mControllers.taskbarStashController.isInStashedLauncherState();
+    }
+
     protected void dumpLogs(String prefix, PrintWriter pw) {
         pw.println(prefix + "TaskbarActivityContext:");
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
index 72add4f..88fea31 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -198,7 +198,7 @@
 
                 @Override
                 public boolean shouldStartDrag(double distanceDragged) {
-                    return mDragView != null && mDragView.isAnimationFinished();
+                    return mDragView != null && mDragView.isScaleAnimationFinished();
                 }
 
                 @Override
@@ -231,7 +231,6 @@
                 dragLayerY,
                 (View target, DropTarget.DragObject d, boolean success) -> {} /* DragSource */,
                 (ItemInfo) btv.getTag(),
-                /* dragVisualizeOffset = */ null,
                 dragRect,
                 scale * iconScale,
                 scale,
@@ -241,7 +240,7 @@
     @Override
     protected DragView startDrag(@Nullable Drawable drawable, @Nullable View view,
             DraggableView originalView, int dragLayerX, int dragLayerY, DragSource source,
-            ItemInfo dragInfo, Point dragOffset, Rect dragRegion, float initialDragViewScale,
+            ItemInfo dragInfo, Rect dragRegion, float initialDragViewScale,
             float dragViewScaleOnDrop, DragOptions options) {
         mOptions = options;
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
index 4422fd4..d6e559a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
@@ -33,11 +33,13 @@
 import android.view.WindowManager
 import android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD
 import android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION
+import androidx.core.graphics.toRegion
 import com.android.internal.policy.GestureNavigationSettingsObserver
 import com.android.launcher3.DeviceProfile
 import com.android.launcher3.R
 import com.android.launcher3.anim.AlphaUpdateListener
 import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
+import com.android.launcher3.util.DisplayController
 import java.io.PrintWriter
 
 /** Handles the insets that Taskbar provides to underlying apps and the IME. */
@@ -220,7 +222,16 @@
             controllers.taskbarViewController.areIconsVisible() || context.isNavBarKidsModeActive
         ) {
             // Taskbar has some touchable elements, take over the full taskbar area
-            insetsInfo.touchableRegion.set(touchableRegion)
+            if (
+                controllers.uiController.isInOverview &&
+                    DisplayController.isTransientTaskbar(context)
+            ) {
+                insetsInfo.touchableRegion.set(
+                    controllers.taskbarActivityContext.dragLayer.lastDrawnTransientRect.toRegion()
+                )
+            } else {
+                insetsInfo.touchableRegion.set(touchableRegion)
+            }
             insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION)
             insetsIsTouchableRegion = false
         } else {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index 75cfd05..dfbd5bb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -207,10 +207,6 @@
                     com.android.launcher3.taskbar.Utilities.setOverviewDragState(
                             mControllers, finalState.disallowTaskbarGlobalDrag(),
                             disallowLongClick, finalState.allowTaskbarInitialSplitSelection());
-                    // LauncherTaskbarUIController depends on the state when checking whether
-                    // to handle resume, so it should also be poked if current state changes
-                    mLauncher.getTaskbarUIController().onLauncherResumedOrPaused(
-                            mLauncher.hasBeenResumed());
                 }
             };
 
@@ -424,6 +420,10 @@
                 // We're changing state to home, should close open popups e.g. Taskbar AllApps
                 handleOpenFloatingViews = true;
             }
+            if (mLauncherState == LauncherState.OVERVIEW) {
+                // Calling to update the insets in TaskbarInsetController#updateInsetsTouchability
+                mControllers.taskbarActivityContext.notifyUpdateLayoutParams();
+            }
         }
 
         if (hasAnyFlag(changedFlags, FLAGS_LAUNCHER_ACTIVE)) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
index 5abeac7..5eec726 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
@@ -32,7 +32,6 @@
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.dot.FolderDotInfo;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
@@ -176,9 +175,9 @@
                     deepShortcutCount,
                     mPopupDataProvider.getNotificationKeysForItem(item),
                     systemShortcuts);
-            icon.clearAccessibilityFocus();
         }
 
+        icon.clearAccessibilityFocus();
         container.addOnAttachStateChangeListener(
                 new PopupLiveUpdateHandler<BaseTaskbarContext>(context, container) {
                     @Override
@@ -205,9 +204,7 @@
         // append split options to APP_INFO shortcut, the order here will reflect in the popup
         return Stream.concat(
                 Stream.of(APP_INFO),
-                Utilities.getSplitPositionOptions(mContext.getDeviceProfile())
-                        .stream()
-                        .map(this::createSplitShortcutFactory)
+                mControllers.uiController.getSplitMenuOptions()
         );
     }
 
@@ -265,7 +262,7 @@
      *                 right.
      * @return A factory function to be used in populating the long-press menu.
      */
-    private SystemShortcut.Factory<BaseTaskbarContext> createSplitShortcutFactory(
+    SystemShortcut.Factory<BaseTaskbarContext> createSplitShortcutFactory(
             SplitPositionOption position) {
         return (context, itemInfo, originalView) -> new TaskbarSplitShortcut(context, itemInfo,
                 originalView, position, mAllowInitialSplitSelection);
@@ -328,6 +325,7 @@
                                 mItemInfo.getIntent().getComponent(),
                                 null,
                                 mItemInfo.user),
+                        mItemInfo.user.getIdentifier(),
                         new Intent(),
                         getPosition().stagePosition,
                         null,
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarShortcutMenuAccessibilityDelegate.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarShortcutMenuAccessibilityDelegate.java
index 054689b..e8c8fc4 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarShortcutMenuAccessibilityDelegate.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarShortcutMenuAccessibilityDelegate.java
@@ -111,7 +111,8 @@
                                 item.getIntent().getComponent(),
                                 /* startActivityOptions= */null,
                                 item.user),
-                        new Intent(), side, null, instanceIds.first);
+                        item.user.getIdentifier(), new Intent(), side, null,
+                        instanceIds.first);
             }
             return true;
         } else if (action == DEEP_SHORTCUTS || action == SHORTCUTS_AND_NOTIFICATIONS) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarSpringOnStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarSpringOnStashController.java
index d65b5c0..f87c21e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarSpringOnStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarSpringOnStashController.java
@@ -17,6 +17,7 @@
 
 import static com.android.launcher3.anim.AnimatedFloat.VALUE;
 
+import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 
 import androidx.annotation.Nullable;
@@ -85,6 +86,15 @@
                 .build(mTranslationForStash, VALUE);
     }
 
+    /**
+     * Returns an animation to reset the stash translation back to 0 when unstashing.
+     */
+    public @Nullable ObjectAnimator createResetAnimForUnstash() {
+        if (!mIsTransientTaskbar) {
+            return null;
+        }
+        return mTranslationForStash.animateToValue(0);
+    }
 
     @Override
     public void dumpLogs(String prefix, PrintWriter pw) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index c2175f2..1727dd3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -799,6 +799,9 @@
         if (isStashed) {
             play(skippable, mControllers.taskbarSpringOnStashController.createSpringToStash(),
                     0, duration, LINEAR);
+        } else {
+            play(skippable, mControllers.taskbarSpringOnStashController.createResetAnimForUnstash(),
+                    0, duration, LINEAR);
         }
 
         mControllers.taskbarViewController.addRevealAnimToIsStashed(skippable, isStashed, duration,
@@ -1218,6 +1221,15 @@
                     && mLastStartedTransitionType == TRANSITION_DEFAULT
                     && animationType != TRANSITION_DEFAULT;
 
+            // It is possible for stash=false to be requested by TRANSITION_HOME_TO_APP and
+            // TRANSITION_DEFAULT in quick succession. In this case, we should ignore
+            // transitionTypeChanged because the animations are exactly the same.
+            if (transitionTypeChanged
+                    && (!mIsStashed && !isStashed)
+                    && animationType == TRANSITION_HOME_TO_APP) {
+                transitionTypeChanged = false;
+            }
+
             if (mIsStashed != isStashed || transitionTypeChanged) {
                 mIsStashed = isStashed;
                 mLastStartedTransitionType = animationType;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index f3e2ee2..be5cbac 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -30,8 +30,10 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.Utilities;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
+import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.SplitConfigurationOptions;
@@ -41,6 +43,7 @@
 import com.android.quickstep.views.TaskView.TaskIdAttributeContainer;
 
 import java.io.PrintWriter;
+import java.util.stream.Stream;
 
 /**
  * Base class for providing different taskbar UI
@@ -318,4 +321,19 @@
         }
         return null;
     }
+
+    /**
+     * Refreshes the resumed state of this ui controller.
+     */
+    public void refreshResumedState() {}
+
+    /**
+     * Returns a stream of split screen menu options appropriate to the device.
+     */
+    Stream<SystemShortcut.Factory<BaseTaskbarContext>> getSplitMenuOptions() {
+        return Utilities
+                .getSplitPositionOptions(mControllers.taskbarActivityContext.getDeviceProfile())
+                .stream()
+                .map(mControllers.taskbarPopupController::createSplitShortcutFactory);
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
index 667c6f5..7397159 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
@@ -43,7 +43,8 @@
     private var shadowBlur = 0f
     private var keyShadowDistance = 0f
 
-    private var arrowPositionX: Float = 0f
+    var arrowPositionX: Float = 0f
+        private set
     private var showingArrow: Boolean = false
     private var arrowDrawable: ShapeDrawable
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
index a466548..6d19692 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
@@ -360,7 +360,7 @@
         Path dotPath;
         int dotColor;
 
-        boolean isImportantConvo = false; // TODO: (b/269671451) needs to be added to BubbleInfo
+        boolean isImportantConvo = b.isImportantConversation();
 
         ShortcutRequest.QueryResult result = new ShortcutRequest(context,
                 new UserHandle(b.getUserId()))
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index 07de3b8..0e1e0e1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.taskbar.bubbles;
 
+import android.animation.ValueAnimator;
 import android.annotation.Nullable;
 import android.content.Context;
 import android.graphics.Rect;
@@ -41,14 +42,14 @@
  * - stashed as a handle
  * - unstashed but collapsed, in this state the bar is showing but the bubbles are stacked within it
  * - unstashed and expanded, in this state the bar is showing and the bubbles are shown in a row
- *   with one of the bubbles being selected. Additionally, WMShell will display the expanded bubble
- *   view above the bar.
+ * with one of the bubbles being selected. Additionally, WMShell will display the expanded bubble
+ * view above the bar.
  * <p>
  * The bubble bar has some behavior related to taskbar:
  * - When taskbar is unstashed, bubble bar will also become unstashed (but in its "collapsed"
- *   state)
+ * state)
  * - When taskbar is stashed, bubble bar will also become stashed (unless bubble bar is in its
- *   "expanded" state)
+ * "expanded" state)
  * - When bubble bar is in its "expanded" state, taskbar becomes stashed
  * <p>
  * If there are no bubbles, the bubble bar and bubble stashed handle are not shown. Additionally
@@ -64,6 +65,7 @@
     // TODO: (b/273594744) calculate the amount of space we have and base the max on that
     //  if it's smaller than 5.
     private static final int MAX_BUBBLES = 5;
+    private static final int ARROW_POSITION_ANIMATION_DURATION_MS = 200;
 
     private final TaskbarActivityContext mActivityContext;
     private final BubbleBarBackground mBubbleBarBackground;
@@ -209,14 +211,18 @@
     /**
      * Sets which bubble view should be shown as selected.
      */
-    // TODO: (b/273592694) animate it
     public void setSelectedBubble(BubbleView view) {
         mSelectedBubbleView = view;
-        updateArrowForSelected();
-        invalidate();
+        updateArrowForSelected(/* shouldAnimate= */ true);
     }
 
-    private void updateArrowForSelected() {
+    /**
+     * Update the arrow position to match the selected bubble.
+     *
+     * @param shouldAnimate whether or not to animate the arrow. If the bar was just expanded, this
+     *                      should be set to {@code false}. Otherwise set this to {@code true}.
+     */
+    private void updateArrowForSelected(boolean shouldAnimate) {
         if (mSelectedBubbleView == null) {
             Log.w(TAG, "trying to update selection arrow without a selected view!");
             return;
@@ -224,7 +230,21 @@
         final int index = indexOfChild(mSelectedBubbleView);
         // Find the center of the bubble when it's expanded, set the arrow position to it.
         final float tx = getPaddingStart() + index * (mIconSize + mIconSpacing) + mIconSize / 2f;
-        mBubbleBarBackground.setArrowPosition(tx);
+
+        if (shouldAnimate) {
+            final float currentArrowPosition = mBubbleBarBackground.getArrowPositionX();
+            ValueAnimator animator = ValueAnimator.ofFloat(currentArrowPosition, tx);
+            animator.setDuration(ARROW_POSITION_ANIMATION_DURATION_MS);
+            animator.addUpdateListener(animation -> {
+                float x = (float) animation.getAnimatedValue();
+                mBubbleBarBackground.setArrowPosition(x);
+                invalidate();
+            });
+            animator.start();
+        } else {
+            mBubbleBarBackground.setArrowPosition(tx);
+            invalidate();
+        }
     }
 
     @Override
@@ -248,7 +268,7 @@
     public void setExpanded(boolean isBarExpanded) {
         if (mIsBarExpanded != isBarExpanded) {
             mIsBarExpanded = isBarExpanded;
-            updateArrowForSelected();
+            updateArrowForSelected(/* shouldAnimate= */ false);
             setOrUnsetClickListener();
             if (!isBarExpanded && mReorderRunnable != null) {
                 mReorderRunnable.run();
diff --git a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java
index 84a5228..5902912 100644
--- a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java
@@ -92,6 +92,11 @@
     }
 
     @Override
+    public View.AccessibilityDelegate getAccessibilityDelegate() {
+        return mTaskbarContext.getAccessibilityDelegate();
+    }
+
+    @Override
     public TaskbarDragController getDragController() {
         return mDragController;
     }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index 0eef70e..a8b7698 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -57,7 +57,6 @@
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.touch.ItemLongClickListener;
 import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.views.ActivityContext;
@@ -183,7 +182,16 @@
                 : null;
         super.applyFromWorkspaceItem(info, animate, staggerIndex);
         int oldPlateColor = mPlateColor;
-        int newPlateColor = ColorUtils.setAlphaComponent(mDotParams.appColor, 200);
+
+        int newPlateColor;
+        if (getIcon().isThemed()) {
+            newPlateColor = getResources().getColor(android.R.color.system_accent1_300);
+        } else {
+            float[] hctPlateColor = new float[3];
+            ColorUtils.colorToM3HCT(mDotParams.appColor, hctPlateColor);
+            newPlateColor = ColorUtils.M3HCTtoColor(hctPlateColor[0], 36, 85);
+        }
+
         if (!animate) {
             mPlateColor = newPlateColor;
         }
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 7d47945..b49eb24 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -334,6 +334,7 @@
     private boolean mCanSlowSwipeGoHome = true;
     // Indicates whether the divider is shown, only used when split screen is activated.
     private boolean mIsDividerShown = true;
+    private boolean mStartMovingTasks;
 
     @Nullable
     private RemoteAnimationTargets.ReleaseCheck mSwipePipToHomeReleaseCheck = null;
@@ -1722,12 +1723,19 @@
         return keepClearArea;
     }
 
+    /**
+     * Notifies to start intercepting touches in the app window and hide the divider bar if needed.
+     * @see RecentsAnimationController#enableInputConsumer()
+     */
     private void startInterceptingTouchesForGesture() {
-        if (mRecentsAnimationController == null) {
+        if (mRecentsAnimationController == null || !mStartMovingTasks) {
             return;
         }
 
         mRecentsAnimationController.enableInputConsumer();
+
+        // Hide the divider as it starts intercepting touches in the app window.
+        setDividerShown(false);
     }
 
     private void computeRecentsScrollIfInvisible() {
@@ -2339,9 +2347,9 @@
         boolean setRecentsScroll = mRecentsViewScrollLinked && mRecentsView != null;
         float progress = Math.max(mCurrentShift.value, getScaleProgressDueToScroll());
         int scrollOffset = setRecentsScroll ? mRecentsView.getScrollOffset() : 0;
-        if (progress > 0 || scrollOffset != 0) {
-            // Hide the divider as the tasks start moving.
-            setDividerShown(false);
+        if (!mStartMovingTasks && (progress > 0 || scrollOffset != 0)) {
+            mStartMovingTasks = true;
+            startInterceptingTouchesForGesture();
         }
         for (RemoteTargetHandle remoteHandle : mRemoteTargetHandles) {
             AnimatorControllerWithResistance playbackController =
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index d3e4ce5..64ec1d8 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -58,6 +58,7 @@
 import android.os.UserManager;
 import android.provider.Settings;
 import android.view.MotionEvent;
+import android.view.ViewConfiguration;
 
 import androidx.annotation.BinderThread;
 import androidx.annotation.NonNull;
@@ -87,6 +88,10 @@
 
     static final String SUPPORT_ONE_HANDED_MODE = "ro.support_one_handed_mode";
 
+    // TODO: Move to quickstep contract
+    private static final float QUICKSTEP_TOUCH_SLOP_RATIO_TWO_BUTTON = 9;
+    private static final float QUICKSTEP_TOUCH_SLOP_RATIO_GESTURAL = 2;
+
     private final Context mContext;
     private final DisplayController mDisplayController;
     private final int mDisplayId;
@@ -577,6 +582,19 @@
                 && ((mSystemUiStateFlags & SYSUI_STATE_IME_SHOWING) != 0);
     }
 
+    /**
+     * Returns the touch slop for {@link InputConsumer}s to compare against before pilfering
+     * pointers. Note that this is squared because it expects to be compared against
+     * {@link com.android.launcher3.Utilities#squaredHypot} (to avoid square root on each event).
+     */
+    public float getSquaredTouchSlop() {
+        float slopMultiplier = isFullyGesturalNavMode()
+                ? QUICKSTEP_TOUCH_SLOP_RATIO_GESTURAL
+                : QUICKSTEP_TOUCH_SLOP_RATIO_TWO_BUTTON;
+        float touchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
+        return slopMultiplier * touchSlop * touchSlop;
+    }
+
     public String getSystemUiStateString() {
         return  QuickStepContract.getSystemUiStateString(mSystemUiStateFlags);
     }
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 0549d9f..29aed25 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -683,12 +683,12 @@
         }
     }
 
-    public void startIntentAndTask(PendingIntent pendingIntent, Bundle options1, int taskId,
-            Bundle options2, @SplitConfigurationOptions.StagePosition int splitPosition,
+    public void startIntentAndTask(PendingIntent pendingIntent, int userId1, Bundle options1,
+            int taskId, Bundle options2, @SplitConfigurationOptions.StagePosition int splitPosition,
             float splitRatio, RemoteTransition remoteTransition, InstanceId instanceId) {
         if (mSystemUiProxy != null) {
             try {
-                mSplitScreen.startIntentAndTask(pendingIntent, options1, taskId, options2,
+                mSplitScreen.startIntentAndTask(pendingIntent, userId1, options1, taskId, options2,
                         splitPosition, splitRatio, remoteTransition, instanceId);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed call startIntentAndTask");
@@ -696,15 +696,16 @@
         }
     }
 
-    public void startIntents(PendingIntent pendingIntent1, @Nullable ShortcutInfo shortcutInfo1,
-            Bundle options1, PendingIntent pendingIntent2, @Nullable ShortcutInfo shortcutInfo2,
-            Bundle options2, @SplitConfigurationOptions.StagePosition int splitPosition,
-            float splitRatio, RemoteTransition remoteTransition, InstanceId instanceId) {
+    public void startIntents(PendingIntent pendingIntent1, int userId1,
+            @Nullable ShortcutInfo shortcutInfo1, Bundle options1, PendingIntent pendingIntent2,
+            int userId2, @Nullable ShortcutInfo shortcutInfo2, Bundle options2,
+            @SplitConfigurationOptions.StagePosition int splitPosition, float splitRatio,
+            RemoteTransition remoteTransition, InstanceId instanceId) {
         if (mSystemUiProxy != null) {
             try {
-                mSplitScreen.startIntents(pendingIntent1, shortcutInfo1, options1, pendingIntent2,
-                        shortcutInfo2, options2, splitPosition, splitRatio, remoteTransition,
-                        instanceId);
+                mSplitScreen.startIntents(pendingIntent1, userId1, shortcutInfo1, options1,
+                        pendingIntent2, userId2, shortcutInfo2, options2, splitPosition, splitRatio,
+                        remoteTransition, instanceId);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed call startIntents");
             }
@@ -740,14 +741,14 @@
         }
     }
 
-    public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent,
+    public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, int userId1,
             Bundle options1, int taskId, Bundle options2,
             @SplitConfigurationOptions.StagePosition int splitPosition, float splitRatio,
             RemoteAnimationAdapter adapter, InstanceId instanceId) {
         if (mSystemUiProxy != null) {
             try {
-                mSplitScreen.startIntentAndTaskWithLegacyTransition(pendingIntent, options1, taskId,
-                        options2, splitPosition, splitRatio, adapter, instanceId);
+                mSplitScreen.startIntentAndTaskWithLegacyTransition(pendingIntent, userId1,
+                        options1, taskId, options2, splitPosition, splitRatio, adapter, instanceId);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed call startIntentAndTaskWithLegacyTransition");
             }
@@ -771,16 +772,16 @@
      * Starts a pair of intents or shortcuts in split-screen using legacy transition. Passing a
      * non-null shortcut info means to start the app as a shortcut.
      */
-    public void startIntentsWithLegacyTransition(PendingIntent pendingIntent1,
+    public void startIntentsWithLegacyTransition(PendingIntent pendingIntent1, int userId1,
             @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
-            PendingIntent pendingIntent2, @Nullable ShortcutInfo shortcutInfo2,
+            PendingIntent pendingIntent2, int userId2, @Nullable ShortcutInfo shortcutInfo2,
             @Nullable Bundle options2, @SplitConfigurationOptions.StagePosition int sidePosition,
             float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
         if (mSystemUiProxy != null) {
             try {
-                mSplitScreen.startIntentsWithLegacyTransition(pendingIntent1, shortcutInfo1,
-                        options1, pendingIntent2, shortcutInfo2, options2, sidePosition, splitRatio,
-                        adapter, instanceId);
+                mSplitScreen.startIntentsWithLegacyTransition(pendingIntent1, userId1,
+                        shortcutInfo1, options1, pendingIntent2, userId2, shortcutInfo2, options2,
+                        sidePosition, splitRatio, adapter, instanceId);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed call startIntentsWithLegacyTransition");
             }
@@ -799,11 +800,12 @@
         }
     }
 
-    public void startIntent(PendingIntent intent, Intent fillInIntent, int position,
+    public void startIntent(PendingIntent intent, int userId, Intent fillInIntent, int position,
             Bundle options, InstanceId instanceId) {
         if (mSplitScreen != null) {
             try {
-                mSplitScreen.startIntent(intent, fillInIntent, position, options, instanceId);
+                mSplitScreen.startIntent(intent, userId, fillInIntent, position, options,
+                        instanceId);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed call startIntent");
             }
diff --git a/quickstep/src/com/android/quickstep/TopTaskTracker.java b/quickstep/src/com/android/quickstep/TopTaskTracker.java
index 6f502d0..d34cddf 100644
--- a/quickstep/src/com/android/quickstep/TopTaskTracker.java
+++ b/quickstep/src/com/android/quickstep/TopTaskTracker.java
@@ -18,6 +18,7 @@
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.content.Intent.ACTION_CHOOSER;
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
@@ -127,23 +128,16 @@
 
     @Override
     public void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {
-        // If task is not visible but we are tracking it, stop tracking it
-        if (!visible) {
+        // If a task is not visible anymore or has been moved to undefined, stop tracking it.
+        if (!visible || stage == SplitConfigurationOptions.STAGE_TYPE_UNDEFINED) {
             if (mMainStagePosition.taskId == taskId) {
-                resetTaskId(mMainStagePosition);
+                mMainStagePosition.taskId = INVALID_TASK_ID;
             } else if (mSideStagePosition.taskId == taskId) {
-                resetTaskId(mSideStagePosition);
+                mSideStagePosition.taskId = INVALID_TASK_ID;
             } // else it's an un-tracked child
             return;
         }
 
-        // If stage has moved to undefined, stop tracking the task
-        if (stage == SplitConfigurationOptions.STAGE_TYPE_UNDEFINED) {
-            resetTaskId(taskId == mMainStagePosition.taskId
-                    ? mMainStagePosition : mSideStagePosition);
-            return;
-        }
-
         if (stage == SplitConfigurationOptions.STAGE_TYPE_MAIN) {
             mMainStagePosition.taskId = taskId;
         } else {
@@ -161,10 +155,6 @@
         mPinnedTaskId = INVALID_TASK_ID;
     }
 
-    private void resetTaskId(SplitStageInfo taskPosition) {
-        taskPosition.taskId = -1;
-    }
-
     /**
      * @return index 0 will be task in left/top position, index 1 in right/bottom position.
      *         Will return empty array if device is not in staged split
@@ -255,6 +245,11 @@
                     .getActivityType() == ACTIVITY_TYPE_HOME;
         }
 
+        public boolean isRecentsTask() {
+            return mTopTask != null && mTopTask.configuration.windowConfiguration
+                    .getActivityType() == ACTIVITY_TYPE_RECENTS;
+        }
+
         /**
          * Returns {@code true} if this task windowing mode is set to {@link
          * android.app.WindowConfiguration#WINDOWING_MODE_FREEFORM}
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 682763f..99a57a2 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -862,7 +862,8 @@
             if (tac != null) {
                 // Present always on large screen or on small screen w/ flag
                 DeviceProfile dp = tac.getDeviceProfile();
-                boolean useTaskbarConsumer = dp.isTaskbarPresent && !TaskbarManager.isPhoneMode(dp);
+                boolean useTaskbarConsumer = dp.isTaskbarPresent && !TaskbarManager.isPhoneMode(dp)
+                        && !tac.isInStashedLauncherState();
                 if (canStartSystemGesture && useTaskbarConsumer) {
                     reasonString.append(NEWLINE_PREFIX)
                             .append(reasonPrefix)
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
index 42a74d9..59a9582 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
@@ -20,7 +20,6 @@
 import static android.view.MotionEvent.ACTION_UP;
 
 import static com.android.launcher3.Utilities.squaredHypot;
-import static com.android.launcher3.Utilities.squaredTouchSlop;
 import static com.android.launcher3.util.VelocityUtils.PX_PER_MS;
 import static com.android.quickstep.AbsSwipeUpHandler.MIN_PROGRESS_FOR_OVERVIEW;
 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
@@ -115,7 +114,7 @@
         mDeviceState = deviceState;
         mTaskAnimationManager = taskAnimationManager;
         mGestureState = gestureState;
-        mTouchSlopSquared = squaredTouchSlop(context);
+        mTouchSlopSquared = mDeviceState.getSquaredTouchSlop();
         mTransformParams = new TransformParams();
         mInputMonitorCompat = inputMonitorCompat;
         mMaxTranslationY = context.getResources().getDimensionPixelSize(
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java
index d7ed79b..5387c8a 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java
@@ -21,8 +21,8 @@
 import static android.view.MotionEvent.ACTION_MOVE;
 import static android.view.MotionEvent.ACTION_UP;
 
-import static com.android.launcher3.testing.shared.ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE;
 import static com.android.launcher3.Utilities.squaredHypot;
+import static com.android.launcher3.testing.shared.ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE;
 
 import android.content.Context;
 import android.graphics.Point;
@@ -31,7 +31,6 @@
 
 import com.android.launcher3.R;
 import com.android.launcher3.testing.shared.ResourceUtils;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.util.DisplayController;
 import com.android.quickstep.InputConsumer;
 import com.android.quickstep.RecentsAnimationDeviceState;
@@ -69,7 +68,7 @@
         mDeviceState = deviceState;
         mDragDistThreshold = context.getResources().getDimensionPixelSize(
                 R.dimen.gestures_onehanded_drag_threshold);
-        mSquaredSlop = Utilities.squaredTouchSlop(context);
+        mSquaredSlop = mDeviceState.getSquaredTouchSlop();
         mDisplaySize = DisplayController.INSTANCE.get(mContext).getInfo().currentSize;
         mNavBarSize = ResourceUtils.getNavbarSize(NAVBAR_BOTTOM_GESTURE_SIZE,
                 mContext.getResources());
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index 2dcbbb9..f9cd4ee 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -80,10 +80,6 @@
     public static final String DOWN_EVT = "OtherActivityInputConsumer.DOWN";
     private static final String UP_EVT = "OtherActivityInputConsumer.UP";
 
-    // TODO: Move to quickstep contract
-    public static final float QUICKSTEP_TOUCH_SLOP_RATIO_TWO_BUTTON = 9;
-    public static final float QUICKSTEP_TOUCH_SLOP_RATIO_GESTURAL = 2;
-
     // Minimum angle of a gesture's coordinate where a release goes to overview.
     public static final int OVERVIEW_MIN_DEGREES = 15;
 
@@ -157,11 +153,8 @@
         boolean continuingPreviousGesture = mTaskAnimationManager.isRecentsAnimationRunning();
         mIsDeferredDownTarget = !continuingPreviousGesture && isDeferredDownTarget;
 
-        float slopMultiplier = mDeviceState.isFullyGesturalNavMode()
-                ? QUICKSTEP_TOUCH_SLOP_RATIO_GESTURAL
-                : QUICKSTEP_TOUCH_SLOP_RATIO_TWO_BUTTON;
         mTouchSlop = ViewConfiguration.get(this).getScaledTouchSlop();
-        mSquaredTouchSlop = slopMultiplier * mTouchSlop * mTouchSlop;
+        mSquaredTouchSlop = mDeviceState.getSquaredTouchSlop();
 
         mPassedPilferInputSlop = mPassedWindowMoveSlop = continuingPreviousGesture;
         mDisableHorizontalSwipe = !mPassedPilferInputSlop && disableHorizontalSwipe;
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
index 64165b6..3388642 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
@@ -51,6 +51,7 @@
 
     private final boolean mStartingInActivityBounds;
     private boolean mTargetHandledTouch;
+    private boolean mHasSetTouchModeForFirstDPadEvent;
 
     public OverviewInputConsumer(GestureState gestureState, T activity,
             @Nullable InputMonitorCompat inputMonitor, boolean startingInActivityBounds) {
@@ -95,6 +96,9 @@
                 mInputMonitor.pilferPointers();
             }
         }
+        if (mHasSetTouchModeForFirstDPadEvent) {
+            mActivity.getRootView().clearFocus();
+        }
     }
 
     @Override
@@ -112,6 +116,19 @@
                 mgr.dispatchVolumeKeyEventAsSystemService(ev,
                         AudioManager.USE_DEFAULT_STREAM_TYPE);
                 break;
+            case KeyEvent.KEYCODE_DPAD_LEFT:
+            case KeyEvent.KEYCODE_DPAD_RIGHT:
+                if (!mHasSetTouchModeForFirstDPadEvent) {
+                    // When Overview is launched via meta+tab or swipe up from an app, the touch
+                    // mode somehow is not changed to false by the Android framework. The subsequent
+                    // key events (e.g. DPAD_LEFT, DPAD_RIGHT) can only be dispatched to focused
+                    // views, while focus can only be requested in
+                    // {@link View#requestFocusNoSearch(int, Rect)} when touch mode is false. To
+                    // note, here we launch overview with live tile.
+                    mHasSetTouchModeForFirstDPadEvent = true;
+                    mActivity.getRootView().getViewRootImpl().touchModeChanged(false);
+                }
+                break;
             default:
                 break;
         }
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
index 65c825c..fbe7fde 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
@@ -81,6 +81,7 @@
             InputMonitorCompat inputMonitor, TaskbarActivityContext taskbarActivityContext) {
         super(delegate, inputMonitor);
         mTaskbarActivityContext = taskbarActivityContext;
+        // TODO(b/270395798): remove this when cleaning up old Persistent Taskbar code.
         mSquaredTouchSlop = Utilities.squaredTouchSlop(context);
         mScreenWidth = taskbarActivityContext.getDeviceProfile().widthPx;
 
diff --git a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
index 1ac0742..aeac760 100644
--- a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
@@ -186,7 +186,8 @@
     public void launchTutorialMenu() {
         mFragment = new MenuFragment();
         getSupportFragmentManager().beginTransaction()
-                .add(R.id.gesture_tutorial_fragment_container, mFragment)
+                .replace(R.id.gesture_tutorial_fragment_container, mFragment)
+                .runOnCommit(() -> mFragment.onAttachedToWindow())
                 .commit();
     }
 
diff --git a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
index 11c9e37..0497f0a 100644
--- a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
@@ -106,6 +106,7 @@
                     case BACK_CANCELLED_FROM_LEFT:
                     case BACK_CANCELLED_FROM_RIGHT:
                     case BACK_NOT_STARTED_TOO_FAR_FROM_EDGE:
+                        resetTaskView();
                         showFeedback(R.string.home_gesture_feedback_swipe_too_far_from_edge);
                         break;
                 }
@@ -135,6 +136,7 @@
                     }
                     case HOME_NOT_STARTED_TOO_FAR_FROM_EDGE:
                     case OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE:
+                        resetTaskView();
                         showFeedback(R.string.home_gesture_feedback_swipe_too_far_from_edge);
                         break;
                     case OVERVIEW_GESTURE_COMPLETED:
diff --git a/quickstep/src/com/android/quickstep/interaction/MenuFragment.java b/quickstep/src/com/android/quickstep/interaction/MenuFragment.java
index ccff30d..46f79b1 100644
--- a/quickstep/src/com/android/quickstep/interaction/MenuFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/MenuFragment.java
@@ -19,6 +19,7 @@
 import static com.android.quickstep.interaction.GestureSandboxActivity.KEY_TUTORIAL_TYPE;
 import static com.android.quickstep.interaction.GestureSandboxActivity.KEY_USE_TUTORIAL_MENU;
 
+import android.graphics.Rect;
 import android.os.Bundle;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -27,17 +28,33 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.R;
 
 /** Displays the gesture nav tutorial menu. */
 public final class MenuFragment extends GestureSandboxFragment {
 
+    @NonNull private Rect mInsets = new Rect();
+
+    @Override
+    public void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mInsets = InvariantDeviceProfile.INSTANCE.get(getContext())
+                .getDeviceProfile(getContext()).getInsets();
+    }
+
     @Override
     public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
             @Nullable Bundle savedInstanceState) {
         View root = inflater.inflate(
                 R.layout.gesture_tutorial_step_menu, container, false);
 
+        root.setPadding(
+                root.getPaddingLeft() + mInsets.left,
+                root.getPaddingTop() + mInsets.top,
+                root.getPaddingRight() + mInsets.right,
+                root.getPaddingBottom() + mInsets.bottom);
+
         root.findViewById(R.id.gesture_tutorial_menu_home_button).setOnClickListener(
                 v -> launchTutorialStep(TutorialController.TutorialType.HOME_NAVIGATION));
         root.findViewById(R.id.gesture_tutorial_menu_back_button).setOnClickListener(
diff --git a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
index dfbcf4d..c5d0ebe 100644
--- a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
@@ -21,7 +21,6 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
-import android.annotation.Nullable;
 import android.annotation.TargetApi;
 import android.graphics.PointF;
 import android.os.Build;
@@ -112,6 +111,7 @@
                     case BACK_CANCELLED_FROM_LEFT:
                     case BACK_CANCELLED_FROM_RIGHT:
                     case BACK_NOT_STARTED_TOO_FAR_FROM_EDGE:
+                        resetTaskView();
                         showFeedback(R.string.overview_gesture_feedback_swipe_too_far_from_edge);
                         break;
                 }
@@ -142,33 +142,15 @@
                     }
                     case HOME_NOT_STARTED_TOO_FAR_FROM_EDGE:
                     case OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE:
+                        resetTaskView();
                         showFeedback(R.string.overview_gesture_feedback_swipe_too_far_from_edge);
                         break;
                     case OVERVIEW_GESTURE_COMPLETED:
+                        setGestureCompleted();
                         mTutorialFragment.releaseFeedbackAnimation();
-                        if (ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()) {
-                            onMotionPaused(true /*arbitrary value*/);
-                            animateTaskViewToOverview(() -> {
-                                mFakeTaskView.setVisibility(View.INVISIBLE);
-                                if(!mTutorialFragment.isLargeScreen()){
-                                    mFakePreviousTaskView.animateToFillScreen(() -> {
-                                        mFakeLauncherView.setBackgroundColor(
-                                                mContext.getColor(
-                                                        R.color.gesture_overview_tutorial_swipe_rect
-                                                ));
-                                        showSuccessFeedback();
-                                    });
-                                } else {
-                                    mFakeLauncherView.setBackgroundColor(
-                                            mContext.getColor(
-                                                    R.color.gesture_overview_tutorial_swipe_rect
-                                            ));
-                                    showSuccessFeedback();
-                                }
-                            });
-                        } else {
-                            animateTaskViewToOverview(null);
-                            onMotionPaused(true /*arbitrary value*/);
+                        animateTaskViewToOverview(ENABLE_NEW_GESTURE_NAV_TUTORIAL.get());
+                        onMotionPaused(true /*arbitrary value*/);
+                        if (!ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()) {
                             showSuccessFeedback();
                         }
                         break;
@@ -189,21 +171,28 @@
 
     /**
      * runnable executed with slight delay to ease the swipe animation after landing on overview
-     * @param runnable
      */
-    public void animateTaskViewToOverview(@Nullable Runnable runnable) {
+    public void animateTaskViewToOverview(boolean animateDelayedSuccessFeedback) {
         PendingAnimation anim = new PendingAnimation(TASK_VIEW_END_ANIMATION_DURATION_MILLIS);
         anim.setFloat(mTaskViewSwipeUpAnimation
                 .getCurrentShift(), AnimatedFloat.VALUE, 1, ACCEL);
 
-        anim.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animator) {
-                if (runnable != null) {
-                    new Handler().postDelayed(runnable, 300);
+        if (animateDelayedSuccessFeedback) {
+            anim.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animator) {
+                    new Handler().postDelayed(() -> {
+                        mFakeTaskView.setVisibility(View.INVISIBLE);
+                        if (!mTutorialFragment.isLargeScreen()) {
+                            mFakePreviousTaskView.animateToFillScreen(
+                                    () -> onSuccessAnimationComplete());
+                        } else {
+                            onSuccessAnimationComplete();
+                        }
+                    }, TASK_VIEW_FILL_SCREEN_ANIMATION_DELAY_MILLIS);
                 }
-            }
-        });
+            });
+        }
 
         ArrayList<Animator> animators = new ArrayList<>();
 
@@ -222,4 +211,9 @@
         animset.start();
         mRunningWindowAnim = SwipeUpAnimationLogic.RunningWindowAnim.wrap(animset);
     }
+
+    private void onSuccessAnimationComplete() {
+        setLauncherViewColor(R.color.gesture_overview_tutorial_swipe_rect);
+        showSuccessFeedback();
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java
index c471a13..01074dd 100644
--- a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java
@@ -73,7 +73,7 @@
             @Override
             public void onAnimationStart(Animator animation) {
                 super.onAnimationStart(animation);
-                controller.animateTaskViewToOverview(null);
+                controller.animateTaskViewToOverview(false);
             }
         });
 
diff --git a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
index a8af05e..66c659a 100644
--- a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
@@ -61,9 +61,10 @@
 @TargetApi(Build.VERSION_CODES.R)
 abstract class SwipeUpGestureTutorialController extends TutorialController {
 
-    private static final int FAKE_PREVIOUS_TASK_MARGIN = Utilities.dpToPx(12);
+    private static final int FAKE_PREVIOUS_TASK_MARGIN = Utilities.dpToPx(24);
 
     protected static final long TASK_VIEW_END_ANIMATION_DURATION_MILLIS = 300;
+    protected static final long TASK_VIEW_FILL_SCREEN_ANIMATION_DELAY_MILLIS = 300;
     private static final long HOME_SWIPE_ANIMATION_DURATION_MILLIS = 625;
     private static final long OVERVIEW_SWIPE_ANIMATION_DURATION_MILLIS = 1000;
 
@@ -77,23 +78,7 @@
     private final AnimatorListenerAdapter mResetTaskView = new AnimatorListenerAdapter() {
         @Override
         public void onAnimationEnd(Animator animation) {
-            mFakeHotseatView.setVisibility(View.INVISIBLE);
-            mFakeIconView.setVisibility(View.INVISIBLE);
-            if (mTutorialFragment.getActivity() != null) {
-                int height = mTutorialFragment.getRootView().getFullscreenHeight();
-                int width = mTutorialFragment.getRootView().getWidth();
-                mFakeTaskViewRect.set(0, 0, width, height);
-            }
-            mFakeTaskViewRadius = 0;
-            mFakeTaskView.invalidateOutline();
-            mFakeTaskView.setVisibility(View.VISIBLE);
-            mFakeTaskView.setAlpha(1);
-            mFakePreviousTaskView.setVisibility(View.INVISIBLE);
-            mFakePreviousTaskView.setAlpha(1);
-            mFakePreviousTaskView.setToSingleRowLayout(false);
-            mShowTasks = false;
-            mShowPreviousTasks = false;
-            mRunningWindowAnim = null;
+            resetTaskView();
         }
     };
 
@@ -137,6 +122,26 @@
         mRunningWindowAnim = null;
     }
 
+    void resetTaskView() {
+        mFakeHotseatView.setVisibility(View.INVISIBLE);
+        mFakeIconView.setVisibility(View.INVISIBLE);
+        if (mTutorialFragment.getActivity() != null) {
+            int height = mTutorialFragment.getRootView().getFullscreenHeight();
+            int width = mTutorialFragment.getRootView().getWidth();
+            mFakeTaskViewRect.set(0, 0, width, height);
+        }
+        mFakeTaskViewRadius = 0;
+        mFakeTaskView.invalidateOutline();
+        mFakeTaskView.setVisibility(View.VISIBLE);
+        mFakeTaskView.setAlpha(1);
+        mFakePreviousTaskView.setVisibility(View.INVISIBLE);
+        mFakePreviousTaskView.setAlpha(1);
+        mFakePreviousTaskView.setToSingleRowLayout(false);
+        mShowTasks = false;
+        mShowPreviousTasks = false;
+        mRunningWindowAnim = null;
+    }
+
     /** Fades the task view, optionally after animating to a fake Overview. */
     void fadeOutFakeTaskView(boolean toOverviewFirst, boolean reset,
                              @Nullable Runnable onEndRunnable) {
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
index d4ff457..198305f 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
@@ -317,6 +317,14 @@
     }
 
     /**
+     * Only use this when a gesture is completed, but the feedback shouldn't be shown immediately.
+     * In that case, call this method immediately instead.
+     */
+    public void setGestureCompleted() {
+        mGestureCompleted = true;
+    }
+
+    /**
      * Show feedback reflecting a successful gesture attempt.
      **/
     void showSuccessFeedback() {
@@ -641,13 +649,17 @@
         }
     }
 
+    void setLauncherViewColor(@ColorRes int backgroundColorRes) {
+        mFakeLauncherView.setBackgroundColor(mContext.getColor(backgroundColorRes));
+    }
+
     private void updateDrawables() {
         if (mContext != null) {
             mTutorialFragment.getRootView().setBackground(AppCompatResources.getDrawable(
                     mContext, getMockWallpaperResId()));
             mTutorialFragment.updateFeedbackAnimation();
-            mFakeLauncherView.setBackgroundColor(
-                    mContext.getColor(R.color.gesture_tutorial_fake_wallpaper_color));
+            setLauncherViewColor(ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()
+                    ? getSwipeActionColorResId() : R.color.gesture_tutorial_fake_wallpaper_color);
             updateFakeViewLayout(mFakeHotseatView, getMockHotseatResId());
             mHotseatIconView = mFakeHotseatView.findViewById(R.id.hotseat_icon_1);
             updateFakeViewLayout(mFakeTaskView, getMockAppTaskLayoutResId());
@@ -657,11 +669,6 @@
                     getMockPreviousAppTaskThumbnailColorResId()));
             mFakeIconView.setBackground(AppCompatResources.getDrawable(
                     mContext, getMockAppIconResId()));
-
-            if (ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()) {
-                mFakeLauncherView.setBackgroundColor(
-                        mContext.getColor(getSwipeActionColorResId()));
-            }
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
index 3d5c143..d3a01f2 100644
--- a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
+++ b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
@@ -50,6 +50,7 @@
 import com.android.launcher3.util.DisplayController.Info;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.NavigationMode;
+import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.util.SettingsCache;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -62,7 +63,8 @@
  * Utility class to log launcher settings changes
  */
 public class SettingsChangeLogger implements
-        DisplayController.DisplayInfoChangeListener, OnSharedPreferenceChangeListener {
+        DisplayController.DisplayInfoChangeListener, OnSharedPreferenceChangeListener,
+        SafeCloseable {
 
     /**
      * Singleton instance
@@ -188,6 +190,12 @@
                 prefs.getBoolean(key, lp.defaultValue) ? lp.eventIdOn : lp.eventIdOff));
     }
 
+    @Override
+    public void close() {
+        getPrefs(mContext).unregisterOnSharedPreferenceChangeListener(this);
+        getDevicePrefs(mContext).unregisterOnSharedPreferenceChangeListener(this);
+    }
+
     private static class LoggablePref {
         public boolean defaultValue;
         public int eventIdOn;
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index 298d49a..fbb8109 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -494,6 +494,7 @@
         private long mLatencyInMillis;
         private int mQueryLength = -1;
         private int mSubEventType = 0;
+        private int mCardinality = -1;
 
         @Override
         public StatsLatencyLogger withInstanceId(InstanceId instanceId) {
@@ -532,6 +533,12 @@
         }
 
         @Override
+        public StatsLatencyLogger withCardinality(int cardinality) {
+            this.mCardinality = cardinality;
+            return this;
+        }
+
+        @Override
         public void log(EventEnum event) {
             if (IS_VERBOSE) {
                 String name = (event instanceof Enum) ? ((Enum) event).name() :
@@ -549,7 +556,8 @@
                     mLatencyInMillis, // latency_in_millis
                     mType.getId(), //type
                     mQueryLength, // query_length
-                    mSubEventType // sub_event_type
+                    mSubEventType, // sub_event_type
+                    mCardinality // cardinality
             );
         }
     }
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt b/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt
index ebea58c..614dfe8 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt
@@ -207,6 +207,8 @@
                 secondTaskId,
                 initialPendingIntent,
                 secondPendingIntent,
+                initialUser?.identifier ?: -1,
+                secondUser?.identifier ?: -1,
                 initialShortcut,
                 secondShortcut,
                 itemInfo,
@@ -291,6 +293,8 @@
             var secondTaskId: Int = INVALID_TASK_ID,
             var initialPendingIntent: PendingIntent? = null,
             var secondPendingIntent: PendingIntent? = null,
+            var initialUserId: Int = -1,
+            var secondUserId: Int = -1,
             var initialShortcut: ShortcutInfo? = null,
             var secondShortcut: ShortcutInfo? = null,
             var itemInfo: ItemInfo? = null,
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index acc3ba1..da81410 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -372,12 +372,13 @@
                         shellInstanceId);
             } else {
                 mSystemUiProxy.startIntents(getPendingIntent(intent1, mInitialUser),
-                        getShortcutInfo(intent1, mInitialUser), options1.toBundle(),
-                        hasSecondaryPendingIntent
+                        mInitialUser.getIdentifier(), getShortcutInfo(intent1, mInitialUser),
+                        options1.toBundle(), hasSecondaryPendingIntent
                                 ? mSecondPendingIntent
                                 : getPendingIntent(intent2, mSecondUser),
-                        getShortcutInfo(intent2, mSecondUser), null /* options2 */,
-                        stagePosition, splitRatio, remoteTransition, shellInstanceId);
+                        mSecondUser.getIdentifier(), getShortcutInfo(intent2, mSecondUser),
+                        null /* options2 */, stagePosition, splitRatio, remoteTransition,
+                        shellInstanceId);
             }
         } else {
             final RemoteSplitLaunchAnimationRunner animationRunner =
@@ -399,13 +400,13 @@
                         shellInstanceId);
             } else {
                 mSystemUiProxy.startIntentsWithLegacyTransition(
-                        getPendingIntent(intent1, mInitialUser),
+                        getPendingIntent(intent1, mInitialUser), mInitialUser.getIdentifier(),
                         getShortcutInfo(intent1, mInitialUser), options1.toBundle(),
                         hasSecondaryPendingIntent
                                 ? mSecondPendingIntent
                                 : getPendingIntent(intent2, mSecondUser),
-                        getShortcutInfo(intent2, mSecondUser), null /* options2 */, stagePosition,
-                        splitRatio, adapter, shellInstanceId);
+                        mSecondUser.getIdentifier(), getShortcutInfo(intent2, mSecondUser),
+                        null /* options2 */, stagePosition, splitRatio, adapter, shellInstanceId);
             }
         }
     }
@@ -425,6 +426,8 @@
         ShortcutInfo secondShortcut = launchData.getSecondShortcut();
         PendingIntent firstPI = launchData.getInitialPendingIntent();
         PendingIntent secondPI = launchData.getSecondPendingIntent();
+        int firstUserId = launchData.getInitialUserId();
+        int secondUserId = launchData.getSecondUserId();
         int initialStagePosition = launchData.getInitialStagePosition();
         Bundle optionsBundle = options1.toBundle();
 
@@ -441,8 +444,8 @@
                                 remoteTransition, shellInstanceId);
 
                 case SPLIT_TASK_PENDINGINTENT ->
-                        mSystemUiProxy.startIntentAndTask(secondPI, optionsBundle, firstTaskId,
-                                null /*options2*/, initialStagePosition, splitRatio,
+                        mSystemUiProxy.startIntentAndTask(secondPI, secondUserId, optionsBundle,
+                                firstTaskId, null /*options2*/, initialStagePosition, splitRatio,
                                 remoteTransition, shellInstanceId);
 
                 case SPLIT_TASK_SHORTCUT ->
@@ -451,13 +454,14 @@
                                 remoteTransition, shellInstanceId);
 
                 case SPLIT_PENDINGINTENT_TASK ->
-                        mSystemUiProxy.startIntentAndTask(firstPI, optionsBundle, secondTaskId,
-                                null /*options2*/, initialStagePosition, splitRatio,
+                        mSystemUiProxy.startIntentAndTask(firstPI, firstUserId, optionsBundle,
+                                secondTaskId, null /*options2*/, initialStagePosition, splitRatio,
                                 remoteTransition, shellInstanceId);
 
                 case SPLIT_PENDINGINTENT_PENDINGINTENT ->
-                        mSystemUiProxy.startIntents(firstPI, firstShortcut, optionsBundle, secondPI,
-                                secondShortcut, null /*options2*/, initialStagePosition, splitRatio,
+                        mSystemUiProxy.startIntents(firstPI, firstUserId, firstShortcut,
+                                optionsBundle, secondPI, secondUserId, secondShortcut,
+                                null /*options2*/, initialStagePosition, splitRatio,
                                 remoteTransition, shellInstanceId);
 
                 case SPLIT_SHORTCUT_TASK ->
@@ -479,8 +483,8 @@
 
                 case SPLIT_TASK_PENDINGINTENT ->
                         mSystemUiProxy.startIntentAndTaskWithLegacyTransition(secondPI,
-                                optionsBundle, firstTaskId, null /*options2*/, initialStagePosition,
-                                splitRatio, adapter, shellInstanceId);
+                                secondUserId, optionsBundle, firstTaskId, null /*options2*/,
+                                initialStagePosition, splitRatio, adapter, shellInstanceId);
 
                 case SPLIT_TASK_SHORTCUT ->
                         mSystemUiProxy.startShortcutAndTaskWithLegacyTransition(secondShortcut,
@@ -488,14 +492,15 @@
                                 splitRatio, adapter, shellInstanceId);
 
                 case SPLIT_PENDINGINTENT_TASK ->
-                        mSystemUiProxy.startIntentAndTaskWithLegacyTransition(firstPI,
+                        mSystemUiProxy.startIntentAndTaskWithLegacyTransition(firstPI, firstUserId,
                                 optionsBundle, secondTaskId, null /*options2*/,
                                 initialStagePosition, splitRatio, adapter, shellInstanceId);
 
                 case SPLIT_PENDINGINTENT_PENDINGINTENT ->
-                        mSystemUiProxy.startIntentsWithLegacyTransition(firstPI, firstShortcut,
-                                optionsBundle, secondPI, secondShortcut, null /*options2*/,
-                                initialStagePosition, splitRatio, adapter, shellInstanceId);
+                        mSystemUiProxy.startIntentsWithLegacyTransition(firstPI, firstUserId,
+                                firstShortcut, optionsBundle, secondPI, secondUserId,
+                                secondShortcut, null /*options2*/, initialStagePosition, splitRatio,
+                                adapter, shellInstanceId);
 
                 case SPLIT_SHORTCUT_TASK ->
                         mSystemUiProxy.startShortcutAndTaskWithLegacyTransition(firstShortcut,
@@ -514,7 +519,7 @@
                     options1.toBundle(), taskId, null /* options2 */, stagePosition,
                     splitRatio, remoteTransition, shellInstanceId);
         } else {
-            mSystemUiProxy.startIntentAndTask(getPendingIntent(intent, user),
+            mSystemUiProxy.startIntentAndTask(getPendingIntent(intent, user), user.getIdentifier(),
                     options1.toBundle(), taskId, null /* options2 */, stagePosition, splitRatio,
                     remoteTransition, shellInstanceId);
         }
@@ -531,8 +536,9 @@
                     splitRatio, adapter, shellInstanceId);
         } else {
             mSystemUiProxy.startIntentAndTaskWithLegacyTransition(
-                    getPendingIntent(intent, user), options1.toBundle(), taskId,
-                    null /* options2 */, stagePosition, splitRatio, adapter, shellInstanceId);
+                    getPendingIntent(intent, user), user.getIdentifier(), options1.toBundle(),
+                    taskId, null /* options2 */, stagePosition, splitRatio, adapter,
+                    shellInstanceId);
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/util/TransformParams.java b/quickstep/src/com/android/quickstep/util/TransformParams.java
index aa9a45b..0f20e43 100644
--- a/quickstep/src/com/android/quickstep/util/TransformParams.java
+++ b/quickstep/src/com/android/quickstep/util/TransformParams.java
@@ -17,11 +17,9 @@
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 
 import android.util.FloatProperty;
 import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
 
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.Interpolators;
@@ -61,7 +59,6 @@
     private float mCornerRadius;
     private RemoteAnimationTargets mTargetSet;
     private SurfaceTransactionApplier mSyncTransactionApplier;
-    private SurfaceControl mRecentsSurface;
 
     private BuilderProxy mHomeBuilderProxy = BuilderProxy.ALWAYS_VISIBLE;
     private BuilderProxy mBaseBuilderProxy = BuilderProxy.ALWAYS_VISIBLE;
@@ -141,8 +138,9 @@
     public SurfaceTransaction createSurfaceParams(BuilderProxy proxy) {
         RemoteAnimationTargets targets = mTargetSet;
         SurfaceTransaction transaction = new SurfaceTransaction();
-        mRecentsSurface = getRecentsSurface(targets);
-
+        if (targets == null) {
+            return transaction;
+        }
         for (int i = 0; i < targets.unfilteredApps.length; i++) {
             RemoteAnimationTarget app = targets.unfilteredApps[i];
             SurfaceProperties builder = transaction.forSurface(app.leash);
@@ -176,20 +174,6 @@
         return transaction;
     }
 
-    private static SurfaceControl getRecentsSurface(RemoteAnimationTargets targets) {
-        for (int i = 0; i < targets.unfilteredApps.length; i++) {
-            RemoteAnimationTarget app = targets.unfilteredApps[i];
-            if (app.mode == targets.targetMode) {
-                if (app.windowConfiguration.getActivityType() == ACTIVITY_TYPE_RECENTS) {
-                    return app.leash;
-                }
-            } else {
-                return app.leash;
-            }
-        }
-        return null;
-    }
-
     // Pubic getters so outside packages can read the values.
 
     public float getProgress() {
@@ -204,10 +188,6 @@
         return mCornerRadius;
     }
 
-    public SurfaceControl getRecentsSurface() {
-        return mRecentsSurface;
-    }
-
     public RemoteAnimationTargets getTargetSet() {
         return mTargetSet;
     }
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index ff5af28..d8fe32d 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -213,6 +213,7 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.function.Consumer;
+import java.util.stream.Collectors;
 
 /**
  * A list of recent tasks.
@@ -1320,6 +1321,29 @@
         return null;
     }
 
+    /**
+     * Returns a {@link TaskView} that has taskIds matching {@code taskIds} or null if no match.
+     */
+    @Nullable
+    public TaskView getTaskViewByTaskIds(int[] taskIds) {
+        if (!hasAnyValidTaskIds(taskIds)) {
+            return null;
+        }
+
+        for (int i = 0; i < getTaskViewCount(); i++) {
+            TaskView taskView = requireTaskViewAt(i);
+            if (Arrays.equals(taskIds, taskView.getTaskIds())) {
+                return taskView;
+            }
+        }
+        return null;
+    }
+
+    /** Returns false if {@code taskIds} is null or contains invalid values, true otherwise */
+    private boolean hasAnyValidTaskIds(int[] taskIds) {
+        return taskIds != null && !Arrays.equals(taskIds, INVALID_TASK_IDS);
+    }
+
     public void setOverviewStateEnabled(boolean enabled) {
         mOverviewStateEnabled = enabled;
         updateTaskStackListenerState();
@@ -1589,10 +1613,10 @@
             return;
         }
 
-        int currentTaskId = INVALID_TASK_ID;
+        int[] currentTaskId = INVALID_TASK_IDS;
         TaskView currentTaskView = getTaskViewAt(mCurrentPage);
         if (currentTaskView != null && currentTaskView.getTask() != null) {
-            currentTaskId = currentTaskView.getTask().key.id;
+            currentTaskId = currentTaskView.getTaskIds();
         }
 
         // Unload existing visible task data
@@ -1604,8 +1628,8 @@
 
         // Save running task ID if it exists before rebinding all taskViews, otherwise the task from
         // the runningTaskView currently bound could get assigned to another TaskView
-        int runningTaskId = getTaskIdsForTaskViewId(mRunningTaskViewId)[0];
-        int focusedTaskId = getTaskIdsForTaskViewId(mFocusedTaskViewId)[0];
+        int[] runningTaskId = getTaskIdsForTaskViewId(mRunningTaskViewId);
+        int[] focusedTaskId = getTaskIdsForTaskViewId(mFocusedTaskViewId);
 
         // Removing views sets the currentPage to 0, so we save this and restore it after
         // the new set of views are added
@@ -1699,7 +1723,7 @@
         }
 
         // Keep same previous focused task
-        TaskView newFocusedTaskView = getTaskViewByTaskId(focusedTaskId);
+        TaskView newFocusedTaskView = getTaskViewByTaskIds(focusedTaskId);
         // If the list changed, maybe the focused task doesn't exist anymore
         if (newFocusedTaskView == null && getTaskViewCount() > 0) {
             newFocusedTaskView = getTaskViewAt(0);
@@ -1716,10 +1740,10 @@
         updateChildTaskOrientations();
 
         TaskView newRunningTaskView = null;
-        if (runningTaskId != INVALID_TASK_ID) {
+        if (hasAnyValidTaskIds(runningTaskId)) {
             // Update mRunningTaskViewId to be the new TaskView that was assigned by binding
             // the full list of tasks to taskViews
-            newRunningTaskView = getTaskViewByTaskId(runningTaskId);
+            newRunningTaskView = getTaskViewByTaskIds(runningTaskId);
             if (newRunningTaskView != null) {
                 mRunningTaskViewId = newRunningTaskView.getTaskViewId();
             } else {
@@ -1731,15 +1755,15 @@
         if (mNextPage != INVALID_PAGE) {
             // Restore mCurrentPage but don't call setCurrentPage() as that clobbers the scroll.
             mCurrentPage = previousCurrentPage;
-            if (currentTaskId != INVALID_TASK_ID) {
-                currentTaskView = getTaskViewByTaskId(currentTaskId);
+            if (hasAnyValidTaskIds(currentTaskId)) {
+                currentTaskView = getTaskViewByTaskIds(currentTaskId);
                 if (currentTaskView != null) {
                     targetPage = indexOfChild(currentTaskView);
                 }
             }
         } else {
             // Set the current page to the running task, but not if settling on new task.
-            if (runningTaskId != INVALID_TASK_ID) {
+            if (hasAnyValidTaskIds(runningTaskId)) {
                 targetPage = indexOfChild(newRunningTaskView);
             } else if (getTaskViewCount() > 0) {
                 TaskView taskView = requireTaskViewAt(0);
@@ -2210,8 +2234,8 @@
         // Update the task data for the in/visible children
         for (int i = 0; i < getTaskViewCount(); i++) {
             TaskView taskView = requireTaskViewAt(i);
-            Task task = taskView.getTask();
-            if (task == null) {
+            TaskIdAttributeContainer[] containers = taskView.getTaskIdAttributeContainers();
+            if (containers[0] == null && containers[1] == null) {
                 continue;
             }
             int index = indexOfChild(taskView);
@@ -2222,34 +2246,43 @@
                 visible = lower <= index && index <= upper;
             }
             if (visible) {
-                boolean skipLoadingTask = false;
+                // Default update all non-null tasks, then remove running ones
+                List<Task> tasksToUpdate = Arrays.stream(containers).filter(Objects::nonNull)
+                        .map(TaskIdAttributeContainer::getTask)
+                        .collect(Collectors.toCollection(ArrayList::new));
                 if (mTmpRunningTasks != null) {
                     for (Task t : mTmpRunningTasks) {
-                        if (task == t) {
-                            // Skip loading if this is the task that we are animating into
-                            skipLoadingTask = true;
-                            break;
-                        }
+                        // Skip loading if this is the task that we are animating into
+                        // TODO(b/280812109) change this equality check to use A.equals(B)
+                        tasksToUpdate.removeIf(task -> task == t);
                     }
                 }
-                if (skipLoadingTask) {
+                if (tasksToUpdate.isEmpty()) {
                     continue;
                 }
-                if (!mHasVisibleTaskData.get(task.key.id)) {
-                    // Ignore thumbnail update if it's current running task during the gesture
-                    // We snapshot at end of gesture, it will update then
-                    int changes = dataChanges;
-                    if (taskView == getRunningTaskView() && mGestureActive) {
-                        changes &= ~TaskView.FLAG_UPDATE_THUMBNAIL;
+                for (Task task : tasksToUpdate) {
+                    if (!mHasVisibleTaskData.get(task.key.id)) {
+                        // Ignore thumbnail update if it's current running task during the gesture
+                        // We snapshot at end of gesture, it will update then
+                        int changes = dataChanges;
+                        if (taskView == getRunningTaskView() && mGestureActive) {
+                            changes &= ~TaskView.FLAG_UPDATE_THUMBNAIL;
+                        }
+                        taskView.onTaskListVisibilityChanged(true /* visible */, changes);
                     }
-                    taskView.onTaskListVisibilityChanged(true /* visible */, changes);
+                    mHasVisibleTaskData.put(task.key.id, visible);
                 }
-                mHasVisibleTaskData.put(task.key.id, visible);
             } else {
-                if (mHasVisibleTaskData.get(task.key.id)) {
-                    taskView.onTaskListVisibilityChanged(false /* visible */, dataChanges);
+                for (TaskIdAttributeContainer container : containers) {
+                    if (container == null) {
+                        continue;
+                    }
+
+                    if (mHasVisibleTaskData.get(container.getTask().key.id)) {
+                        taskView.onTaskListVisibilityChanged(false /* visible */, dataChanges);
+                    }
+                    mHasVisibleTaskData.delete(container.getTask().key.id);
                 }
-                mHasVisibleTaskData.delete(task.key.id);
             }
         }
     }
@@ -2312,7 +2345,6 @@
             remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(false);
         });
         resetFromSplitSelectionState();
-        mSplitSelectStateController.resetState();
 
         // These are relatively expensive and don't need to be done this frame (RecentsView isn't
         // visible anyway), so defer by a frame to get off the critical path, e.g. app to home.
@@ -4707,6 +4739,7 @@
         setTaskViewsPrimarySplitTranslation(0);
         setTaskViewsSecondarySplitTranslation(0);
 
+        mSplitSelectStateController.resetState();
         if (mSplitHiddenTaskViewIndex == -1) {
             return;
         }
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 50a1dad..d570708 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -653,7 +653,7 @@
      *         index 0 will contain the taskId, index 1 will be -1 indicating a null taskID value
      */
     public int[] getTaskIds() {
-        return mTaskIdContainer;
+        return Arrays.copyOf(mTaskIdContainer, mTaskIdContainer.length);
     }
 
     public boolean containsMultipleTasks() {
@@ -849,6 +849,13 @@
                     // QuickstepTransitionManager.createWallpaperOpenAnimations when launcher
                     // shows again
                     getRecentsView().startHome(false /* animated */);
+                    RecentsView rv = getRecentsView();
+                    if (rv != null && rv.mSizeStrategy.getTaskbarController() != null) {
+                        // LauncherTaskbarUIController depends on the launcher state when checking
+                        // whether to handle resume, but that can come in before startHome() changes
+                        // the state, so force-refresh here to ensure the taskbar is updated
+                        rv.mSizeStrategy.getTaskbarController().refreshResumedState();
+                    }
                 });
             }
             // Indicate success once the system has indicated that the transition has started
diff --git a/quickstep/tests/src/com/android/quickstep/AbstractTaplTestsTaskbar.java b/quickstep/tests/src/com/android/quickstep/AbstractTaplTestsTaskbar.java
new file mode 100644
index 0000000..4ca3563
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/AbstractTaplTestsTaskbar.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep;
+
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
+import static junit.framework.TestCase.assertEquals;
+
+import android.content.Context;
+import android.content.Intent;
+
+import com.android.launcher3.tapl.LauncherInstrumentation;
+import com.android.launcher3.tapl.Overview;
+import com.android.launcher3.tapl.Taskbar;
+import com.android.launcher3.ui.AbstractLauncherUiTest;
+import com.android.launcher3.ui.TaplTestsLauncher3;
+import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.LauncherLayoutBuilder;
+import com.android.launcher3.util.TestUtil;
+
+import org.junit.After;
+import org.junit.Assume;
+
+import java.util.List;
+
+public class AbstractTaplTestsTaskbar extends AbstractQuickStepTest {
+
+    protected static final String TEST_APP_NAME = "LauncherTestApp";
+    protected static final String TEST_APP_PACKAGE =
+            getInstrumentation().getContext().getPackageName();
+    protected static final String CALCULATOR_APP_PACKAGE =
+            resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR);
+
+    protected AutoCloseable mLauncherLayout;
+    protected boolean mTaskbarWasInTransientMode;
+
+
+    @Override
+    public void setUp() throws Exception {
+        Assume.assumeTrue(mLauncher.isTablet());
+        super.setUp();
+
+        LauncherLayoutBuilder layoutBuilder = new LauncherLayoutBuilder().atHotseat(0).putApp(
+                "com.google.android.apps.nexuslauncher.tests",
+                "com.android.launcher3.testcomponent.BaseTestingActivity");
+        mLauncherLayout = TestUtil.setLauncherDefaultLayout(mTargetContext, layoutBuilder);
+        TaplTestsLauncher3.initialize(this);
+        Overview overview = mLauncher.getWorkspace().switchToOverview();
+        if (overview.hasTasks()) {
+            overview.dismissAllTasks();
+        }
+        startAppFast(CALCULATOR_APP_PACKAGE);
+        mLauncher.enableBlockTimeout(true);
+        mLauncher.showTaskbarIfHidden();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mLauncher.enableBlockTimeout(false);
+        if (mLauncherLayout != null) {
+            mLauncherLayout.close();
+        }
+    }
+
+    protected static boolean isTaskbarInTransientMode(Context context) {
+        return DisplayController.isTransientTaskbar(context);
+    }
+
+    protected Taskbar getTaskbar() {
+        Taskbar taskbar = mLauncher.getLaunchedAppState().getTaskbar();
+        List<String> taskbarIconNames = taskbar.getIconNames();
+        List<String> hotseatIconNames = mLauncher.getHotseatIconNames();
+
+        assertEquals("Taskbar and hotseat icon counts do not match",
+                taskbarIconNames.size(), hotseatIconNames.size());
+
+        for (int i = 0; i < taskbarIconNames.size(); i++) {
+            assertEquals("Taskbar and Hotseat icons do not match",
+                    taskbarIconNames, hotseatIconNames);
+        }
+
+        return taskbar;
+    }
+
+    protected static void setTaskbarMode(LauncherInstrumentation launcher,
+            boolean expectTransientTaskbar) {
+        launcher.enableTransientTaskbar(expectTransientTaskbar);
+        launcher.recreateTaskbar();
+        launcher.checkForAnomaly(true, true);
+        AbstractLauncherUiTest.checkDetectedLeaks(launcher);
+    }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsPersistentTaskbar.java b/quickstep/tests/src/com/android/quickstep/TaplTestsPersistentTaskbar.java
new file mode 100644
index 0000000..1b5313b
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsPersistentTaskbar.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep;
+
+import static com.android.quickstep.TaskbarModeSwitchRule.Mode.PERSISTENT;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.quickstep.TaskbarModeSwitchRule.TaskbarModeSwitch;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class TaplTestsPersistentTaskbar extends AbstractTaplTestsTaskbar {
+
+    @Test
+    @TaskbarModeSwitch(mode = PERSISTENT)
+    public void testHideShowTaskbar() {
+        getTaskbar().hide();
+        mLauncher.getLaunchedAppState().showTaskbar();
+    }
+
+    @Test
+    @TaskbarModeSwitch(mode = PERSISTENT)
+    public void testHideTaskbarPersistsOnRecreate() {
+        getTaskbar().hide();
+        mLauncher.recreateTaskbar();
+        mLauncher.getLaunchedAppState().assertTaskbarHidden();
+    }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java b/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java
index f5c78f6..40be480 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java
@@ -15,121 +15,72 @@
  */
 package com.android.quickstep;
 
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
-
-import static com.android.launcher3.config.FeatureFlags.ENABLE_CURSOR_HOVER_STATES;
-import static com.android.quickstep.TaskbarModeSwitchRule.Mode.PERSISTENT;
-import static com.android.quickstep.TaskbarModeSwitchRule.Mode.TRANSIENT;
-
-import static junit.framework.TestCase.assertEquals;
-
-import android.content.Intent;
+import static com.android.quickstep.TaplTestsTaskbar.TaskbarMode.PERSISTENT;
+import static com.android.quickstep.TaplTestsTaskbar.TaskbarMode.TRANSIENT;
 
 import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
 
-import com.android.launcher3.tapl.Taskbar;
-import com.android.launcher3.ui.TaplTestsLauncher3;
-import com.android.launcher3.util.LauncherLayoutBuilder;
-import com.android.launcher3.util.TestUtil;
 import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
-import com.android.quickstep.TaskbarModeSwitchRule.TaskbarModeSwitch;
 
-import org.junit.After;
-import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
-import java.util.List;
+import java.util.Arrays;
+import java.util.Collection;
 
 @LargeTest
-@RunWith(AndroidJUnit4.class)
-public class TaplTestsTaskbar extends AbstractQuickStepTest {
+@RunWith(Parameterized.class)
+public class TaplTestsTaskbar extends AbstractTaplTestsTaskbar {
 
-    private static final String TEST_APP_NAME = "LauncherTestApp";
-    private static final String TEST_APP_PACKAGE =
-            getInstrumentation().getContext().getPackageName();
-    private static final String CALCULATOR_APP_PACKAGE =
-            resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR);
+    private final TaplTestsTaskbar.TaskbarMode mTaskbarMode;
 
-    private AutoCloseable mLauncherLayout;
+    public enum TaskbarMode {
+        TRANSIENT, PERSISTENT
+    }
+
+    @Parameterized.Parameters(name = "{0}")
+    public static Collection<Object[]> data() {
+        return Arrays.asList(new Object[][]{
+                {PERSISTENT}, {TRANSIENT}
+        });
+    }
+
+    public TaplTestsTaskbar(TaskbarMode mode) {
+        mTaskbarMode = mode;
+    }
 
     @Override
     public void setUp() throws Exception {
-        Assume.assumeTrue(mLauncher.isTablet());
+        mTaskbarWasInTransientMode = isTaskbarInTransientMode(mTargetContext);
+        setTaskbarMode(mLauncher, isTaskbarTestModeTransient());
         super.setUp();
-
-        LauncherLayoutBuilder layoutBuilder = new LauncherLayoutBuilder().atHotseat(0).putApp(
-                "com.google.android.apps.nexuslauncher.tests",
-                "com.android.launcher3.testcomponent.BaseTestingActivity");
-        mLauncherLayout = TestUtil.setLauncherDefaultLayout(mTargetContext, layoutBuilder);
-        TaplTestsLauncher3.initialize(this);
-
-        startAppFast(CALCULATOR_APP_PACKAGE);
-        mLauncher.enableBlockTimeout(true);
-        mLauncher.showTaskbarIfHidden();
     }
 
-    @After
+    @Override
     public void tearDown() throws Exception {
-        mLauncher.enableBlockTimeout(false);
-        if (mLauncherLayout != null) {
-            mLauncherLayout.close();
+        setTaskbarMode(mLauncher, mTaskbarWasInTransientMode);
+        super.tearDown();
+    }
+
+    @Test
+    public void testLaunchApp() {
+        getTaskbar().getAppIcon(TEST_APP_NAME).launch(TEST_APP_PACKAGE);
+        // We are using parameterized test runner to share code between different test cases with
+        // taskbar variants. But, sometimes we only need to assert things for particular Taskbar
+        // variants.
+        if (isTaskbarTestModeTransient()) {
+            mLauncher.getLaunchedAppState().assertTaskbarHidden();
         }
     }
 
     @Test
-    @TaskbarModeSwitch(mode = PERSISTENT)
-    public void testHideShowTaskbar() {
-        getTaskbar().hide();
-        mLauncher.getLaunchedAppState().showTaskbar();
-    }
-
-    @Test
-    @TaskbarModeSwitch(mode = PERSISTENT)
-    public void testHideTaskbarPersistsOnRecreate() {
-        getTaskbar().hide();
-        mLauncher.recreateTaskbar();
-        mLauncher.getLaunchedAppState().assertTaskbarHidden();
-    }
-
-    @Test
-    @TaskbarModeSwitch(mode = PERSISTENT)
-    public void testLaunchApp() throws Exception {
-        getTaskbar().getAppIcon(TEST_APP_NAME).launch(TEST_APP_PACKAGE);
-    }
-
-    @Test
-    @TaskbarModeSwitch(mode = TRANSIENT)
-    public void testTransientLaunchApp() throws Exception {
-        getTaskbar().getAppIcon(TEST_APP_NAME).launch(TEST_APP_PACKAGE);
-        mLauncher.getLaunchedAppState().assertTaskbarHidden();
-    }
-
-    @Test
-    @TaskbarModeSwitch(mode = PERSISTENT)
-    public void testOpenMenu() throws Exception {
+    public void testOpenMenu() {
         getTaskbar().getAppIcon(TEST_APP_NAME).openMenu();
     }
 
     @Test
-    @TaskbarModeSwitch(mode = TRANSIENT)
-    public void testTransientOpenMenu() throws Exception {
-        getTaskbar().getAppIcon(TEST_APP_NAME).openMenu();
-    }
-
-    @Test
-    @TaskbarModeSwitch(mode = PERSISTENT)
-    public void testLaunchShortcut() throws Exception {
-        getTaskbar().getAppIcon(TEST_APP_NAME)
-                .openDeepShortcutMenu()
-                .getMenuItem("Shortcut 1")
-                .launch(TEST_APP_PACKAGE);
-    }
-
-    @Test
-    @TaskbarModeSwitch(mode = TRANSIENT)
-    public void testTransientLaunchShortcut() throws Exception {
+    public void testLaunchShortcut() {
         getTaskbar().getAppIcon(TEST_APP_NAME)
                 .openDeepShortcutMenu()
                 .getMenuItem("Shortcut 1")
@@ -139,27 +90,21 @@
     @Test
     @ScreenRecord // b/231615831
     @PortraitLandscape
-    @TaskbarModeSwitch(mode = PERSISTENT)
-    public void testLaunchAppInSplitscreen() throws Exception {
+    public void testLaunchAppInSplitscreen() {
         getTaskbar().getAppIcon(TEST_APP_NAME).dragToSplitscreen(
                 TEST_APP_PACKAGE, CALCULATOR_APP_PACKAGE);
+        // We are using parameterized test runner to share code between different test cases with
+        // taskbar variants. But, sometimes we only need to assert things for particular Taskbar
+        // variants.
+        if (isTaskbarTestModeTransient()) {
+            mLauncher.getLaunchedAppState().assertTaskbarHidden();
+        }
     }
 
     @Test
     @ScreenRecord // b/231615831
     @PortraitLandscape
-    @TaskbarModeSwitch(mode = TRANSIENT)
-    public void testTransientLaunchAppInSplitscreen() throws Exception {
-        getTaskbar().getAppIcon(TEST_APP_NAME).dragToSplitscreen(
-                TEST_APP_PACKAGE, CALCULATOR_APP_PACKAGE);
-        mLauncher.getLaunchedAppState().assertTaskbarHidden();
-    }
-
-    @Test
-    @ScreenRecord // b/231615831
-    @PortraitLandscape
-    @TaskbarModeSwitch(mode = PERSISTENT)
-    public void testLaunchShortcutInSplitscreen() throws Exception {
+    public void testLaunchShortcutInSplitscreen() {
         getTaskbar().getAppIcon(TEST_APP_NAME)
                 .openDeepShortcutMenu()
                 .getMenuItem("Shortcut 1")
@@ -167,53 +112,17 @@
     }
 
     @Test
-    @ScreenRecord // b/231615831
-    @PortraitLandscape
-    @TaskbarModeSwitch(mode = TRANSIENT)
-    public void testTransientLaunchShortcutInSplitscreen() throws Exception {
-        getTaskbar().getAppIcon(TEST_APP_NAME)
-                .openDeepShortcutMenu()
-                .getMenuItem("Shortcut 1")
-                .dragToSplitscreen(TEST_APP_PACKAGE, CALCULATOR_APP_PACKAGE);
-    }
-
-    @Test
-    @TaskbarModeSwitch(mode = PERSISTENT)
-    public void testLaunchApp_FromTaskbarAllApps() throws Exception {
+    public void testLaunchApp_fromTaskbarAllApps() {
         getTaskbar().openAllApps().getAppIcon(TEST_APP_NAME).launch(TEST_APP_PACKAGE);
     }
 
     @Test
-    @TaskbarModeSwitch(mode = TRANSIENT)
-    public void testTransientLaunchApp_FromTaskbarAllApps() throws Exception {
-        getTaskbar().openAllApps().getAppIcon(TEST_APP_NAME).launch(TEST_APP_PACKAGE);
-    }
-
-    @Test
-    @TaskbarModeSwitch(mode = PERSISTENT)
-    public void testOpenMenu_FromTaskbarAllApps() throws Exception {
+    public void testOpenMenu_fromTaskbarAllApps() {
         getTaskbar().openAllApps().getAppIcon(TEST_APP_NAME).openMenu();
     }
 
     @Test
-    @TaskbarModeSwitch(mode = TRANSIENT)
-    public void testTransientOpenMenu_FromTaskbarAllApps() throws Exception {
-        getTaskbar().openAllApps().getAppIcon(TEST_APP_NAME).openMenu();
-    }
-
-    @Test
-    @TaskbarModeSwitch(mode = PERSISTENT)
-    public void testLaunchShortcut_FromTaskbarAllApps() throws Exception {
-        getTaskbar().openAllApps()
-                .getAppIcon(TEST_APP_NAME)
-                .openDeepShortcutMenu()
-                .getMenuItem("Shortcut 1")
-                .launch(TEST_APP_PACKAGE);
-    }
-
-    @Test
-    @TaskbarModeSwitch(mode = TRANSIENT)
-    public void testTransientLaunchShortcut_FromTaskbarAllApps() throws Exception {
+    public void testLaunchShortcut_fromTaskbarAllApps() {
         getTaskbar().openAllApps()
                 .getAppIcon(TEST_APP_NAME)
                 .openDeepShortcutMenu()
@@ -224,8 +133,7 @@
     @Test
     @ScreenRecord // b/231615831
     @PortraitLandscape
-    @TaskbarModeSwitch(mode = PERSISTENT)
-    public void testLaunchAppInSplitscreen_FromTaskbarAllApps() throws Exception {
+    public void testLaunchAppInSplitscreen_fromTaskbarAllApps() {
         getTaskbar().openAllApps()
                 .getAppIcon(TEST_APP_NAME)
                 .dragToSplitscreen(TEST_APP_PACKAGE, CALCULATOR_APP_PACKAGE);
@@ -234,18 +142,7 @@
     @Test
     @ScreenRecord // b/231615831
     @PortraitLandscape
-    @TaskbarModeSwitch(mode = TRANSIENT)
-    public void testTransientLaunchAppInSplitscreen_FromTaskbarAllApps() throws Exception {
-        getTaskbar().openAllApps()
-                .getAppIcon(TEST_APP_NAME)
-                .dragToSplitscreen(TEST_APP_PACKAGE, CALCULATOR_APP_PACKAGE);
-    }
-
-    @Test
-    @ScreenRecord // b/231615831
-    @PortraitLandscape
-    @TaskbarModeSwitch(mode = PERSISTENT)
-    public void testLaunchShortcutInSplitscreen_FromTaskbarAllApps() throws Exception {
+    public void testLaunchShortcutInSplitscreen_fromTaskbarAllApps() {
         getTaskbar().openAllApps()
                 .getAppIcon(TEST_APP_NAME)
                 .openDeepShortcutMenu()
@@ -253,64 +150,7 @@
                 .dragToSplitscreen(TEST_APP_PACKAGE, CALCULATOR_APP_PACKAGE);
     }
 
-    @Test
-    @ScreenRecord // b/231615831
-    @PortraitLandscape
-    @TaskbarModeSwitch(mode = TRANSIENT)
-    public void testTransientLaunchShortcutInSplitscreen_FromTaskbarAllApps() throws Exception {
-        getTaskbar().openAllApps()
-                .getAppIcon(TEST_APP_NAME)
-                .openDeepShortcutMenu()
-                .getMenuItem("Shortcut 1")
-                .dragToSplitscreen(TEST_APP_PACKAGE, CALCULATOR_APP_PACKAGE);
-    }
-
-    @Test
-    @TaskbarModeSwitch(mode = TRANSIENT)
-    public void testShowTaskbarUnstashHintOnHover() {
-        try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_CURSOR_HOVER_STATES, true)) {
-            getTaskbar().getAppIcon(TEST_APP_NAME).launch(TEST_APP_PACKAGE);
-            mLauncher.getLaunchedAppState().hoverToShowTaskbarUnstashHint();
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    @Test
-    @TaskbarModeSwitch(mode = TRANSIENT)
-    public void testUnstashTaskbarOnScreenBottomEdgeHover() {
-        try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_CURSOR_HOVER_STATES, true)) {
-            getTaskbar().getAppIcon(TEST_APP_NAME).launch(TEST_APP_PACKAGE);
-            mLauncher.getLaunchedAppState().hoverScreenBottomEdgeToUnstashTaskbar();
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    @Test
-    @TaskbarModeSwitch(mode = TRANSIENT)
-    public void testHoverBelowHintedTaskbarToUnstash() {
-        try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_CURSOR_HOVER_STATES, true)) {
-            getTaskbar().getAppIcon(TEST_APP_NAME).launch(TEST_APP_PACKAGE);
-            mLauncher.getLaunchedAppState().hoverBelowHintedTaskbarToUnstash();
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    private Taskbar getTaskbar() {
-        Taskbar taskbar = mLauncher.getLaunchedAppState().getTaskbar();
-        List<String> taskbarIconNames = taskbar.getIconNames();
-        List<String> hotseatIconNames = mLauncher.getHotseatIconNames();
-
-        assertEquals("Taskbar and hotseat icon counts do not match",
-                taskbarIconNames.size(), hotseatIconNames.size());
-
-        for (int i = 0; i < taskbarIconNames.size(); i++) {
-            assertEquals("Taskbar and Hotseat icons do not match",
-                    taskbarIconNames, hotseatIconNames);
-        }
-
-        return taskbar;
+    private boolean isTaskbarTestModeTransient() {
+        return TRANSIENT == mTaskbarMode;
     }
 }
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsTransientTaskbar.java b/quickstep/tests/src/com/android/quickstep/TaplTestsTransientTaskbar.java
new file mode 100644
index 0000000..b58fe29
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsTransientTaskbar.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep;
+
+import static com.android.launcher3.config.FeatureFlags.ENABLE_CURSOR_HOVER_STATES;
+import static com.android.quickstep.TaskbarModeSwitchRule.Mode.TRANSIENT;
+
+
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.launcher3.util.TestUtil;
+import com.android.quickstep.TaskbarModeSwitchRule.TaskbarModeSwitch;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class TaplTestsTransientTaskbar extends AbstractTaplTestsTaskbar {
+
+    @Test
+    @TaskbarModeSwitch(mode = TRANSIENT)
+    public void testShowTaskbarUnstashHintOnHover() {
+        try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_CURSOR_HOVER_STATES, true)) {
+            getTaskbar().getAppIcon(TEST_APP_NAME).launch(TEST_APP_PACKAGE);
+            mLauncher.getLaunchedAppState().hoverToShowTaskbarUnstashHint();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Test
+    @TaskbarModeSwitch(mode = TRANSIENT)
+    public void testUnstashTaskbarOnScreenBottomEdgeHover() {
+        try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_CURSOR_HOVER_STATES, true)) {
+            getTaskbar().getAppIcon(TEST_APP_NAME).launch(TEST_APP_PACKAGE);
+            mLauncher.getLaunchedAppState().hoverScreenBottomEdgeToUnstashTaskbar();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Test
+    @TaskbarModeSwitch(mode = TRANSIENT)
+    public void testHoverBelowHintedTaskbarToUnstash() {
+        try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_CURSOR_HOVER_STATES, true)) {
+            getTaskbar().getAppIcon(TEST_APP_NAME).launch(TEST_APP_PACKAGE);
+            mLauncher.getLaunchedAppState().hoverBelowHintedTaskbarToUnstash();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt b/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
index acfd54c..65542cf 100644
--- a/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
@@ -33,14 +33,10 @@
 import com.android.launcher3.statemanager.StateManager
 import com.android.launcher3.util.ComponentKey
 import com.android.launcher3.util.SplitConfigurationOptions
-import com.android.launcher3.util.SplitConfigurationOptions.StagePosition
-import com.android.launcher3.util.mock
 import com.android.launcher3.util.withArgCaptor
 import com.android.quickstep.RecentsModel
 import com.android.quickstep.SystemUiProxy
 import com.android.systemui.shared.recents.model.Task
-import java.util.ArrayList
-import java.util.function.Consumer
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertNull
@@ -50,7 +46,9 @@
 import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
+import java.util.function.Consumer
 
 @RunWith(AndroidJUnit4::class)
 class SplitSelectStateControllerTest {
@@ -355,6 +353,7 @@
     @Test
     fun secondPendingIntentSet() {
         val itemInfo = ItemInfo()
+        `when`(pendingIntent.creatorUserHandle).thenReturn(primaryUserHandle)
         splitSelectStateController.setInitialTaskSelect(null, 0, itemInfo, null, 1)
         splitSelectStateController.setSecondTask(pendingIntent)
         assertTrue(splitSelectStateController.isBothSplitAppsConfirmed)
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index b2ad597..1ffb81c 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -50,8 +50,7 @@
     <string name="widgets_full_sheet_personal_tab" msgid="2743540105607120182">"الأدوات الشخصية"</string>
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"أدوات العمل"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"المحادثات"</string>
-    <!-- no translation found for widget_category_note_taking (3469689394504266039) -->
-    <skip />
+    <string name="widget_category_note_taking" msgid="3469689394504266039">"تدوين الملاحظات"</string>
     <string name="widget_education_header" msgid="4874760613775913787">"معلومات مفيدة في متناول يديك"</string>
     <string name="widget_education_content" msgid="1731667670753497052">"للحصول على معلومات بدون فتح التطبيقات، يمكنك إضافة التطبيقات المصغّرة إلى الشاشة الرئيسية."</string>
     <string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"انقر لتغيير إعدادات الأداة"</string>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index f6f9a44..2b3ed53 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -169,7 +169,7 @@
     <string name="work_apps_paused_edu_banner" msgid="8872412121608402058">"Poslovne aplikacije su označene značkom i IT administrator može da ih vidi"</string>
     <string name="work_apps_paused_edu_accept" msgid="6377476824357318532">"Važi"</string>
     <string name="work_apps_pause_btn_text" msgid="4669288269140620646">"Pauziraj poslovne aplikacije"</string>
-    <string name="work_apps_enable_btn_text" msgid="1736198302467317371">"Opozovi pauzu"</string>
+    <string name="work_apps_enable_btn_text" msgid="1736198302467317371">"Ponovo aktiviraj"</string>
     <string name="developer_options_filter_hint" msgid="5896817443635989056">"Filter"</string>
     <string name="search_pref_screen_title" msgid="3258959643336315962">"Pretražite telefon"</string>
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Pretražite tablet"</string>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index 34b92fe..8c3dd08 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -169,7 +169,7 @@
     <string name="work_apps_paused_edu_banner" msgid="8872412121608402058">"Poslovne aplikacije su označene i vaš IT administrator ih može vidjeti"</string>
     <string name="work_apps_paused_edu_accept" msgid="6377476824357318532">"Razumijem"</string>
     <string name="work_apps_pause_btn_text" msgid="4669288269140620646">"Pauziraj poslovne aplikacije"</string>
-    <string name="work_apps_enable_btn_text" msgid="1736198302467317371">"Prekini pauzu"</string>
+    <string name="work_apps_enable_btn_text" msgid="1736198302467317371">"Ponovo pokreni"</string>
     <string name="developer_options_filter_hint" msgid="5896817443635989056">"Filtrirajte"</string>
     <string name="search_pref_screen_title" msgid="3258959643336315962">"Pretražite telefon"</string>
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Pretražite tablet"</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 6e19e15..40e1397 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -27,7 +27,7 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"V nouzovém režimu jsou widgety zakázány."</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Zkratka není k dispozici"</string>
     <string name="home_screen" msgid="5629429142036709174">"Domů"</string>
-    <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Rozdělená obrazovka"</string>
+    <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Rozdělit obrazovku"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Informace o aplikaci %1$s"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Uložit pár aplikací"</string>
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Widget přesunete klepnutím a podržením."</string>
@@ -50,8 +50,7 @@
     <string name="widgets_full_sheet_personal_tab" msgid="2743540105607120182">"Osobní"</string>
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Práce"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Konverzace"</string>
-    <!-- no translation found for widget_category_note_taking (3469689394504266039) -->
-    <skip />
+    <string name="widget_category_note_taking" msgid="3469689394504266039">"Psaní poznámek"</string>
     <string name="widget_education_header" msgid="4874760613775913787">"Užitečné informace na dosah"</string>
     <string name="widget_education_content" msgid="1731667670753497052">"Pokud chcete mít informace k dispozici bez otevírání aplikací, můžete si na plochu přidat widgety"</string>
     <string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Klepnutím změníte nastavení widgetu"</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 9a1a37a..63869f6 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -50,8 +50,7 @@
     <string name="widgets_full_sheet_personal_tab" msgid="2743540105607120182">"Personnels"</string>
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Professionnels"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Conversations"</string>
-    <!-- no translation found for widget_category_note_taking (3469689394504266039) -->
-    <skip />
+    <string name="widget_category_note_taking" msgid="3469689394504266039">"Prise de notes"</string>
     <string name="widget_education_header" msgid="4874760613775913787">"Infos utiles à portée de main"</string>
     <string name="widget_education_content" msgid="1731667670753497052">"Pour obtenir des infos sans ouvrir d\'applis, vous pouvez ajouter des widgets à votre écran d\'accueil"</string>
     <string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Appuyez pour modifier les paramètres du widget"</string>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index 454c454..442f4f6 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -50,8 +50,7 @@
     <string name="widgets_full_sheet_personal_tab" msgid="2743540105607120182">"Անձնական"</string>
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Աշխատանքային"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Զրույցներ"</string>
-    <!-- no translation found for widget_category_note_taking (3469689394504266039) -->
-    <skip />
+    <string name="widget_category_note_taking" msgid="3469689394504266039">"Նշումների ստեղծում"</string>
     <string name="widget_education_header" msgid="4874760613775913787">"Բոլոր կարևոր տեղեկությունները՝ ձեռքի տակ"</string>
     <string name="widget_education_content" msgid="1731667670753497052">"Ավելացրեք վիջեթներ ձեր հիմնական էկրանին, որպեսզի տեղեկություններ ստանաք՝ առանց հավելվածները բացելու։"</string>
     <string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Հպեք՝ վիջեթի կարգավորումները փոփոխելու համար"</string>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index e968d71..1d4b989 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -89,9 +89,9 @@
     <string name="folder_hint_text" msgid="5174843001373488816">"नाव संपादित करा"</string>
     <string name="disabled_app_label" msgid="6673129024321402780">"<xliff:g id="APP_NAME">%1$s</xliff:g> अक्षम केला आहे"</string>
     <string name="dotted_app_label" msgid="1865617679843363410">"{count,plural, =1{{app_name} संबंधित # सूचना आहे}other{{app_name} संबंधित # सूचना आहेत}}"</string>
-    <string name="default_scroll_format" msgid="7475544710230993317">"%2$d पैकी %1$d पृष्ठ"</string>
+    <string name="default_scroll_format" msgid="7475544710230993317">"%2$d पैकी %1$d पेज"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"%2$d पैकी %1$d मुख्य स्क्रीन"</string>
-    <string name="workspace_new_page" msgid="257366611030256142">"नवीन मुख्य स्क्रीन पृष्ठ"</string>
+    <string name="workspace_new_page" msgid="257366611030256142">"नवीन मुख्य स्क्रीन पेज"</string>
     <string name="folder_opened" msgid="94695026776264709">"फोल्डर उघडले, <xliff:g id="WIDTH">%1$d</xliff:g> बाय <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"फोल्डर बंद करण्यासाठी टॅप करा"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"पुनर्नामित करणे सेव्ह करण्यासाठी टॅप करा"</string>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index 027b285..04f9865 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -70,7 +70,7 @@
     <string name="all_apps_search_results" msgid="5889367432531296759">"ସନ୍ଧାନ ଫଳାଫଳ"</string>
     <string name="all_apps_button_personal_label" msgid="1315764287305224468">"ବ୍ୟକ୍ତିଗତ ଆପ୍ ତାଲିକା"</string>
     <string name="all_apps_button_work_label" msgid="7270707118948892488">"କାର୍ଯ୍ୟକାରୀ ଆପ୍‌ ତାଲିକା"</string>
-    <string name="remove_drop_target_label" msgid="7812859488053230776">"ବାହାର କରନ୍ତୁ"</string>
+    <string name="remove_drop_target_label" msgid="7812859488053230776">"କାଢ଼ି ଦିଅନ୍ତୁ"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"ଅନଇନଷ୍ଟଲ କରନ୍ତୁ"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"ଆପ୍‌ ସୂଚନା"</string>
     <string name="install_drop_target_label" msgid="2539096853673231757">"ଇନଷ୍ଟଲ୍‌ କରନ୍ତୁ"</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index f754bee..a4d7795 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -169,7 +169,7 @@
     <string name="work_apps_paused_edu_banner" msgid="8872412121608402058">"Пословне апликације су означене значком и ИТ администратор може да их види"</string>
     <string name="work_apps_paused_edu_accept" msgid="6377476824357318532">"Важи"</string>
     <string name="work_apps_pause_btn_text" msgid="4669288269140620646">"Паузирај пословне апликације"</string>
-    <string name="work_apps_enable_btn_text" msgid="1736198302467317371">"Опозови паузу"</string>
+    <string name="work_apps_enable_btn_text" msgid="1736198302467317371">"Поново активирај"</string>
     <string name="developer_options_filter_hint" msgid="5896817443635989056">"Филтер"</string>
     <string name="search_pref_screen_title" msgid="3258959643336315962">"Претражите телефон"</string>
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Претражите таблет"</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 6025aa7..ef28f18 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -50,8 +50,7 @@
     <string name="widgets_full_sheet_personal_tab" msgid="2743540105607120182">"Kişisel"</string>
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"İş"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Görüşmeler"</string>
-    <!-- no translation found for widget_category_note_taking (3469689394504266039) -->
-    <skip />
+    <string name="widget_category_note_taking" msgid="3469689394504266039">"Not alma"</string>
     <string name="widget_education_header" msgid="4874760613775913787">"Faydalı bilgiler parmaklarınızın ucunda"</string>
     <string name="widget_education_content" msgid="1731667670753497052">"Uygulama açmadan bilgi almak için ana ekranınıza widget ekleyebilirsiniz"</string>
     <string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Widget ayarlarını değiştirmek için dokunun"</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 5127d3b..822936b 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -50,8 +50,7 @@
     <string name="widgets_full_sheet_personal_tab" msgid="2743540105607120182">"Особисті"</string>
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Робочі"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Розмови"</string>
-    <!-- no translation found for widget_category_note_taking (3469689394504266039) -->
-    <skip />
+    <string name="widget_category_note_taking" msgid="3469689394504266039">"Створення нотаток"</string>
     <string name="widget_education_header" msgid="4874760613775913787">"Корисна інформація завжди під рукою"</string>
     <string name="widget_education_content" msgid="1731667670753497052">"Щоб отримувати інформацію, не відкриваючи додатки, ви можете додати на головний екран віджети"</string>
     <string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Натисніть, щоб змінити налаштування віджета"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 5c3bcaf..20c8ea9 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -100,7 +100,7 @@
     <string name="folder_name_format_exact" msgid="8626242716117004803">"文件夹:<xliff:g id="NAME">%1$s</xliff:g>,<xliff:g id="SIZE">%2$d</xliff:g> 个项目"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"文件夹:<xliff:g id="NAME">%1$s</xliff:g>,<xliff:g id="SIZE">%2$d</xliff:g> 个或更多项目"</string>
     <string name="wallpaper_button_text" msgid="8404103075899945851">"壁纸"</string>
-    <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"壁纸和样式"</string>
+    <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"壁纸与个性化"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"修改主屏幕"</string>
     <string name="settings_button_text" msgid="8873672322605444408">"主屏幕设置"</string>
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"已被您的管理员停用"</string>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index ee33dd0..418f5a7 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -198,6 +198,9 @@
         <attr name="demoModeLayoutId" format="reference" />
         <attr name="isScalable" format="boolean" />
         <attr name="devicePaddingId" format="reference" />
+        <!-- File that contains the specs for the workspace.
+        Needs FeatureFlags.ENABLE_RESPONSIVE_WORKSPACE enabled -->
+        <attr name="workspaceSpecsId" format="reference" />
         <!-- By default all categories are enabled -->
         <attr name="deviceCategory" format="integer">
             <!-- Enable on phone only -->
diff --git a/res/values/colors.xml b/res/values/colors.xml
index a06931f..b1bff18 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -77,7 +77,6 @@
     <color name="text_color_tertiary_dark">#CCFFFFFF</color>
 
     <color name="wallpaper_popup_scrim">?android:attr/colorAccent</color>
-    <color name="wallpaper_scrim_color">#0D878787</color>
 
     <color name="workspace_accent_color_light">#ff8df5e3</color>
     <color name="workspace_accent_color_dark">#ff3d665f</color>
diff --git a/res/xml/dynamic_resources.xml b/res/xml/dynamic_resources.xml
index 3a3e239..f7a631b 100644
--- a/res/xml/dynamic_resources.xml
+++ b/res/xml/dynamic_resources.xml
@@ -4,6 +4,5 @@
     <entry id="@color/delete_target_hover_tint" />
     <entry id="@color/delete_target_hover_tint" />
     <entry id="@color/delete_target_hover_tint" />
-    <entry id="@color/wallpaper_scrim_color" />
 
 </DynamicResources>
diff --git a/res/xml/widget_sections.xml b/res/xml/widget_sections.xml
index 6165bf7..742991f 100644
--- a/res/xml/widget_sections.xml
+++ b/res/xml/widget_sections.xml
@@ -26,6 +26,6 @@
         launcher:category="1"
         launcher:sectionDrawable="@drawable/ic_note_taking_widget_category"
         launcher:sectionTitle="@string/widget_category_note_taking">
-        <widget launcher:provider="com.android.settings/com.android.settings.notetask.shortcut.CreateNoteTaskShortcutActivity" />
+        <widget launcher:provider="com.android.systemui/.notetask.shortcut.CreateNoteTaskShortcutActivity" />
     </section>
 </widget-sections>
\ No newline at end of file
diff --git a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
index f041ffb..3dd8627 100644
--- a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
+++ b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
@@ -6,7 +6,6 @@
 import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.database.Cursor;
@@ -16,13 +15,15 @@
 import androidx.annotation.WorkerThread;
 
 import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.model.DatabaseHelper;
 import com.android.launcher3.model.LoaderTask;
+import com.android.launcher3.model.ModelDbController;
 import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.provider.LauncherDbUtils;
 import com.android.launcher3.provider.RestoreDbTask;
 import com.android.launcher3.util.ContentWriter;
+import com.android.launcher3.util.IntArray;
 import com.android.launcher3.widget.LauncherWidgetHolder;
 
 public class AppWidgetsRestoredReceiver extends BroadcastReceiver {
@@ -52,7 +53,7 @@
      * Updates the app widgets whose id has changed during the restore process.
      */
     @WorkerThread
-    public static void restoreAppWidgetIds(Context context, DatabaseHelper helper,
+    public static void restoreAppWidgetIds(Context context, ModelDbController controller,
             int[] oldWidgetIds, int[] newWidgetIds, @NonNull AppWidgetHost host) {
         if (WidgetsModel.GO_DISABLE_WIDGETS) {
             Log.e(TAG, "Skipping widget ID remap as widgets not supported");
@@ -69,9 +70,24 @@
             }
             return;
         }
-        final ContentResolver cr = context.getContentResolver();
+
         final AppWidgetManager widgets = AppWidgetManager.getInstance(context);
 
+        Log.d(TAG, "restoreAppWidgetIds: "
+                + "oldWidgetIds=" + IntArray.wrap(oldWidgetIds).toConcatString()
+                + ", newWidgetIds=" + IntArray.wrap(newWidgetIds).toConcatString());
+
+        try {
+            IntArray result = LauncherDbUtils.queryIntArray(false, controller.getDb(),
+                    Favorites.TABLE_NAME, Favorites.APPWIDGET_ID,
+                    Favorites.APPWIDGET_ID + "!=" + LauncherAppWidgetInfo.NO_ID, null, null);
+            // TODO(b/234700507): Remove the logs after the bug is fixed
+            Log.d(TAG, "restoreAppWidgetIds: all widget ids in database: "
+                    + result.toConcatString());
+        } catch (Exception ex) {
+            Log.e(TAG, "Getting widget ids from the database failed", ex);
+        }
+
         for (int i = 0; i < oldWidgetIds.length; i++) {
             Log.i(TAG, "Widget state restore id " + oldWidgetIds[i] + " => " + newWidgetIds[i]);
 
@@ -92,22 +108,24 @@
             final String where = "appWidgetId=? and (restored & 1) = 1 and profileId=?";
             final String[] args = new String[] { oldWidgetId, Long.toString(mainProfileId) };
             int result = new ContentWriter(context,
-                            new ContentWriter.CommitParams(helper, where, args))
+                            new ContentWriter.CommitParams(controller, where, args))
                     .put(LauncherSettings.Favorites.APPWIDGET_ID, newWidgetIds[i])
                     .put(LauncherSettings.Favorites.RESTORED, state)
                     .commit();
             if (result == 0) {
-                Cursor cursor = helper.getWritableDatabase().query(
+                // TODO(b/234700507): Remove the logs after the bug is fixed
+                Log.e(TAG, "restoreAppWidgetIds: remapping failed since the widget is not in"
+                        + " the database anymore");
+                try (Cursor cursor = controller.getDb().query(
                         Favorites.TABLE_NAME,
-                        new String[] {Favorites.APPWIDGET_ID},
-                        "appWidgetId=?", new String[] { oldWidgetId }, null, null, null);
-                try {
+                        new String[]{Favorites.APPWIDGET_ID},
+                        "appWidgetId=?", new String[]{oldWidgetId}, null, null, null)) {
                     if (!cursor.moveToFirst()) {
                         // The widget no long exists.
+                        Log.d(TAG, "Deleting widgetId: " + newWidgetIds[i] + " with old id: "
+                                + oldWidgetId);
                         host.deleteAppWidgetId(newWidgetIds[i]);
                     }
-                } finally {
-                    cursor.close();
                 }
             }
         }
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
index 1a86009..108e557 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -115,6 +115,12 @@
         setContentDescription(mText);
     }
 
+    protected void updateText(CharSequence text) {
+        setText(text);
+        mText = getText();
+        setContentDescription(mText);
+    }
+
     protected void setDrawable(int resId) {
         // We do not set the drawable in the xml as that inflates two drawables corresponding to
         // drawableLeft and drawableStart.
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 8675226..0231090 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -101,6 +101,7 @@
     public final float aspectRatio;
 
     public final boolean isScalableGrid;
+    public final boolean isResponsiveGrid;
     private final int mTypeIndex;
 
     /**
@@ -293,6 +294,10 @@
         this.rotationHint = windowBounds.rotationHint;
         mInsets.set(windowBounds.insets);
 
+        // TODO(b/241386436):
+        //  for testing that the flag works only, shouldn't change any launcher behaviour
+        isResponsiveGrid = inv.workspaceSpecsId != INVALID_RESOURCE_HANDLE;
+
         isScalableGrid = inv.isScalable && !isVerticalBarLayout() && !isMultiWindowMode;
         // Determine device posture.
         mInfo = info;
@@ -1577,6 +1582,7 @@
 
         writer.println(prefix + "\taspectRatio:" + aspectRatio);
 
+        writer.println(prefix + "\tisResponsiveGrid:" + isResponsiveGrid);
         writer.println(prefix + "\tisScalableGrid:" + isScalableGrid);
 
         writer.println(prefix + "\tinv.numRows: " + inv.numRows);
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 3aa582d..376f54d 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -48,6 +48,7 @@
 import androidx.annotation.XmlRes;
 import androidx.core.content.res.ResourcesCompat;
 
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.icons.DotRenderer;
 import com.android.launcher3.model.DeviceGridState;
 import com.android.launcher3.provider.RestoreDbTask;
@@ -105,7 +106,7 @@
     static final int INDEX_TWO_PANEL_PORTRAIT = 2;
     static final int INDEX_TWO_PANEL_LANDSCAPE = 3;
 
-    /** These resources are used to override the device profile  */
+    /** These resources are used to override the device profile */
     private static final String RES_GRID_NUM_ROWS = "grid_num_rows";
     private static final String RES_GRID_NUM_COLUMNS = "grid_num_columns";
     private static final String RES_GRID_ICON_SIZE_DP = "grid_icon_size_dp";
@@ -177,6 +178,8 @@
     protected boolean isScalable;
     @XmlRes
     public int devicePaddingId = INVALID_RESOURCE_HANDLE;
+    @XmlRes
+    public int workspaceSpecsId = INVALID_RESOURCE_HANDLE;
 
     public String dbFile;
     public int defaultLayoutId;
@@ -350,6 +353,7 @@
 
         isScalable = closestProfile.isScalable;
         devicePaddingId = closestProfile.devicePaddingId;
+        workspaceSpecsId = closestProfile.mWorkspaceSpecsId;
         this.deviceType = deviceType;
 
         inlineNavButtonsEndSpacing = closestProfile.inlineNavButtonsEndSpacing;
@@ -795,6 +799,7 @@
 
         private final boolean isScalable;
         private final int devicePaddingId;
+        private final int mWorkspaceSpecsId;
 
         public GridOption(Context context, AttributeSet attrs) {
             TypedArray a = context.obtainStyledAttributes(
@@ -836,7 +841,7 @@
 
             inlineNavButtonsEndSpacing =
                     a.getResourceId(R.styleable.GridDisplayOption_inlineNavButtonsEndSpacing,
-                    R.dimen.taskbar_button_margin_default);
+                            R.dimen.taskbar_button_margin_default);
 
             numFolderRows = a.getInt(
                     R.styleable.GridDisplayOption_numFolderRows, numRows);
@@ -856,6 +861,13 @@
             deviceCategory = a.getInt(R.styleable.GridDisplayOption_deviceCategory,
                     DEVICE_CATEGORY_ALL);
 
+            if (FeatureFlags.ENABLE_RESPONSIVE_WORKSPACE.get()) {
+                mWorkspaceSpecsId = a.getResourceId(
+                        R.styleable.GridDisplayOption_workspaceSpecsId, INVALID_RESOURCE_HANDLE);
+            } else {
+                mWorkspaceSpecsId = INVALID_RESOURCE_HANDLE;
+            }
+
             int inlineForRotation = a.getInt(R.styleable.GridDisplayOption_inlineQsb,
                     DONT_INLINE_QSB);
             inlineQsb[INDEX_DEFAULT] =
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 29b0f08..ffd56cc 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -214,6 +214,7 @@
 import com.android.launcher3.util.TraceHelper;
 import com.android.launcher3.util.ViewOnDrawExecutor;
 import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.views.ComposeInitializer;
 import com.android.launcher3.views.FloatingIconView;
 import com.android.launcher3.views.FloatingSurfaceView;
 import com.android.launcher3.views.OptionsPopupView;
@@ -553,6 +554,8 @@
         setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
 
         setContentView(getRootView());
+        ComposeInitializer.initCompose(this);
+
         if (mOnInitialBindListener != null) {
             getRootView().getViewTreeObserver().addOnPreDrawListener(mOnInitialBindListener);
         }
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index abf5866..4d15ac7 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -111,6 +111,7 @@
                 Intent.ACTION_MANAGED_PROFILE_AVAILABLE,
                 Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE,
                 Intent.ACTION_MANAGED_PROFILE_UNLOCKED,
+                Intent.ACTION_PROFILE_INACCESSIBLE,
                 ACTION_DEVICE_POLICY_RESOURCE_UPDATED);
         if (FeatureFlags.IS_STUDIO_BUILD) {
             modelChangeReceiver.register(mContext, ACTION_FORCE_ROLOAD);
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index d3e94e1..65870d3 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -289,9 +289,10 @@
         if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
             // If we have changed locale we need to clear out the labels in all apps/workspace.
             forceReload();
-        } else if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) ||
-                Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) ||
-                Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) {
+        } else if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action)
+                || Intent.ACTION_PROFILE_INACCESSIBLE.equals(action)
+                || Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)
+                || Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) {
             UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
             if (TestProtocol.sDebugTracing) {
                 Log.d(TestProtocol.WORK_TAB_MISSING, "onBroadcastIntent intentAction: " + action +
@@ -304,10 +305,10 @@
                             PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user));
                 }
 
-                // ACTION_MANAGED_PROFILE_UNAVAILABLE sends the profile back to locked mode, so
+                // ACTION_PROFILE_INACCESSIBLE sends the profile back to locked mode, so
                 // we need to run the state change task again.
-                if (Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) ||
-                        Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) {
+                if (Intent.ACTION_PROFILE_INACCESSIBLE.equals(action)
+                        || Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) {
                     enqueueModelUpdateTask(new UserLockStateChangedTask(
                             user, Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)));
                 }
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 0df4bd4..9abec50 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -263,18 +263,6 @@
                 getModelDbController().refreshHotseatRestoreTable();
                 return null;
             }
-            case LauncherSettings.Settings.METHOD_UPDATE_CURRENT_OPEN_HELPER: {
-                Bundle result = new Bundle();
-                result.putBoolean(LauncherSettings.Settings.EXTRA_VALUE,
-                        getModelDbController().updateCurrentOpenHelper(arg /* dbFile */));
-                return result;
-            }
-            case LauncherSettings.Settings.METHOD_PREP_FOR_PREVIEW: {
-                Bundle result = new Bundle();
-                result.putBoolean(LauncherSettings.Settings.EXTRA_VALUE,
-                        getModelDbController().prepareForPreview(arg /* dbFile */));
-                return result;
-            }
         }
         return null;
     }
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index b65e96b..7fda326 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -148,11 +148,6 @@
         public static final String HYBRID_HOTSEAT_BACKUP_TABLE = "hotseat_restore_backup";
 
         /**
-         * Temporary table used specifically for grid migrations during wallpaper preview
-         */
-        public static final String PREVIEW_TABLE_NAME = "favorites_preview";
-
-        /**
          * Temporary table used specifically for multi-db grid migrations
          */
         public static final String TMP_TABLE = "favorites_tmp";
@@ -164,18 +159,6 @@
                 + 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 "favorites_tmp" table
-         */
-        public static final Uri TMP_CONTENT_URI = Uri.parse("content://"
-                + LauncherProvider.AUTHORITY + "/" + TMP_TABLE);
-
-        /**
          * The content:// style URL for a given row, identified by its id.
          *
          * @param id The row id.
@@ -376,10 +359,6 @@
 
         public static final String METHOD_REFRESH_HOTSEAT_RESTORE_TABLE = "restore_hotseat_table";
 
-        public static final String METHOD_UPDATE_CURRENT_OPEN_HELPER = "update_current_open_helper";
-
-        public static final String METHOD_PREP_FOR_PREVIEW = "prep_for_preview";
-
         public static final String EXTRA_VALUE = "value";
 
         public static final String EXTRA_DB_NAME = "db_name";
@@ -393,11 +372,8 @@
         }
 
         public static Bundle call(ContentResolver cr, String method, String arg) {
-            return call(cr, method, arg, null /* extras */);
+            return cr.call(CONTENT_URI, method, arg, null);
         }
 
-        public static Bundle call(ContentResolver cr, String method, String arg, Bundle extras) {
-            return cr.call(CONTENT_URI, method, arg, extras);
-        }
     }
 }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 98016f6..2fa1a57 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -1661,17 +1661,14 @@
             scale = previewProvider.getScaleAndPosition(contentView, mTempXY);
         }
 
-        int halfPadding = previewProvider.previewPadding / 2;
         int dragLayerX = mTempXY[0];
         int dragLayerY = mTempXY[1];
 
-        Point dragVisualizeOffset = null;
         Rect dragRect = new Rect();
 
         if (draggableView != null) {
             draggableView.getSourceVisualDragBounds(dragRect);
             dragLayerY += dragRect.top;
-            dragVisualizeOffset = new Point(-halfPadding, halfPadding);
         }
 
 
@@ -1689,6 +1686,15 @@
             }
         }
 
+        if (dragOptions.preDragCondition != null) {
+            int xDragOffSet = dragOptions.preDragCondition.getDragOffset().x;
+            int yDragOffSet = dragOptions.preDragCondition.getDragOffset().y;
+            if (xDragOffSet != 0 || yDragOffSet != 0) {
+                dragLayerX += xDragOffSet;
+                dragLayerY += yDragOffSet;
+            }
+        }
+
         final DragView dv;
         if (contentView instanceof View) {
             if (contentView instanceof LauncherAppWidgetHostView) {
@@ -1701,7 +1707,6 @@
                     dragLayerY,
                     source,
                     dragObject,
-                    dragVisualizeOffset,
                     dragRect,
                     scale * iconScale,
                     scale,
@@ -1714,7 +1719,6 @@
                     dragLayerY,
                     source,
                     dragObject,
-                    dragVisualizeOffset,
                     dragRect,
                     scale * iconScale,
                     scale,
diff --git a/src/com/android/launcher3/allapps/WorkProfileManager.java b/src/com/android/launcher3/allapps/WorkProfileManager.java
index bf36e02..a671c6e 100644
--- a/src/com/android/launcher3/allapps/WorkProfileManager.java
+++ b/src/com/android/launcher3/allapps/WorkProfileManager.java
@@ -97,7 +97,11 @@
             StatsLogManager statsLogManager) {
         mUserManager = userManager;
         mAllApps = allApps;
-        if (FeatureFlags.ENABLE_APP_CLONING_CHANGES_IN_LAUNCHER.get()) {
+        boolean cloningChanges = FeatureFlags.ENABLE_APP_CLONING_CHANGES_IN_LAUNCHER.get();
+        if (TestProtocol.sDebugTracing) {
+            Log.d(WORK_TAB_MISSING, "matcher flag: " + cloningChanges);
+        }
+        if (cloningChanges) {
             mMatcher = ofWorkProfileUser(userManager);
         } else {
             mMatcher = mAllApps.mPersonalMatcher.negate();
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 7f04860..2891ce1 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -372,10 +372,6 @@
             "ENABLE_ENFORCED_ROUNDED_CORNERS", ENABLED,
             "Enforce rounded corners on all App Widgets");
 
-    public static final BooleanFlag ENABLE_WALLPAPER_SCRIM = getDebugFlag(270393604,
-            "ENABLE_WALLPAPER_SCRIM", DISABLED,
-            "Enables scrim over wallpaper for text protection.");
-
     public static final BooleanFlag ENABLE_ICON_LABEL_AUTO_SCALING = getDebugFlag(270393294,
             "ENABLE_ICON_LABEL_AUTO_SCALING", ENABLED,
             "Enables scaling/spacing for icon labels to make more characters visible");
@@ -398,7 +394,7 @@
             "Enable initiating split screen from workspace to workspace.");
 
     public static final BooleanFlag ENABLE_TRACKPAD_GESTURE = getDebugFlag(271010401,
-            "ENABLE_TRACKPAD_GESTURE", ENABLED, "Enables trackpad gesture.");
+            "ENABLE_TRACKPAD_GESTURE", DISABLED, "Enables trackpad gesture.");
 
     // TODO(Block 29): Clean up flags
     public static final BooleanFlag ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT = getDebugFlag(270393897,
@@ -415,11 +411,15 @@
 
     // TODO(Block 31)
     public static final BooleanFlag ENABLE_SPLIT_LAUNCH_DATA_REFACTOR = getDebugFlag(279494325,
-            "ENABLE_SPLIT_LAUNCH_DATA_REFACTOR", DISABLED,
+            "ENABLE_SPLIT_LAUNCH_DATA_REFACTOR", ENABLED,
             "Use refactored split launching code path");
 
     // TODO(Block 32): Empty block
 
+    public static final BooleanFlag ENABLE_RESPONSIVE_WORKSPACE = getDebugFlag(241386436,
+            "ENABLE_RESPONSIVE_WORKSPACE", DISABLED,
+            "Enables new workspace grid calculations method.");
+
     public static class BooleanFlag {
 
         private final boolean mCurrentValue;
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 328d769..9867268 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -143,14 +143,12 @@
             int dragLayerY,
             DragSource source,
             ItemInfo dragInfo,
-            Point dragOffset,
             Rect dragRegion,
             float initialDragViewScale,
             float dragViewScaleOnDrop,
             DragOptions options) {
-        return startDrag(drawable, /* view= */ null, originalView, dragLayerX, dragLayerY,
-                source, dragInfo, dragOffset, dragRegion, initialDragViewScale, dragViewScaleOnDrop,
-                options);
+        return startDrag(drawable, /* view= */ null, originalView, dragLayerX, dragLayerY, source,
+                dragInfo, dragRegion, initialDragViewScale, dragViewScaleOnDrop, options);
     }
 
     /**
@@ -180,14 +178,12 @@
             int dragLayerY,
             DragSource source,
             ItemInfo dragInfo,
-            Point dragOffset,
             Rect dragRegion,
             float initialDragViewScale,
             float dragViewScaleOnDrop,
             DragOptions options) {
-        return startDrag(/* drawable= */ null, view, originalView, dragLayerX, dragLayerY,
-                source, dragInfo, dragOffset, dragRegion, initialDragViewScale, dragViewScaleOnDrop,
-                options);
+        return startDrag(/* drawable= */ null, view, originalView, dragLayerX, dragLayerY, source,
+                dragInfo, dragRegion, initialDragViewScale, dragViewScaleOnDrop, options);
     }
 
     protected abstract DragView startDrag(
@@ -198,7 +194,6 @@
             int dragLayerY,
             DragSource source,
             ItemInfo dragInfo,
-            Point dragOffset,
             Rect dragRegion,
             float initialDragViewScale,
             float dragViewScaleOnDrop,
diff --git a/src/com/android/launcher3/dragndrop/DragOptions.java b/src/com/android/launcher3/dragndrop/DragOptions.java
index 1ff4335..2af81db 100644
--- a/src/com/android/launcher3/dragndrop/DragOptions.java
+++ b/src/com/android/launcher3/dragndrop/DragOptions.java
@@ -78,5 +78,12 @@
          *                    This will be true if the condition was met, otherwise false.
          */
         void onPreDragEnd(DropTarget.DragObject dragObject, boolean dragStarted);
+
+        /**
+         * The offset points that should be overridden to update the dragLayer.
+         */
+        default Point getDragOffset() {
+            return new Point(0,0);
+        }
     }
 }
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 46c8e81..0d0717e 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -37,7 +37,6 @@
 import android.graphics.ColorFilter;
 import android.graphics.Path;
 import android.graphics.Picture;
-import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.drawable.AdaptiveIconDrawable;
 import android.graphics.drawable.ColorDrawable;
@@ -89,15 +88,17 @@
 
     private final RunnableList mOnDragStartCallback = new RunnableList();
 
-    private Point mDragVisualizeOffset = null;
+    private boolean mHasDragOffset;
     private Rect mDragRegion = null;
     protected final T mActivity;
     private final BaseDragLayer<T> mDragLayer;
     private boolean mHasDrawn = false;
 
-    final ValueAnimator mAnim;
+    final ValueAnimator mScaleAnim;
+    final ValueAnimator mShiftAnim;
+
     // Whether mAnim has started. Unlike mAnim.isStarted(), this is true even after mAnim ends.
-    private boolean mAnimStarted;
+    private boolean mScaleAnimStarted;
     private Runnable mOnAnimEndCallback = null;
 
     private int mLastTouchX;
@@ -166,9 +167,9 @@
         setScaleY(initialScale);
 
         // Animate the view into the correct position
-        mAnim = ValueAnimator.ofFloat(0f, 1f);
-        mAnim.setDuration(VIEW_ZOOM_DURATION);
-        mAnim.addUpdateListener(animation -> {
+        mScaleAnim = ValueAnimator.ofFloat(0f, 1f);
+        mScaleAnim.setDuration(VIEW_ZOOM_DURATION);
+        mScaleAnim.addUpdateListener(animation -> {
             final float value = (Float) animation.getAnimatedValue();
             setScaleX(Utilities.mapRange(value, initialScale, mEndScale));
             setScaleY(Utilities.mapRange(value, initialScale, mEndScale));
@@ -176,10 +177,10 @@
                 animation.cancel();
             }
         });
-        mAnim.addListener(new AnimatorListenerAdapter() {
+        mScaleAnim.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(Animator animation) {
-                mAnimStarted = true;
+                mScaleAnimStarted = true;
             }
 
             @Override
@@ -190,6 +191,8 @@
                 }
             }
         });
+        // Set up the shift animator.
+        mShiftAnim = ValueAnimator.ofFloat(0f, 1f);
 
         setDragRegion(new Rect(0, 0, width, height));
 
@@ -319,12 +322,12 @@
         return mDragRegion.height();
     }
 
-    public void setDragVisualizeOffset(Point p) {
-        mDragVisualizeOffset = p;
+    public void setHasDragOffset(boolean hasDragOffset) {
+        mHasDragOffset = hasDragOffset;
     }
 
-    public Point getDragVisualizeOffset() {
-        return mDragVisualizeOffset;
+    public boolean getHasDragOffset() {
+        return mHasDragOffset;
     }
 
     public void setDragRegion(Rect r) {
@@ -392,22 +395,29 @@
 
         if (mContent != null) {
             // At the drag start, the source view visibility is set to invisible.
-            mContent.setVisibility(VISIBLE);
+            if (getHasDragOffset()) {
+                // If there is any dragOffset, this means the content will show away of the original
+                // icon location, otherwise it's fine since original content would just show at the
+                // same spot.
+                mContent.setVisibility(INVISIBLE);
+            } else {
+                mContent.setVisibility(VISIBLE);
+            }
         }
 
         move(touchX, touchY);
         // Post the animation to skip other expensive work happening on the first frame
-        post(mAnim::start);
+        post(mScaleAnim::start);
     }
 
     public void cancelAnimation() {
-        if (mAnim != null && mAnim.isRunning()) {
-            mAnim.cancel();
+        if (mScaleAnim != null && mScaleAnim.isRunning()) {
+            mScaleAnim.cancel();
         }
     }
 
-    public boolean isAnimationFinished() {
-        return mAnimStarted && !mAnim.isRunning();
+    public boolean isScaleAnimationFinished() {
+        return mScaleAnimStarted && !mScaleAnim.isRunning();
     }
 
     /**
@@ -434,13 +444,15 @@
             int duration);
 
     public void animateShift(final int shiftX, final int shiftY) {
-        if (mAnim.isStarted()) {
-            return;
-        }
+        if (mShiftAnim.isStarted()) return;
+
+        // Set mContent visibility to visible to show icon regardless in case it is INVISIBLE.
+        if (mContent != null) mContent.setVisibility(VISIBLE);
+
         mAnimatedShiftX = shiftX;
         mAnimatedShiftY = shiftY;
         applyTranslation();
-        mAnim.addUpdateListener(new AnimatorUpdateListener() {
+        mShiftAnim.addUpdateListener(new AnimatorUpdateListener() {
             @Override
             public void onAnimationUpdate(ValueAnimator animation) {
                 float fraction = 1 - animation.getAnimatedFraction();
@@ -449,6 +461,7 @@
                 applyTranslation();
             }
         });
+        mShiftAnim.start();
     }
 
     private void applyTranslation() {
diff --git a/src/com/android/launcher3/dragndrop/LauncherDragController.java b/src/com/android/launcher3/dragndrop/LauncherDragController.java
index 0e8b0a5..aaa5b1a 100644
--- a/src/com/android/launcher3/dragndrop/LauncherDragController.java
+++ b/src/com/android/launcher3/dragndrop/LauncherDragController.java
@@ -21,7 +21,6 @@
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
 import android.content.res.Resources;
-import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.view.HapticFeedbackConstants;
@@ -60,7 +59,6 @@
             int dragLayerY,
             DragSource source,
             ItemInfo dragInfo,
-            Point dragOffset,
             Rect dragRegion,
             float initialDragViewScale,
             float dragViewScaleOnDrop,
@@ -129,9 +127,11 @@
         mDragObject.dragInfo = dragInfo;
         mDragObject.originalDragInfo = mDragObject.dragInfo.makeShallowCopy();
 
-        if (dragOffset != null) {
-            dragView.setDragVisualizeOffset(new Point(dragOffset));
+        if (mOptions.preDragCondition != null) {
+            dragView.setHasDragOffset(mOptions.preDragCondition.getDragOffset().x != 0 ||
+                    mOptions.preDragCondition.getDragOffset().y != 0);
         }
+
         if (dragRegion != null) {
             dragView.setDragRegion(new Rect(dragRegion));
         }
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 48239ae..2c1100f 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -74,7 +74,6 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemFactory;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.MultiTranslateDelegate;
 import com.android.launcher3.util.Thunk;
diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
index 372e9bf..8f0b8ec 100644
--- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
+++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.graphics;
 
+import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
@@ -52,6 +53,8 @@
 import com.android.launcher3.model.BgDataModel;
 import com.android.launcher3.model.GridSizeMigrationUtil;
 import com.android.launcher3.model.LoaderTask;
+import com.android.launcher3.model.ModelDbController;
+import com.android.launcher3.provider.LauncherDbUtils;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.RunnableList;
 import com.android.launcher3.util.Themes;
@@ -145,7 +148,9 @@
         final String query = LauncherSettings.Favorites.ITEM_TYPE + " = "
                 + LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
 
-        try (Cursor c = context.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
+        ModelDbController mainController =
+                LauncherAppState.getInstance(mContext).getModel().getModelDbController();
+        try (Cursor c = mainController.query(TABLE_NAME,
                 new String[] {
                         LauncherSettings.Favorites.APPWIDGET_ID,
                         LauncherSettings.Favorites.SPANX,
@@ -190,8 +195,6 @@
 
     @WorkerThread
     private void loadModelData() {
-        final boolean migrated = doGridMigrationIfNecessary();
-
         final Context inflationContext;
         if (mWallpaperColors != null) {
             // Create a themed context, without affecting the main application context
@@ -209,8 +212,20 @@
                     Themes.getActivityThemeRes(mContext));
         }
 
-        if (migrated) {
+        if (GridSizeMigrationUtil.needsToMigrate(inflationContext, mIdp)) {
+            // Start the migration
             PreviewContext previewContext = new PreviewContext(inflationContext, mIdp);
+            // Copy existing data to preview DB
+            LauncherDbUtils.copyTable(LauncherAppState.getInstance(mContext)
+                    .getModel().getModelDbController().getDb(),
+                    TABLE_NAME,
+                    LauncherAppState.getInstance(previewContext)
+                            .getModel().getModelDbController().getDb(),
+                    TABLE_NAME,
+                    mContext);
+            LauncherAppState.getInstance(previewContext)
+                    .getModel().getModelDbController().clearEmptyDbFlag();
+
             new LoaderTask(
                     LauncherAppState.getInstance(previewContext),
                     /* bgAllAppsList= */ null,
@@ -229,8 +244,7 @@
                         query += " or " + LauncherSettings.Favorites.SCREEN + " = "
                                 + Workspace.SECOND_SCREEN_ID;
                     }
-                    loadWorkspaceForPreviewSurfaceRenderer(new ArrayList<>(),
-                            LauncherSettings.Favorites.PREVIEW_CONTENT_URI, query);
+                    loadWorkspace(new ArrayList<>(), query, null);
 
                     final SparseArray<Size> spanInfo =
                             getLoadedLauncherWidgetInfo(previewContext.getBaseContext());
@@ -253,14 +267,6 @@
         }
     }
 
-    @WorkerThread
-    private boolean doGridMigrationIfNecessary() {
-        if (!GridSizeMigrationUtil.needsToMigrate(mContext, mIdp)) {
-            return false;
-        }
-        return GridSizeMigrationUtil.migrateGridIfNeeded(mContext, mIdp);
-    }
-
     @UiThread
     private void renderView(Context inflationContext, BgDataModel dataModel,
             Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap,
diff --git a/src/com/android/launcher3/graphics/SysUiScrim.java b/src/com/android/launcher3/graphics/SysUiScrim.java
index be995bc..21ebc98 100644
--- a/src/com/android/launcher3/graphics/SysUiScrim.java
+++ b/src/com/android/launcher3/graphics/SysUiScrim.java
@@ -36,13 +36,10 @@
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.testing.shared.ResourceUtils;
-import com.android.launcher3.util.DynamicResource;
 import com.android.launcher3.util.ScreenOnTracker;
 import com.android.launcher3.util.ScreenOnTracker.ScreenOnListener;
 import com.android.launcher3.util.Themes;
-import com.android.systemui.plugins.ResourceProvider;
 
 /**
  * View scrim which draws behind hotseat and workspace
@@ -100,10 +97,8 @@
     private static final int ALPHA_MASK_BITMAP_DP = 200;
     private static final int ALPHA_MASK_WIDTH_DP = 2;
 
-    private boolean mDrawTopScrim, mDrawBottomScrim, mDrawWallpaperScrim;
+    private boolean mDrawTopScrim, mDrawBottomScrim;
 
-    private final RectF mWallpaperScrimRect = new RectF();
-    private final Paint mWallpaperScrimPaint = new Paint();
     private final RectF mFinalMaskRect = new RectF();
     private final Paint mBottomMaskPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
     private final Bitmap mBottomMask;
@@ -118,7 +113,6 @@
 
     private boolean mAnimateScrimOnNextDraw = false;
     private float mSysUiAnimMultiplier = 1;
-    private int mWallpaperScrimMaxAlpha;
 
     public SysUiScrim(View view) {
         mRoot = view;
@@ -135,14 +129,6 @@
             mHideSysUiScrim = true;
         }
 
-        mDrawWallpaperScrim = FeatureFlags.ENABLE_WALLPAPER_SCRIM.get()
-                && !Themes.getAttrBoolean(view.getContext(), R.attr.isMainColorDark)
-                && !Themes.getAttrBoolean(view.getContext(), R.attr.isWorkspaceDarkText);
-        ResourceProvider rp = DynamicResource.provider(view.getContext());
-        int wallpaperScrimColor = rp.getColor(R.color.wallpaper_scrim_color);
-        mWallpaperScrimMaxAlpha = Color.alpha(wallpaperScrimColor);
-        mWallpaperScrimPaint.setColor(wallpaperScrimColor);
-
         view.addOnAttachStateChangeListener(this);
     }
 
@@ -167,9 +153,6 @@
                 mAnimateScrimOnNextDraw = false;
             }
 
-            if (mDrawWallpaperScrim) {
-                canvas.drawRect(mWallpaperScrimRect, mWallpaperScrimPaint);
-            }
             if (mDrawTopScrim) {
                 mTopScrim.draw(canvas);
             }
@@ -228,7 +211,6 @@
             mTopScrim.setBounds(0, 0, w, h);
             mFinalMaskRect.set(0, h - mMaskHeight, w, h);
         }
-        mWallpaperScrimRect.set(0, 0, w, h);
     }
 
     private void setSysUiProgress(float progress) {
@@ -251,7 +233,6 @@
         if (mTopScrim != null) {
             mTopScrim.setAlpha(Math.round(255 * factor));
         }
-        mWallpaperScrimPaint.setAlpha(Math.round(mWallpaperScrimMaxAlpha * factor));
     }
 
     private Bitmap createDitheredAlphaMask() {
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 8197b73..15f3538 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -891,6 +891,12 @@
             return this;
         }
 
+
+        /** Sets cardinality of the event. */
+        default StatsLatencyLogger withCardinality(int cardinality) {
+            return this;
+        }
+
         /**
          * Sets packageId of log message.
          */
diff --git a/src/com/android/launcher3/model/DatabaseHelper.java b/src/com/android/launcher3/model/DatabaseHelper.java
index dc5fcf7..ecf5f67 100644
--- a/src/com/android/launcher3/model/DatabaseHelper.java
+++ b/src/com/android/launcher3/model/DatabaseHelper.java
@@ -15,8 +15,8 @@
  */
 package com.android.launcher3.model;
 
+import static com.android.launcher3.LauncherSettings.Favorites.addTableToDb;
 import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
-import static com.android.launcher3.provider.LauncherDbUtils.tableExists;
 
 import android.content.ContentValues;
 import android.content.Context;
@@ -36,9 +36,6 @@
 
 import com.android.launcher3.AutoInstallsLayout;
 import com.android.launcher3.AutoInstallsLayout.LayoutParserCallback;
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.LauncherFiles;
-import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.Utilities;
@@ -58,6 +55,7 @@
 import java.net.URISyntaxException;
 import java.util.Arrays;
 import java.util.Locale;
+import java.util.function.ToLongFunction;
 import java.util.stream.Collectors;
 
 /**
@@ -76,45 +74,23 @@
     private static final boolean LOGD = false;
 
     private static final String DOWNGRADE_SCHEMA_FILE = "downgrade_schema.json";
-    public static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED";
 
     private final Context mContext;
-    private final boolean mForMigration;
+    private final ToLongFunction<UserHandle> mUserSerialProvider;
+    private final Runnable mOnEmptyDbCreateCallback;
+
     private int mMaxItemId = -1;
     public boolean mHotseatRestoreTableExists;
 
-    public static DatabaseHelper createDatabaseHelper(Context context, boolean forMigration) {
-        return createDatabaseHelper(context, null, forMigration);
-    }
-
-    public static DatabaseHelper createDatabaseHelper(Context context, String dbName,
-            boolean forMigration) {
-        if (dbName == null) {
-            dbName = InvariantDeviceProfile.INSTANCE.get(context).dbFile;
-        }
-        DatabaseHelper databaseHelper = new DatabaseHelper(context, dbName, forMigration);
-        // Table creation sometimes fails silently, which leads to a crash loop.
-        // This way, we will try to create a table every time after crash, so the device
-        // would eventually be able to recover.
-        if (!tableExists(databaseHelper.getReadableDatabase(), Favorites.TABLE_NAME)) {
-            Log.e(TAG, "Tables are missing after onCreate has been called. Trying to recreate");
-            // This operation is a no-op if the table already exists.
-            databaseHelper.addFavoritesTable(databaseHelper.getWritableDatabase(), true);
-        }
-        databaseHelper.mHotseatRestoreTableExists = tableExists(
-                databaseHelper.getReadableDatabase(), Favorites.HYBRID_HOTSEAT_BACKUP_TABLE);
-
-        databaseHelper.initIds();
-        return databaseHelper;
-    }
-
     /**
      * Constructor used in tests and for restore.
      */
-    public DatabaseHelper(Context context, String dbName, boolean forMigration) {
+    public DatabaseHelper(Context context, String dbName,
+            ToLongFunction<UserHandle> userSerialProvider, Runnable onEmptyDbCreateCallback) {
         super(context, dbName, SCHEMA_VERSION);
         mContext = context;
-        mForMigration = forMigration;
+        mUserSerialProvider = userSerialProvider;
+        mOnEmptyDbCreateCallback = onEmptyDbCreateCallback;
     }
 
     protected void initIds() {
@@ -131,13 +107,11 @@
 
         mMaxItemId = 1;
 
-        addFavoritesTable(db, false);
+        addTableToDb(db, getDefaultUserSerial(), false /* optional */);
 
         // Fresh and clean launcher DB.
         mMaxItemId = initializeMaxItemId(db);
-        if (!mForMigration) {
-            onEmptyDbCreated();
-        }
+        mOnEmptyDbCreateCallback.run();
     }
 
     public void onAddOrDeleteOp(SQLiteDatabase db) {
@@ -147,38 +121,8 @@
         }
     }
 
-    /**
-     * Re-composite given key in respect to database. If the current db is
-     * {@link LauncherFiles#LAUNCHER_DB}, return the key as-is. Otherwise append the db name to
-     * given key. e.g. consider key="EMPTY_DATABASE_CREATED", dbName="minimal.db", the returning
-     * string will be "EMPTY_DATABASE_CREATED@minimal.db".
-     */
-    public String getKey(final String key) {
-        if (TextUtils.equals(getDatabaseName(), LauncherFiles.LAUNCHER_DB)) {
-            return key;
-        }
-        return key + "@" + getDatabaseName();
-    }
-
-    /**
-     * Overridden in tests.
-     */
-    protected void onEmptyDbCreated() {
-        // Set the flag for empty DB
-        LauncherPrefs.getPrefs(mContext).edit().putBoolean(getKey(EMPTY_DATABASE_CREATED), true)
-                .commit();
-    }
-
-    public long getSerialNumberForUser(UserHandle user) {
-        return UserCache.INSTANCE.get(mContext).getSerialNumberForUser(user);
-    }
-
-    public long getDefaultUserSerial() {
-        return getSerialNumberForUser(Process.myUserHandle());
-    }
-
-    private void addFavoritesTable(SQLiteDatabase db, boolean optional) {
-        Favorites.addTableToDb(db, getDefaultUserSerial(), optional);
+    private long getDefaultUserSerial() {
+        return mUserSerialProvider.applyAsLong(Process.myUserHandle());
     }
 
     @Override
diff --git a/src/com/android/launcher3/model/GridSizeMigrationUtil.java b/src/com/android/launcher3/model/GridSizeMigrationUtil.java
index eded5ea..9a6cde6 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationUtil.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationUtil.java
@@ -16,6 +16,9 @@
 
 package com.android.launcher3.model;
 
+import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
+import static com.android.launcher3.LauncherSettings.Favorites.TMP_TABLE;
+import static com.android.launcher3.provider.LauncherDbUtils.copyTable;
 import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
 
 import android.content.ComponentName;
@@ -34,16 +37,15 @@
 import androidx.annotation.NonNull;
 
 import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.graphics.LauncherPreviewRenderer;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.pm.InstallSessionHelper;
 import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
 import com.android.launcher3.util.GridOccupancy;
 import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.widget.WidgetManagerHelper;
 
@@ -89,81 +91,38 @@
         return needsToMigrate;
     }
 
-    /** See {@link #migrateGridIfNeeded(Context, InvariantDeviceProfile)} */
-    public static boolean migrateGridIfNeeded(Context context) {
-        if (context instanceof LauncherPreviewRenderer.PreviewContext) {
-            return true;
-        }
-        return migrateGridIfNeeded(context, null);
-    }
-
     /**
-     * When migrating the grid for preview, we copy the table
-     * {@link LauncherSettings.Favorites#TABLE_NAME} into
-     * {@link LauncherSettings.Favorites#PREVIEW_TABLE_NAME}, run grid size migration from the
-     * former to the later, then use the later table for preview.
-     *
-     * Similarly when doing the actual grid migration, the former grid option's table
-     * {@link LauncherSettings.Favorites#TABLE_NAME} is copied into the new grid option's
-     * {@link LauncherSettings.Favorites#TMP_TABLE}, we then run the grid size migration algorithm
+     * When migrating the grid, we copy the table
+     * {@link LauncherSettings.Favorites#TABLE_NAME} from {@code source} into
+     * {@link LauncherSettings.Favorites#TMP_TABLE}, run the grid size migration algorithm
      * to migrate the later to the former, and load the workspace from the default
      * {@link LauncherSettings.Favorites#TABLE_NAME}.
      *
      * @return false if the migration failed.
      */
-    public static boolean migrateGridIfNeeded(Context context, InvariantDeviceProfile idp) {
-        boolean migrateForPreview = idp != null;
-        if (!migrateForPreview) {
-            idp = LauncherAppState.getIDP(context);
-        }
+    public static boolean migrateGridIfNeeded(
+            @NonNull Context context,
+            @NonNull InvariantDeviceProfile idp,
+            @NonNull DatabaseHelper target,
+            @NonNull SQLiteDatabase source) {
 
         DeviceGridState srcDeviceState = new DeviceGridState(context);
         DeviceGridState destDeviceState = new DeviceGridState(idp);
         if (!needsToMigrate(srcDeviceState, destDeviceState)) {
             return true;
         }
+        copyTable(source, TABLE_NAME, target.getWritableDatabase(), TMP_TABLE, context);
 
         HashSet<String> validPackages = getValidPackages(context);
-
-        if (migrateForPreview) {
-            if (!LauncherSettings.Settings.call(
-                    context.getContentResolver(),
-                    LauncherSettings.Settings.METHOD_PREP_FOR_PREVIEW,
-                    destDeviceState.getDbFile()).getBoolean(
-                    LauncherSettings.Settings.EXTRA_VALUE)) {
-                return false;
-            }
-        } else if (!LauncherSettings.Settings.call(
-                context.getContentResolver(),
-                LauncherSettings.Settings.METHOD_UPDATE_CURRENT_OPEN_HELPER,
-                destDeviceState.getDbFile()).getBoolean(
-                LauncherSettings.Settings.EXTRA_VALUE)) {
-            return false;
-        }
-
         long migrationStartTime = System.currentTimeMillis();
-        try (SQLiteTransaction t = (SQLiteTransaction) LauncherSettings.Settings.call(
-                context.getContentResolver(),
-                LauncherSettings.Settings.METHOD_NEW_TRANSACTION).getBinder(
-                LauncherSettings.Settings.EXTRA_VALUE)) {
-
-            DbReader srcReader = new DbReader(t.getDb(),
-                    migrateForPreview ? LauncherSettings.Favorites.TABLE_NAME
-                            : LauncherSettings.Favorites.TMP_TABLE,
-                    context, validPackages);
-            DbReader destReader = new DbReader(t.getDb(),
-                    migrateForPreview ? LauncherSettings.Favorites.PREVIEW_TABLE_NAME
-                            : LauncherSettings.Favorites.TABLE_NAME,
-                    context, validPackages);
+        try (SQLiteTransaction t = new SQLiteTransaction(target.getWritableDatabase())) {
+            DbReader srcReader = new DbReader(t.getDb(), TMP_TABLE, context, validPackages);
+            DbReader destReader = new DbReader(t.getDb(), TABLE_NAME, context, validPackages);
 
             Point targetSize = new Point(destDeviceState.getColumns(), destDeviceState.getRows());
-            migrate(context, t.getDb(), srcReader, destReader, destDeviceState.getNumHotseat(),
+            migrate(target, srcReader, destReader, destDeviceState.getNumHotseat(),
                     targetSize, srcDeviceState, destDeviceState);
-
-            if (!migrateForPreview) {
-                dropTable(t.getDb(), LauncherSettings.Favorites.TMP_TABLE);
-            }
-
+            dropTable(t.getDb(), TMP_TABLE);
             t.commit();
             return true;
         } catch (Exception e) {
@@ -174,7 +133,7 @@
             Log.v(TAG, "Workspace migration completed in "
                     + (System.currentTimeMillis() - migrationStartTime));
 
-            if (!migrateForPreview) {
+            if (!(context instanceof SandboxContext)) {
                 // Save current configuration, so that the migration does not run again.
                 destDeviceState.writeToPrefs(context);
             }
@@ -182,7 +141,7 @@
     }
 
     public static boolean migrate(
-            @NonNull final Context context, @NonNull final SQLiteDatabase db,
+            @NonNull DatabaseHelper helper,
             @NonNull final DbReader srcReader, @NonNull final DbReader destReader,
             final int destHotseatSize, @NonNull final Point targetSize,
             @NonNull final DeviceGridState srcDeviceState,
@@ -234,8 +193,8 @@
         Collections.sort(workspaceToBeAdded);
 
         // Migrate hotseat
-        solveHotseatPlacement(db, srcReader,
-                destReader, context, destHotseatSize, dstHotseatItems, hotseatToBeAdded);
+        solveHotseatPlacement(helper, destHotseatSize,
+                srcReader, destReader, dstHotseatItems, hotseatToBeAdded);
 
         // Migrate workspace.
         // First we create a collection of the screens
@@ -255,8 +214,8 @@
             if (DEBUG) {
                 Log.d(TAG, "Migrating " + screenId);
             }
-            solveGridPlacement(db, srcReader,
-                    destReader, context, screenId, trgX, trgY, workspaceToBeAdded, false);
+            solveGridPlacement(helper, srcReader,
+                    destReader, screenId, trgX, trgY, workspaceToBeAdded, false);
             if (workspaceToBeAdded.isEmpty()) {
                 break;
             }
@@ -266,8 +225,8 @@
         // any of the screens, in this case we add them to new screens until all of them are placed.
         int screenId = destReader.mLastScreenId + 1;
         while (!workspaceToBeAdded.isEmpty()) {
-            solveGridPlacement(db, srcReader,
-                    destReader, context, screenId, trgX, trgY, workspaceToBeAdded, preservePages);
+            solveGridPlacement(helper, srcReader,
+                    destReader, screenId, trgX, trgY, workspaceToBeAdded, preservePages);
             screenId++;
         }
 
@@ -298,33 +257,33 @@
         });
     }
 
-    private static void insertEntryInDb(SQLiteDatabase db, Context context, DbEntry entry,
+    private static void insertEntryInDb(DatabaseHelper helper, DbEntry entry,
             String srcTableName, String destTableName) {
-        int id = copyEntryAndUpdate(db, context, entry, srcTableName, destTableName);
+        int id = copyEntryAndUpdate(helper, entry, srcTableName, destTableName);
 
         if (entry.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
             for (Set<Integer> itemIds : entry.mFolderItems.values()) {
                 for (int itemId : itemIds) {
-                    copyEntryAndUpdate(db, context, itemId, id, srcTableName, destTableName);
+                    copyEntryAndUpdate(helper, itemId, id, srcTableName, destTableName);
                 }
             }
         }
     }
 
-    private static int copyEntryAndUpdate(SQLiteDatabase db, Context context,
+    private static int copyEntryAndUpdate(DatabaseHelper helper,
             DbEntry entry, String srcTableName, String destTableName) {
-        return copyEntryAndUpdate(db, context, entry, -1, -1, srcTableName, destTableName);
+        return copyEntryAndUpdate(helper, entry, -1, -1, srcTableName, destTableName);
     }
 
-    private static int copyEntryAndUpdate(SQLiteDatabase db, Context context,
+    private static int copyEntryAndUpdate(DatabaseHelper helper,
             int id, int folderId, String srcTableName, String destTableName) {
-        return copyEntryAndUpdate(db, context, null, id, folderId, srcTableName, destTableName);
+        return copyEntryAndUpdate(helper, null, id, folderId, srcTableName, destTableName);
     }
 
-    private static int copyEntryAndUpdate(SQLiteDatabase db, Context context,
-            DbEntry entry, int id, int folderId, String srcTableName, String destTableName) {
+    private static int copyEntryAndUpdate(DatabaseHelper helper, DbEntry entry,
+            int id, int folderId, String srcTableName, String destTableName) {
         int newId = -1;
-        Cursor c = db.query(srcTableName, null,
+        Cursor c = helper.getWritableDatabase().query(srcTableName, null,
                 LauncherSettings.Favorites._ID + " = '" + (entry != null ? entry.id : id) + "'",
                 null, null, null, null);
         while (c.moveToNext()) {
@@ -335,11 +294,9 @@
             } else {
                 values.put(LauncherSettings.Favorites.CONTAINER, folderId);
             }
-            newId = LauncherSettings.Settings.call(context.getContentResolver(),
-                    LauncherSettings.Settings.METHOD_NEW_ITEM_ID).getInt(
-                    LauncherSettings.Settings.EXTRA_VALUE);
+            newId = helper.generateNewItemId();
             values.put(LauncherSettings.Favorites._ID, newId);
-            db.insert(destTableName, null, values);
+            helper.getWritableDatabase().insert(destTableName, null, values);
         }
         c.close();
         return newId;
@@ -367,9 +324,9 @@
         return validPackages;
     }
 
-    private static void solveGridPlacement(@NonNull final SQLiteDatabase db,
+    private static void solveGridPlacement(@NonNull final DatabaseHelper helper,
             @NonNull final DbReader srcReader, @NonNull final DbReader destReader,
-            @NonNull final Context context, final int screenId, final int trgX, final int trgY,
+            final int screenId, final int trgX, final int trgY,
             @NonNull final List<DbEntry> sortedItemsToPlace, final boolean matchingScreenIdOnly) {
         final GridOccupancy occupied = new GridOccupancy(trgX, trgY);
         final Point trg = new Point(trgX, trgY);
@@ -391,7 +348,7 @@
                 continue;
             }
             if (findPlacementForEntry(entry, next, trg, occupied, screenId)) {
-                insertEntryInDb(db, context, entry, srcReader.mTableName, destReader.mTableName);
+                insertEntryInDb(helper, entry, srcReader.mTableName, destReader.mTableName);
                 iterator.remove();
             }
         }
@@ -428,9 +385,9 @@
         return false;
     }
 
-    private static void solveHotseatPlacement(@NonNull final SQLiteDatabase db,
+    private static void solveHotseatPlacement(
+            @NonNull final DatabaseHelper helper, final int hotseatSize,
             @NonNull final DbReader srcReader, @NonNull final DbReader destReader,
-            @NonNull final Context context, final int hotseatSize,
             @NonNull final  List<DbEntry> placedHotseatItems,
             @NonNull final List<DbEntry> itemsToPlace) {
 
@@ -447,7 +404,7 @@
                 // to something other than -1.
                 entry.cellX = i;
                 entry.cellY = 0;
-                insertEntryInDb(db, context, entry, srcReader.mTableName, destReader.mTableName);
+                insertEntryInDb(helper, entry, srcReader.mTableName, destReader.mTableName);
                 occupied[entry.screenId] = true;
             }
         }
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index a5dccc1..2054d93 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -16,16 +16,16 @@
 
 package com.android.launcher3.model;
 
+import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
+
 import android.content.ComponentName;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.LauncherApps;
-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;
@@ -66,9 +66,7 @@
     private final LongSparseArray<UserHandle> allUsers;
 
     private final LauncherAppState mApp;
-    private final Uri mContentUri;
     private final Context mContext;
-    private final PackageManager mPM;
     private final IconCache mIconCache;
     private final InvariantDeviceProfile mIDP;
 
@@ -108,17 +106,14 @@
     public int itemType;
     public int restoreFlag;
 
-    public LoaderCursor(Cursor cursor, Uri contentUri, LauncherAppState app,
-            UserManagerState userManagerState) {
+    public LoaderCursor(Cursor cursor, LauncherAppState app, UserManagerState userManagerState) {
         super(cursor);
 
         mApp = app;
         allUsers = userManagerState.allUsers;
-        mContentUri = contentUri;
         mContext = app.getContext();
         mIconCache = app.getIconCache();
         mIDP = app.getInvariantDeviceProfile();
-        mPM = mContext.getPackageManager();
 
         // Init column indices
         mIconIndex = getColumnIndexOrThrow(Favorites.ICON);
@@ -390,7 +385,7 @@
      */
     public ContentWriter updater() {
        return new ContentWriter(mContext, new ContentWriter.CommitParams(
-               mApp.getModel().getModelDbController().getDatabaseHelper(),
+               mApp.getModel().getModelDbController(),
                BaseColumns._ID + "= ?", new String[]{Integer.toString(id)}));
     }
 
@@ -409,8 +404,8 @@
     public boolean commitDeleted() {
         if (mItemsToRemove.size() > 0) {
             // Remove dead items
-            mContext.getContentResolver().delete(mContentUri, Utilities.createDbSelectionQuery(
-                    Favorites._ID, mItemsToRemove), null);
+            mApp.getModel().getModelDbController().delete(TABLE_NAME,
+                    Utilities.createDbSelectionQuery(Favorites._ID, mItemsToRemove), null);
             return true;
         }
         return false;
@@ -435,9 +430,8 @@
             // Update restored items that no longer require special handling
             ContentValues values = new ContentValues();
             values.put(Favorites.RESTORED, 0);
-            mContext.getContentResolver().update(mContentUri, values,
-                    Utilities.createDbSelectionQuery(
-                            Favorites._ID, mRestoredRows), null);
+            mApp.getModel().getModelDbController().update(TABLE_NAME, values,
+                    Utilities.createDbSelectionQuery(Favorites._ID, mRestoredRows), null);
         }
     }
 
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 9053d19..d4eded5 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.model;
 
+import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
@@ -41,7 +42,6 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ShortcutInfo;
 import android.graphics.Point;
-import android.net.Uri;
 import android.os.Bundle;
 import android.os.Trace;
 import android.os.UserHandle;
@@ -50,7 +50,6 @@
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.LongSparseArray;
-import android.util.TimingLogger;
 
 import androidx.annotation.Nullable;
 
@@ -200,25 +199,10 @@
         }
 
         Object traceToken = TraceHelper.INSTANCE.beginSection(TAG);
-        TimingLogger timingLogger = new TimingLogger(TAG, "run");
         LoaderMemoryLogger memoryLogger = new LoaderMemoryLogger();
         try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
             List<ShortcutInfo> allShortcuts = new ArrayList<>();
-            Trace.beginSection("LoadWorkspace");
-            try {
-                loadWorkspace(allShortcuts, memoryLogger);
-            } finally {
-                Trace.endSection();
-            }
-            logASplit(timingLogger, "loadWorkspace");
-
-            if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
-                verifyNotStopped();
-                mModelDelegate.loadAndBindWorkspaceItems(mUserManagerState,
-                        mLauncherBinder.mCallbacksList, mShortcutKeyToPinnedShortcuts);
-                mModelDelegate.markActive();
-                logASplit(timingLogger, "workspaceDelegateItems");
-            }
+            loadWorkspace(allShortcuts, "", memoryLogger);
 
             // Sanitize data re-syncs widgets/shortcuts based on the workspace loaded from db.
             // sanitizeData should not be invoked if the workspace is loaded from a db different
@@ -228,21 +212,21 @@
                 verifyNotStopped();
                 sanitizeFolders(mItemsDeleted);
                 sanitizeWidgetsShortcutsAndPackages();
-                logASplit(timingLogger, "sanitizeData");
+                logASplit("sanitizeData");
             }
 
             verifyNotStopped();
             mLauncherBinder.bindWorkspace(true /* incrementBindId */, /* isBindSync= */ false);
-            logASplit(timingLogger, "bindWorkspace");
+            logASplit("bindWorkspace");
 
             mModelDelegate.workspaceLoadComplete();
             // Notify the installer packages of packages with active installs on the first screen.
             sendFirstScreenActiveInstallsBroadcast();
-            logASplit(timingLogger, "sendFirstScreenActiveInstallsBroadcast");
+            logASplit("sendFirstScreenActiveInstallsBroadcast");
 
             // Take a break
             waitForIdle();
-            logASplit(timingLogger, "step 1 complete");
+            logASplit("step 1 complete");
             verifyNotStopped();
 
             // second step
@@ -253,16 +237,16 @@
             } finally {
                 Trace.endSection();
             }
-            logASplit(timingLogger, "loadAllApps");
+            logASplit("loadAllApps");
 
             if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
                 mModelDelegate.loadAndBindAllAppsItems(mUserManagerState,
                         mLauncherBinder.mCallbacksList, mShortcutKeyToPinnedShortcuts);
-                logASplit(timingLogger, "allAppsDelegateItems");
+                logASplit("allAppsDelegateItems");
             }
             verifyNotStopped();
             mLauncherBinder.bindAllApps();
-            logASplit(timingLogger, "bindAllApps");
+            logASplit("bindAllApps");
 
             verifyNotStopped();
             IconCacheUpdateHandler updateHandler = mIconCache.getUpdateHandler();
@@ -270,75 +254,73 @@
             updateHandler.updateIcons(allActivityList,
                     LauncherActivityCachingLogic.newInstance(mApp.getContext()),
                     mApp.getModel()::onPackageIconsUpdated);
-            logASplit(timingLogger, "update icon cache");
+            logASplit("update icon cache");
 
             verifyNotStopped();
-            logASplit(timingLogger, "save shortcuts in icon cache");
+            logASplit("save shortcuts in icon cache");
             updateHandler.updateIcons(allShortcuts, new ShortcutCachingLogic(),
                     mApp.getModel()::onPackageIconsUpdated);
 
             // Take a break
             waitForIdle();
-            logASplit(timingLogger, "step 2 complete");
+            logASplit("step 2 complete");
             verifyNotStopped();
 
             // third step
             List<ShortcutInfo> allDeepShortcuts = loadDeepShortcuts();
-            logASplit(timingLogger, "loadDeepShortcuts");
+            logASplit("loadDeepShortcuts");
 
             verifyNotStopped();
             mLauncherBinder.bindDeepShortcuts();
-            logASplit(timingLogger, "bindDeepShortcuts");
+            logASplit("bindDeepShortcuts");
 
             verifyNotStopped();
-            logASplit(timingLogger, "save deep shortcuts in icon cache");
+            logASplit("save deep shortcuts in icon cache");
             updateHandler.updateIcons(allDeepShortcuts,
                     new ShortcutCachingLogic(), (pkgs, user) -> { });
 
             // Take a break
             waitForIdle();
-            logASplit(timingLogger, "step 3 complete");
+            logASplit("step 3 complete");
             verifyNotStopped();
 
             // fourth step
             List<ComponentWithLabelAndIcon> allWidgetsList =
                     mBgDataModel.widgetsModel.update(mApp, null);
-            logASplit(timingLogger, "load widgets");
+            logASplit("load widgets");
 
             verifyNotStopped();
             mLauncherBinder.bindWidgets();
-            logASplit(timingLogger, "bindWidgets");
+            logASplit("bindWidgets");
             verifyNotStopped();
 
             if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
                 mModelDelegate.loadAndBindOtherItems(mLauncherBinder.mCallbacksList);
-                logASplit(timingLogger, "otherDelegateItems");
+                logASplit("otherDelegateItems");
                 verifyNotStopped();
             }
 
             updateHandler.updateIcons(allWidgetsList,
                     new ComponentWithIconCachingLogic(mApp.getContext(), true),
                     mApp.getModel()::onWidgetLabelsUpdated);
-            logASplit(timingLogger, "save widgets in icon cache");
+            logASplit("save widgets in icon cache");
 
             // fifth step
             loadFolderNames();
 
             verifyNotStopped();
             updateHandler.finish();
-            logASplit(timingLogger, "finish icon update");
+            logASplit("finish icon update");
 
             mModelDelegate.modelLoadComplete();
             transaction.commit();
             memoryLogger.clearLogs();
         } catch (CancellationException e) {
             // Loader stopped, ignore
-            logASplit(timingLogger, "Cancelled");
+            logASplit("Cancelled");
         } catch (Exception e) {
             memoryLogger.printLogs();
             throw e;
-        } finally {
-            timingLogger.dumpToLog();
         }
         TraceHelper.INSTANCE.endSection(traceToken);
     }
@@ -348,25 +330,29 @@
         this.notify();
     }
 
-    private void loadWorkspace(
-            List<ShortcutInfo> allDeepShortcuts, LoaderMemoryLogger memoryLogger) {
-        loadWorkspace(allDeepShortcuts, Favorites.CONTENT_URI,
-                null /* selection */, memoryLogger);
-    }
+    protected void loadWorkspace(
+            List<ShortcutInfo> allDeepShortcuts,
+            String selection,
+            LoaderMemoryLogger memoryLogger) {
+        Trace.beginSection("LoadWorkspace");
+        try {
+            loadWorkspaceImpl(allDeepShortcuts, selection, memoryLogger);
+        } finally {
+            Trace.endSection();
+        }
+        logASplit("loadWorkspace");
 
-    protected void loadWorkspaceForPreviewSurfaceRenderer(
-            List<ShortcutInfo> allDeepShortcuts, Uri contentUri, String selection) {
-        loadWorkspace(allDeepShortcuts, contentUri, selection, null);
         if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
+            verifyNotStopped();
             mModelDelegate.loadAndBindWorkspaceItems(mUserManagerState,
                     mLauncherBinder.mCallbacksList, mShortcutKeyToPinnedShortcuts);
             mModelDelegate.markActive();
+            logASplit("workspaceDelegateItems");
         }
     }
 
-    protected void loadWorkspace(
+    private void loadWorkspaceImpl(
             List<ShortcutInfo> allDeepShortcuts,
-            Uri contentUri,
             String selection,
             @Nullable LoaderMemoryLogger memoryLogger) {
         final Context context = mApp.getContext();
@@ -377,7 +363,7 @@
         final WidgetManagerHelper widgetHelper = new WidgetManagerHelper(context);
 
         boolean clearDb = false;
-        if (!GridSizeMigrationUtil.migrateGridIfNeeded(context)) {
+        if (!mApp.getModel().getModelDbController().migrateGridIfNeeded()) {
             // Migration failed. Clear workspace.
             clearDb = true;
         }
@@ -402,8 +388,9 @@
             mFirstScreenBroadcast = new FirstScreenBroadcast(installingPkgs);
 
             mShortcutKeyToPinnedShortcuts = new HashMap<>();
+            ModelDbController dbController = mApp.getModel().getModelDbController();
             final LoaderCursor c = new LoaderCursor(
-                    contentResolver.query(contentUri, null, selection, null, null), contentUri,
+                    dbController.query(TABLE_NAME, null, selection, null, null),
                     mApp, mUserManagerState);
             final Bundle extras = c.getExtras();
             mDbName = extras == null ? null : extras.getString(Settings.EXTRA_DB_NAME);
@@ -1112,12 +1099,9 @@
         FileLog.d(TAG, widgetDimension.toString());
     }
 
-    private static void logASplit(@Nullable TimingLogger timingLogger, String label) {
-        if (timingLogger != null) {
-            timingLogger.addSplit(label);
-            if (DEBUG) {
-                Log.d(TAG, label);
-            }
+    private static void logASplit(String label) {
+        if (DEBUG) {
+            Log.d(TAG, label);
         }
     }
 }
diff --git a/src/com/android/launcher3/model/ModelDbController.java b/src/com/android/launcher3/model/ModelDbController.java
index 97bce8c..f0e5ef6 100644
--- a/src/com/android/launcher3/model/ModelDbController.java
+++ b/src/com/android/launcher3/model/ModelDbController.java
@@ -19,11 +19,10 @@
 import static android.util.Base64.NO_WRAP;
 
 import static com.android.launcher3.DefaultLayoutParser.RES_PARTNER_DEFAULT_LAYOUT;
+import static com.android.launcher3.LauncherSettings.Favorites.addTableToDb;
 import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_KEY;
 import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_LABEL;
 import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_TAG;
-import static com.android.launcher3.model.DatabaseHelper.EMPTY_DATABASE_CREATED;
-import static com.android.launcher3.provider.LauncherDbUtils.copyTable;
 import static com.android.launcher3.provider.LauncherDbUtils.tableExists;
 
 import android.app.blob.BlobHandle;
@@ -31,7 +30,6 @@
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
-import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
 import android.content.pm.ProviderInfo;
 import android.content.res.Resources;
@@ -43,6 +41,7 @@
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
+import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
 import android.text.TextUtils;
@@ -54,18 +53,22 @@
 
 import com.android.launcher3.AutoInstallsLayout;
 import com.android.launcher3.AutoInstallsLayout.SourceResources;
+import com.android.launcher3.ConstantItem;
 import com.android.launcher3.DefaultLayoutParser;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherFiles;
 import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.provider.LauncherDbUtils;
 import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
 import com.android.launcher3.provider.RestoreDbTask;
 import com.android.launcher3.util.IOUtils;
 import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
 import com.android.launcher3.util.Partner;
 import com.android.launcher3.widget.LauncherWidgetHolder;
 
@@ -73,7 +76,6 @@
 
 import java.io.InputStream;
 import java.io.StringReader;
-import java.util.function.Supplier;
 
 /**
  * Utility class which maintains an instance of Launcher database and provides utility methods
@@ -82,6 +84,8 @@
 public class ModelDbController {
     private static final String TAG = "LauncherProvider";
 
+    private static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED";
+
     protected DatabaseHelper mOpenHelper;
 
     private final Context mContext;
@@ -92,26 +96,36 @@
 
     private synchronized void createDbIfNotExists() {
         if (mOpenHelper == null) {
-            mOpenHelper = DatabaseHelper.createDatabaseHelper(
-                    mContext, false /* forMigration */);
-
-            RestoreDbTask.restoreIfNeeded(mContext, mOpenHelper);
+            mOpenHelper = createDatabaseHelper(false /* forMigration */);
+            RestoreDbTask.restoreIfNeeded(mContext, this);
         }
     }
 
-    private synchronized boolean prepForMigration(String dbFile, String targetTableName,
-            Supplier<DatabaseHelper> src, Supplier<DatabaseHelper> dst) {
-        if (TextUtils.equals(dbFile, mOpenHelper.getDatabaseName())) {
-            Log.e(TAG, "prepForMigration - target db is same as current: " + dbFile);
-            return false;
-        }
+    protected DatabaseHelper createDatabaseHelper(boolean forMigration) {
+        boolean isSandbox = mContext instanceof SandboxContext;
+        String dbName = isSandbox ? null : InvariantDeviceProfile.INSTANCE.get(mContext).dbFile;
 
-        final DatabaseHelper helper = src.get();
-        mOpenHelper = dst.get();
-        copyTable(helper.getReadableDatabase(), Favorites.TABLE_NAME,
-                mOpenHelper.getWritableDatabase(), targetTableName, mContext);
-        helper.close();
-        return true;
+        // Set the flag for empty DB
+        Runnable onEmptyDbCreateCallback = forMigration ? () -> { }
+                : () -> LauncherPrefs.get(mContext).putSync(getEmptyDbCreatedKey(dbName).to(true));
+
+        DatabaseHelper databaseHelper = new DatabaseHelper(mContext, dbName,
+                this::getSerialNumberForUser, onEmptyDbCreateCallback);
+        // Table creation sometimes fails silently, which leads to a crash loop.
+        // This way, we will try to create a table every time after crash, so the device
+        // would eventually be able to recover.
+        if (!tableExists(databaseHelper.getReadableDatabase(), Favorites.TABLE_NAME)) {
+            Log.e(TAG, "Tables are missing after onCreate has been called. Trying to recreate");
+            // This operation is a no-op if the table already exists.
+            addTableToDb(databaseHelper.getWritableDatabase(),
+                    getSerialNumberForUser(Process.myUserHandle()),
+                    true /* optional */);
+        }
+        databaseHelper.mHotseatRestoreTableExists = tableExists(
+                databaseHelper.getReadableDatabase(), Favorites.HYBRID_HOTSEAT_BACKUP_TABLE);
+
+        databaseHelper.initIds();
+        return databaseHelper;
     }
 
     /**
@@ -267,42 +281,41 @@
     }
 
     /**
-     * Updates the current DB and copies all the existing data to the temp table
-     * @param dbFile name of the target db file name
+     * Migrates the DB if needed, and returns false if the migration failed
+     * and DB needs to be cleared.
+     * @return true if migration was success or ignored, false if migration failed
+     * and the DB should be reset.
      */
-    @WorkerThread
-    public boolean updateCurrentOpenHelper(String dbFile) {
+    public boolean migrateGridIfNeeded() {
         createDbIfNotExists();
-        return prepForMigration(
-                dbFile,
-                Favorites.TMP_TABLE,
-                () -> mOpenHelper,
-                () -> DatabaseHelper.createDatabaseHelper(
-                        mContext, true /* forMigration */));
+        InvariantDeviceProfile idp = LauncherAppState.getIDP(mContext);
+        if (!GridSizeMigrationUtil.needsToMigrate(mContext, idp)) {
+            return true;
+        }
+        String targetDbName = new DeviceGridState(idp).getDbFile();
+        if (TextUtils.equals(targetDbName, mOpenHelper.getDatabaseName())) {
+            Log.e(TAG, "migrateGridIfNeeded - target db is same as current: " + targetDbName);
+            return false;
+        }
+        DatabaseHelper oldHelper = mOpenHelper;
+        mOpenHelper = (mContext instanceof SandboxContext) ? oldHelper
+                : createDatabaseHelper(true /* forMigration */);
+        try {
+            return GridSizeMigrationUtil.migrateGridIfNeeded(mContext, idp, mOpenHelper,
+                   oldHelper.getWritableDatabase());
+        } finally {
+            if (mOpenHelper != oldHelper) {
+                oldHelper.close();
+            }
+        }
     }
 
     /**
-     * Returns the current DatabaseHelper.
-     * Only for tests
+     * Returns the underlying model database
      */
-    @WorkerThread
-    public DatabaseHelper getDatabaseHelper() {
+    public SQLiteDatabase getDb() {
         createDbIfNotExists();
-        return mOpenHelper;
-    }
-
-    /**
-     * Prepares the DB for preview by copying all existing data to preview table
-     */
-    @WorkerThread
-    public boolean prepareForPreview(String dbFile) {
-        createDbIfNotExists();
-        return prepForMigration(
-                dbFile,
-                Favorites.PREVIEW_TABLE_NAME,
-                () -> DatabaseHelper.createDatabaseHelper(
-                        mContext, dbFile, true /* forMigration */),
-                () -> mOpenHelper);
+        return mOpenHelper.getWritableDatabase();
     }
 
     private void onAddOrDeleteOp(SQLiteDatabase db) {
@@ -345,8 +358,7 @@
     }
 
     private void clearFlagEmptyDbCreated() {
-        LauncherPrefs.getPrefs(mContext).edit()
-                .remove(mOpenHelper.getKey(EMPTY_DATABASE_CREATED)).commit();
+        LauncherPrefs.get(mContext).removeSync(getEmptyDbCreatedKey(mOpenHelper.getDatabaseName()));
     }
 
     /**
@@ -359,9 +371,8 @@
     @WorkerThread
     public synchronized void loadDefaultFavoritesIfNecessary() {
         createDbIfNotExists();
-        SharedPreferences sp = LauncherPrefs.getPrefs(mContext);
 
-        if (sp.getBoolean(mOpenHelper.getKey(EMPTY_DATABASE_CREATED), false)) {
+        if (LauncherPrefs.get(mContext).get(getEmptyDbCreatedKey(mOpenHelper.getDatabaseName()))) {
             Log.d(TAG, "loading default workspace");
 
             LauncherWidgetHolder widgetHolder = mOpenHelper.newLauncherWidgetHolder();
@@ -479,4 +490,27 @@
         return new DefaultLayoutParser(mContext, widgetHolder,
                 mOpenHelper, mContext.getResources(), defaultLayout);
     }
+
+    /**
+     * Re-composite given key in respect to database. If the current db is
+     * {@link LauncherFiles#LAUNCHER_DB}, return the key as-is. Otherwise append the db name to
+     * given key. e.g. consider key="EMPTY_DATABASE_CREATED", dbName="minimal.db", the returning
+     * string will be "EMPTY_DATABASE_CREATED@minimal.db".
+     */
+    private ConstantItem<Boolean> getEmptyDbCreatedKey(String dbName) {
+        if (mContext instanceof SandboxContext) {
+            return LauncherPrefs.nonRestorableItem(EMPTY_DATABASE_CREATED,
+                    false /* default value */, false /* boot aware */);
+        }
+        String key = TextUtils.equals(dbName, LauncherFiles.LAUNCHER_DB)
+                ? EMPTY_DATABASE_CREATED : EMPTY_DATABASE_CREATED + "@" + dbName;
+        return LauncherPrefs.backedUpItem(key, false /* default value */, false /* boot aware */);
+    }
+
+    /**
+     * Returns the serial number for the provided user
+     */
+    public long getSerialNumberForUser(UserHandle user) {
+        return UserCache.INSTANCE.get(mContext).getSerialNumberForUser(user);
+    }
 }
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index d4a5e1b..8c938f4 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -95,7 +95,7 @@
         mPackages = packages;
         if (TestProtocol.sDebugTracing) {
             Log.d(TestProtocol.WORK_TAB_MISSING, "PackageUpdatedTask mOp: " + mOp +
-                    " packageCount: " + mPackages.length);
+                    " packageCount: " + mPackages.length + " user: " + user);
             DEBUG = true;
         }
     }
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index bc492fd..8274789 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -241,8 +241,8 @@
                     deepShortcutCount,
                     popupDataProvider.getNotificationKeysForItem(item),
                     systemShortcuts);
-            launcher.tryClearAccessibilityFocus(icon);
         }
+        launcher.tryClearAccessibilityFocus(icon);
         launcher.refreshAndBindWidgetsForPackageUser(PackageUserKey.fromItemInfo(item));
         container.requestFocus();
         return container;
diff --git a/src/com/android/launcher3/provider/LauncherDbUtils.java b/src/com/android/launcher3/provider/LauncherDbUtils.java
index 48969fc..c718dcc 100644
--- a/src/com/android/launcher3/provider/LauncherDbUtils.java
+++ b/src/com/android/launcher3/provider/LauncherDbUtils.java
@@ -101,7 +101,7 @@
         UserManagerState ums = new UserManagerState();
         ums.init(UserCache.INSTANCE.get(context),
                 context.getSystemService(UserManager.class));
-        LoaderCursor lc = new LoaderCursor(c, null, LauncherAppState.getInstance(context), ums);
+        LoaderCursor lc = new LoaderCursor(c, LauncherAppState.getInstance(context), ums);
         IntSet deletedShortcuts = new IntSet();
 
         while (lc.moveToNext()) {
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index ac72164..a6e064a 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.provider;
 
+import static android.os.Process.myUserHandle;
+
 import static com.android.launcher3.InvariantDeviceProfile.TYPE_MULTI_DISPLAY;
 import static com.android.launcher3.LauncherPrefs.APP_WIDGET_IDS;
 import static com.android.launcher3.LauncherPrefs.OLD_APP_WIDGET_IDS;
@@ -47,8 +49,8 @@
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.logging.FileLog;
-import com.android.launcher3.model.DatabaseHelper;
 import com.android.launcher3.model.DeviceGridState;
+import com.android.launcher3.model.ModelDbController;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -83,12 +85,12 @@
     /**
      * Tries to restore the backup DB if needed
      */
-    public static void restoreIfNeeded(Context context, DatabaseHelper helper) {
+    public static void restoreIfNeeded(Context context, ModelDbController dbController) {
         if (!isPending(context)) {
             return;
         }
-        if (!performRestore(context, helper)) {
-            helper.createEmptyDB(helper.getWritableDatabase());
+        if (!performRestore(context, dbController)) {
+            dbController.createEmptyDB();
         }
 
         // Obtain InvariantDeviceProfile first before setting pending to false, so
@@ -102,12 +104,12 @@
         idp.reinitializeAfterRestore(context);
     }
 
-    private static boolean performRestore(Context context, DatabaseHelper helper) {
-        SQLiteDatabase db = helper.getWritableDatabase();
+    private static boolean performRestore(Context context, ModelDbController controller) {
+        SQLiteDatabase db = controller.getDb();
         try (SQLiteTransaction t = new SQLiteTransaction(db)) {
             RestoreDbTask task = new RestoreDbTask();
-            task.sanitizeDB(context, helper, db, new BackupManager(context));
-            task.restoreAppWidgetIdsIfExists(context, helper);
+            task.sanitizeDB(context, controller, db, new BackupManager(context));
+            task.restoreAppWidgetIdsIfExists(context, controller);
             t.commit();
             return true;
         } catch (Exception e) {
@@ -129,10 +131,10 @@
      * @return number of items deleted.
      */
     @VisibleForTesting
-    protected int sanitizeDB(Context context, DatabaseHelper helper, SQLiteDatabase db,
+    protected int sanitizeDB(Context context, ModelDbController controller, SQLiteDatabase db,
             BackupManager backupManager) throws Exception {
         // Primary user ids
-        long myProfileId = helper.getDefaultUserSerial();
+        long myProfileId = controller.getSerialNumberForUser(myUserHandle());
         long oldProfileId = getDefaultProfileId(db);
         LongSparseArray<Long> oldManagedProfileIds = getManagedProfileIds(db, oldProfileId);
         LongSparseArray<Long> profileMapping = new LongSparseArray<>(oldManagedProfileIds.size()
@@ -144,7 +146,7 @@
             long oldManagedProfileId = oldManagedProfileIds.keyAt(i);
             UserHandle user = getUserForAncestralSerialNumber(backupManager, oldManagedProfileId);
             if (user != null) {
-                long newManagedProfileId = helper.getSerialNumberForUser(user);
+                long newManagedProfileId = controller.getSerialNumberForUser(user);
                 profileMapping.put(oldManagedProfileId, newManagedProfileId);
             }
         }
@@ -213,7 +215,7 @@
         }
 
         // Override shortcuts
-        maybeOverrideShortcuts(context, helper, db, myProfileId);
+        maybeOverrideShortcuts(context, controller, db, myProfileId);
 
         return itemsDeleted;
     }
@@ -321,11 +323,11 @@
                 .putSync(RESTORE_DEVICE.to(new DeviceGridState(context).getDeviceType()));
     }
 
-    private void restoreAppWidgetIdsIfExists(Context context, DatabaseHelper helper) {
+    private void restoreAppWidgetIdsIfExists(Context context, ModelDbController controller) {
         LauncherPrefs lp = LauncherPrefs.get(context);
         if (lp.has(APP_WIDGET_IDS, OLD_APP_WIDGET_IDS)) {
             AppWidgetHost host = new AppWidgetHost(context, APPWIDGET_HOST_ID);
-            AppWidgetsRestoredReceiver.restoreAppWidgetIds(context, helper,
+            AppWidgetsRestoredReceiver.restoreAppWidgetIds(context, controller,
                     IntArray.fromConcatString(lp.get(OLD_APP_WIDGET_IDS)).toArray(),
                     IntArray.fromConcatString(lp.get(APP_WIDGET_IDS)).toArray(),
                     host);
@@ -343,7 +345,7 @@
                 APP_WIDGET_IDS.to(IntArray.wrap(newIds).toConcatString()));
     }
 
-    protected static void maybeOverrideShortcuts(Context context, DatabaseHelper helper,
+    protected static void maybeOverrideShortcuts(Context context, ModelDbController controller,
             SQLiteDatabase db, long currentUser) {
         Map<String, LauncherActivityInfo> activityOverrides = ApiWrapper.getActivityOverrides(
                 context);
@@ -367,7 +369,7 @@
                 if (override != null) {
                     ContentValues values = new ContentValues();
                     values.put(Favorites.PROFILE_ID,
-                            helper.getSerialNumberForUser(override.getUser()));
+                            controller.getSerialNumberForUser(override.getUser()));
                     values.put(Favorites.INTENT, AppInfo.makeLaunchIntent(override).toUri(0));
                     db.update(Favorites.TABLE_NAME, values, String.format("%s=?", Favorites._ID),
                             new String[]{String.valueOf(c.getInt(idIndex))});
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
index 56dffa9..fcc62a7 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
@@ -18,7 +18,6 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.content.Intent;
-import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
@@ -405,17 +404,25 @@
             drawable = null;
             scale = previewProvider.getScaleAndPosition(contentView, mTempXY);
         }
-        int halfPadding = previewProvider.previewPadding / 2;
+
         int dragLayerX = mTempXY[0];
         int dragLayerY = mTempXY[1];
 
-        Point dragVisualizeOffset = null;
         Rect dragRect = new Rect();
         if (draggableView != null) {
             draggableView.getSourceVisualDragBounds(dragRect);
             dragLayerY += dragRect.top;
-            dragVisualizeOffset = new Point(-halfPadding, halfPadding);
         }
+
+        if (options.preDragCondition != null) {
+            int xOffSet = options.preDragCondition.getDragOffset().x;
+            int yOffSet = options.preDragCondition.getDragOffset().y;
+            if (xOffSet != 0 && yOffSet != 0) {
+                dragLayerX += xOffSet;
+                dragLayerY += yOffSet;
+            }
+        }
+
         if (contentView != null) {
             mDragController.startDrag(
                     contentView,
@@ -424,7 +431,6 @@
                     dragLayerY,
                     source,
                     dragObject,
-                    dragVisualizeOffset,
                     dragRect,
                     scale * iconScale,
                     scale,
@@ -437,7 +443,6 @@
                     dragLayerY,
                     source,
                     dragObject,
-                    dragVisualizeOffset,
                     dragRect,
                     scale * iconScale,
                     scale,
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDragController.java b/src/com/android/launcher3/secondarydisplay/SecondaryDragController.java
index b1a9b86..8d1d96b 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDragController.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDragController.java
@@ -17,7 +17,6 @@
 package com.android.launcher3.secondarydisplay;
 
 import android.content.res.Resources;
-import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.view.HapticFeedbackConstants;
@@ -51,7 +50,7 @@
     @Override
     protected DragView startDrag(@Nullable Drawable drawable, @Nullable View view,
             DraggableView originalView, int dragLayerX, int dragLayerY, DragSource source,
-            ItemInfo dragInfo, Point dragOffset, Rect dragRegion, float initialDragViewScale,
+            ItemInfo dragInfo, Rect dragRegion, float initialDragViewScale,
             float dragViewScaleOnDrop, DragOptions options) {
         if (PROFILE_DRAWING_DURING_DRAG) {
             android.os.Debug.startMethodTracing("Launcher");
@@ -117,9 +116,11 @@
         mDragObject.dragInfo = dragInfo;
         mDragObject.originalDragInfo = mDragObject.dragInfo.makeShallowCopy();
 
-        if (dragOffset != null) {
-            dragView.setDragVisualizeOffset(new Point(dragOffset));
+        if (mOptions.preDragCondition != null) {
+            dragView.setHasDragOffset(mOptions.preDragCondition.getDragOffset().x != 0 ||
+                    mOptions.preDragCondition.getDragOffset().y != 0);
         }
+
         if (dragRegion != null) {
             dragView.setDragRegion(new Rect(dragRegion));
         }
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java b/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
index a917b68..87afcab 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
@@ -236,7 +236,7 @@
 
                 @Override
                 public boolean shouldStartDrag(double distanceDragged) {
-                    return mDragView != null && mDragView.isAnimationFinished();
+                    return mDragView != null && mDragView.isScaleAnimationFinished();
                 }
 
                 @Override
diff --git a/src/com/android/launcher3/statemanager/StateManager.java b/src/com/android/launcher3/statemanager/StateManager.java
index 89d89d6..198dad3 100644
--- a/src/com/android/launcher3/statemanager/StateManager.java
+++ b/src/com/android/launcher3/statemanager/StateManager.java
@@ -229,8 +229,10 @@
                     listener.onAnimationEnd(null);
                 }
                 return;
-            } else if (!mConfig.userControlled && animated && mConfig.targetState == state) {
-                // We are running the same animation as requested
+            } else if ((!mConfig.userControlled && animated && mConfig.targetState == state)
+                    || mState.shouldPreserveDataStateOnReapply()) {
+                // We are running the same animation as requested, and/or target state should not be
+                // reset -- allow the current animation to complete instead of canceling it.
                 if (listener != null) {
                     mConfig.currentAnimation.addListener(listener);
                 }
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index 050e88f..6a972eb 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -589,17 +589,17 @@
         float scaledDividerHeight = dividerHeight * scale;
 
         if (desiredStagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT) {
-            if (splitInfo.appsStackedVertically) {
-                outRect.bottom = Math.round(outRect.top + scaledTopTaskHeight);
-            } else {
+            if (dp.isLandscape) {
                 outRect.right = outRect.left + Math.round(outRect.width() * topLeftTaskPercent);
+            } else {
+                outRect.bottom = Math.round(outRect.top + scaledTopTaskHeight);
             }
         } else {
-            if (splitInfo.appsStackedVertically) {
-                outRect.top += Math.round(scaledTopTaskHeight + scaledDividerHeight);
-            } else {
+            if (dp.isLandscape) {
                 outRect.left += Math.round(outRect.width()
                         * (topLeftTaskPercent + dividerBarPercent));
+            } else {
+                outRect.top += Math.round(scaledTopTaskHeight + scaledDividerHeight);
             }
         }
     }
@@ -610,9 +610,9 @@
             DeviceProfile dp, boolean isRtl) {
         int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
         int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
-        int dividerBar = Math.round(splitBoundsConfig.appsStackedVertically
-                ? splitBoundsConfig.dividerHeightPercent * dp.availableHeightPx
-                : splitBoundsConfig.dividerWidthPercent * parentWidth);
+        float dividerScale = splitBoundsConfig.appsStackedVertically
+                ? splitBoundsConfig.dividerHeightPercent
+                : splitBoundsConfig.dividerWidthPercent;
         int primarySnapshotHeight;
         int primarySnapshotWidth;
         int secondarySnapshotHeight;
@@ -620,12 +620,13 @@
         float taskPercent = splitBoundsConfig.appsStackedVertically ?
                 splitBoundsConfig.topTaskPercent : splitBoundsConfig.leftTaskPercent;
         if (dp.isLandscape) {
+            int scaledDividerBar = Math.round(parentWidth * dividerScale);
             primarySnapshotHeight = totalThumbnailHeight;
             primarySnapshotWidth = Math.round(parentWidth * taskPercent);
 
             secondarySnapshotHeight = totalThumbnailHeight;
-            secondarySnapshotWidth = parentWidth - primarySnapshotWidth - dividerBar;
-            int translationX = primarySnapshotWidth + dividerBar;
+            secondarySnapshotWidth = parentWidth - primarySnapshotWidth - scaledDividerBar;
+            int translationX = primarySnapshotWidth + scaledDividerBar;
             if (isRtl) {
                 primarySnapshot.setTranslationX(-translationX);
                 secondarySnapshot.setTranslationX(0);
@@ -640,7 +641,7 @@
         } else {
             float scale = (float) totalThumbnailHeight / dp.availableHeightPx;
             float topTaskHeight = dp.availableHeightPx * taskPercent;
-            float finalDividerHeight = dividerBar * scale;
+            float finalDividerHeight = Math.round(totalThumbnailHeight * dividerScale);
             float scaledTopTaskHeight = topTaskHeight * scale;
             primarySnapshotWidth = parentWidth;
             primarySnapshotHeight = Math.round(scaledTopTaskHeight);
diff --git a/src/com/android/launcher3/util/ContentWriter.java b/src/com/android/launcher3/util/ContentWriter.java
index e509235..7c5ef4d 100644
--- a/src/com/android/launcher3/util/ContentWriter.java
+++ b/src/com/android/launcher3/util/ContentWriter.java
@@ -26,7 +26,7 @@
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.icons.GraphicsUtils;
-import com.android.launcher3.model.DatabaseHelper;
+import com.android.launcher3.model.ModelDbController;
 import com.android.launcher3.pm.UserCache;
 
 /**
@@ -106,7 +106,7 @@
 
     public int commit() {
         if (mCommitParams != null) {
-            mCommitParams.mDatabaseHelper.getWritableDatabase().update(
+            mCommitParams.mDbController.update(
                     Favorites.TABLE_NAME, getValues(mContext),
                     mCommitParams.mWhere, mCommitParams.mSelectionArgs);
         }
@@ -115,12 +115,12 @@
 
     public static final class CommitParams {
 
-        final DatabaseHelper mDatabaseHelper;
+        final ModelDbController mDbController;
         final String mWhere;
         final String[] mSelectionArgs;
 
-        public CommitParams(DatabaseHelper helper, String where, String[] selectionArgs) {
-            mDatabaseHelper = helper;
+        public CommitParams(ModelDbController controller, String where, String[] selectionArgs) {
+            mDbController = controller;
             mWhere = where;
             mSelectionArgs = selectionArgs;
         }
diff --git a/src/com/android/launcher3/util/ItemInfoMatcher.java b/src/com/android/launcher3/util/ItemInfoMatcher.java
index b6af314..8a27381 100644
--- a/src/com/android/launcher3/util/ItemInfoMatcher.java
+++ b/src/com/android/launcher3/util/ItemInfoMatcher.java
@@ -18,6 +18,7 @@
 
 import android.content.ComponentName;
 import android.os.UserHandle;
+import android.util.Log;
 
 import androidx.annotation.NonNull;
 
@@ -25,6 +26,7 @@
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.testing.shared.TestProtocol;
 
 import java.util.Collection;
 import java.util.HashSet;
@@ -42,7 +44,14 @@
     private static final ComponentName EMPTY_COMPONENT = new ComponentName("", "");
 
     public static Predicate<ItemInfo> ofUser(UserHandle user) {
-        return info -> info != null && info.user.equals(user);
+        return info -> {
+            if (TestProtocol.sDebugTracing) {
+                Log.d(TestProtocol.WORK_TAB_MISSING, "userHandle: " + user
+                        + ", itemUserHandle: " + info.user
+                        + " package: " + info.getTargetPackage());
+            }
+            return info != null && info.user.equals(user);
+        };
     }
 
     public static Predicate<ItemInfo> ofComponents(
diff --git a/src/com/android/launcher3/views/ComposeInitializer.java b/src/com/android/launcher3/views/ComposeInitializer.java
new file mode 100644
index 0000000..0929885
--- /dev/null
+++ b/src/com/android/launcher3/views/ComposeInitializer.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.views;
+
+import android.os.Build;
+import android.view.View;
+import android.view.ViewParent;
+import android.view.ViewTreeObserver;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleRegistry;
+import androidx.lifecycle.ViewTreeLifecycleOwner;
+import androidx.savedstate.SavedStateRegistry;
+import androidx.savedstate.SavedStateRegistryController;
+import androidx.savedstate.SavedStateRegistryOwner;
+import androidx.savedstate.ViewTreeSavedStateRegistryOwner;
+
+import com.android.launcher3.Utilities;
+
+/**
+ * An initializer to use Compose for classes implementing {@code ActivityContext}. This allows
+ * adding ComposeView to ViewTree outside a {@link androidx.activity.ComponentActivity}.
+ */
+public final class ComposeInitializer {
+    /**
+     * Performs the initialization to use Compose in the ViewTree of {@code target}.
+     */
+    public static void initCompose(ActivityContext target) {
+        getContentChild(target).addOnAttachStateChangeListener(
+                new View.OnAttachStateChangeListener() {
+
+                    @Override
+                    public void onViewAttachedToWindow(View v) {
+                        ComposeInitializer.onAttachedToWindow(v);
+                    }
+
+                    @Override
+                    public void onViewDetachedFromWindow(View v) {
+                        ComposeInitializer.onDetachedFromWindow(v);
+                    }
+                });
+    }
+
+    /**
+     * Find the "content child" for {@code target}.
+     *
+     * @see "WindowRecomposer.android.kt: [View.contentChild]"
+     */
+    private static View getContentChild(ActivityContext target) {
+        View self = target.getDragLayer();
+        ViewParent parent = self.getParent();
+        while (parent instanceof View parentView) {
+            if (parentView.getId() == android.R.id.content) return self;
+            self = parentView;
+            parent = self.getParent();
+        }
+        return self;
+    }
+
+    /**
+     * Function to be called on your window root view's [View.onAttachedToWindow] function.
+     */
+    private static void onAttachedToWindow(View root) {
+        if (ViewTreeLifecycleOwner.get(root) != null) {
+            throw new IllegalStateException(
+                    "View " + root + " already has a LifecycleOwner");
+        }
+
+        ViewParent parent = root.getParent();
+        if (parent instanceof View && ((View) parent).getId() != android.R.id.content) {
+            throw new IllegalStateException(
+                    "ComposeInitializer.onContentChildAttachedToWindow(View) must be called on "
+                            + "the content child. Outside of activities and dialogs, this is "
+                            + "usually the top-most View of a window.");
+        }
+
+        // The lifecycle owner, which is STARTED when [root] is visible and RESUMED when [root]
+        // is both visible and focused.
+        ViewLifecycleOwner lifecycleOwner = new ViewLifecycleOwner(root);
+
+        // We must call [ViewLifecycleOwner.onCreate] after creating the
+        // [SavedStateRegistryOwner] because `onCreate` might move the lifecycle state to STARTED
+        // which will make [SavedStateRegistryController.performRestore] throw.
+        lifecycleOwner.onCreate();
+
+        // Set the owners on the root. They will be reused by any ComposeView inside the root
+        // hierarchy.
+        ViewTreeLifecycleOwner.set(root, lifecycleOwner);
+        ViewTreeSavedStateRegistryOwner.set(root, lifecycleOwner);
+    }
+
+    /**
+     * Function to be called on your window root view's [View.onDetachedFromWindow] function.
+     */
+    private static void onDetachedFromWindow(View root) {
+        final LifecycleOwner lifecycleOwner = ViewTreeLifecycleOwner.get(root);
+        if (lifecycleOwner != null) {
+            ((ViewLifecycleOwner) lifecycleOwner).onDestroy();
+        }
+        ViewTreeLifecycleOwner.set(root, null);
+        ViewTreeSavedStateRegistryOwner.set(root, null);
+    }
+
+    /**
+     * A [LifecycleOwner] for a [View] that updates lifecycle state based on window state.
+     *
+     * Also a trivial implementation of [SavedStateRegistryOwner] that does not do any save or
+     * restore. This works for processes similar to the SystemUI process, which is always running
+     * and top-level windows using this initialization are created once, when the process is
+     * started.
+     *
+     * The implementation requires the caller to call [onCreate] and [onDestroy] when the view is
+     * attached to or detached from a view hierarchy. After [onCreate] and before [onDestroy] is
+     * called, the implementation monitors window state in the following way
+     * * If the window is not visible, we are in the [Lifecycle.State.CREATED] state
+     * * If the window is visible but not focused, we are in the [Lifecycle.State.STARTED] state
+     * * If the window is visible and focused, we are in the [Lifecycle.State.RESUMED] state
+     *
+     * Or in table format:
+     * ```
+     * ┌───────────────┬───────────────────┬──────────────┬─────────────────┐
+     * │ View attached │ Window Visibility │ Window Focus │ Lifecycle State │
+     * ├───────────────┼───────────────────┴──────────────┼─────────────────┤
+     * │ Not attached  │                 Any              │       N/A       │
+     * ├───────────────┼───────────────────┬──────────────┼─────────────────┤
+     * │               │    Not visible    │     Any      │     CREATED     │
+     * │               ├───────────────────┼──────────────┼─────────────────┤
+     * │   Attached    │                   │   No focus   │     STARTED     │
+     * │               │      Visible      ├──────────────┼─────────────────┤
+     * │               │                   │  Has focus   │     RESUMED     │
+     * └───────────────┴───────────────────┴──────────────┴─────────────────┘
+     * ```
+     */
+    private static class ViewLifecycleOwner implements SavedStateRegistryOwner {
+        private final ViewTreeObserver.OnWindowFocusChangeListener mWindowFocusListener =
+                hasFocus -> updateState();
+        private final LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
+
+        private final SavedStateRegistryController mSavedStateRegistryController =
+                SavedStateRegistryController.create(this);
+
+        private final View mView;
+        private final Api34Impl mApi34Impl;
+
+        ViewLifecycleOwner(View view) {
+            mView = view;
+            if (Utilities.ATLEAST_U) {
+                mApi34Impl = new Api34Impl();
+            } else {
+                mApi34Impl = null;
+            }
+
+            mSavedStateRegistryController.performRestore(null);
+        }
+
+        @NonNull
+        @Override
+        public Lifecycle getLifecycle() {
+            return mLifecycleRegistry;
+        }
+
+        @NonNull
+        @Override
+        public SavedStateRegistry getSavedStateRegistry() {
+            return mSavedStateRegistryController.getSavedStateRegistry();
+        }
+
+        void onCreate() {
+            mLifecycleRegistry.setCurrentState(Lifecycle.State.CREATED);
+            if (Utilities.ATLEAST_U) {
+                mApi34Impl.addOnWindowVisibilityChangeListener();
+            }
+            mView.getViewTreeObserver().addOnWindowFocusChangeListener(
+                    mWindowFocusListener);
+            updateState();
+        }
+
+        void onDestroy() {
+            if (Utilities.ATLEAST_U) {
+                mApi34Impl.removeOnWindowVisibilityChangeListener();
+            }
+            mView.getViewTreeObserver().removeOnWindowFocusChangeListener(
+                    mWindowFocusListener);
+            mLifecycleRegistry.setCurrentState(Lifecycle.State.DESTROYED);
+        }
+
+        private void updateState() {
+            Lifecycle.State state =
+                    mView.getWindowVisibility() != View.VISIBLE ? Lifecycle.State.CREATED
+                            : (!mView.hasWindowFocus() ? Lifecycle.State.STARTED
+                                    : Lifecycle.State.RESUMED);
+            mLifecycleRegistry.setCurrentState(state);
+        }
+
+        @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+        private class Api34Impl {
+            private final ViewTreeObserver.OnWindowVisibilityChangeListener
+                    mWindowVisibilityListener =
+                    visibility -> updateState();
+
+            void addOnWindowVisibilityChangeListener() {
+                mView.getViewTreeObserver().addOnWindowVisibilityChangeListener(
+                        mWindowVisibilityListener);
+            }
+
+            void removeOnWindowVisibilityChangeListener() {
+                mView.getViewTreeObserver().removeOnWindowVisibilityChangeListener(
+                        mWindowVisibilityListener);
+            }
+        }
+    }
+}
diff --git a/src/com/android/launcher3/widget/PendingItemDragHelper.java b/src/com/android/launcher3/widget/PendingItemDragHelper.java
index f269434..faad307 100644
--- a/src/com/android/launcher3/widget/PendingItemDragHelper.java
+++ b/src/com/android/launcher3/widget/PendingItemDragHelper.java
@@ -103,7 +103,6 @@
         final int previewWidth;
         final int previewHeight;
         final float scale;
-        final Point dragOffset;
         final Rect dragRegion;
 
         mEstimatedCellSize = launcher.getWorkspace().estimateItemSize(mAddInfo);
@@ -173,7 +172,6 @@
             scale = previewBounds.width() / (float) previewWidth;
             launcher.getDragController().addDragListener(new WidgetHostViewLoader(launcher, mView));
 
-            dragOffset = null;
             dragRegion = null;
             draggableView = DraggableView.ofType(DraggableView.DRAGGABLE_WIDGET);
         } else {
@@ -188,8 +186,6 @@
             li.recycle();
             scale = ((float) launcher.getDeviceProfile().iconSizePx) / previewWidth;
 
-            dragOffset = new Point(previewPadding / 2, previewPadding / 2);
-
             // Create a preview same as the workspace cell size and draw the icon at the
             // appropriate position.
             DeviceProfile dp = launcher.getDeviceProfile();
@@ -217,11 +213,10 @@
         // Start the drag
         if (mAppWidgetHostViewPreview != null) {
             launcher.getDragController().startDrag(mAppWidgetHostViewPreview, draggableView,
-                    dragLayerX, dragLayerY, source, mAddInfo, dragOffset, dragRegion, scale, scale,
-                    options);
+                    dragLayerX, dragLayerY, source, mAddInfo, dragRegion, scale, scale, options);
         } else {
             launcher.getDragController().startDrag(preview, draggableView, dragLayerX, dragLayerY,
-                    source, mAddInfo, dragOffset, dragRegion, scale, scale, options);
+                    source, mAddInfo, dragRegion, scale, scale, options);
         }
     }
 
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index d565dc9..33c4f8d 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -608,6 +608,12 @@
         }
     }
 
+    /** b/209579563: "Widgets" header should be focused first. */
+    @Override
+    protected View getAccessibilityInitialFocusView() {
+        return mHeaderTitle;
+    }
+
     protected float getMaxTableHeight(float noWidgetsViewHeight) {
         return (mContent.getMeasuredHeight()
                 - mTabsHeight - getHeaderViewHeight()
diff --git a/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt b/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
index 0fe8bee..01f494b 100644
--- a/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
+++ b/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
@@ -50,7 +50,7 @@
     private lateinit var originalWindowManagerProxy: WindowManagerProxy
 
     @Before
-    fun setUp() {
+    open fun setUp() {
         val appContext: Context = ApplicationProvider.getApplicationContext()
         originalWindowManagerProxy = WindowManagerProxy.INSTANCE.get(appContext)
         originalDisplayController = DisplayController.INSTANCE.get(appContext)
@@ -59,7 +59,7 @@
     }
 
     @After
-    fun tearDown() {
+    open fun tearDown() {
         WindowManagerProxy.INSTANCE.initializeForTesting(originalWindowManagerProxy)
         DisplayController.INSTANCE.initializeForTesting(originalDisplayController)
     }
diff --git a/tests/src/com/android/launcher3/DeleteDropTargetTest.kt b/tests/src/com/android/launcher3/DeleteDropTargetTest.kt
index 84c617c..bcfb90b 100644
--- a/tests/src/com/android/launcher3/DeleteDropTargetTest.kt
+++ b/tests/src/com/android/launcher3/DeleteDropTargetTest.kt
@@ -8,7 +8,6 @@
 import com.android.launcher3.util.ActivityContextWrapper
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
-import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -26,19 +25,15 @@
         enableRunningInTestHarnessForTests()
     }
 
-    // Needs mText, mTempRect, getPaddingTop, getPaddingBottom
-    // availableHeight as a parameter
-    @Ignore("TODO(b/279464742)")
     @Test
     fun isTextClippedVerticallyTest() {
-        buttonDropTarget.mText = "My Test"
+        buttonDropTarget.updateText("My Test")
+        buttonDropTarget.setPadding(0, 0, 0, 0)
+        buttonDropTarget.setTextMultiLine(false)
 
         // No space for text
         assertThat(buttonDropTarget.isTextClippedVertically(30)).isTrue()
 
-        // Some space for text, and just enough that the text should not be clipped
-        assertThat(buttonDropTarget.isTextClippedVertically(50)).isFalse()
-
         // A lot of space for text so the text should not be clipped
         assertThat(buttonDropTarget.isTextClippedVertically(100)).isFalse()
     }
diff --git a/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java b/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java
index e3de500..ee05fe6 100644
--- a/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java
+++ b/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java
@@ -87,15 +87,13 @@
         // modify the device profile.
         dp.inv.numColumns = width;
         dp.inv.numRows = height;
+        dp.cellLayoutBorderSpacePx = new Point(0, 0);
 
         CellLayout cl = new CellLayout(getWrappedContext(c, dp));
         // I put a very large number for width and height so that all the items can fit, it doesn't
         // need to be exact, just bigger than the sum of cell border
         cl.measure(View.MeasureSpec.makeMeasureSpec(10000, View.MeasureSpec.EXACTLY),
                 View.MeasureSpec.makeMeasureSpec(10000, View.MeasureSpec.EXACTLY));
-
-        cl.measure(View.MeasureSpec.makeMeasureSpec(cl.getDesiredWidth(), View.MeasureSpec.EXACTLY),
-                View.MeasureSpec.makeMeasureSpec(cl.getDesiredHeight(), View.MeasureSpec.EXACTLY));
         return cl;
     }
 
diff --git a/tests/src/com/android/launcher3/icons/IconCacheTest.java b/tests/src/com/android/launcher3/icons/IconCacheTest.java
index 08d6df3..495d583 100644
--- a/tests/src/com/android/launcher3/icons/IconCacheTest.java
+++ b/tests/src/com/android/launcher3/icons/IconCacheTest.java
@@ -116,7 +116,7 @@
             @Nullable ComponentName cn, @Nullable String badgeOverride) throws Exception {
         Builder builder = new Builder(context, "test-shortcut")
                 .setIntent(new Intent(Intent.ACTION_VIEW))
-                .setTitle("Test");
+                .setShortLabel("Test");
         if (cn != null) {
             builder.setActivity(cn);
         }
diff --git a/tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java b/tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java
index 0a1a9ba..cea95e5 100644
--- a/tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java
+++ b/tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java
@@ -39,6 +39,7 @@
 
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.R;
+import com.android.launcher3.pm.UserCache;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -222,7 +223,9 @@
     private class MyDatabaseHelper extends DatabaseHelper {
 
         MyDatabaseHelper() {
-            super(mContext, DB_FILE, false);
+            super(mContext, DB_FILE,
+                    UserCache.INSTANCE.get(mContext)::getSerialNumberForUser,
+                    () -> { });
         }
 
         @Override
diff --git a/tests/src/com/android/launcher3/model/GridSizeMigrationUtilTest.kt b/tests/src/com/android/launcher3/model/GridSizeMigrationUtilTest.kt
index f24f0da..3b480ca 100644
--- a/tests/src/com/android/launcher3/model/GridSizeMigrationUtilTest.kt
+++ b/tests/src/com/android/launcher3/model/GridSizeMigrationUtilTest.kt
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.model
 
+import android.content.ContentValues
 import android.content.Context
 import android.content.Intent
 import android.database.Cursor
@@ -23,6 +24,7 @@
 import android.os.Process
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
 import com.android.launcher3.InvariantDeviceProfile
 import com.android.launcher3.LauncherPrefs
 import com.android.launcher3.LauncherPrefs.Companion.WORKSPACE_SIZE
@@ -32,7 +34,6 @@
 import com.android.launcher3.pm.UserCache
 import com.android.launcher3.provider.LauncherDbUtils
 import com.android.launcher3.util.LauncherModelHelper
-import com.android.launcher3.util.LauncherModelHelper.*
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
 import org.junit.Before
@@ -43,11 +44,13 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class GridSizeMigrationUtilTest {
+
     private lateinit var modelHelper: LauncherModelHelper
     private lateinit var context: Context
-    private lateinit var db: SQLiteDatabase
     private lateinit var validPackages: Set<String>
     private lateinit var idp: InvariantDeviceProfile
+    private lateinit var dbHelper: DatabaseHelper
+    private lateinit var db: SQLiteDatabase
     private val testPackage1 = "com.android.launcher3.validpackage1"
     private val testPackage2 = "com.android.launcher3.validpackage2"
     private val testPackage3 = "com.android.launcher3.validpackage3"
@@ -63,11 +66,16 @@
     fun setUp() {
         modelHelper = LauncherModelHelper()
         context = modelHelper.sandboxContext
-        db = modelHelper.provider.db
+        dbHelper =
+            DatabaseHelper(
+                context,
+                null,
+                UserCache.INSTANCE.get(context)::getSerialNumberForUser
+            ) {}
+        db = dbHelper.writableDatabase
 
         validPackages =
             setOf(
-                TEST_PACKAGE,
                 testPackage1,
                 testPackage2,
                 testPackage3,
@@ -99,26 +107,26 @@
     @Throws(Exception::class)
     fun testMigration() {
         // Src Hotseat icons
-        modelHelper.addItem(APP_ICON, 0, HOTSEAT, 0, 0, testPackage1, 1, TMP_CONTENT_URI)
-        modelHelper.addItem(SHORTCUT, 1, HOTSEAT, 0, 0, testPackage2, 2, TMP_CONTENT_URI)
-        modelHelper.addItem(SHORTCUT, 3, HOTSEAT, 0, 0, testPackage3, 3, TMP_CONTENT_URI)
-        modelHelper.addItem(APP_ICON, 4, HOTSEAT, 0, 0, testPackage4, 4, TMP_CONTENT_URI)
+        addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_HOTSEAT, 0, 0, testPackage1, 1, TMP_TABLE)
+        addItem(ITEM_TYPE_SHORTCUT, 1, CONTAINER_HOTSEAT, 0, 0, testPackage2, 2, TMP_TABLE)
+        addItem(ITEM_TYPE_SHORTCUT, 3, CONTAINER_HOTSEAT, 0, 0, testPackage3, 3, TMP_TABLE)
+        addItem(ITEM_TYPE_APPLICATION, 4, CONTAINER_HOTSEAT, 0, 0, testPackage4, 4, TMP_TABLE)
         // Src grid icons
         // _ _ _ _ _
         // _ _ _ _ 5
         // _ _ 6 _ 7
         // _ _ 8 _ 9
         // _ _ _ _ _
-        modelHelper.addItem(APP_ICON, 0, DESKTOP, 4, 1, testPackage5, 5, TMP_CONTENT_URI)
-        modelHelper.addItem(APP_ICON, 0, DESKTOP, 2, 2, testPackage6, 6, TMP_CONTENT_URI)
-        modelHelper.addItem(APP_ICON, 0, DESKTOP, 4, 2, testPackage7, 7, TMP_CONTENT_URI)
-        modelHelper.addItem(APP_ICON, 0, DESKTOP, 2, 3, testPackage8, 8, TMP_CONTENT_URI)
-        modelHelper.addItem(APP_ICON, 0, DESKTOP, 4, 3, testPackage9, 9, TMP_CONTENT_URI)
+        addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 4, 1, testPackage5, 5, TMP_TABLE)
+        addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 2, 2, testPackage6, 6, TMP_TABLE)
+        addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 4, 2, testPackage7, 7, TMP_TABLE)
+        addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 2, 3, testPackage8, 8, TMP_TABLE)
+        addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 4, 3, testPackage9, 9, TMP_TABLE)
 
         // Dest hotseat icons
-        modelHelper.addItem(SHORTCUT, 1, HOTSEAT, 0, 0, testPackage2)
+        addItem(ITEM_TYPE_SHORTCUT, 1, CONTAINER_HOTSEAT, 0, 0, testPackage2)
         // Dest grid icons
-        modelHelper.addItem(APP_ICON, 0, DESKTOP, 2, 2, testPackage10)
+        addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 2, 2, testPackage10)
 
         idp.numDatabaseHotseatIcons = 4
         idp.numColumns = 4
@@ -126,8 +134,7 @@
         val srcReader = DbReader(db, TMP_TABLE, context, validPackages)
         val destReader = DbReader(db, TABLE_NAME, context, validPackages)
         GridSizeMigrationUtil.migrate(
-            context,
-            db,
+            dbHelper,
             srcReader,
             destReader,
             idp.numDatabaseHotseatIcons,
@@ -138,12 +145,13 @@
 
         // Check hotseat items
         var c =
-            context.contentResolver.query(
-                CONTENT_URI,
+            db.query(
+                TABLE_NAME,
                 arrayOf(SCREEN, INTENT),
                 "container=$CONTAINER_HOTSEAT",
                 null,
                 SCREEN,
+                null,
                 null
             )
                 ?: throw IllegalStateException()
@@ -168,12 +176,13 @@
 
         // Check workspace items
         c =
-            context.contentResolver.query(
-                CONTENT_URI,
+            db.query(
+                TABLE_NAME,
                 arrayOf(CELLX, CELLY, INTENT),
                 "container=$CONTAINER_DESKTOP",
                 null,
                 null,
+                null,
                 null
             )
                 ?: throw IllegalStateException()
@@ -209,30 +218,30 @@
     fun testMigrationBackAndForth() {
         // Hotseat items in grid A
         // 1 2 _ 3 4
-        modelHelper.addItem(APP_ICON, 0, HOTSEAT, 0, 0, testPackage1, 1, TMP_CONTENT_URI)
-        modelHelper.addItem(SHORTCUT, 1, HOTSEAT, 0, 0, testPackage2, 2, TMP_CONTENT_URI)
-        modelHelper.addItem(SHORTCUT, 3, HOTSEAT, 0, 0, testPackage3, 3, TMP_CONTENT_URI)
-        modelHelper.addItem(APP_ICON, 4, HOTSEAT, 0, 0, testPackage4, 4, TMP_CONTENT_URI)
+        addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_HOTSEAT, 0, 0, testPackage1, 1, TMP_TABLE)
+        addItem(ITEM_TYPE_SHORTCUT, 1, CONTAINER_HOTSEAT, 0, 0, testPackage2, 2, TMP_TABLE)
+        addItem(ITEM_TYPE_SHORTCUT, 3, CONTAINER_HOTSEAT, 0, 0, testPackage3, 3, TMP_TABLE)
+        addItem(ITEM_TYPE_APPLICATION, 4, CONTAINER_HOTSEAT, 0, 0, testPackage4, 4, TMP_TABLE)
         // Workspace items in grid A
         // _ _ _ _ _
         // _ _ _ _ 5
         // _ _ 6 _ 7
         // _ _ 8 _ _
         // _ _ _ _ _
-        modelHelper.addItem(APP_ICON, 0, DESKTOP, 4, 1, testPackage5, 5, TMP_CONTENT_URI)
-        modelHelper.addItem(APP_ICON, 0, DESKTOP, 2, 2, testPackage6, 6, TMP_CONTENT_URI)
-        modelHelper.addItem(APP_ICON, 0, DESKTOP, 4, 2, testPackage7, 7, TMP_CONTENT_URI)
-        modelHelper.addItem(APP_ICON, 0, DESKTOP, 2, 3, testPackage8, 8, TMP_CONTENT_URI)
+        addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 4, 1, testPackage5, 5, TMP_TABLE)
+        addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 2, 2, testPackage6, 6, TMP_TABLE)
+        addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 4, 2, testPackage7, 7, TMP_TABLE)
+        addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 2, 3, testPackage8, 8, TMP_TABLE)
 
         // Hotseat items in grid B
         // 2 _ _ _
-        modelHelper.addItem(SHORTCUT, 0, HOTSEAT, 0, 0, testPackage2)
+        addItem(ITEM_TYPE_SHORTCUT, 0, CONTAINER_HOTSEAT, 0, 0, testPackage2)
         // Workspace items in grid B
         // _ _ _ _
         // _ _ _ 10
         // _ _ _ _
         // _ _ _ _
-        modelHelper.addItem(APP_ICON, 0, DESKTOP, 1, 3, testPackage10)
+        addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 1, 3, testPackage10)
 
         idp.numDatabaseHotseatIcons = 4
         idp.numColumns = 4
@@ -241,8 +250,7 @@
         val readerGridB = DbReader(db, TABLE_NAME, context, validPackages)
         // migrate from A -> B
         GridSizeMigrationUtil.migrate(
-            context,
-            db,
+            dbHelper,
             readerGridA,
             readerGridB,
             idp.numDatabaseHotseatIcons,
@@ -253,12 +261,13 @@
 
         // Check hotseat items in grid B
         var c =
-            context.contentResolver.query(
-                CONTENT_URI,
+            db.query(
+                TABLE_NAME,
                 arrayOf(SCREEN, INTENT),
                 "container=$CONTAINER_HOTSEAT",
                 null,
                 SCREEN,
+                null,
                 null
             )
                 ?: throw IllegalStateException()
@@ -272,12 +281,13 @@
 
         // Check workspace items in grid B
         c =
-            context.contentResolver.query(
-                CONTENT_URI,
+            db.query(
+                TABLE_NAME,
                 arrayOf(SCREEN, CELLX, CELLY, INTENT),
                 "container=$CONTAINER_DESKTOP",
                 null,
                 null,
+                null,
                 null
             )
                 ?: throw IllegalStateException()
@@ -294,12 +304,11 @@
         assertThat(locMap[testPackage8]).isEqualTo(Triple(0, 3, 1))
 
         // add item in B
-        modelHelper.addItem(APP_ICON, 0, DESKTOP, 0, 2, testPackage9)
+        addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 0, 2, testPackage9)
 
         // migrate from B -> A
         GridSizeMigrationUtil.migrate(
-            context,
-            db,
+            dbHelper,
             readerGridB,
             readerGridA,
             5,
@@ -309,12 +318,13 @@
         )
         // Check hotseat items in grid A
         c =
-            context.contentResolver.query(
-                TMP_CONTENT_URI,
+            db.query(
+                TMP_TABLE,
                 arrayOf(SCREEN, INTENT),
                 "container=$CONTAINER_HOTSEAT",
                 null,
                 SCREEN,
+                null,
                 null
             )
                 ?: throw IllegalStateException()
@@ -328,12 +338,13 @@
 
         // Check workspace items in grid A
         c =
-            context.contentResolver.query(
-                TMP_CONTENT_URI,
+            db.query(
+                TMP_TABLE,
                 arrayOf(SCREEN, CELLX, CELLY, INTENT),
                 "container=$CONTAINER_DESKTOP",
                 null,
                 null,
+                null,
                 null
             )
                 ?: throw IllegalStateException()
@@ -354,12 +365,11 @@
         assertThat(locMap[testPackage9]).isEqualTo(Triple(0, 0, 2))
 
         // remove item from B
-        modelHelper.deleteItem(7, TMP_TABLE)
+        db.delete(TMP_TABLE, "$_ID=7", null)
 
         // migrate from A -> B
         GridSizeMigrationUtil.migrate(
-            context,
-            db,
+            dbHelper,
             readerGridA,
             readerGridB,
             idp.numDatabaseHotseatIcons,
@@ -370,12 +380,13 @@
 
         // Check hotseat items in grid B
         c =
-            context.contentResolver.query(
-                CONTENT_URI,
+            db.query(
+                TABLE_NAME,
                 arrayOf(SCREEN, INTENT),
                 "container=$CONTAINER_HOTSEAT",
                 null,
                 SCREEN,
+                null,
                 null
             )
                 ?: throw IllegalStateException()
@@ -389,12 +400,13 @@
 
         // Check workspace items in grid B
         c =
-            context.contentResolver.query(
-                CONTENT_URI,
+            db.query(
+                TABLE_NAME,
                 arrayOf(SCREEN, CELLX, CELLY, INTENT),
                 "container=$CONTAINER_DESKTOP",
                 null,
                 null,
+                null,
                 null
             )
                 ?: throw IllegalStateException()
@@ -443,10 +455,28 @@
     fun migrateToLargerHotseat() {
         val srcHotseatItems =
             intArrayOf(
-                modelHelper.addItem(APP_ICON, 0, HOTSEAT, 0, 0, testPackage1, 1, TMP_CONTENT_URI),
-                modelHelper.addItem(SHORTCUT, 1, HOTSEAT, 0, 0, testPackage2, 2, TMP_CONTENT_URI),
-                modelHelper.addItem(APP_ICON, 2, HOTSEAT, 0, 0, testPackage3, 3, TMP_CONTENT_URI),
-                modelHelper.addItem(SHORTCUT, 3, HOTSEAT, 0, 0, testPackage4, 4, TMP_CONTENT_URI)
+                addItem(
+                    ITEM_TYPE_APPLICATION,
+                    0,
+                    CONTAINER_HOTSEAT,
+                    0,
+                    0,
+                    testPackage1,
+                    1,
+                    TMP_TABLE
+                ),
+                addItem(ITEM_TYPE_SHORTCUT, 1, CONTAINER_HOTSEAT, 0, 0, testPackage2, 2, TMP_TABLE),
+                addItem(
+                    ITEM_TYPE_APPLICATION,
+                    2,
+                    CONTAINER_HOTSEAT,
+                    0,
+                    0,
+                    testPackage3,
+                    3,
+                    TMP_TABLE
+                ),
+                addItem(ITEM_TYPE_SHORTCUT, 3, CONTAINER_HOTSEAT, 0, 0, testPackage4, 4, TMP_TABLE)
             )
         val numSrcDatabaseHotseatIcons = srcHotseatItems.size
         idp.numDatabaseHotseatIcons = 6
@@ -455,8 +485,7 @@
         val srcReader = DbReader(db, TMP_TABLE, context, validPackages)
         val destReader = DbReader(db, TABLE_NAME, context, validPackages)
         GridSizeMigrationUtil.migrate(
-            context,
-            db,
+            dbHelper,
             srcReader,
             destReader,
             idp.numDatabaseHotseatIcons,
@@ -467,12 +496,13 @@
 
         // Check hotseat items
         val c =
-            context.contentResolver.query(
-                CONTENT_URI,
+            db.query(
+                TABLE_NAME,
                 arrayOf(SCREEN, INTENT),
                 "container=$CONTAINER_HOTSEAT",
                 null,
                 SCREEN,
+                null,
                 null
             )
                 ?: throw IllegalStateException()
@@ -501,11 +531,11 @@
 
     @Test
     fun migrateFromLargerHotseat() {
-        modelHelper.addItem(APP_ICON, 0, HOTSEAT, 0, 0, testPackage1, 1, TMP_CONTENT_URI)
-        modelHelper.addItem(SHORTCUT, 2, HOTSEAT, 0, 0, testPackage2, 2, TMP_CONTENT_URI)
-        modelHelper.addItem(APP_ICON, 3, HOTSEAT, 0, 0, testPackage3, 3, TMP_CONTENT_URI)
-        modelHelper.addItem(SHORTCUT, 4, HOTSEAT, 0, 0, testPackage4, 4, TMP_CONTENT_URI)
-        modelHelper.addItem(APP_ICON, 5, HOTSEAT, 0, 0, testPackage5, 5, TMP_CONTENT_URI)
+        addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_HOTSEAT, 0, 0, testPackage1, 1, TMP_TABLE)
+        addItem(ITEM_TYPE_SHORTCUT, 2, CONTAINER_HOTSEAT, 0, 0, testPackage2, 2, TMP_TABLE)
+        addItem(ITEM_TYPE_APPLICATION, 3, CONTAINER_HOTSEAT, 0, 0, testPackage3, 3, TMP_TABLE)
+        addItem(ITEM_TYPE_SHORTCUT, 4, CONTAINER_HOTSEAT, 0, 0, testPackage4, 4, TMP_TABLE)
+        addItem(ITEM_TYPE_APPLICATION, 5, CONTAINER_HOTSEAT, 0, 0, testPackage5, 5, TMP_TABLE)
 
         idp.numDatabaseHotseatIcons = 4
         idp.numColumns = 4
@@ -513,8 +543,7 @@
         val srcReader = DbReader(db, TMP_TABLE, context, validPackages)
         val destReader = DbReader(db, TABLE_NAME, context, validPackages)
         GridSizeMigrationUtil.migrate(
-            context,
-            db,
+            dbHelper,
             srcReader,
             destReader,
             idp.numDatabaseHotseatIcons,
@@ -525,12 +554,13 @@
 
         // Check hotseat items
         val c =
-            context.contentResolver.query(
-                CONTENT_URI,
+            db.query(
+                TABLE_NAME,
                 arrayOf(SCREEN, INTENT),
                 "container=$CONTAINER_HOTSEAT",
                 null,
                 SCREEN,
+                null,
                 null
             )
                 ?: throw IllegalStateException()
@@ -568,11 +598,11 @@
         enableNewMigrationLogic("4,4")
 
         // Setup src grid
-        modelHelper.addItem(APP_ICON, 0, DESKTOP, 2, 2, testPackage1, 5, TMP_CONTENT_URI)
-        modelHelper.addItem(APP_ICON, 0, DESKTOP, 2, 3, testPackage2, 6, TMP_CONTENT_URI)
-        modelHelper.addItem(APP_ICON, 1, DESKTOP, 3, 1, testPackage3, 7, TMP_CONTENT_URI)
-        modelHelper.addItem(APP_ICON, 1, DESKTOP, 3, 2, testPackage4, 8, TMP_CONTENT_URI)
-        modelHelper.addItem(APP_ICON, 2, DESKTOP, 3, 3, testPackage5, 9, TMP_CONTENT_URI)
+        addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 2, 2, testPackage1, 5, TMP_TABLE)
+        addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 2, 3, testPackage2, 6, TMP_TABLE)
+        addItem(ITEM_TYPE_APPLICATION, 1, CONTAINER_DESKTOP, 3, 1, testPackage3, 7, TMP_TABLE)
+        addItem(ITEM_TYPE_APPLICATION, 1, CONTAINER_DESKTOP, 3, 2, testPackage4, 8, TMP_TABLE)
+        addItem(ITEM_TYPE_APPLICATION, 2, CONTAINER_DESKTOP, 3, 3, testPackage5, 9, TMP_TABLE)
 
         idp.numDatabaseHotseatIcons = 4
         idp.numColumns = 6
@@ -581,8 +611,7 @@
         val srcReader = DbReader(db, TMP_TABLE, context, validPackages)
         val destReader = DbReader(db, TABLE_NAME, context, validPackages)
         GridSizeMigrationUtil.migrate(
-            context,
-            db,
+            dbHelper,
             srcReader,
             destReader,
             idp.numDatabaseHotseatIcons,
@@ -593,12 +622,13 @@
 
         // Get workspace items
         val c =
-            context.contentResolver.query(
-                CONTENT_URI,
+            db.query(
+                TABLE_NAME,
                 arrayOf(INTENT, SCREEN),
                 "container=$CONTAINER_DESKTOP",
                 null,
                 null,
+                null,
                 null
             )
                 ?: throw IllegalStateException()
@@ -630,11 +660,11 @@
         enableNewMigrationLogic("2,2")
 
         // Setup src grid
-        modelHelper.addItem(APP_ICON, 0, DESKTOP, 0, 1, testPackage1, 5, TMP_CONTENT_URI)
-        modelHelper.addItem(APP_ICON, 0, DESKTOP, 1, 1, testPackage2, 6, TMP_CONTENT_URI)
-        modelHelper.addItem(APP_ICON, 1, DESKTOP, 0, 0, testPackage3, 7, TMP_CONTENT_URI)
-        modelHelper.addItem(APP_ICON, 1, DESKTOP, 1, 0, testPackage4, 8, TMP_CONTENT_URI)
-        modelHelper.addItem(APP_ICON, 2, DESKTOP, 0, 0, testPackage5, 9, TMP_CONTENT_URI)
+        addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 0, 1, testPackage1, 5, TMP_TABLE)
+        addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 1, 1, testPackage2, 6, TMP_TABLE)
+        addItem(ITEM_TYPE_APPLICATION, 1, CONTAINER_DESKTOP, 0, 0, testPackage3, 7, TMP_TABLE)
+        addItem(ITEM_TYPE_APPLICATION, 1, CONTAINER_DESKTOP, 1, 0, testPackage4, 8, TMP_TABLE)
+        addItem(ITEM_TYPE_APPLICATION, 2, CONTAINER_DESKTOP, 0, 0, testPackage5, 9, TMP_TABLE)
 
         idp.numDatabaseHotseatIcons = 4
         idp.numColumns = 5
@@ -642,8 +672,7 @@
         val srcReader = DbReader(db, TMP_TABLE, context, validPackages)
         val destReader = DbReader(db, TABLE_NAME, context, validPackages)
         GridSizeMigrationUtil.migrate(
-            context,
-            db,
+            dbHelper,
             srcReader,
             destReader,
             idp.numDatabaseHotseatIcons,
@@ -654,12 +683,13 @@
 
         // Get workspace items
         val c =
-            context.contentResolver.query(
-                CONTENT_URI,
+            db.query(
+                TABLE_NAME,
                 arrayOf(INTENT, SCREEN),
                 "container=$CONTAINER_DESKTOP",
                 null,
                 null,
+                null,
                 null
             )
                 ?: throw IllegalStateException()
@@ -691,11 +721,11 @@
         enableNewMigrationLogic("5,5")
 
         // Setup src grid
-        modelHelper.addItem(APP_ICON, 0, DESKTOP, 0, 1, testPackage1, 5, TMP_CONTENT_URI)
-        modelHelper.addItem(APP_ICON, 0, DESKTOP, 1, 1, testPackage2, 6, TMP_CONTENT_URI)
-        modelHelper.addItem(APP_ICON, 1, DESKTOP, 0, 0, testPackage3, 7, TMP_CONTENT_URI)
-        modelHelper.addItem(APP_ICON, 1, DESKTOP, 1, 0, testPackage4, 8, TMP_CONTENT_URI)
-        modelHelper.addItem(APP_ICON, 2, DESKTOP, 0, 0, testPackage5, 9, TMP_CONTENT_URI)
+        addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 0, 1, testPackage1, 5, TMP_TABLE)
+        addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 1, 1, testPackage2, 6, TMP_TABLE)
+        addItem(ITEM_TYPE_APPLICATION, 1, CONTAINER_DESKTOP, 0, 0, testPackage3, 7, TMP_TABLE)
+        addItem(ITEM_TYPE_APPLICATION, 1, CONTAINER_DESKTOP, 1, 0, testPackage4, 8, TMP_TABLE)
+        addItem(ITEM_TYPE_APPLICATION, 2, CONTAINER_DESKTOP, 0, 0, testPackage5, 9, TMP_TABLE)
 
         idp.numDatabaseHotseatIcons = 4
         idp.numColumns = 4
@@ -703,8 +733,7 @@
         val srcReader = DbReader(db, TMP_TABLE, context, validPackages)
         val destReader = DbReader(db, TABLE_NAME, context, validPackages)
         GridSizeMigrationUtil.migrate(
-            context,
-            db,
+            dbHelper,
             srcReader,
             destReader,
             idp.numDatabaseHotseatIcons,
@@ -715,12 +744,13 @@
 
         // Get workspace items
         val c =
-            context.contentResolver.query(
-                CONTENT_URI,
+            db.query(
+                TABLE_NAME,
                 arrayOf(INTENT, SCREEN),
                 "container=$CONTAINER_DESKTOP",
                 null,
                 null,
+                null,
                 null
             )
                 ?: throw IllegalStateException()
@@ -747,4 +777,48 @@
     private fun enableNewMigrationLogic(srcGridSize: String) {
         LauncherPrefs.get(context).putSync(WORKSPACE_SIZE.to(srcGridSize))
     }
+
+    private fun addItem(
+        type: Int,
+        screen: Int,
+        container: Int,
+        x: Int,
+        y: Int,
+        packageName: String?
+    ): Int {
+        return addItem(
+            type,
+            screen,
+            container,
+            x,
+            y,
+            packageName,
+            dbHelper.generateNewItemId(),
+            TABLE_NAME
+        )
+    }
+
+    private fun addItem(
+        type: Int,
+        screen: Int,
+        container: Int,
+        x: Int,
+        y: Int,
+        packageName: String?,
+        id: Int,
+        tableName: String
+    ): Int {
+        val values = ContentValues()
+        values.put(_ID, id)
+        values.put(CONTAINER, container)
+        values.put(SCREEN, screen)
+        values.put(CELLX, x)
+        values.put(CELLY, y)
+        values.put(SPANX, 1)
+        values.put(SPANY, 1)
+        values.put(ITEM_TYPE, type)
+        values.put(INTENT, Intent(Intent.ACTION_MAIN).setPackage(packageName).toUri(0))
+        db.insert(tableName, null, values)
+        return id
+    }
 }
diff --git a/tests/src/com/android/launcher3/model/LoaderCursorTest.java b/tests/src/com/android/launcher3/model/LoaderCursorTest.java
index d192be4..78812c0 100644
--- a/tests/src/com/android/launcher3/model/LoaderCursorTest.java
+++ b/tests/src/com/android/launcher3/model/LoaderCursorTest.java
@@ -59,7 +59,6 @@
 
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.util.Executors;
@@ -102,7 +101,7 @@
         });
 
         UserManagerState ums = new UserManagerState();
-        mLoaderCursor = new LoaderCursor(mCursor, Favorites.CONTENT_URI, mApp, ums);
+        mLoaderCursor = new LoaderCursor(mCursor, mApp, ums);
         ums.allUsers.put(0, Process.myUserHandle());
     }
 
diff --git a/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt b/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt
index 13db6c7..a81413e 100644
--- a/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt
+++ b/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt
@@ -58,6 +58,7 @@
                     "\tmInsets.right: 0.0px (0.0dp)\n" +
                     "\tmInsets.bottom: 126.0px (48.0dp)\n" +
                     "\taspectRatio:2.2222223\n" +
+                    "\tisResponsiveGrid:false\n" +
                     "\tisScalableGrid:false\n" +
                     "\tinv.numRows: 5\n" +
                     "\tinv.numColumns: 5\n" +
@@ -193,6 +194,7 @@
                     "\tmInsets.right: 0.0px (0.0dp)\n" +
                     "\tmInsets.bottom: 63.0px (24.0dp)\n" +
                     "\taspectRatio:2.2222223\n" +
+                    "\tisResponsiveGrid:false\n" +
                     "\tisScalableGrid:false\n" +
                     "\tinv.numRows: 5\n" +
                     "\tinv.numColumns: 5\n" +
@@ -328,6 +330,7 @@
                     "\tmInsets.right: 126.0px (48.0dp)\n" +
                     "\tmInsets.bottom: 0.0px (0.0dp)\n" +
                     "\taspectRatio:2.2222223\n" +
+                    "\tisResponsiveGrid:false\n" +
                     "\tisScalableGrid:false\n" +
                     "\tinv.numRows: 5\n" +
                     "\tinv.numColumns: 5\n" +
@@ -463,6 +466,7 @@
                     "\tmInsets.right: 0.0px (0.0dp)\n" +
                     "\tmInsets.bottom: 63.0px (24.0dp)\n" +
                     "\taspectRatio:2.2222223\n" +
+                    "\tisResponsiveGrid:false\n" +
                     "\tisScalableGrid:false\n" +
                     "\tinv.numRows: 5\n" +
                     "\tinv.numColumns: 5\n" +
@@ -599,6 +603,7 @@
                     "\tmInsets.right: 0.0px (0.0dp)\n" +
                     "\tmInsets.bottom: 0.0px (0.0dp)\n" +
                     "\taspectRatio:1.6\n" +
+                    "\tisResponsiveGrid:false\n" +
                     "\tisScalableGrid:true\n" +
                     "\tinv.numRows: 5\n" +
                     "\tinv.numColumns: 6\n" +
@@ -735,6 +740,7 @@
                     "\tmInsets.right: 0.0px (0.0dp)\n" +
                     "\tmInsets.bottom: 0.0px (0.0dp)\n" +
                     "\taspectRatio:1.6\n" +
+                    "\tisResponsiveGrid:false\n" +
                     "\tisScalableGrid:true\n" +
                     "\tinv.numRows: 5\n" +
                     "\tinv.numColumns: 6\n" +
@@ -871,6 +877,7 @@
                     "\tmInsets.right: 0.0px (0.0dp)\n" +
                     "\tmInsets.bottom: 0.0px (0.0dp)\n" +
                     "\taspectRatio:1.6\n" +
+                    "\tisResponsiveGrid:false\n" +
                     "\tisScalableGrid:true\n" +
                     "\tinv.numRows: 5\n" +
                     "\tinv.numColumns: 6\n" +
@@ -1007,6 +1014,7 @@
                     "\tmInsets.right: 0.0px (0.0dp)\n" +
                     "\tmInsets.bottom: 0.0px (0.0dp)\n" +
                     "\taspectRatio:1.6\n" +
+                    "\tisResponsiveGrid:false\n" +
                     "\tisScalableGrid:true\n" +
                     "\tinv.numRows: 5\n" +
                     "\tinv.numColumns: 6\n" +
@@ -1148,6 +1156,7 @@
                     "\tmInsets.right: 0.0px (0.0dp)\n" +
                     "\tmInsets.bottom: 0.0px (0.0dp)\n" +
                     "\taspectRatio:1.2\n" +
+                    "\tisResponsiveGrid:false\n" +
                     "\tisScalableGrid:false\n" +
                     "\tinv.numRows: 4\n" +
                     "\tinv.numColumns: 4\n" +
@@ -1288,6 +1297,7 @@
                     "\tmInsets.right: 0.0px (0.0dp)\n" +
                     "\tmInsets.bottom: 0.0px (0.0dp)\n" +
                     "\taspectRatio:1.2\n" +
+                    "\tisResponsiveGrid:false\n" +
                     "\tisScalableGrid:false\n" +
                     "\tinv.numRows: 4\n" +
                     "\tinv.numColumns: 4\n" +
@@ -1428,6 +1438,7 @@
                     "\tmInsets.right: 0.0px (0.0dp)\n" +
                     "\tmInsets.bottom: 0.0px (0.0dp)\n" +
                     "\taspectRatio:1.2\n" +
+                    "\tisResponsiveGrid:false\n" +
                     "\tisScalableGrid:false\n" +
                     "\tinv.numRows: 4\n" +
                     "\tinv.numColumns: 4\n" +
@@ -1564,6 +1575,7 @@
                     "\tmInsets.right: 0.0px (0.0dp)\n" +
                     "\tmInsets.bottom: 0.0px (0.0dp)\n" +
                     "\taspectRatio:1.2\n" +
+                    "\tisResponsiveGrid:false\n" +
                     "\tisScalableGrid:false\n" +
                     "\tinv.numRows: 4\n" +
                     "\tinv.numColumns: 4\n" +
diff --git a/tests/src/com/android/launcher3/provider/LauncherDbUtilsTest.java b/tests/src/com/android/launcher3/provider/LauncherDbUtilsTest.java
index 2b6f9ff..d1befd0 100644
--- a/tests/src/com/android/launcher3/provider/LauncherDbUtilsTest.java
+++ b/tests/src/com/android/launcher3/provider/LauncherDbUtilsTest.java
@@ -45,6 +45,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.model.DatabaseHelper;
 import com.android.launcher3.model.DbDowngradeHelper;
+import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.settings.SettingsActivity;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.util.IOUtils;
@@ -128,7 +129,6 @@
         assertEquals(1, getFavoriteDataCount(db));
         ShortcutInfo info = mInfoArgumentCaptor.getValue();
         assertNotNull(info);
-        assertEquals("Hello", info.getTitle());
         try (Cursor c = db.query(Favorites.TABLE_NAME, null, null, null, null, null, null)) {
             c.moveToNext();
             assertEquals(Favorites.ITEM_TYPE_DEEP_SHORTCUT, c.getInt(c.getColumnIndex(ITEM_TYPE)));
@@ -165,12 +165,11 @@
     private class MyDatabaseHelper extends DatabaseHelper {
 
         MyDatabaseHelper() {
-            super(mContext, null, false);
+            super(mContext, null, UserCache.INSTANCE.get(mContext)::getSerialNumberForUser,
+                    () -> { });
         }
 
         @Override
         protected void handleOneTimeDataUpgrade(SQLiteDatabase db) { }
-
-        protected void onEmptyDbCreated() { }
     }
 }
diff --git a/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java b/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
index 67de1f5..73bb586 100644
--- a/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
+++ b/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
@@ -45,8 +45,11 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.model.DatabaseHelper;
+import com.android.launcher3.model.ModelDbController;
+import com.android.launcher3.util.LauncherModelHelper;
 
+import org.junit.After;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -61,15 +64,29 @@
 
     private final UserHandle mWorkUser = UserHandle.getUserHandleForUid(PER_USER_RANGE);
 
+    private LauncherModelHelper mModelHelper;
+    private Context mContext;
+
+    @Before
+    public void setup() {
+        mModelHelper = new LauncherModelHelper();
+        mContext = mModelHelper.sandboxContext;
+    }
+
+    @After
+    public void teardown() {
+        mModelHelper.destroy();
+    }
+
     @Test
     public void testGetProfileId() throws Exception {
-        SQLiteDatabase db = new MyDatabaseHelper(23).getWritableDatabase();
+        SQLiteDatabase db = new MyModelDbController(23).getDb();
         assertEquals(23, new RestoreDbTask().getDefaultProfileId(db));
     }
 
     @Test
     public void testMigrateProfileId() throws Exception {
-        SQLiteDatabase db = new MyDatabaseHelper(42).getWritableDatabase();
+        SQLiteDatabase db = new MyModelDbController(42).getDb();
         // Add some mock data
         for (int i = 0; i < 5; i++) {
             ContentValues values = new ContentValues();
@@ -89,7 +106,7 @@
 
     @Test
     public void testChangeDefaultColumn() throws Exception {
-        SQLiteDatabase db = new MyDatabaseHelper(42).getWritableDatabase();
+        SQLiteDatabase db = new MyModelDbController(42).getDb();
         // Add some mock data
         for (int i = 0; i < 5; i++) {
             ContentValues values = new ContentValues();
@@ -112,28 +129,27 @@
 
     @Test
     public void testSanitizeDB_bothProfiles() throws Exception {
-        Context context = getInstrumentation().getTargetContext();
         UserHandle myUser = myUserHandle();
-        long myProfileId = context.getSystemService(UserManager.class)
+        long myProfileId = mContext.getSystemService(UserManager.class)
                 .getSerialNumberForUser(myUser);
         long myProfileId_old = myProfileId + 1;
         long workProfileId = myProfileId + 2;
         long workProfileId_old = myProfileId + 3;
 
-        MyDatabaseHelper helper = new MyDatabaseHelper(myProfileId);
-        SQLiteDatabase db = helper.getWritableDatabase();
-        BackupManager bm = spy(new BackupManager(context));
+        MyModelDbController controller = new MyModelDbController(myProfileId);
+        SQLiteDatabase db = controller.getDb();
+        BackupManager bm = spy(new BackupManager(mContext));
         doReturn(myUserHandle()).when(bm).getUserForAncestralSerialNumber(eq(myProfileId_old));
         doReturn(mWorkUser).when(bm).getUserForAncestralSerialNumber(eq(workProfileId_old));
-        helper.users.put(workProfileId, mWorkUser);
+        controller.users.put(workProfileId, mWorkUser);
 
-        addIconsBulk(helper, 10, 1, myProfileId_old);
-        addIconsBulk(helper, 6, 2, workProfileId_old);
+        addIconsBulk(controller, 10, 1, myProfileId_old);
+        addIconsBulk(controller, 6, 2, workProfileId_old);
         assertEquals(10, getItemCountForProfile(db, myProfileId_old));
         assertEquals(6, getItemCountForProfile(db, workProfileId_old));
 
         RestoreDbTask task = new RestoreDbTask();
-        task.sanitizeDB(context, helper, helper.getWritableDatabase(), bm);
+        task.sanitizeDB(mContext, controller, controller.getDb(), bm);
 
         // All the data has been migrated to the new user ids
         assertEquals(0, getItemCountForProfile(db, myProfileId_old));
@@ -144,27 +160,26 @@
 
     @Test
     public void testSanitizeDB_workItemsRemoved() throws Exception {
-        Context context = getInstrumentation().getTargetContext();
         UserHandle myUser = myUserHandle();
-        long myProfileId = context.getSystemService(UserManager.class)
+        long myProfileId = mContext.getSystemService(UserManager.class)
                 .getSerialNumberForUser(myUser);
         long myProfileId_old = myProfileId + 1;
         long workProfileId_old = myProfileId + 3;
 
-        MyDatabaseHelper helper = new MyDatabaseHelper(myProfileId);
-        SQLiteDatabase db = helper.getWritableDatabase();
-        BackupManager bm = spy(new BackupManager(context));
+        MyModelDbController controller = new MyModelDbController(myProfileId);
+        SQLiteDatabase db = controller.getDb();
+        BackupManager bm = spy(new BackupManager(mContext));
         doReturn(myUserHandle()).when(bm).getUserForAncestralSerialNumber(eq(myProfileId_old));
         // Work profile is not migrated
         doReturn(null).when(bm).getUserForAncestralSerialNumber(eq(workProfileId_old));
 
-        addIconsBulk(helper, 10, 1, myProfileId_old);
-        addIconsBulk(helper, 6, 2, workProfileId_old);
+        addIconsBulk(controller, 10, 1, myProfileId_old);
+        addIconsBulk(controller, 6, 2, workProfileId_old);
         assertEquals(10, getItemCountForProfile(db, myProfileId_old));
         assertEquals(6, getItemCountForProfile(db, workProfileId_old));
 
         RestoreDbTask task = new RestoreDbTask();
-        task.sanitizeDB(context, helper, helper.getWritableDatabase(), bm);
+        task.sanitizeDB(mContext, controller, controller.getDb(), bm);
 
         // All the data has been migrated to the new user ids
         assertEquals(0, getItemCountForProfile(db, myProfileId_old));
@@ -173,12 +188,13 @@
         assertEquals(10, getCount(db, "select * from favorites"));
     }
 
-    private void addIconsBulk(DatabaseHelper helper, int count, int screen, long profileId) {
-        int columns = LauncherAppState.getIDP(getInstrumentation().getTargetContext()).numColumns;
+    private void addIconsBulk(MyModelDbController controller,
+            int count, int screen, long profileId) {
+        int columns = LauncherAppState.getIDP(mContext).numColumns;
         String packageName = getInstrumentation().getContext().getPackageName();
         for (int i = 0; i < count; i++) {
             ContentValues values = new ContentValues();
-            values.put(LauncherSettings.Favorites._ID, helper.generateNewItemId());
+            values.put(LauncherSettings.Favorites._ID, controller.generateNewItemId());
             values.put(LauncherSettings.Favorites.CONTAINER, CONTAINER_DESKTOP);
             values.put(LauncherSettings.Favorites.SCREEN, screen);
             values.put(LauncherSettings.Favorites.CELLX, i % columns);
@@ -189,11 +205,11 @@
             values.put(LauncherSettings.Favorites.ITEM_TYPE, ITEM_TYPE_APPLICATION);
             values.put(LauncherSettings.Favorites.INTENT,
                     new Intent(Intent.ACTION_MAIN).setPackage(packageName).toUri(0));
-            helper.getWritableDatabase().insert(TABLE_NAME, null, values);
+
+            controller.insert(TABLE_NAME, values);
         }
     }
 
-
     @Test
     public void testRemoveScreenIdGaps_firstScreenEmpty() {
         runRemoveScreenIdGapsTest(
@@ -216,7 +232,7 @@
     }
 
     private void runRemoveScreenIdGapsTest(int[] screenIds, int[] expectedScreenIds) {
-        SQLiteDatabase db = new MyDatabaseHelper(42).getWritableDatabase();
+        SQLiteDatabase db = new MyModelDbController(42).getDb();
         // Add some mock data
         for (int i = 0; i < screenIds.length; i++) {
             ContentValues values = new ContentValues();
@@ -254,13 +270,12 @@
         }
     }
 
-    private class MyDatabaseHelper extends DatabaseHelper {
+    private class MyModelDbController extends ModelDbController {
 
-        public final LongSparseArray<UserHandle> users;
+        public final LongSparseArray<UserHandle> users = new LongSparseArray<>();
 
-        MyDatabaseHelper(long profileId) {
-            super(getInstrumentation().getTargetContext(), null, false);
-            users = new LongSparseArray<>();
+        MyModelDbController(long profileId) {
+            super(mContext);
             users.put(profileId, myUserHandle());
         }
 
@@ -269,10 +284,5 @@
             int index = users.indexOfValue(user);
             return index >= 0 ? users.keyAt(index) : -1;
         }
-
-        @Override
-        protected void handleOneTimeDataUpgrade(SQLiteDatabase db) { }
-
-        protected void onEmptyDbCreated() { }
     }
 }
diff --git a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
index 7312dab..e9a2b0f 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
@@ -36,7 +36,6 @@
 import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.ui.TestViewHelpers;
 import com.android.launcher3.util.Wait;
-import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
 import com.android.launcher3.util.rule.ShellCommandRule;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
 
@@ -76,7 +75,6 @@
 
     @Test
     @PortraitLandscape
-    @ScreenRecord // b/206481237
     public void testConfigCancelled() throws Throwable {
         runTest(false);
     }
diff --git a/tests/src/com/android/launcher3/util/LauncherModelHelper.java b/tests/src/com/android/launcher3/util/LauncherModelHelper.java
index bf31e39..976afcd 100644
--- a/tests/src/com/android/launcher3/util/LauncherModelHelper.java
+++ b/tests/src/com/android/launcher3/util/LauncherModelHelper.java
@@ -113,7 +113,7 @@
     private final HashMap<Class, HashMap<String, Field>> mFieldCache = new HashMap<>();
     private final MockContentResolver mMockResolver = new MockContentResolver();
     public final TestLauncherProvider provider;
-    public final SanboxModelContext sandboxContext;
+    public final SandboxModelContext sandboxContext;
 
     public final long defaultProfileId;
 
@@ -128,7 +128,7 @@
         Settings.Global.getString(context.getContentResolver(), "test");
 
         provider = new TestLauncherProvider();
-        sandboxContext = new SanboxModelContext();
+        sandboxContext = new SandboxModelContext();
         defaultProfileId = UserCache.INSTANCE.get(sandboxContext)
                 .getSerialNumberForUser(Process.myUserHandle());
         setupProvider(LauncherProvider.AUTHORITY, provider);
@@ -363,12 +363,6 @@
         sandboxContext.getContentResolver().insert(contentUri, values);
     }
 
-    public void deleteItem(int itemId, @NonNull final String tableName) {
-        final Uri uri = Uri.parse("content://"
-                + LauncherProvider.AUTHORITY + "/" + tableName + "/" + itemId);
-        sandboxContext.getContentResolver().delete(uri, null, null);
-    }
-
     /**
      * Sets up a mock provider to load the provided layout by default, next time the layout loads
      */
@@ -426,7 +420,7 @@
         }
 
         public SQLiteDatabase getDb() {
-            return getModelDbController().getDatabaseHelper().getWritableDatabase();
+            return getModelDbController().getDb();
         }
     }
 
@@ -446,13 +440,13 @@
         return success;
     }
 
-    public class SanboxModelContext extends SandboxContext {
+    public class SandboxModelContext extends SandboxContext {
 
         private final ArrayMap<String, Object> mSpiedServices = new ArrayMap<>();
         private final PackageManager mPm;
         private final File mDbDir;
 
-        SanboxModelContext() {
+        SandboxModelContext() {
             super(ApplicationProvider.getApplicationContext(),
                     UserCache.INSTANCE, InstallSessionHelper.INSTANCE, LauncherPrefs.INSTANCE,
                     LauncherAppState.INSTANCE, InvariantDeviceProfile.INSTANCE,
@@ -463,7 +457,7 @@
             mDbDir = new File(getCacheDir(), UUID.randomUUID().toString());
         }
 
-        public SanboxModelContext allow(MainThreadInitializedObject object) {
+        public SandboxModelContext allow(MainThreadInitializedObject object) {
             mAllowedObjects.add(object);
             return this;
         }
diff --git a/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java b/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
index f52b82d..a59eff7 100644
--- a/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
+++ b/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
@@ -223,6 +223,9 @@
                     new Point(taskbarUnstashArea.x, taskbarUnstashArea.y), null);
 
             mLauncher.waitForSystemLauncherObject(TASKBAR_RES_ID);
+
+            mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_HOVER_EXIT,
+                    new Point(taskbarUnstashArea.x, taskbarUnstashArea.y), null);
         }
     }