Merge "Revert "Modify Taskbar code to accommodate bubble bar gesture to stash/unstash"" 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/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..84fb077 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -424,6 +424,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..a442849 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
@@ -176,9 +176,9 @@
                     deepShortcutCount,
                     mPopupDataProvider.getNotificationKeysForItem(item),
                     systemShortcuts);
-            icon.clearAccessibilityFocus();
         }
 
+        icon.clearAccessibilityFocus();
         container.addOnAttachStateChangeListener(
                 new PopupLiveUpdateHandler<BaseTaskbarContext>(context, container) {
                     @Override
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/BubbleBarBubble.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBubble.kt
index b1633e7..3cd5f75 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBubble.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBubble.kt
@@ -30,7 +30,5 @@
     val appName: String
 ) {
 
-    fun getKey(): String {
-        return info.key
-    }
+    val key: String = info.key
 }
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/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/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/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/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/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..f17f074 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);
             }
         }
     }
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 796cd62..134ef6c 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -92,6 +92,7 @@
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.RemoteAnimationTargets;
 import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
+import com.android.quickstep.TaskAnimationManager;
 import com.android.quickstep.TaskIconCache;
 import com.android.quickstep.TaskOverlayFactory;
 import com.android.quickstep.TaskThumbnailCache;
@@ -652,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() {
@@ -802,6 +803,14 @@
                     recentsView.addSideTaskLaunchCallback(callbackList);
                     return callbackList;
                 }
+                if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
+                    // If the recents transition is running (ie. in live tile mode), then the start
+                    // of a new task will merge into the existing transition and it currently will
+                    // not be run independently, so we need to rely on the onTaskAppeared() call
+                    // for the new task to trigger the side launch callback to flush this runnable
+                    // list (which is usually flushed when the app launch animation finishes)
+                    recentsView.addSideTaskLaunchCallback(opts.onEndCallback);
+                }
                 return opts.onEndCallback;
             } else {
                 notifyTaskLaunchFailed(TAG);
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 ce8d901..8136534 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -199,6 +199,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 58a447d..9e73453 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -75,7 +75,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..5fbd48c 100644
--- a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
+++ b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
@@ -16,8 +16,8 @@
 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;
@@ -52,7 +52,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");
@@ -92,12 +92,12 @@
             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(
+                Cursor cursor = controller.getDb().query(
                         Favorites.TABLE_NAME,
                         new String[] {Favorites.APPWIDGET_ID},
                         "appWidgetId=?", new String[] { oldWidgetId }, null, null, null);
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/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/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 7f04860..8dc1204 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");
@@ -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/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/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/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/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/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/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..63dbaa7 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
@@ -31,10 +33,7 @@
 import com.android.launcher3.model.GridSizeMigrationUtil.DbReader
 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
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -43,11 +42,12 @@
 @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"
@@ -61,13 +61,17 @@
 
     @Before
     fun setUp() {
-        modelHelper = LauncherModelHelper()
-        context = modelHelper.sandboxContext
-        db = modelHelper.provider.db
+        context = InstrumentationRegistry.getInstrumentation().targetContext
+        dbHelper =
+            DatabaseHelper(
+                context,
+                null,
+                UserCache.INSTANCE.get(context)::getSerialNumberForUser
+            ) {}
+        db = dbHelper.writableDatabase
 
         validPackages =
             setOf(
-                TEST_PACKAGE,
                 testPackage1,
                 testPackage2,
                 testPackage3,
@@ -86,11 +90,6 @@
         addTableToDb(db, userSerial, false, TMP_TABLE)
     }
 
-    @After
-    fun tearDown() {
-        modelHelper.destroy()
-    }
-
     /**
      * Old migration logic, should be modified once [FeatureFlags.ENABLE_NEW_MIGRATION_LOGIC] is not
      * needed anymore
@@ -99,26 +98,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 +125,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 +136,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 +167,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 +209,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 +241,7 @@
         val readerGridB = DbReader(db, TABLE_NAME, context, validPackages)
         // migrate from A -> B
         GridSizeMigrationUtil.migrate(
-            context,
-            db,
+            dbHelper,
             readerGridA,
             readerGridB,
             idp.numDatabaseHotseatIcons,
@@ -253,12 +252,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 +272,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 +295,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 +309,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 +329,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 +356,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 +371,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 +391,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 +446,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 +476,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 +487,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 +522,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 +534,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 +545,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 +589,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 +602,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 +613,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 +651,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 +663,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 +674,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 +712,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 +724,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 +735,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 +768,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..54b8489 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;
@@ -165,12 +166,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..6fca965 100644
--- a/tests/src/com/android/launcher3/util/LauncherModelHelper.java
+++ b/tests/src/com/android/launcher3/util/LauncherModelHelper.java
@@ -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();
         }
     }
 
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);
         }
     }